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 #ifndef nsTObserverArray_h___
8 #define nsTObserverArray_h___
9 
10 #include "mozilla/MemoryReporting.h"
11 #include "mozilla/ReverseIterator.h"
12 #include "nsTArray.h"
13 #include "nsCycleCollectionNoteChild.h"
14 
15 /**
16  * An array of observers. Like a normal array, but supports iterators that are
17  * stable even if the array is modified during iteration.
18  * The template parameter T is the observer type the array will hold;
19  * N is the number of built-in storage slots that come with the array.
20  * NOTE: You probably want to use nsTObserverArray, unless you specifically
21  * want built-in storage. See below.
22  * @see nsTObserverArray, nsTArray
23  */
24 
25 class nsTObserverArray_base {
26  public:
27   typedef size_t index_type;
28   typedef size_t size_type;
29   typedef ptrdiff_t diff_type;
30 
31  protected:
32   class Iterator_base {
33    public:
34     Iterator_base(const Iterator_base&) = delete;
35 
36    protected:
37     friend class nsTObserverArray_base;
38 
Iterator_base(index_type aPosition,Iterator_base * aNext)39     Iterator_base(index_type aPosition, Iterator_base* aNext)
40         : mPosition(aPosition), mNext(aNext) {}
41 
42     // The current position of the iterator. Its exact meaning differs
43     // depending on iterator. See nsTObserverArray<T>::ForwardIterator.
44     index_type mPosition;
45 
46     // The next iterator currently iterating the same array
47     Iterator_base* mNext;
48   };
49 
nsTObserverArray_base()50   nsTObserverArray_base() : mIterators(nullptr) {}
51 
~nsTObserverArray_base()52   ~nsTObserverArray_base() {
53     NS_ASSERTION(mIterators == nullptr, "iterators outlasting array");
54   }
55 
56   /**
57    * Adjusts iterators after an element has been inserted or removed
58    * from the array.
59    * @param aModPos     Position where elements were added or removed.
60    * @param aAdjustment -1 if an element was removed, 1 if an element was
61    *                    added.
62    */
63   void AdjustIterators(index_type aModPos, diff_type aAdjustment);
64 
65   /**
66    * Clears iterators when the array is destroyed.
67    */
68   void ClearIterators();
69 
70   mutable Iterator_base* mIterators;
71 };
72 
73 template <class T, size_t N>
74 class nsAutoTObserverArray : protected nsTObserverArray_base {
75  public:
76   typedef T elem_type;
77   typedef nsTArray<T> array_type;
78 
79   nsAutoTObserverArray() = default;
80 
81   //
82   // Accessor methods
83   //
84 
85   // @return The number of elements in the array.
Length()86   size_type Length() const { return mArray.Length(); }
87 
88   // @return True if the array is empty or false otherwise.
IsEmpty()89   bool IsEmpty() const { return mArray.IsEmpty(); }
90 
91   // This method provides direct, readonly access to the array elements.
92   // @return A pointer to the first element of the array.  If the array is
93   // empty, then this pointer must not be dereferenced.
Elements()94   const elem_type* Elements() const { return mArray.Elements(); }
Elements()95   elem_type* Elements() { return mArray.Elements(); }
96 
97   // This method provides direct access to an element of the array. The given
98   // index must be within the array bounds. If the underlying array may change
99   // during iteration, use an iterator instead of this function.
100   // @param aIndex The index of an element in the array.
101   // @return A reference to the i'th element of the array.
ElementAt(index_type aIndex)102   elem_type& ElementAt(index_type aIndex) { return mArray.ElementAt(aIndex); }
103 
104   // Same as above, but readonly.
ElementAt(index_type aIndex)105   const elem_type& ElementAt(index_type aIndex) const {
106     return mArray.ElementAt(aIndex);
107   }
108 
109   // This method provides direct access to an element of the array in a bounds
110   // safe manner. If the requested index is out of bounds the provided default
111   // value is returned.
112   // @param aIndex The index of an element in the array.
113   // @param aDef   The value to return if the index is out of bounds.
SafeElementAt(index_type aIndex,elem_type & aDef)114   elem_type& SafeElementAt(index_type aIndex, elem_type& aDef) {
115     return mArray.SafeElementAt(aIndex, aDef);
116   }
117 
118   // Same as above, but readonly.
SafeElementAt(index_type aIndex,const elem_type & aDef)119   const elem_type& SafeElementAt(index_type aIndex,
120                                  const elem_type& aDef) const {
121     return mArray.SafeElementAt(aIndex, aDef);
122   }
123 
124   // No operator[] is provided because the point of this class is to support
125   // allow modifying the array during iteration, and ElementAt() is not safe
126   // in those conditions.
127 
128   //
129   // Search methods
130   //
131 
132   // This method searches, starting from the beginning of the array,
133   // for the first element in this array that is equal to the given element.
134   // 'operator==' must be defined for elem_type.
135   // @param aItem The item to search for.
136   // @return      true if the element was found.
137   template <class Item>
Contains(const Item & aItem)138   bool Contains(const Item& aItem) const {
139     return IndexOf(aItem) != array_type::NoIndex;
140   }
141 
142   // This method searches for the offset of the first element in this
143   // array that is equal to the given element.
144   // 'operator==' must be defined for elem_type.
145   // @param aItem  The item to search for.
146   // @param aStart The index to start from.
147   // @return The index of the found element or NoIndex if not found.
148   template <class Item>
149   index_type IndexOf(const Item& aItem, index_type aStart = 0) const {
150     return mArray.IndexOf(aItem, aStart);
151   }
152 
153   //
154   // Mutation methods
155   //
156 
157   // Insert a given element at the given index.
158   // @param aIndex The index at which to insert item.
159   // @param aItem  The item to insert,
160   template <class Item>
InsertElementAt(index_type aIndex,const Item & aItem)161   void InsertElementAt(index_type aIndex, const Item& aItem) {
162     mArray.InsertElementAt(aIndex, aItem);
163     AdjustIterators(aIndex, 1);
164   }
165 
166   // Same as above but without copy constructing.
167   // This is useful to avoid temporaries.
InsertElementAt(index_type aIndex)168   elem_type* InsertElementAt(index_type aIndex) {
169     elem_type* item = mArray.InsertElementAt(aIndex);
170     AdjustIterators(aIndex, 1);
171     return item;
172   }
173 
174   // Prepend an element to the array unless it already exists in the array.
175   // 'operator==' must be defined for elem_type.
176   // @param aItem The item to prepend.
177   template <class Item>
PrependElementUnlessExists(const Item & aItem)178   void PrependElementUnlessExists(const Item& aItem) {
179     if (!Contains(aItem)) {
180       mArray.InsertElementAt(0, aItem);
181       AdjustIterators(0, 1);
182     }
183   }
184 
185   // Append an element to the array.
186   // @param aItem The item to append.
187   template <class Item>
AppendElement(Item && aItem)188   void AppendElement(Item&& aItem) {
189     mArray.AppendElement(std::forward<Item>(aItem));
190   }
191 
192   // Same as above, but without copy-constructing. This is useful to avoid
193   // temporaries.
AppendElement()194   elem_type* AppendElement() { return mArray.AppendElement(); }
195 
196   // Append an element to the array unless it already exists in the array.
197   // 'operator==' must be defined for elem_type.
198   // @param aItem The item to append.
199   template <class Item>
AppendElementUnlessExists(const Item & aItem)200   void AppendElementUnlessExists(const Item& aItem) {
201     if (!Contains(aItem)) {
202       mArray.AppendElement(aItem);
203     }
204   }
205 
206   // Remove an element from the array.
207   // @param aIndex The index of the item to remove.
RemoveElementAt(index_type aIndex)208   void RemoveElementAt(index_type aIndex) {
209     NS_ASSERTION(aIndex < mArray.Length(), "invalid index");
210     mArray.RemoveElementAt(aIndex);
211     AdjustIterators(aIndex, -1);
212   }
213 
214   // This helper function combines IndexOf with RemoveElementAt to "search
215   // and destroy" the first element that is equal to the given element.
216   // 'operator==' must be defined for elem_type.
217   // @param aItem The item to search for.
218   // @return true if the element was found and removed.
219   template <class Item>
RemoveElement(const Item & aItem)220   bool RemoveElement(const Item& aItem) {
221     index_type index = mArray.IndexOf(aItem, 0);
222     if (index == array_type::NoIndex) {
223       return false;
224     }
225 
226     mArray.RemoveElementAt(index);
227     AdjustIterators(index, -1);
228     return true;
229   }
230 
231   // See nsTArray::RemoveElementsBy. Neither the predicate nor the removal of
232   // elements from the array must have any side effects that modify the array.
233   template <typename Predicate>
NonObservingRemoveElementsBy(Predicate aPredicate)234   void NonObservingRemoveElementsBy(Predicate aPredicate) {
235     index_type i = 0;
236     mArray.RemoveElementsBy([&](const elem_type& aItem) {
237       if (aPredicate(aItem)) {
238         // This element is going to be removed.
239         AdjustIterators(i, -1);
240         return true;
241       }
242       ++i;
243       return false;
244     });
245   }
246 
247   // Removes all observers and collapses all iterators to the beginning of
248   // the array. The result is that forward iterators will see all elements
249   // in the array.
Clear()250   void Clear() {
251     mArray.Clear();
252     ClearIterators();
253   }
254 
255   // Compact the array to minimize the memory it uses
Compact()256   void Compact() { mArray.Compact(); }
257 
258   // Returns the number of bytes on the heap taken up by this object, not
259   // including sizeof(*this). If you want to measure anything hanging off the
260   // array, you must iterate over the elements and measure them individually;
261   // hence the "Shallow" prefix.
ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)262   size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
263     return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf);
264   }
265 
266   //
267   // Iterators
268   //
269 
270   // Base class for iterators. Do not use this directly.
271   class Iterator : public Iterator_base {
272    protected:
273     friend class nsAutoTObserverArray;
274     typedef nsAutoTObserverArray<T, N> array_type;
275 
Iterator(const Iterator & aOther)276     Iterator(const Iterator& aOther)
277         : Iterator(aOther.mPosition, aOther.mArray) {}
278 
Iterator(index_type aPosition,const array_type & aArray)279     Iterator(index_type aPosition, const array_type& aArray)
280         : Iterator_base(aPosition, aArray.mIterators),
281           mArray(const_cast<array_type&>(aArray)) {
282       aArray.mIterators = this;
283     }
284 
~Iterator()285     ~Iterator() {
286       NS_ASSERTION(mArray.mIterators == this,
287                    "Iterators must currently be destroyed in opposite order "
288                    "from the construction order. It is suggested that you "
289                    "simply put them on the stack");
290       mArray.mIterators = mNext;
291     }
292 
293     // The array we're iterating
294     array_type& mArray;
295   };
296 
297   // Iterates the array forward from beginning to end. mPosition points
298   // to the element that will be returned on next call to GetNext.
299   // Elements:
300   // - prepended to the array during iteration *will not* be traversed
301   // - appended during iteration *will* be traversed
302   // - removed during iteration *will not* be traversed.
303   // @see EndLimitedIterator
304   class ForwardIterator : protected Iterator {
305    public:
306     typedef nsAutoTObserverArray<T, N> array_type;
307     typedef Iterator base_type;
308 
ForwardIterator(const array_type & aArray)309     explicit ForwardIterator(const array_type& aArray) : Iterator(0, aArray) {}
310 
ForwardIterator(const array_type & aArray,index_type aPos)311     ForwardIterator(const array_type& aArray, index_type aPos)
312         : Iterator(aPos, aArray) {}
313 
314     bool operator<(const ForwardIterator& aOther) const {
315       NS_ASSERTION(&this->mArray == &aOther.mArray,
316                    "not iterating the same array");
317       return base_type::mPosition < aOther.mPosition;
318     }
319 
320     // Returns true if there are more elements to iterate.
321     // This must precede a call to GetNext(). If false is
322     // returned, GetNext() must not be called.
HasMore()323     bool HasMore() const {
324       return base_type::mPosition < base_type::mArray.Length();
325     }
326 
327     // Returns the next element and steps one step. This must
328     // be preceded by a call to HasMore().
329     // @return The next observer.
GetNext()330     elem_type& GetNext() {
331       NS_ASSERTION(HasMore(), "iterating beyond end of array");
332       return base_type::mArray.Elements()[base_type::mPosition++];
333     }
334 
335     // Removes the element at the current iterator position.
336     // (the last element returned from |GetNext()|)
337     // This will not affect the next call to |GetNext()|
Remove()338     void Remove() {
339       return base_type::mArray.RemoveElementAt(base_type::mPosition - 1);
340     }
341   };
342 
343   // EndLimitedIterator works like ForwardIterator, but will not iterate new
344   // observers appended to the array after the iterator was created.
345   class EndLimitedIterator : protected ForwardIterator {
346    public:
347     typedef nsAutoTObserverArray<T, N> array_type;
348     typedef Iterator base_type;
349 
EndLimitedIterator(const array_type & aArray)350     explicit EndLimitedIterator(const array_type& aArray)
351         : ForwardIterator(aArray), mEnd(aArray, aArray.Length()) {}
352 
353     // Returns true if there are more elements to iterate.
354     // This must precede a call to GetNext(). If false is
355     // returned, GetNext() must not be called.
HasMore()356     bool HasMore() const { return *this < mEnd; }
357 
358     // Returns the next element and steps one step. This must
359     // be preceded by a call to HasMore().
360     // @return The next observer.
GetNext()361     elem_type& GetNext() {
362       NS_ASSERTION(HasMore(), "iterating beyond end of array");
363       return base_type::mArray.Elements()[base_type::mPosition++];
364     }
365 
366     // Removes the element at the current iterator position.
367     // (the last element returned from |GetNext()|)
368     // This will not affect the next call to |GetNext()|
Remove()369     void Remove() {
370       return base_type::mArray.RemoveElementAt(base_type::mPosition - 1);
371     }
372 
373    private:
374     ForwardIterator mEnd;
375   };
376 
377   // Iterates the array backward from end to start. mPosition points
378   // to the element that was returned last.
379   // Elements:
380   // - prepended to the array during iteration *will* be traversed,
381   //   unless the iteration already arrived at the first element
382   // - appended during iteration *will not* be traversed
383   // - removed during iteration *will not* be traversed.
384   class BackwardIterator : protected Iterator {
385    public:
386     typedef nsAutoTObserverArray<T, N> array_type;
387     typedef Iterator base_type;
388 
BackwardIterator(const array_type & aArray)389     explicit BackwardIterator(const array_type& aArray)
390         : Iterator(aArray.Length(), aArray) {}
391 
392     // Returns true if there are more elements to iterate.
393     // This must precede a call to GetNext(). If false is
394     // returned, GetNext() must not be called.
HasMore()395     bool HasMore() const { return base_type::mPosition > 0; }
396 
397     // Returns the next element and steps one step. This must
398     // be preceded by a call to HasMore().
399     // @return The next observer.
GetNext()400     elem_type& GetNext() {
401       NS_ASSERTION(HasMore(), "iterating beyond start of array");
402       return base_type::mArray.Elements()[--base_type::mPosition];
403     }
404 
405     // Removes the element at the current iterator position.
406     // (the last element returned from |GetNext()|)
407     // This will not affect the next call to |GetNext()|
Remove()408     void Remove() {
409       return base_type::mArray.RemoveElementAt(base_type::mPosition);
410     }
411   };
412 
413   struct EndSentinel {};
414 
415   // Internal type, do not use directly, see
416   // ForwardRange()/EndLimitedRange()/BackwardRange().
417   template <typename Iterator, typename U>
418   struct STLIterator {
419     using value_type = std::remove_reference_t<U>;
420 
STLIteratorSTLIterator421     explicit STLIterator(const nsAutoTObserverArray<T, N>& aArray)
422         : mIterator{aArray} {
423       operator++();
424     }
425 
426     bool operator!=(const EndSentinel&) const {
427       // We are a non-end-sentinel and the other is an end-sentinel, so we are
428       // still valid if mCurrent is valid.
429       return mCurrent;
430     }
431 
432     STLIterator& operator++() {
433       mCurrent = mIterator.HasMore() ? &mIterator.GetNext() : nullptr;
434       return *this;
435     }
436 
437     value_type* operator->() { return mCurrent; }
438     U& operator*() { return *mCurrent; }
439 
440    private:
441     Iterator mIterator;
442     value_type* mCurrent;
443   };
444 
445   // Internal type, do not use directly, see
446   // ForwardRange()/EndLimitedRange()/BackwardRange().
447   template <typename Iterator, typename U>
448   class STLIteratorRange {
449    public:
450     using iterator = STLIterator<Iterator, U>;
451 
STLIteratorRange(const nsAutoTObserverArray<T,N> & aArray)452     explicit STLIteratorRange(const nsAutoTObserverArray<T, N>& aArray)
453         : mArray{aArray} {}
454 
455     STLIteratorRange(const STLIteratorRange& aOther) = delete;
456 
begin()457     iterator begin() const { return iterator{mArray}; }
end()458     EndSentinel end() const { return {}; }
459 
460    private:
461     const nsAutoTObserverArray<T, N>& mArray;
462   };
463 
464   template <typename U>
465   using STLForwardIteratorRange = STLIteratorRange<ForwardIterator, U>;
466 
467   template <typename U>
468   using STLEndLimitedIteratorRange = STLIteratorRange<EndLimitedIterator, U>;
469 
470   template <typename U>
471   using STLBackwardIteratorRange = STLIteratorRange<BackwardIterator, U>;
472 
473   // Constructs a range (usable with range-based for) based on the
474   // ForwardIterator semantics. Note that this range does not provide
475   // full-feature STL-style iterators usable with STL-style algorithms.
ForwardRange()476   auto ForwardRange() { return STLForwardIteratorRange<T>{*this}; }
477 
478   // Constructs a const range (usable with range-based for) based on the
479   // ForwardIterator semantics. Note that this range does not provide
480   // full-feature STL-style iterators usable with STL-style algorithms.
ForwardRange()481   auto ForwardRange() const { return STLForwardIteratorRange<const T>{*this}; }
482 
483   // Constructs a range (usable with range-based for) based on the
484   // EndLimitedIterator semantics. Note that this range does not provide
485   // full-feature STL-style iterators usable with STL-style algorithms.
EndLimitedRange()486   auto EndLimitedRange() { return STLEndLimitedIteratorRange<T>{*this}; }
487 
488   // Constructs a const range (usable with range-based for) based on the
489   // EndLimitedIterator semantics. Note that this range does not provide
490   // full-feature STL-style iterators usable with STL-style algorithms.
EndLimitedRange()491   auto EndLimitedRange() const {
492     return STLEndLimitedIteratorRange<const T>{*this};
493   }
494 
495   // Constructs a range (usable with range-based for) based on the
496   // BackwardIterator semantics. Note that this range does not provide
497   // full-feature STL-style iterators usable with STL-style algorithms.
BackwardRange()498   auto BackwardRange() { return STLBackwardIteratorRange<T>{*this}; }
499 
500   // Constructs a const range (usable with range-based for) based on the
501   // BackwardIterator semantics. Note that this range does not provide
502   // full-feature STL-style iterators usable with STL-style algorithms.
BackwardRange()503   auto BackwardRange() const {
504     return STLBackwardIteratorRange<const T>{*this};
505   }
506 
507   // Constructs a const range (usable with range-based for and STL-style
508   // algorithms) based on a non-observing iterator. The array must not be
509   // modified during iteration.
NonObservingRange()510   auto NonObservingRange() const {
511     return mozilla::detail::IteratorRange<
512         typename AutoTArray<T, N>::const_iterator,
513         typename AutoTArray<T, N>::const_reverse_iterator>{mArray.cbegin(),
514                                                            mArray.cend()};
515   }
516 
517  protected:
518   AutoTArray<T, N> mArray;
519 };
520 
521 template <class T>
522 class nsTObserverArray : public nsAutoTObserverArray<T, 0> {
523  public:
524   typedef nsAutoTObserverArray<T, 0> base_type;
525   typedef nsTObserverArray_base::size_type size_type;
526 
527   //
528   // Initialization methods
529   //
530 
531   nsTObserverArray() = default;
532 
533   // Initialize this array and pre-allocate some number of elements.
nsTObserverArray(size_type aCapacity)534   explicit nsTObserverArray(size_type aCapacity) {
535     base_type::mArray.SetCapacity(aCapacity);
536   }
537 
Clone()538   nsTObserverArray Clone() const {
539     auto result = nsTObserverArray{};
540     result.mArray.Assign(this->mArray);
541     return result;
542   }
543 };
544 
545 template <typename T, size_t N>
MakeBackInserter(nsAutoTObserverArray<T,N> & aArray)546 auto MakeBackInserter(nsAutoTObserverArray<T, N>& aArray) {
547   return mozilla::nsTArrayBackInserter<T, nsAutoTObserverArray<T, N>>{aArray};
548 }
549 
550 template <typename T, size_t N>
ImplCycleCollectionUnlink(nsAutoTObserverArray<T,N> & aField)551 inline void ImplCycleCollectionUnlink(nsAutoTObserverArray<T, N>& aField) {
552   aField.Clear();
553 }
554 
555 template <typename T, size_t N>
556 inline void ImplCycleCollectionTraverse(
557     nsCycleCollectionTraversalCallback& aCallback,
558     nsAutoTObserverArray<T, N>& aField, const char* aName,
559     uint32_t aFlags = 0) {
560   aFlags |= CycleCollectionEdgeNameArrayFlag;
561   size_t length = aField.Length();
562   for (size_t i = 0; i < length; ++i) {
563     ImplCycleCollectionTraverse(aCallback, aField.ElementAt(i), aName, aFlags);
564   }
565 }
566 
567 // Note that this macro only works if the array holds pointers to XPCOM objects.
568 #define NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(array_, func_, params_) \
569   do {                                                                   \
570     for (RefPtr obs_ : (array_).ForwardRange()) {                        \
571       obs_->func_ params_;                                               \
572     }                                                                    \
573   } while (0)
574 
575 // Note that this macro only works if the array holds pointers to XPCOM objects.
576 #define NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(array_, func_, params_) \
577   do {                                                             \
578     for (auto* obs_ : (array_).ForwardRange()) {                   \
579       obs_->func_ params_;                                         \
580     }                                                              \
581   } while (0)
582 
583 #endif  // nsTObserverArray_h___
584