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 "nsIWeakReferenceUtils.h"
11 #include "mozilla/ClearOnShutdown.h"
12 
13 namespace mozilla::dom {
14 
15 template <class T>
16 struct Flagged {
Flaggedmozilla::dom::Flagged17   Flagged(uint32_t aFlags, T&& aValue)
18       : flags(aFlags), value(std::forward<T>(aValue)) {}
Flaggedmozilla::dom::Flagged19   Flagged(Flagged&& aOther)
20       : Flagged(std::move(aOther.flags), std::move(aOther.value)) {}
21   Flagged(const Flagged& aOther) = default;
22   ~Flagged() = default;
23 
24   uint32_t flags;
25   T value;
26 };
27 
28 template <class T>
29 using FlaggedArray = nsTArray<Flagged<T>>;
30 
31 template <class T>
32 struct ListenerCollection {
33   static StaticAutoPtr<FlaggedArray<T>> gListeners;
34   static StaticAutoPtr<FlaggedArray<T>> gListenersToRemove;
35 
GetListenersmozilla::dom::ListenerCollection36   static FlaggedArray<T>* GetListeners(bool aDoNotInit = false) {
37     MOZ_ASSERT(NS_IsMainThread());
38     if (!gListeners && !aDoNotInit) {
39       gListeners = new FlaggedArray<T>();
40       ClearOnShutdown(&gListeners);
41     }
42     return gListeners;
43   }
44 
GetListenersToRemovemozilla::dom::ListenerCollection45   static FlaggedArray<T>* GetListenersToRemove(bool aDoNotInit = false) {
46     MOZ_ASSERT(NS_IsMainThread());
47     if (!gListenersToRemove && !aDoNotInit) {
48       gListenersToRemove = new FlaggedArray<T>();
49       ClearOnShutdown(&gListenersToRemove);
50     }
51     return gListenersToRemove;
52   }
53 };
54 
55 template <class T>
56 StaticAutoPtr<FlaggedArray<T>> ListenerCollection<T>::gListeners;
57 template <class T>
58 StaticAutoPtr<FlaggedArray<T>> ListenerCollection<T>::gListenersToRemove;
59 
60 typedef ListenerCollection<RefPtr<PlacesEventCallback>> JSListeners;
61 typedef ListenerCollection<WeakPtr<PlacesWeakCallbackWrapper>> WeakJSListeners;
62 typedef ListenerCollection<WeakPtr<places::INativePlacesEventCallback>>
63     WeakNativeListeners;
64 
65 static bool gCallingListeners = false;
66 
GetEventTypeFlag(PlacesEventType aEventType)67 uint32_t GetEventTypeFlag(PlacesEventType aEventType) {
68   if (aEventType == PlacesEventType::None) {
69     return 0;
70   }
71   return 1 << ((uint32_t)aEventType - 1);
72 }
73 
GetFlagsForEventTypes(const nsTArray<PlacesEventType> & aEventTypes)74 uint32_t GetFlagsForEventTypes(const nsTArray<PlacesEventType>& aEventTypes) {
75   uint32_t flags = 0;
76   for (PlacesEventType eventType : aEventTypes) {
77     flags |= GetEventTypeFlag(eventType);
78   }
79   return flags;
80 }
81 
GetFlagsForEvents(const nsTArray<OwningNonNull<PlacesEvent>> & aEvents)82 uint32_t GetFlagsForEvents(
83     const nsTArray<OwningNonNull<PlacesEvent>>& aEvents) {
84   uint32_t flags = 0;
85   for (const PlacesEvent& event : aEvents) {
86     flags |= GetEventTypeFlag(event.Type());
87   }
88   return flags;
89 }
90 
91 template <class TWrapped, class TUnwrapped>
CallListeners(uint32_t aEventFlags,FlaggedArray<TWrapped> & aListeners,const Sequence<OwningNonNull<PlacesEvent>> & aEvents,const std::function<TUnwrapped (TWrapped &)> & aUnwrapListener,const std::function<void (TUnwrapped &,const Sequence<OwningNonNull<PlacesEvent>> &)> & aCallListener)92 MOZ_CAN_RUN_SCRIPT void CallListeners(
93     uint32_t aEventFlags, FlaggedArray<TWrapped>& aListeners,
94     const Sequence<OwningNonNull<PlacesEvent>>& aEvents,
95     const std::function<TUnwrapped(TWrapped&)>& aUnwrapListener,
96     const std::function<void(TUnwrapped&,
97                              const Sequence<OwningNonNull<PlacesEvent>>&)>&
98         aCallListener) {
99   for (uint32_t i = 0; i < aListeners.Length(); i++) {
100     Flagged<TWrapped>& l = aListeners[i];
101     TUnwrapped unwrapped = aUnwrapListener(l.value);
102     if (!unwrapped) {
103       aListeners.RemoveElementAt(i);
104       i--;
105       continue;
106     }
107 
108     if ((l.flags & aEventFlags) == aEventFlags) {
109       aCallListener(unwrapped, aEvents);
110     } else if (l.flags & aEventFlags) {
111       Sequence<OwningNonNull<PlacesEvent>> filtered;
112       for (const OwningNonNull<PlacesEvent>& event : aEvents) {
113         if (l.flags & GetEventTypeFlag(event->Type())) {
114           bool success = !!filtered.AppendElement(event, fallible);
115           MOZ_RELEASE_ASSERT(success);
116         }
117       }
118       aCallListener(unwrapped, filtered);
119     }
120   }
121 }
122 
AddListener(GlobalObject & aGlobal,const nsTArray<PlacesEventType> & aEventTypes,PlacesEventCallback & aCallback,ErrorResult & rv)123 void PlacesObservers::AddListener(GlobalObject& aGlobal,
124                                   const nsTArray<PlacesEventType>& aEventTypes,
125                                   PlacesEventCallback& aCallback,
126                                   ErrorResult& rv) {
127   uint32_t flags = GetFlagsForEventTypes(aEventTypes);
128 
129   FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
130       JSListeners::GetListeners();
131   Flagged<RefPtr<PlacesEventCallback>> pair(flags, &aCallback);
132   listeners->AppendElement(pair);
133 }
134 
AddListener(GlobalObject & aGlobal,const nsTArray<PlacesEventType> & aEventTypes,PlacesWeakCallbackWrapper & aCallback,ErrorResult & rv)135 void PlacesObservers::AddListener(GlobalObject& aGlobal,
136                                   const nsTArray<PlacesEventType>& aEventTypes,
137                                   PlacesWeakCallbackWrapper& aCallback,
138                                   ErrorResult& rv) {
139   uint32_t flags = GetFlagsForEventTypes(aEventTypes);
140 
141   FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
142       WeakJSListeners::GetListeners();
143   WeakPtr<PlacesWeakCallbackWrapper> weakCb(&aCallback);
144   MOZ_ASSERT(weakCb.get());
145   Flagged<WeakPtr<PlacesWeakCallbackWrapper>> flagged(flags, std::move(weakCb));
146   listeners->AppendElement(flagged);
147 }
148 
AddListener(const nsTArray<PlacesEventType> & aEventTypes,places::INativePlacesEventCallback * aCallback)149 void PlacesObservers::AddListener(
150     const nsTArray<PlacesEventType>& aEventTypes,
151     places::INativePlacesEventCallback* aCallback) {
152   uint32_t flags = GetFlagsForEventTypes(aEventTypes);
153 
154   FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
155       WeakNativeListeners::GetListeners();
156   Flagged<WeakPtr<places::INativePlacesEventCallback>> pair(flags, aCallback);
157   listeners->AppendElement(pair);
158 }
159 
RemoveListener(GlobalObject & aGlobal,const nsTArray<PlacesEventType> & aEventTypes,PlacesEventCallback & aCallback,ErrorResult & rv)160 void PlacesObservers::RemoveListener(
161     GlobalObject& aGlobal, const nsTArray<PlacesEventType>& aEventTypes,
162     PlacesEventCallback& aCallback, ErrorResult& rv) {
163   uint32_t flags = GetFlagsForEventTypes(aEventTypes);
164   if (gCallingListeners) {
165     FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
166         JSListeners::GetListenersToRemove();
167     Flagged<RefPtr<PlacesEventCallback>> pair(flags, &aCallback);
168     listeners->AppendElement(pair);
169   } else {
170     RemoveListener(flags, aCallback);
171   }
172 }
173 
RemoveListener(GlobalObject & aGlobal,const nsTArray<PlacesEventType> & aEventTypes,PlacesWeakCallbackWrapper & aCallback,ErrorResult & rv)174 void PlacesObservers::RemoveListener(
175     GlobalObject& aGlobal, const nsTArray<PlacesEventType>& aEventTypes,
176     PlacesWeakCallbackWrapper& aCallback, ErrorResult& rv) {
177   uint32_t flags = GetFlagsForEventTypes(aEventTypes);
178   if (gCallingListeners) {
179     FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
180         WeakJSListeners::GetListenersToRemove();
181     WeakPtr<PlacesWeakCallbackWrapper> weakCb(&aCallback);
182     MOZ_ASSERT(weakCb.get());
183     Flagged<WeakPtr<PlacesWeakCallbackWrapper>> flagged(flags,
184                                                         std::move(weakCb));
185     listeners->AppendElement(flagged);
186   } else {
187     RemoveListener(flags, aCallback);
188   }
189 }
190 
RemoveListener(const nsTArray<PlacesEventType> & aEventTypes,places::INativePlacesEventCallback * aCallback)191 void PlacesObservers::RemoveListener(
192     const nsTArray<PlacesEventType>& aEventTypes,
193     places::INativePlacesEventCallback* aCallback) {
194   uint32_t flags = GetFlagsForEventTypes(aEventTypes);
195   if (gCallingListeners) {
196     FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
197         WeakNativeListeners::GetListenersToRemove();
198     Flagged<WeakPtr<places::INativePlacesEventCallback>> pair(flags, aCallback);
199     listeners->AppendElement(pair);
200   } else {
201     RemoveListener(flags, aCallback);
202   }
203 }
204 
RemoveListener(uint32_t aFlags,PlacesEventCallback & aCallback)205 void PlacesObservers::RemoveListener(uint32_t aFlags,
206                                      PlacesEventCallback& aCallback) {
207   FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
208       JSListeners::GetListeners(/* aDoNotInit: */ true);
209   if (!listeners) {
210     return;
211   }
212   for (uint32_t i = 0; i < listeners->Length(); i++) {
213     Flagged<RefPtr<PlacesEventCallback>>& l = listeners->ElementAt(i);
214     if (!(*l.value == aCallback)) {
215       continue;
216     }
217     if (l.flags == (aFlags & l.flags)) {
218       listeners->RemoveElementAt(i);
219       i--;
220     } else {
221       l.flags &= ~aFlags;
222     }
223   }
224 }
225 
RemoveListener(uint32_t aFlags,PlacesWeakCallbackWrapper & aCallback)226 void PlacesObservers::RemoveListener(uint32_t aFlags,
227                                      PlacesWeakCallbackWrapper& aCallback) {
228   FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
229       WeakJSListeners::GetListeners(/* aDoNotInit: */ true);
230   if (!listeners) {
231     return;
232   }
233   for (uint32_t i = 0; i < listeners->Length(); i++) {
234     Flagged<WeakPtr<PlacesWeakCallbackWrapper>>& l = listeners->ElementAt(i);
235     RefPtr<PlacesWeakCallbackWrapper> unwrapped = l.value.get();
236     if (unwrapped != &aCallback) {
237       continue;
238     }
239     if (l.flags == (aFlags & l.flags)) {
240       listeners->RemoveElementAt(i);
241       i--;
242     } else {
243       l.flags &= ~aFlags;
244     }
245   }
246 }
247 
RemoveListener(uint32_t aFlags,places::INativePlacesEventCallback * aCallback)248 void PlacesObservers::RemoveListener(
249     uint32_t aFlags, places::INativePlacesEventCallback* aCallback) {
250   FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
251       WeakNativeListeners::GetListeners(/* aDoNotInit: */ true);
252   if (!listeners) {
253     return;
254   }
255   for (uint32_t i = 0; i < listeners->Length(); i++) {
256     Flagged<WeakPtr<places::INativePlacesEventCallback>>& l =
257         listeners->ElementAt(i);
258     RefPtr<places::INativePlacesEventCallback> unwrapped = l.value.get();
259     if (unwrapped != aCallback) {
260       continue;
261     }
262     if (l.flags == (aFlags & l.flags)) {
263       listeners->RemoveElementAt(i);
264       i--;
265     } else {
266       l.flags &= ~aFlags;
267     }
268   }
269 }
270 
NotifyListeners(GlobalObject & aGlobal,const Sequence<OwningNonNull<PlacesEvent>> & aEvents,ErrorResult & rv)271 void PlacesObservers::NotifyListeners(
272     GlobalObject& aGlobal, const Sequence<OwningNonNull<PlacesEvent>>& aEvents,
273     ErrorResult& rv) {
274   NotifyListeners(aEvents);
275 }
276 
NotifyListeners(const Sequence<OwningNonNull<PlacesEvent>> & aEvents)277 void PlacesObservers::NotifyListeners(
278     const Sequence<OwningNonNull<PlacesEvent>>& aEvents) {
279   MOZ_ASSERT(aEvents.Length() > 0, "Must pass a populated array of events");
280   MOZ_RELEASE_ASSERT(!gCallingListeners);
281   gCallingListeners = true;
282 
283   if (aEvents.Length() > 0) {
284     uint32_t flags = GetFlagsForEvents(aEvents);
285 
286     CallListeners<RefPtr<PlacesEventCallback>, RefPtr<PlacesEventCallback>>(
287         flags, *JSListeners::GetListeners(), aEvents,
288         [](auto& cb) { return cb; },
289         // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from
290         // some internals of the std::function implementation that we can't
291         // annotate.  We handle this by annotating CallListeners and making sure
292         // it holds a strong ref to the callback.
293         [&](auto& cb, const auto& events)
294             MOZ_CAN_RUN_SCRIPT_BOUNDARY { MOZ_KnownLive(cb)->Call(events); });
295 
296     CallListeners<WeakPtr<places::INativePlacesEventCallback>,
297                   RefPtr<places::INativePlacesEventCallback>>(
298         flags, *WeakNativeListeners::GetListeners(), aEvents,
299         [](auto& cb) { return cb.get(); },
300         [&](auto& cb, const Sequence<OwningNonNull<PlacesEvent>>& events) {
301           cb->HandlePlacesEvent(events);
302         });
303 
304     CallListeners<WeakPtr<PlacesWeakCallbackWrapper>,
305                   RefPtr<PlacesWeakCallbackWrapper>>(
306         flags, *WeakJSListeners::GetListeners(), aEvents,
307         [](auto& cb) { return cb.get(); },
308         // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from
309         // some internals of the std::function implementation that we can't
310         // annotate.  We handle this by annotating CallListeners and making sure
311         // it holds a strong ref to the callback.
312         [&](auto& cb, const auto& events) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
313           RefPtr<PlacesEventCallback> callback(cb->mCallback);
314           callback->Call(events);
315         });
316   }
317 
318   auto& listenersToRemove = *JSListeners::GetListenersToRemove();
319   if (listenersToRemove.Length() > 0) {
320     for (auto& listener : listenersToRemove) {
321       RemoveListener(listener.flags, *listener.value);
322     }
323   }
324   listenersToRemove.Clear();
325 
326   auto& weakListenersToRemove = *WeakJSListeners::GetListenersToRemove();
327   if (weakListenersToRemove.Length() > 0) {
328     for (auto& listener : weakListenersToRemove) {
329       RemoveListener(listener.flags, *listener.value.get());
330     }
331   }
332   weakListenersToRemove.Clear();
333 
334   auto& nativeListenersToRemove = *WeakNativeListeners::GetListenersToRemove();
335   if (nativeListenersToRemove.Length() > 0) {
336     for (auto& listener : nativeListenersToRemove) {
337       RemoveListener(listener.flags, listener.value.get());
338     }
339   }
340   nativeListenersToRemove.Clear();
341 
342   gCallingListeners = false;
343 }
344 
345 }  // namespace mozilla::dom
346