summaryrefslogtreecommitdiffstats
path: root/xpcom/ds
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xpcom/ds/ArenaAllocator.h214
-rw-r--r--xpcom/ds/ArenaAllocatorExtensions.h76
-rw-r--r--xpcom/ds/ArrayAlgorithm.h109
-rw-r--r--xpcom/ds/ArrayIterator.h165
-rw-r--r--xpcom/ds/Atom.py64
-rw-r--r--xpcom/ds/AtomArray.h19
-rw-r--r--xpcom/ds/Dafsa.cpp153
-rw-r--r--xpcom/ds/Dafsa.h54
-rw-r--r--xpcom/ds/HTMLAtoms.py257
-rw-r--r--xpcom/ds/IncrementalTokenizer.cpp190
-rw-r--r--xpcom/ds/IncrementalTokenizer.h125
-rw-r--r--xpcom/ds/MruCache.h166
-rw-r--r--xpcom/ds/Observer.h76
-rw-r--r--xpcom/ds/PLDHashTable.cpp870
-rw-r--r--xpcom/ds/PLDHashTable.h805
-rw-r--r--xpcom/ds/PerfectHash.h50
-rw-r--r--xpcom/ds/SimpleEnumerator.h73
-rw-r--r--xpcom/ds/StaticAtoms.py2643
-rw-r--r--xpcom/ds/StickyTimeDuration.h238
-rw-r--r--xpcom/ds/Tokenizer.cpp805
-rw-r--r--xpcom/ds/Tokenizer.h524
-rw-r--r--xpcom/ds/components.conf24
-rw-r--r--xpcom/ds/moz.build157
-rw-r--r--xpcom/ds/nsArray.cpp146
-rw-r--r--xpcom/ds/nsArray.h78
-rw-r--r--xpcom/ds/nsArrayEnumerator.cpp213
-rw-r--r--xpcom/ds/nsArrayEnumerator.h31
-rw-r--r--xpcom/ds/nsArrayUtils.cpp22
-rw-r--r--xpcom/ds/nsArrayUtils.h34
-rw-r--r--xpcom/ds/nsAtom.h297
-rw-r--r--xpcom/ds/nsAtomTable.cpp670
-rw-r--r--xpcom/ds/nsAtomTable.h30
-rw-r--r--xpcom/ds/nsBaseHashtable.h1029
-rw-r--r--xpcom/ds/nsCOMArray.cpp252
-rw-r--r--xpcom/ds/nsCOMArray.h398
-rw-r--r--xpcom/ds/nsCRT.cpp123
-rw-r--r--xpcom/ds/nsCRT.h119
-rw-r--r--xpcom/ds/nsCharSeparatedTokenizer.cpp10
-rw-r--r--xpcom/ds/nsCharSeparatedTokenizer.h274
-rw-r--r--xpcom/ds/nsCheapSets.h155
-rw-r--r--xpcom/ds/nsClassHashtable.h115
-rw-r--r--xpcom/ds/nsDeque.cpp265
-rw-r--r--xpcom/ds/nsDeque.h538
-rw-r--r--xpcom/ds/nsEnumeratorUtils.cpp248
-rw-r--r--xpcom/ds/nsEnumeratorUtils.h24
-rw-r--r--xpcom/ds/nsExpirationTracker.h618
-rw-r--r--xpcom/ds/nsGkAtoms.cpp70
-rw-r--r--xpcom/ds/nsGkAtoms.h192
-rw-r--r--xpcom/ds/nsHashKeys.h636
-rw-r--r--xpcom/ds/nsHashPropertyBag.cpp366
-rw-r--r--xpcom/ds/nsHashPropertyBag.h80
-rw-r--r--xpcom/ds/nsHashtablesFwd.h94
-rw-r--r--xpcom/ds/nsIArray.idl103
-rw-r--r--xpcom/ds/nsIArrayExtensions.idl51
-rw-r--r--xpcom/ds/nsIINIParser.idl61
-rw-r--r--xpcom/ds/nsIMutableArray.idl92
-rw-r--r--xpcom/ds/nsINIParserImpl.cpp143
-rw-r--r--xpcom/ds/nsINIParserImpl.h23
-rw-r--r--xpcom/ds/nsIObserver.idl37
-rw-r--r--xpcom/ds/nsIObserverService.idl109
-rw-r--r--xpcom/ds/nsIPersistentProperties.h13
-rw-r--r--xpcom/ds/nsIPersistentProperties2.idl59
-rw-r--r--xpcom/ds/nsIProperties.idl46
-rw-r--r--xpcom/ds/nsIProperty.idl25
-rw-r--r--xpcom/ds/nsIPropertyBag.idl28
-rw-r--r--xpcom/ds/nsIPropertyBag2.idl80
-rw-r--r--xpcom/ds/nsISerializable.idl32
-rw-r--r--xpcom/ds/nsISimpleEnumerator.idl77
-rw-r--r--xpcom/ds/nsIStringEnumerator.idl37
-rw-r--r--xpcom/ds/nsISupportsIterators.idl292
-rw-r--r--xpcom/ds/nsISupportsPrimitives.idl222
-rw-r--r--xpcom/ds/nsIVariant.idl162
-rw-r--r--xpcom/ds/nsIWindowsRegKey.idl336
-rw-r--r--xpcom/ds/nsIWritablePropertyBag.idl27
-rw-r--r--xpcom/ds/nsIWritablePropertyBag2.idl22
-rw-r--r--xpcom/ds/nsInterfaceHashtable.h14
-rw-r--r--xpcom/ds/nsMathUtils.h109
-rw-r--r--xpcom/ds/nsObserverList.cpp92
-rw-r--r--xpcom/ds/nsObserverList.h67
-rw-r--r--xpcom/ds/nsObserverService.cpp367
-rw-r--r--xpcom/ds/nsObserverService.h56
-rw-r--r--xpcom/ds/nsPersistentProperties.cpp584
-rw-r--r--xpcom/ds/nsPersistentProperties.h57
-rw-r--r--xpcom/ds/nsPointerHashKeys.h50
-rw-r--r--xpcom/ds/nsProperties.cpp61
-rw-r--r--xpcom/ds/nsProperties.h29
-rw-r--r--xpcom/ds/nsQuickSort.cpp175
-rw-r--r--xpcom/ds/nsQuickSort.h39
-rw-r--r--xpcom/ds/nsRefCountedHashtable.h245
-rw-r--r--xpcom/ds/nsRefPtrHashtable.h12
-rw-r--r--xpcom/ds/nsSimpleEnumerator.cpp78
-rw-r--r--xpcom/ds/nsSimpleEnumerator.h22
-rw-r--r--xpcom/ds/nsStaticAtomUtils.h36
-rw-r--r--xpcom/ds/nsStaticNameTable.cpp180
-rw-r--r--xpcom/ds/nsStaticNameTable.h48
-rw-r--r--xpcom/ds/nsStringEnumerator.cpp311
-rw-r--r--xpcom/ds/nsStringEnumerator.h102
-rw-r--r--xpcom/ds/nsSupportsPrimitives.cpp603
-rw-r--r--xpcom/ds/nsSupportsPrimitives.h279
-rw-r--r--xpcom/ds/nsTArray-inl.h691
-rw-r--r--xpcom/ds/nsTArray.cpp28
-rw-r--r--xpcom/ds/nsTArray.h3345
-rw-r--r--xpcom/ds/nsTArrayForwardDeclare.h39
-rw-r--r--xpcom/ds/nsTHashMap.h70
-rw-r--r--xpcom/ds/nsTHashSet.h193
-rw-r--r--xpcom/ds/nsTHashtable.h966
-rw-r--r--xpcom/ds/nsTObserverArray.cpp27
-rw-r--r--xpcom/ds/nsTObserverArray.h583
-rw-r--r--xpcom/ds/nsTPriorityQueue.h147
-rw-r--r--xpcom/ds/nsVariant.cpp1881
-rw-r--r--xpcom/ds/nsVariant.h223
-rw-r--r--xpcom/ds/nsWhitespaceTokenizer.h96
-rw-r--r--xpcom/ds/nsWindowsRegKey.cpp523
-rw-r--r--xpcom/ds/nsWindowsRegKey.h45
-rw-r--r--xpcom/ds/test/python.ini4
-rw-r--r--xpcom/ds/test/test_dafsa.py546
-rw-r--r--xpcom/ds/tools/incremental_dafsa.py509
-rw-r--r--xpcom/ds/tools/make_dafsa.py372
-rw-r--r--xpcom/ds/tools/perfecthash.py371
119 files changed, 31336 insertions, 0 deletions
diff --git a/xpcom/ds/ArenaAllocator.h b/xpcom/ds/ArenaAllocator.h
new file mode 100644
index 0000000000..76498986ce
--- /dev/null
+++ b/xpcom/ds/ArenaAllocator.h
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ArenaAllocator_h
+#define mozilla_ArenaAllocator_h
+
+#include <algorithm>
+#include <cstdint>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/fallible.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryChecking.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/Poison.h"
+#include "mozilla/TemplateLib.h"
+#include "nsDebug.h"
+
+namespace mozilla {
+
+/**
+ * A very simple arena allocator based on NSPR's PLArena.
+ *
+ * The arena allocator only provides for allocation, all memory is retained
+ * until the allocator is destroyed. It's useful for situations where a large
+ * amount of small transient allocations are expected.
+ *
+ * Example usage:
+ *
+ * // Define an allocator that is page sized and returns allocations that are
+ * // 8-byte aligned.
+ * ArenaAllocator<4096, 8> a;
+ * for (int i = 0; i < 1000; i++) {
+ * DoSomething(a.Allocate(i));
+ * }
+ */
+template <size_t ArenaSize, size_t Alignment = 1>
+class ArenaAllocator {
+ public:
+ constexpr ArenaAllocator() : mHead(), mCurrent(nullptr) {
+ static_assert(mozilla::tl::FloorLog2<Alignment>::value ==
+ mozilla::tl::CeilingLog2<Alignment>::value,
+ "ArenaAllocator alignment must be a power of two");
+ }
+
+ ArenaAllocator(const ArenaAllocator&) = delete;
+ ArenaAllocator& operator=(const ArenaAllocator&) = delete;
+
+ /**
+ * Frees all internal arenas but does not call destructors for objects
+ * allocated out of the arena.
+ */
+ ~ArenaAllocator() { Clear(); }
+
+ /**
+ * Fallibly allocates a chunk of memory with the given size from the internal
+ * arenas. If the allocation size is larger than the chosen arena a size an
+ * entire arena is allocated and used.
+ */
+ MOZ_ALWAYS_INLINE void* Allocate(size_t aSize, const fallible_t&) {
+ MOZ_RELEASE_ASSERT(aSize, "Allocation size must be non-zero");
+ return InternalAllocate(AlignedSize(aSize));
+ }
+
+ void* Allocate(size_t aSize) {
+ void* p = Allocate(aSize, fallible);
+ if (MOZ_UNLIKELY(!p)) {
+ NS_ABORT_OOM(std::max(aSize, ArenaSize));
+ }
+
+ return p;
+ }
+
+ /**
+ * Frees all entries. The allocator can be reused after this is called.
+ *
+ * NB: This will not run destructors of any objects that were allocated from
+ * the arena.
+ */
+ void Clear() {
+ // Free all chunks.
+ auto a = mHead.next;
+ while (a) {
+ auto tmp = a;
+ a = a->next;
+ free(tmp);
+ }
+
+ // Reset the list head.
+ mHead.next = nullptr;
+ mCurrent = nullptr;
+ }
+
+ /**
+ * Adjusts the given size to the required alignment.
+ */
+ static constexpr size_t AlignedSize(size_t aSize) {
+ return (aSize + (Alignment - 1)) & ~(Alignment - 1);
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t s = 0;
+ for (auto arena = mHead.next; arena; arena = arena->next) {
+ s += aMallocSizeOf(arena);
+ }
+
+ return s;
+ }
+
+ void Check() {
+ if (mCurrent) {
+ mCurrent->canary.Check();
+ }
+ }
+
+ private:
+ struct ArenaHeader {
+ /**
+ * The location in memory of the data portion of the arena.
+ */
+ uintptr_t offset;
+ /**
+ * The location in memory of the end of the data portion of the arena.
+ */
+ uintptr_t tail;
+ };
+
+ struct ArenaChunk {
+ constexpr ArenaChunk() : header{0, 0}, next(nullptr) {}
+
+ explicit ArenaChunk(size_t aSize)
+ : header{AlignedSize(uintptr_t(this + 1)), uintptr_t(this) + aSize},
+ next(nullptr) {}
+
+ CorruptionCanary canary;
+ ArenaHeader header;
+ ArenaChunk* next;
+
+ /**
+ * Allocates a chunk of memory out of the arena and advances the offset.
+ */
+ void* Allocate(size_t aSize) {
+ MOZ_ASSERT(aSize <= Available());
+ char* p = reinterpret_cast<char*>(header.offset);
+ MOZ_RELEASE_ASSERT(p);
+ header.offset += aSize;
+ canary.Check();
+ MOZ_MAKE_MEM_UNDEFINED(p, aSize);
+ return p;
+ }
+
+ /**
+ * Calculates the amount of space available for allocation in this chunk.
+ */
+ size_t Available() const { return header.tail - header.offset; }
+ };
+
+ /**
+ * Allocates an arena chunk of the given size and initializes its header.
+ */
+ ArenaChunk* AllocateChunk(size_t aSize) {
+ static const size_t kOffset = AlignedSize(sizeof(ArenaChunk));
+ MOZ_ASSERT(kOffset < aSize);
+
+ const size_t chunkSize = aSize + kOffset;
+ void* p = malloc(chunkSize);
+ if (!p) {
+ return nullptr;
+ }
+
+ ArenaChunk* arena = new (KnownNotNull, p) ArenaChunk(chunkSize);
+ MOZ_MAKE_MEM_NOACCESS((void*)arena->header.offset,
+ arena->header.tail - arena->header.offset);
+
+ // Insert into the head of the list.
+ arena->next = mHead.next;
+ mHead.next = arena;
+
+ // Only update |mCurrent| if this is a standard allocation, large
+ // allocations will always end up full so there's no point in updating
+ // |mCurrent| in that case.
+ if (aSize == ArenaSize - kOffset) {
+ mCurrent = arena;
+ }
+
+ return arena;
+ }
+
+ MOZ_ALWAYS_INLINE void* InternalAllocate(size_t aSize) {
+ static_assert(ArenaSize > AlignedSize(sizeof(ArenaChunk)),
+ "Arena size must be greater than the header size");
+
+ static const size_t kMaxArenaCapacity =
+ ArenaSize - AlignedSize(sizeof(ArenaChunk));
+
+ if (mCurrent && aSize <= mCurrent->Available()) {
+ return mCurrent->Allocate(aSize);
+ }
+
+ ArenaChunk* arena = AllocateChunk(std::max(kMaxArenaCapacity, aSize));
+ return arena ? arena->Allocate(aSize) : nullptr;
+ }
+
+ ArenaChunk mHead;
+ ArenaChunk* mCurrent;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ArenaAllocator_h
diff --git a/xpcom/ds/ArenaAllocatorExtensions.h b/xpcom/ds/ArenaAllocatorExtensions.h
new file mode 100644
index 0000000000..60819381d5
--- /dev/null
+++ b/xpcom/ds/ArenaAllocatorExtensions.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ArenaAllocatorExtensions_h
+#define mozilla_ArenaAllocatorExtensions_h
+
+#include "mozilla/ArenaAllocator.h"
+#include "mozilla/CheckedInt.h"
+#include "nsAString.h"
+
+/**
+ * Extensions to the ArenaAllocator class.
+ */
+namespace mozilla {
+
+namespace detail {
+
+template <typename T, size_t ArenaSize, size_t Alignment>
+T* DuplicateString(const T* aSrc, const CheckedInt<size_t>& aLen,
+ ArenaAllocator<ArenaSize, Alignment>& aArena);
+
+} // namespace detail
+
+/**
+ * Makes an arena allocated null-terminated copy of the source string. The
+ * source string must be null-terminated.
+ *
+ * @param aSrc String to copy.
+ * @param aArena The arena to allocate the string copy out of.
+ * @return An arena allocated null-terminated string.
+ */
+template <typename T, size_t ArenaSize, size_t Alignment>
+T* ArenaStrdup(const T* aStr, ArenaAllocator<ArenaSize, Alignment>& aArena) {
+ return detail::DuplicateString(aStr, nsCharTraits<T>::length(aStr), aArena);
+}
+
+/**
+ * Makes an arena allocated null-terminated copy of the source string.
+ *
+ * @param aSrc String to copy.
+ * @param aArena The arena to allocate the string copy out of.
+ * @return An arena allocated null-terminated string.
+ */
+template <typename T, size_t ArenaSize, size_t Alignment>
+T* ArenaStrdup(const detail::nsTStringRepr<T>& aStr,
+ ArenaAllocator<ArenaSize, Alignment>& aArena) {
+ return detail::DuplicateString(aStr.BeginReading(), aStr.Length(), aArena);
+}
+
+/**
+ * Copies the source string and adds a null terminator. Source string does not
+ * have to be null terminated.
+ */
+template <typename T, size_t ArenaSize, size_t Alignment>
+T* detail::DuplicateString(const T* aSrc, const CheckedInt<size_t>& aLen,
+ ArenaAllocator<ArenaSize, Alignment>& aArena) {
+ const auto byteLen = (aLen + 1) * sizeof(T);
+ if (!byteLen.isValid()) {
+ return nullptr;
+ }
+
+ T* p = static_cast<T*>(aArena.Allocate(byteLen.value(), mozilla::fallible));
+ if (p) {
+ memcpy(p, aSrc, byteLen.value() - sizeof(T));
+ p[aLen.value()] = T(0);
+ }
+
+ return p;
+}
+
+} // namespace mozilla
+
+#endif // mozilla_ArenaAllocatorExtensions_h
diff --git a/xpcom/ds/ArrayAlgorithm.h b/xpcom/ds/ArrayAlgorithm.h
new file mode 100644
index 0000000000..2556eef465
--- /dev/null
+++ b/xpcom/ds/ArrayAlgorithm.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ArrayAlgorithm_h___
+#define ArrayAlgorithm_h___
+
+#include "nsTArray.h"
+
+#include "mozilla/Algorithm.h"
+#include "mozilla/ResultExtensions.h"
+
+namespace mozilla {
+
+// An algorithm similar to TransformAbortOnErr, which creates a new nsTArray
+// rather than inserting into an output iterator. The capacity of the result
+// array is set to the number of elements that will be inserted if all
+// transformations are successful. This variant is fallible, i.e. if setting the
+// capacity fails, NS_ERROR_OUT_OF_MEMORY is returned as an error value. This
+// variant only works with nsresult errors.
+template <
+ typename SrcIter, typename Transform,
+ typename = std::enable_if_t<std::is_same_v<
+ typename detail::TransformTraits<Transform, SrcIter>::result_err_type,
+ nsresult>>>
+Result<nsTArray<typename detail::TransformTraits<Transform,
+ SrcIter>::result_ok_type>,
+ nsresult>
+TransformIntoNewArrayAbortOnErr(SrcIter aIter, SrcIter aEnd,
+ Transform aTransform, fallible_t) {
+ nsTArray<typename detail::TransformTraits<Transform, SrcIter>::result_ok_type>
+ res;
+ if (!res.SetCapacity(std::distance(aIter, aEnd), fallible)) {
+ return Err(NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ auto transformRes = TransformAbortOnErr(aIter, aEnd, MakeBackInserter(res),
+ std::move(aTransform));
+ if (NS_WARN_IF(transformRes.isErr())) {
+ return Err(transformRes.unwrapErr());
+ }
+
+ return res;
+}
+
+template <typename SrcRange, typename Transform>
+auto TransformIntoNewArrayAbortOnErr(SrcRange& aRange, Transform aTransform,
+ fallible_t) {
+ using std::begin;
+ using std::end;
+ return TransformIntoNewArrayAbortOnErr(begin(aRange), end(aRange), aTransform,
+ fallible);
+}
+
+// An algorithm similar to std::transform, which creates a new nsTArray
+// rather than inserting into an output iterator. The capacity of the result
+// array is set to the number of elements that will be inserted. This variant is
+// fallible, i.e. if setting the capacity fails, NS_ERROR_OUT_OF_MEMORY is
+// returned as an error value. This variant only works with nsresult errors.
+template <typename SrcIter, typename Transform>
+Result<nsTArray<detail::ArrayElementTransformType<Transform, SrcIter>>,
+ nsresult>
+TransformIntoNewArray(SrcIter aIter, SrcIter aEnd, Transform aTransform,
+ fallible_t) {
+ nsTArray<detail::ArrayElementTransformType<Transform, SrcIter>> res;
+ if (!res.SetCapacity(std::distance(aIter, aEnd), fallible)) {
+ return Err(NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ std::transform(aIter, aEnd, MakeBackInserter(res), std::move(aTransform));
+
+ return res;
+}
+
+template <typename SrcRange, typename Transform>
+auto TransformIntoNewArray(SrcRange& aRange, Transform aTransform, fallible_t) {
+ using std::begin;
+ using std::end;
+ return TransformIntoNewArray(begin(aRange), end(aRange), aTransform,
+ fallible);
+}
+
+// An algorithm similar to std::transform, which creates a new nsTArray
+// rather than inserting into an output iterator. The capacity of the result
+// array is set to the number of elements that will be inserted. This variant is
+// infallible, i.e. if setting the capacity fails, the process is aborted.
+template <typename SrcIter, typename Transform>
+nsTArray<detail::ArrayElementTransformType<Transform, SrcIter>>
+TransformIntoNewArray(SrcIter aIter, SrcIter aEnd, Transform aTransform) {
+ nsTArray<detail::ArrayElementTransformType<Transform, SrcIter>> res;
+ res.SetCapacity(std::distance(aIter, aEnd));
+
+ std::transform(aIter, aEnd, MakeBackInserter(res), std::move(aTransform));
+
+ return res;
+}
+
+template <typename SrcRange, typename Transform>
+auto TransformIntoNewArray(SrcRange& aRange, Transform aTransform) {
+ using std::begin;
+ using std::end;
+ return TransformIntoNewArray(begin(aRange), end(aRange), aTransform);
+}
+
+} // namespace mozilla
+
+#endif // !defined(ArrayAlgorithm_h___)
diff --git a/xpcom/ds/ArrayIterator.h b/xpcom/ds/ArrayIterator.h
new file mode 100644
index 0000000000..cc6c8d1cb4
--- /dev/null
+++ b/xpcom/ds/ArrayIterator.h
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Common iterator implementation for array classes e.g. nsTArray.
+
+#ifndef mozilla_ArrayIterator_h
+#define mozilla_ArrayIterator_h
+
+#include <iterator>
+#include <type_traits>
+
+namespace mozilla {
+
+namespace detail {
+template <typename T>
+struct AddInnerConst;
+
+template <typename T>
+struct AddInnerConst<T&> {
+ using Type = const T&;
+};
+
+template <typename T>
+struct AddInnerConst<T*> {
+ using Type = const T*;
+};
+
+template <typename T>
+using AddInnerConstT = typename AddInnerConst<T>::Type;
+} // namespace detail
+
+// We have implemented a custom iterator class for array rather than using
+// raw pointers into the backing storage to improve the safety of C++11-style
+// range based iteration in the presence of array mutation, or script execution
+// (bug 1299489).
+//
+// Mutating an array which is being iterated is still wrong, and will either
+// cause elements to be missed or firefox to crash, but will not trigger memory
+// safety problems due to the release-mode bounds checking found in ElementAt.
+//
+// Dereferencing this iterator returns type Element. When Element is a reference
+// type, this iterator implements the full standard random access iterator spec,
+// and can be treated in many ways as though it is a pointer. Otherwise, it is
+// just enough to be used in range-based for loop.
+template <class Element, class ArrayType>
+class ArrayIterator {
+ public:
+ typedef ArrayType array_type;
+ typedef ArrayIterator<Element, ArrayType> iterator_type;
+ typedef typename array_type::index_type index_type;
+ typedef std::remove_reference_t<Element> value_type;
+ typedef ptrdiff_t difference_type;
+ typedef value_type* pointer;
+ typedef value_type& reference;
+ typedef std::random_access_iterator_tag iterator_category;
+ typedef ArrayIterator<detail::AddInnerConstT<Element>, ArrayType>
+ const_iterator_type;
+
+ private:
+ const array_type* mArray;
+ index_type mIndex;
+
+ public:
+ ArrayIterator() : mArray(nullptr), mIndex(0) {}
+ ArrayIterator(const iterator_type& aOther)
+ : mArray(aOther.mArray), mIndex(aOther.mIndex) {}
+ ArrayIterator(const array_type& aArray, index_type aIndex)
+ : mArray(&aArray), mIndex(aIndex) {}
+
+ iterator_type& operator=(const iterator_type& aOther) {
+ mArray = aOther.mArray;
+ mIndex = aOther.mIndex;
+ return *this;
+ }
+
+ constexpr operator const_iterator_type() const {
+ return mArray ? const_iterator_type{*mArray, mIndex}
+ : const_iterator_type{};
+ }
+
+ bool operator==(const iterator_type& aRhs) const {
+ return mIndex == aRhs.mIndex;
+ }
+ bool operator!=(const iterator_type& aRhs) const { return !(*this == aRhs); }
+ bool operator<(const iterator_type& aRhs) const {
+ return mIndex < aRhs.mIndex;
+ }
+ bool operator>(const iterator_type& aRhs) const {
+ return mIndex > aRhs.mIndex;
+ }
+ bool operator<=(const iterator_type& aRhs) const {
+ return mIndex <= aRhs.mIndex;
+ }
+ bool operator>=(const iterator_type& aRhs) const {
+ return mIndex >= aRhs.mIndex;
+ }
+
+ // These operators depend on the release mode bounds checks in
+ // ArrayIterator::ElementAt for safety.
+ value_type* operator->() const {
+ return const_cast<value_type*>(&mArray->ElementAt(mIndex));
+ }
+ Element operator*() const {
+ return const_cast<Element>(mArray->ElementAt(mIndex));
+ }
+
+ iterator_type& operator++() {
+ ++mIndex;
+ return *this;
+ }
+ iterator_type operator++(int) {
+ iterator_type it = *this;
+ ++*this;
+ return it;
+ }
+ iterator_type& operator--() {
+ --mIndex;
+ return *this;
+ }
+ iterator_type operator--(int) {
+ iterator_type it = *this;
+ --*this;
+ return it;
+ }
+
+ iterator_type& operator+=(difference_type aDiff) {
+ mIndex += aDiff;
+ return *this;
+ }
+ iterator_type& operator-=(difference_type aDiff) {
+ mIndex -= aDiff;
+ return *this;
+ }
+
+ iterator_type operator+(difference_type aDiff) const {
+ iterator_type it = *this;
+ it += aDiff;
+ return it;
+ }
+ iterator_type operator-(difference_type aDiff) const {
+ iterator_type it = *this;
+ it -= aDiff;
+ return it;
+ }
+
+ difference_type operator-(const iterator_type& aOther) const {
+ return static_cast<difference_type>(mIndex) -
+ static_cast<difference_type>(aOther.mIndex);
+ }
+
+ Element operator[](difference_type aIndex) const {
+ return *this->operator+(aIndex);
+ }
+
+ constexpr const array_type* GetArray() const { return mArray; }
+
+ constexpr index_type GetIndex() const { return mIndex; }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ArrayIterator_h
diff --git a/xpcom/ds/Atom.py b/xpcom/ds/Atom.py
new file mode 100644
index 0000000000..1399ba840a
--- /dev/null
+++ b/xpcom/ds/Atom.py
@@ -0,0 +1,64 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+class Atom:
+ def __init__(self, ident, string, ty="nsStaticAtom"):
+ self.ident = ident
+ self.string = string
+ self.ty = ty
+ self.atom_type = self.__class__.__name__
+ self.hash = hash_string(string)
+ self.is_ascii_lowercase = is_ascii_lowercase(string)
+
+
+class PseudoElementAtom(Atom):
+ def __init__(self, ident, string):
+ Atom.__init__(self, ident, string, ty="nsCSSPseudoElementStaticAtom")
+
+
+class AnonBoxAtom(Atom):
+ def __init__(self, ident, string):
+ Atom.__init__(self, ident, string, ty="nsCSSAnonBoxPseudoStaticAtom")
+
+
+class NonInheritingAnonBoxAtom(AnonBoxAtom):
+ def __init__(self, ident, string):
+ AnonBoxAtom.__init__(self, ident, string)
+
+
+class InheritingAnonBoxAtom(AnonBoxAtom):
+ def __init__(self, ident, string):
+ AnonBoxAtom.__init__(self, ident, string)
+
+
+GOLDEN_RATIO_U32 = 0x9E3779B9
+
+
+def rotate_left_5(value):
+ return ((value << 5) | (value >> 27)) & 0xFFFFFFFF
+
+
+def wrapping_multiply(x, y):
+ return (x * y) & 0xFFFFFFFF
+
+
+# Calculate the precomputed hash of the static atom. This is a port of
+# mozilla::HashString(const char16_t*), which is what we use for atomizing
+# strings. An assertion in nsAtomTable::RegisterStaticAtoms ensures that
+# the value we compute here matches what HashString() would produce.
+def hash_string(s):
+ h = 0
+ for c in s:
+ h = wrapping_multiply(GOLDEN_RATIO_U32, rotate_left_5(h) ^ ord(c))
+ return h
+
+
+# Returns true if lowercasing this string in an ascii-case-insensitive way
+# would leave the string unchanged.
+def is_ascii_lowercase(s):
+ for c in s:
+ if c >= "A" and c <= "Z":
+ return False
+ return True
diff --git a/xpcom/ds/AtomArray.h b/xpcom/ds/AtomArray.h
new file mode 100644
index 0000000000..5b1e0a0eae
--- /dev/null
+++ b/xpcom/ds/AtomArray.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AtomArray_h
+#define mozilla_AtomArray_h
+
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+
+class nsAtom;
+
+namespace mozilla {
+typedef AutoTArray<RefPtr<nsAtom>, 4> AtomArray;
+}
+
+#endif // mozilla_AtomArray_h
diff --git a/xpcom/ds/Dafsa.cpp b/xpcom/ds/Dafsa.cpp
new file mode 100644
index 0000000000..8854e0ff1d
--- /dev/null
+++ b/xpcom/ds/Dafsa.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "Dafsa.h"
+
+#include "mozilla/Assertions.h"
+#include "nsAString.h"
+
+const int mozilla::Dafsa::kKeyNotFound = -1;
+
+// Note the DAFSA implementation was lifted from eTLD code in Chromium that was
+// originally lifted from Firefox.
+
+// Read next offset from pos.
+// Returns true if an offset could be read, false otherwise.
+static bool GetNextOffset(const unsigned char** pos, const unsigned char* end,
+ const unsigned char** offset) {
+ if (*pos == end) return false;
+
+ // When reading an offset the byte array must always contain at least
+ // three more bytes to consume. First the offset to read, then a node
+ // to skip over and finally a destination node. No object can be smaller
+ // than one byte.
+ MOZ_ASSERT(*pos + 2 < end);
+ size_t bytes_consumed;
+ switch (**pos & 0x60) {
+ case 0x60: // Read three byte offset
+ *offset += (((*pos)[0] & 0x1F) << 16) | ((*pos)[1] << 8) | (*pos)[2];
+ bytes_consumed = 3;
+ break;
+ case 0x40: // Read two byte offset
+ *offset += (((*pos)[0] & 0x1F) << 8) | (*pos)[1];
+ bytes_consumed = 2;
+ break;
+ default:
+ *offset += (*pos)[0] & 0x3F;
+ bytes_consumed = 1;
+ }
+ if ((**pos & 0x80) != 0) {
+ *pos = end;
+ } else {
+ *pos += bytes_consumed;
+ }
+ return true;
+}
+
+// Check if byte at offset is last in label.
+static bool IsEOL(const unsigned char* offset, const unsigned char* end) {
+ MOZ_ASSERT(offset < end);
+ return (*offset & 0x80) != 0;
+}
+
+// Check if byte at offset matches first character in key.
+// This version matches characters not last in label.
+static bool IsMatch(const unsigned char* offset, const unsigned char* end,
+ const char* key) {
+ MOZ_ASSERT(offset < end);
+ return *offset == *key;
+}
+
+// Check if byte at offset matches first character in key.
+// This version matches characters last in label.
+static bool IsEndCharMatch(const unsigned char* offset,
+ const unsigned char* end, const char* key) {
+ MOZ_ASSERT(offset < end);
+ return *offset == (*key | 0x80);
+}
+
+// Read return value at offset.
+// Returns true if a return value could be read, false otherwise.
+static bool GetReturnValue(const unsigned char* offset,
+ const unsigned char* end, int* return_value) {
+ MOZ_ASSERT(offset < end);
+ if ((*offset & 0xE0) == 0x80) {
+ *return_value = *offset & 0x0F;
+ return true;
+ }
+ return false;
+}
+
+// Lookup a domain key in a byte array generated by make_dafsa.py.
+// The rule type is returned if key is found, otherwise kKeyNotFound is
+// returned.
+static int LookupString(const unsigned char* graph, size_t length,
+ const char* key, size_t key_length) {
+ const unsigned char* pos = graph;
+ const unsigned char* end = graph + length;
+ const unsigned char* offset = pos;
+ const char* key_end = key + key_length;
+ while (GetNextOffset(&pos, end, &offset)) {
+ // char <char>+ end_char offsets
+ // char <char>+ return value
+ // char end_char offsets
+ // char return value
+ // end_char offsets
+ // return_value
+ bool did_consume = false;
+ if (key != key_end && !IsEOL(offset, end)) {
+ // Leading <char> is not a match. Don't dive into this child
+ if (!IsMatch(offset, end, key)) continue;
+ did_consume = true;
+ ++offset;
+ ++key;
+ // Possible matches at this point:
+ // <char>+ end_char offsets
+ // <char>+ return value
+ // end_char offsets
+ // return value
+ // Remove all remaining <char> nodes possible
+ while (!IsEOL(offset, end) && key != key_end) {
+ if (!IsMatch(offset, end, key)) return mozilla::Dafsa::kKeyNotFound;
+ ++key;
+ ++offset;
+ }
+ }
+ // Possible matches at this point:
+ // end_char offsets
+ // return_value
+ // If one or more <char> elements were consumed, a failure
+ // to match is terminal. Otherwise, try the next node.
+ if (key == key_end) {
+ int return_value;
+ if (GetReturnValue(offset, end, &return_value)) return return_value;
+ // The DAFSA guarantees that if the first char is a match, all
+ // remaining char elements MUST match if the key is truly present.
+ if (did_consume) return mozilla::Dafsa::kKeyNotFound;
+ continue;
+ }
+ if (!IsEndCharMatch(offset, end, key)) {
+ if (did_consume) return mozilla::Dafsa::kKeyNotFound; // Unexpected
+ continue;
+ }
+ ++key;
+ pos = ++offset; // Dive into child
+ }
+ return mozilla::Dafsa::kKeyNotFound; // No match
+}
+
+namespace mozilla {
+
+int Dafsa::Lookup(const nsACString& aKey) const {
+ return LookupString(mData.Elements(), mData.Length(), aKey.BeginReading(),
+ aKey.Length());
+}
+
+} // namespace mozilla
diff --git a/xpcom/ds/Dafsa.h b/xpcom/ds/Dafsa.h
new file mode 100644
index 0000000000..1a5584f632
--- /dev/null
+++ b/xpcom/ds/Dafsa.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Dafsa_h
+#define mozilla_Dafsa_h
+
+#include "stdint.h"
+
+#include "mozilla/Span.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+
+/**
+ * A deterministic acyclic finite state automaton suitable for storing static
+ * dictionaries of tagged ascii strings. Consider using this if you have a very
+ * large set of strings that need an associated enum value.
+ *
+ * Currently the string tag is limited by `make_dafsa.py` to a value of [0-4].
+ * In theory [0-15] can easily be supported.
+ *
+ * See `make_dafsa.py` for more details.
+ */
+class Dafsa {
+ public:
+ using Graph = Span<const uint8_t>;
+
+ /**
+ * Initializes the DAFSA with a binary encoding generated by `make_dafsa.py`.
+ */
+ explicit Dafsa(const Graph& aData) : mData(aData) {}
+
+ ~Dafsa() = default;
+
+ /**
+ * Searches for the given string in the DAFSA.
+ *
+ * @param aKey The string to search for.
+ * @returns kKeyNotFound if not found, otherwise the associated tag.
+ */
+ int Lookup(const nsACString& aKey) const;
+
+ static const int kKeyNotFound;
+
+ private:
+ const Graph mData;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_Dafsa_h
diff --git a/xpcom/ds/HTMLAtoms.py b/xpcom/ds/HTMLAtoms.py
new file mode 100644
index 0000000000..ae4df76cc3
--- /dev/null
+++ b/xpcom/ds/HTMLAtoms.py
@@ -0,0 +1,257 @@
+# THIS FILE IS GENERATED BY THE HTML PARSER TRANSLATOR AND WILL BE OVERWRITTEN!
+from Atom import Atom
+
+HTML_PARSER_ATOMS = [
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xlink", "xlink"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xml_space", "xml:space"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xml_lang", "xml:lang"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("aria_grab", "aria-grab"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("aria_channel", "aria-channel"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("aria_secret", "aria-secret"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("aria_templateid", "aria-templateid"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("aria_datatype", "aria-datatype"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("local", "local"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xchannelselector", "xchannelselector"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("ychannelselector", "ychannelselector"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("enable_background", "enable-background"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("calcmode", "calcmode"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("specularexponent", "specularexponent"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("specularconstant", "specularconstant"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("gradienttransform", "gradienttransform"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("gradientunits", "gradientunits"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("rendering_intent", "rendering-intent"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("stddeviation", "stddeviation"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("basefrequency", "basefrequency"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("baseprofile", "baseprofile"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("baseProfile", "baseProfile"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("edgemode", "edgemode"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("repeatcount", "repeatcount"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("repeatdur", "repeatdur"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("spreadmethod", "spreadmethod"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("diffuseconstant", "diffuseconstant"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("surfacescale", "surfacescale"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("lengthadjust", "lengthadjust"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("origin", "origin"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("targetx", "targetx"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("targety", "targety"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("pathlength", "pathlength"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("definitionurl", "definitionurl"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("limitingconeangle", "limitingconeangle"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("markerheight", "markerheight"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("markerwidth", "markerwidth"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("maskunits", "maskunits"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("markerunits", "markerunits"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("maskcontentunits", "maskcontentunits"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("tablevalues", "tablevalues"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("primitiveunits", "primitiveunits"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("zoomandpan", "zoomandpan"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("kernelmatrix", "kernelmatrix"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("kerning", "kerning"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("kernelunitlength", "kernelunitlength"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("pointsatx", "pointsatx"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("pointsaty", "pointsaty"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("pointsatz", "pointsatz"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xlink_href", "xlink:href"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xlink_title", "xlink:title"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xlink_role", "xlink:role"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xlink_arcrole", "xlink:arcrole"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("arcrole", "arcrole"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xmlns_xlink", "xmlns:xlink"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xlink_type", "xlink:type"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xlink_show", "xlink:show"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("xlink_actuate", "xlink:actuate"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("color_rendering", "color-rendering"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("numoctaves", "numoctaves"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("onmousewheel", "onmousewheel"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("clippathunits", "clippathunits"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("glyph_orientation_vertical", "glyph-orientation-vertical"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("glyph_orientation_horizontal", "glyph-orientation-horizontal"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("glyphref", "glyphref"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("keypoints", "keypoints"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("attributename", "attributename"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("attributetype", "attributetype"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("startoffset", "startoffset"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("keysplines", "keysplines"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("preservealpha", "preservealpha"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("preserveaspectratio", "preserveaspectratio"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("alttext", "alttext"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("filterunits", "filterunits"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("keytimes", "keytimes"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("patterntransform", "patterntransform"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("patternunits", "patternunits"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("patterncontentunits", "patterncontentunits"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("stitchtiles", "stitchtiles"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("systemlanguage", "systemlanguage"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("textlength", "textlength"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("requiredfeatures", "requiredfeatures"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("requiredextensions", "requiredextensions"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("viewtarget", "viewtarget"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("viewbox", "viewbox"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("refx", "refx"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("refy", "refy"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fefunca", "fefunca"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fefuncb", "fefuncb"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("feblend", "feblend"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("feflood", "feflood"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("feturbulence", "feturbulence"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("femergenode", "femergenode"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("feimage", "feimage"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("femerge", "femerge"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fetile", "fetile"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fecomposite", "fecomposite"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("altglyphdef", "altglyphdef"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("altGlyphDef", "altGlyphDef"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fefuncg", "fefuncg"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fediffuselighting", "fediffuselighting"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fespecularlighting", "fespecularlighting"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("altglyph", "altglyph"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("altGlyph", "altGlyph"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("clippath", "clippath"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("textpath", "textpath"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("altglyphitem", "altglyphitem"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("altGlyphItem", "altGlyphItem"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("animatetransform", "animatetransform"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("animatemotion", "animatemotion"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fedisplacementmap", "fedisplacementmap"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("animatecolor", "animatecolor"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fefuncr", "fefuncr"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fecomponenttransfer", "fecomponenttransfer"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fegaussianblur", "fegaussianblur"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("foreignobject", "foreignobject"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("feoffset", "feoffset"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fespotlight", "fespotlight"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fepointlight", "fepointlight"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fedistantlight", "fedistantlight"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("lineargradient", "lineargradient"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("radialgradient", "radialgradient"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fedropshadow", "fedropshadow"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("fecolormatrix", "fecolormatrix"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("feconvolvematrix", "feconvolvematrix"),
+ # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+ Atom("femorphology", "femorphology"),
+]
diff --git a/xpcom/ds/IncrementalTokenizer.cpp b/xpcom/ds/IncrementalTokenizer.cpp
new file mode 100644
index 0000000000..db6fddb82b
--- /dev/null
+++ b/xpcom/ds/IncrementalTokenizer.cpp
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/IncrementalTokenizer.h"
+
+#include "mozilla/AutoRestore.h"
+
+#include "nsIInputStream.h"
+#include "IncrementalTokenizer.h"
+#include <algorithm>
+
+namespace mozilla {
+
+IncrementalTokenizer::IncrementalTokenizer(Consumer&& aConsumer,
+ const char* aWhitespaces,
+ const char* aAdditionalWordChars,
+ uint32_t aRawMinBuffered)
+ : TokenizerBase(aWhitespaces, aAdditionalWordChars)
+#ifdef DEBUG
+ ,
+ mConsuming(false)
+#endif
+ ,
+ mNeedMoreInput(false),
+ mRollback(false),
+ mInputCursor(0),
+ mConsumer(std::move(aConsumer)) {
+ mInputFinished = false;
+ mMinRawDelivery = aRawMinBuffered;
+}
+
+nsresult IncrementalTokenizer::FeedInput(const nsACString& aInput) {
+ NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED);
+ MOZ_ASSERT(!mInputFinished);
+
+ mInput.Cut(0, mInputCursor);
+ mInputCursor = 0;
+
+ mInput.Append(aInput);
+
+ return Process();
+}
+
+nsresult IncrementalTokenizer::FeedInput(nsIInputStream* aInput,
+ uint32_t aCount) {
+ NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED);
+ MOZ_ASSERT(!mInputFinished);
+ MOZ_ASSERT(!mConsuming);
+
+ mInput.Cut(0, mInputCursor);
+ mInputCursor = 0;
+
+ nsresult rv = NS_OK;
+ while (NS_SUCCEEDED(rv) && aCount) {
+ nsCString::index_type remainder = mInput.Length();
+ nsCString::index_type load =
+ std::min<nsCString::index_type>(aCount, PR_UINT32_MAX - remainder);
+
+ if (!load) {
+ // To keep the API simple, we fail if the input data buffer if filled.
+ // It's highly unlikely there will ever be such amout of data cumulated
+ // unless a logic fault in the consumer code.
+ NS_ERROR("IncrementalTokenizer consumer not reading data?");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!mInput.SetLength(remainder + load, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ auto buffer = mInput.BeginWriting() + remainder;
+
+ uint32_t read;
+ rv = aInput->Read(buffer, load, &read);
+ if (NS_SUCCEEDED(rv)) {
+ // remainder + load fits the uint32_t size, so must remainder + read.
+ mInput.SetLength(remainder + read);
+ aCount -= read;
+
+ rv = Process();
+ }
+ }
+
+ return rv;
+}
+
+nsresult IncrementalTokenizer::FinishInput() {
+ NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED);
+ MOZ_ASSERT(!mInputFinished);
+ MOZ_ASSERT(!mConsuming);
+
+ mInput.Cut(0, mInputCursor);
+ mInputCursor = 0;
+
+ mInputFinished = true;
+ nsresult rv = Process();
+ mConsumer = nullptr;
+ return rv;
+}
+
+bool IncrementalTokenizer::Next(Token& aToken) {
+ // Assert we are called only from the consumer callback
+ MOZ_ASSERT(mConsuming);
+
+ if (mPastEof) {
+ return false;
+ }
+
+ nsACString::const_char_iterator next = Parse(aToken);
+ mPastEof = aToken.Type() == TOKEN_EOF;
+ if (next == mCursor && !mPastEof) {
+ // Not enough input to make a deterministic decision.
+ return false;
+ }
+
+ AssignFragment(aToken, mCursor, next);
+ mCursor = next;
+ return true;
+}
+
+void IncrementalTokenizer::NeedMoreInput() {
+ // Assert we are called only from the consumer callback
+ MOZ_ASSERT(mConsuming);
+
+ // When the input has been finished, we can't set the flag to prevent
+ // indefinite wait for more input (that will never come)
+ mNeedMoreInput = !mInputFinished;
+}
+
+void IncrementalTokenizer::Rollback() {
+ // Assert we are called only from the consumer callback
+ MOZ_ASSERT(mConsuming);
+
+ mRollback = true;
+}
+
+nsresult IncrementalTokenizer::Process() {
+#ifdef DEBUG
+ // Assert we are not re-entered
+ MOZ_ASSERT(!mConsuming);
+
+ AutoRestore<bool> consuming(mConsuming);
+ mConsuming = true;
+#endif
+
+ MOZ_ASSERT(!mPastEof);
+
+ nsresult rv = NS_OK;
+
+ mInput.BeginReading(mCursor);
+ mCursor += mInputCursor;
+ mInput.EndReading(mEnd);
+
+ while (NS_SUCCEEDED(rv) && !mPastEof) {
+ Token token;
+ nsACString::const_char_iterator next = Parse(token);
+ mPastEof = token.Type() == TOKEN_EOF;
+ if (next == mCursor && !mPastEof) {
+ // Not enough input to make a deterministic decision.
+ break;
+ }
+
+ AssignFragment(token, mCursor, next);
+
+ nsACString::const_char_iterator rollback = mCursor;
+ mCursor = next;
+
+ mNeedMoreInput = mRollback = false;
+
+ rv = mConsumer(token, *this);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ if (mNeedMoreInput || mRollback) {
+ mCursor = rollback;
+ mPastEof = false;
+ if (mNeedMoreInput) {
+ break;
+ }
+ }
+ }
+
+ mInputCursor = mCursor - mInput.BeginReading();
+ return rv;
+}
+
+} // namespace mozilla
diff --git a/xpcom/ds/IncrementalTokenizer.h b/xpcom/ds/IncrementalTokenizer.h
new file mode 100644
index 0000000000..c2647052c9
--- /dev/null
+++ b/xpcom/ds/IncrementalTokenizer.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef INCREMENTAL_TOKENIZER_H__
+#define INCREMENTAL_TOKENIZER_H__
+
+#include "mozilla/Tokenizer.h"
+
+#include "nsError.h"
+#include <functional>
+
+class nsIInputStream;
+
+namespace mozilla {
+
+class IncrementalTokenizer : public TokenizerBase<char> {
+ public:
+ /**
+ * The consumer callback. The function is called for every single token
+ * as found in the input. Failure result returned by this callback stops
+ * the tokenization immediately and bubbles to result of Feed/FinishInput.
+ *
+ * Fragment()s of consumed tokens are ensured to remain valid until next call
+ * to Feed/FinishInput and are pointing to a single linear buffer. Hence,
+ * those can be safely used to accumulate the data for processing after
+ * Feed/FinishInput returned.
+ */
+ typedef std::function<nsresult(Token const&, IncrementalTokenizer& i)>
+ Consumer;
+
+ /**
+ * For aWhitespaces and aAdditionalWordChars arguments see TokenizerBase.
+ *
+ * @param aConsumer
+ * A mandatory non-null argument, a function that consumes the tokens as
+ * they come when the tokenizer is fed.
+ * @param aRawMinBuffered
+ * When we have buffered at least aRawMinBuffered data, but there was no
+ * custom token found so far because of too small incremental feed chunks,
+ * deliver the raw data to preserve streaming and to save memory. This only
+ * has effect in OnlyCustomTokenizing mode.
+ */
+ explicit IncrementalTokenizer(Consumer&& aConsumer,
+ const char* aWhitespaces = nullptr,
+ const char* aAdditionalWordChars = nullptr,
+ uint32_t aRawMinBuffered = 1024);
+
+ /**
+ * Pushes the input to be tokenized. These directly call the Consumer
+ * callback on every found token. Result of the Consumer callback is returned
+ * here.
+ *
+ * The tokenizer must be initialized with a valid consumer prior call to these
+ * methods. It's not allowed to call Feed/FinishInput from inside the
+ * Consumer callback.
+ */
+ nsresult FeedInput(const nsACString& aInput);
+ nsresult FeedInput(nsIInputStream* aInput, uint32_t aCount);
+ nsresult FinishInput();
+
+ /**
+ * Can only be called from inside the consumer callback.
+ *
+ * When there is still anything to read from the input, tokenize it, store
+ * the token type and value to aToken result and shift the cursor past this
+ * just parsed token. Each call to Next() reads another token from
+ * the input and shifts the cursor.
+ *
+ * Returns false if there is not enough data to deterministically recognize
+ * tokens or when the last returned token was EOF.
+ */
+ [[nodiscard]] bool Next(Token& aToken);
+
+ /**
+ * Can only be called from inside the consumer callback.
+ *
+ * Tells the tokenizer to revert the cursor and stop the async parsing until
+ * next feed of the input. This is useful when more than one token is needed
+ * to decide on the syntax but there is not enough input to get a next token
+ * (Next() returned false.)
+ */
+ void NeedMoreInput();
+
+ /**
+ * Can only be called from inside the consumer callback.
+ *
+ * This makes the consumer callback be called again while parsing
+ * the input at the previous cursor position again. This is useful when
+ * the tokenizer state (custom tokens, tokenization mode) has changed and
+ * we want to re-parse the input again.
+ */
+ void Rollback();
+
+ private:
+ // Loops over the input with TokenizerBase::Parse and calls the Consumer
+ // callback.
+ nsresult Process();
+
+#ifdef DEBUG
+ // True when inside the consumer callback, used only for assertions.
+ bool mConsuming;
+#endif // DEBUG
+ // Modifyable only from the Consumer callback, tells the parser to break,
+ // rollback and wait for more input.
+ bool mNeedMoreInput;
+ // Modifyable only from the Consumer callback, tells the parser to rollback
+ // and parse the input again, with (if modified) new settings of the
+ // tokenizer.
+ bool mRollback;
+ // The input buffer. Updated with each call to Feed/FinishInput.
+ nsCString mInput;
+ // Numerical index pointing at the current cursor position. We don't keep
+ // direct reference to the string buffer since the buffer gets often
+ // reallocated.
+ nsCString::index_type mInputCursor;
+ // Refernce to the consumer function.
+ Consumer mConsumer;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/ds/MruCache.h b/xpcom/ds/MruCache.h
new file mode 100644
index 0000000000..35c86bf19f
--- /dev/null
+++ b/xpcom/ds/MruCache.h
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MruCache_h
+#define mozilla_MruCache_h
+
+#include <cstdint>
+#include <type_traits>
+#include <utility>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/HashFunctions.h"
+
+namespace mozilla {
+
+namespace detail {
+
+// Helper struct for checking if a value is empty.
+//
+// `IsNotEmpty` will return true if `Value` is not a pointer type or if the
+// pointer value is not null.
+template <typename Value, bool IsPtr = std::is_pointer<Value>::value>
+struct EmptyChecker {
+ static bool IsNotEmpty(const Value&) { return true; }
+};
+// Template specialization for the `IsPtr == true` case.
+template <typename Value>
+struct EmptyChecker<Value, true> {
+ static bool IsNotEmpty(const Value& aVal) { return aVal != nullptr; }
+};
+
+} // namespace detail
+
+// Provides a most recently used cache that can be used as a layer on top of
+// a larger container where lookups can be expensive. The default size is 31,
+// which as a prime number provides a better distrubution of cached entries.
+//
+// Users are expected to provide a `Cache` class that defines two required
+// methods:
+// - A method for providing the hash of a key:
+//
+// static HashNumber Hash(const KeyType& aKey)
+//
+// - A method for matching a key to a value, for pointer types the value
+// is guaranteed not to be null.
+//
+// static bool Match(const KeyType& aKey, const ValueType& aVal)
+//
+// For example:
+// class MruExample : public MruCache<void*, PtrInfo*, MruExample>
+// {
+// static HashNumber Hash(const KeyType& aKey)
+// {
+// return HashGeneric(aKey);
+// }
+// static Match(const KeyType& aKey, const ValueType& aVal)
+// {
+// return aVal->mPtr == aKey;
+// }
+// };
+template <class Key, class Value, class Cache, size_t Size = 31>
+class MruCache {
+ // Best distribution is achieved with a prime number. Ideally the closest
+ // to a power of two will be the most efficient use of memory. This
+ // assertion is pretty weak, but should catch the common inclination to
+ // use a power-of-two.
+ static_assert(Size % 2 != 0, "Use a prime number");
+
+ // This is a stronger assertion but significantly limits the values to just
+ // those close to a power-of-two value.
+ // static_assert(Size == 7 || Size == 13 || Size == 31 || Size == 61 ||
+ // Size == 127 || Size == 251 || Size == 509 || Size == 1021,
+ // "Use a prime number less than 1024");
+
+ public:
+ using KeyType = Key;
+ using ValueType = Value;
+
+ MruCache() = default;
+ MruCache(const MruCache&) = delete;
+ MruCache(const MruCache&&) = delete;
+
+ // Inserts the given value into the cache. Potentially overwrites an
+ // existing entry.
+ template <typename U>
+ void Put(const KeyType& aKey, U&& aVal) {
+ *RawEntry(aKey) = std::forward<U>(aVal);
+ }
+
+ // Removes the given entry if it is in the cache.
+ void Remove(const KeyType& aKey) { Lookup(aKey).Remove(); }
+
+ // Clears all cached entries and resets them to a default value.
+ void Clear() {
+ for (ValueType& val : mCache) {
+ val = ValueType{};
+ }
+ }
+
+ // Helper that holds an entry that matched a lookup key. Usage:
+ //
+ // auto p = mCache.Lookup(aKey);
+ // if (p) {
+ // return p.Data();
+ // }
+ //
+ // auto foo = new Foo();
+ // mTable.Insert(aKey, foo);
+ // p.Set(foo);
+ // return foo;
+ class Entry {
+ public:
+ Entry(ValueType* aEntry, bool aMatch) : mEntry(aEntry), mMatch(aMatch) {
+ MOZ_ASSERT(mEntry);
+ }
+
+ explicit operator bool() const { return mMatch; }
+
+ ValueType& Data() const {
+ MOZ_ASSERT(mMatch);
+ return *mEntry;
+ }
+
+ template <typename U>
+ void Set(U&& aValue) {
+ mMatch = true;
+ Data() = std::forward<U>(aValue);
+ }
+
+ void Remove() {
+ if (mMatch) {
+ Data() = ValueType{};
+ mMatch = false;
+ }
+ }
+
+ private:
+ ValueType* mEntry; // Location of the entry in the cache.
+ bool mMatch; // Whether the value matched.
+ };
+
+ // Retrieves an entry from the cache. Can be used to test if an entry is
+ // present, update the entry to a new value, or remove the entry if one was
+ // matched.
+ Entry Lookup(const KeyType& aKey) {
+ using EmptyChecker = detail::EmptyChecker<ValueType>;
+
+ auto entry = RawEntry(aKey);
+ bool match = EmptyChecker::IsNotEmpty(*entry) && Cache::Match(aKey, *entry);
+ return Entry(entry, match);
+ }
+
+ private:
+ MOZ_ALWAYS_INLINE ValueType* RawEntry(const KeyType& aKey) {
+ return &mCache[Cache::Hash(aKey) % Size];
+ }
+
+ ValueType mCache[Size] = {};
+};
+
+} // namespace mozilla
+
+#endif // mozilla_mrucache_h
diff --git a/xpcom/ds/Observer.h b/xpcom/ds/Observer.h
new file mode 100644
index 0000000000..3d189603eb
--- /dev/null
+++ b/xpcom/ds/Observer.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Observer_h
+#define mozilla_Observer_h
+
+#include "nsTObserverArray.h"
+
+namespace mozilla {
+
+/**
+ * Observer<T> provides a way for a class to observe something.
+ * When an event has to be broadcasted to all Observer<T>, Notify() method
+ * is called.
+ * T represents the type of the object passed in argument to Notify().
+ *
+ * @see ObserverList.
+ */
+template <class T>
+class Observer {
+ public:
+ virtual ~Observer() = default;
+ virtual void Notify(const T& aParam) = 0;
+};
+
+/**
+ * ObserverList<T> tracks Observer<T> and can notify them when Broadcast() is
+ * called.
+ * T represents the type of the object passed in argument to Broadcast() and
+ * sent to Observer<T> objects through Notify().
+ *
+ * @see Observer.
+ */
+template <class T>
+class ObserverList {
+ public:
+ /**
+ * Note: When calling AddObserver, it's up to the caller to make sure the
+ * object isn't going to be release as long as RemoveObserver hasn't been
+ * called.
+ *
+ * @see RemoveObserver()
+ */
+ void AddObserver(Observer<T>* aObserver) {
+ mObservers.AppendElementUnlessExists(aObserver);
+ }
+
+ /**
+ * Remove the observer from the observer list.
+ * @return Whether the observer has been found in the list.
+ */
+ bool RemoveObserver(Observer<T>* aObserver) {
+ return mObservers.RemoveElement(aObserver);
+ }
+
+ uint32_t Length() { return mObservers.Length(); }
+
+ /**
+ * Call Notify() on each item in the list.
+ */
+ void Broadcast(const T& aParam) {
+ for (Observer<T>* obs : mObservers.ForwardRange()) {
+ obs->Notify(aParam);
+ }
+ }
+
+ protected:
+ nsTObserverArray<Observer<T>*> mObservers;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_Observer_h
diff --git a/xpcom/ds/PLDHashTable.cpp b/xpcom/ds/PLDHashTable.cpp
new file mode 100644
index 0000000000..2814813f68
--- /dev/null
+++ b/xpcom/ds/PLDHashTable.cpp
@@ -0,0 +1,870 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <new>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "PLDHashTable.h"
+#include "nsDebug.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/ScopeExit.h"
+#include "nsAlgorithm.h"
+#include "nsPointerHashKeys.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ChaosMode.h"
+
+using namespace mozilla;
+
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+
+class AutoReadOp {
+ Checker& mChk;
+
+ public:
+ explicit AutoReadOp(Checker& aChk) : mChk(aChk) { mChk.StartReadOp(); }
+ ~AutoReadOp() { mChk.EndReadOp(); }
+};
+
+class AutoWriteOp {
+ Checker& mChk;
+
+ public:
+ explicit AutoWriteOp(Checker& aChk) : mChk(aChk) { mChk.StartWriteOp(); }
+ ~AutoWriteOp() { mChk.EndWriteOp(); }
+};
+
+class AutoIteratorRemovalOp {
+ Checker& mChk;
+
+ public:
+ explicit AutoIteratorRemovalOp(Checker& aChk) : mChk(aChk) {
+ mChk.StartIteratorRemovalOp();
+ }
+ ~AutoIteratorRemovalOp() { mChk.EndIteratorRemovalOp(); }
+};
+
+class AutoDestructorOp {
+ Checker& mChk;
+
+ public:
+ explicit AutoDestructorOp(Checker& aChk) : mChk(aChk) {
+ mChk.StartDestructorOp();
+ }
+ ~AutoDestructorOp() { mChk.EndDestructorOp(); }
+};
+
+#endif
+
+/* static */
+PLDHashNumber PLDHashTable::HashStringKey(const void* aKey) {
+ return HashString(static_cast<const char*>(aKey));
+}
+
+/* static */
+PLDHashNumber PLDHashTable::HashVoidPtrKeyStub(const void* aKey) {
+ return nsPtrHashKey<void>::HashKey(aKey);
+}
+
+/* static */
+bool PLDHashTable::MatchEntryStub(const PLDHashEntryHdr* aEntry,
+ const void* aKey) {
+ const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry;
+
+ return stub->key == aKey;
+}
+
+/* static */
+bool PLDHashTable::MatchStringKey(const PLDHashEntryHdr* aEntry,
+ const void* aKey) {
+ const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry;
+
+ // XXX tolerate null keys on account of sloppy Mozilla callers.
+ return stub->key == aKey ||
+ (stub->key && aKey &&
+ strcmp((const char*)stub->key, (const char*)aKey) == 0);
+}
+
+/* static */
+void PLDHashTable::MoveEntryStub(PLDHashTable* aTable,
+ const PLDHashEntryHdr* aFrom,
+ PLDHashEntryHdr* aTo) {
+ memcpy(aTo, aFrom, aTable->mEntrySize);
+}
+
+/* static */
+void PLDHashTable::ClearEntryStub(PLDHashTable* aTable,
+ PLDHashEntryHdr* aEntry) {
+ memset(aEntry, 0, aTable->mEntrySize);
+}
+
+static const PLDHashTableOps gStubOps = {
+ PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub, PLDHashTable::ClearEntryStub, nullptr};
+
+/* static */ const PLDHashTableOps* PLDHashTable::StubOps() {
+ return &gStubOps;
+}
+
+static bool SizeOfEntryStore(uint32_t aCapacity, uint32_t aEntrySize,
+ uint32_t* aNbytes) {
+ uint32_t slotSize = aEntrySize + sizeof(PLDHashNumber);
+ uint64_t nbytes64 = uint64_t(aCapacity) * uint64_t(slotSize);
+ *aNbytes = aCapacity * slotSize;
+ return uint64_t(*aNbytes) == nbytes64; // returns false on overflow
+}
+
+// Compute max and min load numbers (entry counts). We have a secondary max
+// that allows us to overload a table reasonably if it cannot be grown further
+// (i.e. if ChangeTable() fails). The table slows down drastically if the
+// secondary max is too close to 1, but 0.96875 gives only a slight slowdown
+// while allowing 1.3x more elements.
+static inline uint32_t MaxLoad(uint32_t aCapacity) {
+ return aCapacity - (aCapacity >> 2); // == aCapacity * 0.75
+}
+static inline uint32_t MaxLoadOnGrowthFailure(uint32_t aCapacity) {
+ return aCapacity - (aCapacity >> 5); // == aCapacity * 0.96875
+}
+static inline uint32_t MinLoad(uint32_t aCapacity) {
+ return aCapacity >> 2; // == aCapacity * 0.25
+}
+
+// Compute the minimum capacity (and the Log2 of that capacity) for a table
+// containing |aLength| elements while respecting the following contraints:
+// - table must be at most 75% full;
+// - capacity must be a power of two;
+// - capacity cannot be too small.
+static inline void BestCapacity(uint32_t aLength, uint32_t* aCapacityOut,
+ uint32_t* aLog2CapacityOut) {
+ // Callers should ensure this is true.
+ MOZ_ASSERT(aLength <= PLDHashTable::kMaxInitialLength);
+
+ // Compute the smallest capacity allowing |aLength| elements to be inserted
+ // without rehashing.
+ uint32_t capacity = (aLength * 4 + (3 - 1)) / 3; // == ceil(aLength * 4 / 3)
+ if (capacity < PLDHashTable::kMinCapacity) {
+ capacity = PLDHashTable::kMinCapacity;
+ }
+
+ // Round up capacity to next power-of-two.
+ uint32_t log2 = CeilingLog2(capacity);
+ capacity = 1u << log2;
+ MOZ_ASSERT(capacity <= PLDHashTable::kMaxCapacity);
+
+ *aCapacityOut = capacity;
+ *aLog2CapacityOut = log2;
+}
+
+/* static */ MOZ_ALWAYS_INLINE uint32_t
+PLDHashTable::HashShift(uint32_t aEntrySize, uint32_t aLength) {
+ if (aLength > kMaxInitialLength) {
+ MOZ_CRASH("Initial length is too large");
+ }
+
+ uint32_t capacity, log2;
+ BestCapacity(aLength, &capacity, &log2);
+
+ uint32_t nbytes;
+ if (!SizeOfEntryStore(capacity, aEntrySize, &nbytes)) {
+ MOZ_CRASH("Initial entry store size is too large");
+ }
+
+ // Compute the hashShift value.
+ return kPLDHashNumberBits - log2;
+}
+
+PLDHashTable::PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize,
+ uint32_t aLength)
+ : mOps(aOps),
+ mEntryStore(),
+ mGeneration(0),
+ mHashShift(HashShift(aEntrySize, aLength)),
+ mEntrySize(aEntrySize),
+ mEntryCount(0),
+ mRemovedCount(0) {
+ // An entry size greater than 0xff is unlikely, but let's check anyway. If
+ // you hit this, your hashtable would waste lots of space for unused entries
+ // and you should change your hash table's entries to pointers.
+ if (aEntrySize != uint32_t(mEntrySize)) {
+ MOZ_CRASH("Entry size is too large");
+ }
+}
+
+PLDHashTable& PLDHashTable::operator=(PLDHashTable&& aOther) {
+ if (this == &aOther) {
+ return *this;
+ }
+
+ // |mOps| and |mEntrySize| are required to stay the same, they're
+ // conceptually part of the type -- indeed, if PLDHashTable was a templated
+ // type like nsTHashtable, they *would* be part of the type -- so it only
+ // makes sense to assign in cases where they match.
+ MOZ_RELEASE_ASSERT(mOps == aOther.mOps || !mOps);
+ MOZ_RELEASE_ASSERT(mEntrySize == aOther.mEntrySize || !mEntrySize);
+
+ // Reconstruct |this|.
+ const PLDHashTableOps* ops = aOther.mOps;
+ this->~PLDHashTable();
+ new (KnownNotNull, this) PLDHashTable(ops, aOther.mEntrySize, 0);
+
+ // Move non-const pieces over.
+ mHashShift = std::move(aOther.mHashShift);
+ mEntryCount = std::move(aOther.mEntryCount);
+ mRemovedCount = std::move(aOther.mRemovedCount);
+ mEntryStore.Set(aOther.mEntryStore.Get(), &mGeneration);
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ mChecker = std::move(aOther.mChecker);
+#endif
+
+ // Clear up |aOther| so its destruction will be a no-op and it reports being
+ // empty.
+ {
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ AutoDestructorOp op(mChecker);
+#endif
+ aOther.mEntryCount = 0;
+ aOther.mEntryStore.Set(nullptr, &aOther.mGeneration);
+ }
+
+ return *this;
+}
+
+PLDHashNumber PLDHashTable::Hash1(PLDHashNumber aHash0) const {
+ return aHash0 >> mHashShift;
+}
+
+void PLDHashTable::Hash2(PLDHashNumber aHash0, uint32_t& aHash2Out,
+ uint32_t& aSizeMaskOut) const {
+ uint32_t sizeLog2 = kPLDHashNumberBits - mHashShift;
+ uint32_t sizeMask = (PLDHashNumber(1) << sizeLog2) - 1;
+ aSizeMaskOut = sizeMask;
+
+ // The incoming aHash0 always has the low bit unset (since we leave it
+ // free for the collision flag), and should have reasonably random
+ // data in the other 31 bits. We used the high bits of aHash0 for
+ // Hash1, so we use the low bits here. If the table size is large,
+ // the bits we use may overlap, but that's still more random than
+ // filling with 0s.
+ //
+ // Double hashing needs the second hash code to be relatively prime to table
+ // size, so we simply make hash2 odd.
+ //
+ // This also conveniently covers up the fact that we have the low bit
+ // unset since aHash0 has the low bit unset.
+ aHash2Out = (aHash0 & sizeMask) | 1;
+}
+
+// Reserve mKeyHash 0 for free entries and 1 for removed-entry sentinels. Note
+// that a removed-entry sentinel need be stored only if the removed entry had
+// a colliding entry added after it. Therefore we can use 1 as the collision
+// flag in addition to the removed-entry sentinel value. Multiplicative hash
+// uses the high order bits of mKeyHash, so this least-significant reservation
+// should not hurt the hash function's effectiveness much.
+
+// Match an entry's mKeyHash against an unstored one computed from a key.
+/* static */
+bool PLDHashTable::MatchSlotKeyhash(Slot& aSlot, const PLDHashNumber aKeyHash) {
+ return (aSlot.KeyHash() & ~kCollisionFlag) == aKeyHash;
+}
+
+// Compute the address of the indexed entry in table.
+auto PLDHashTable::SlotForIndex(uint32_t aIndex) const -> Slot {
+ return mEntryStore.SlotForIndex(aIndex, mEntrySize, CapacityFromHashShift());
+}
+
+PLDHashTable::~PLDHashTable() {
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ AutoDestructorOp op(mChecker);
+#endif
+
+ if (!mEntryStore.IsAllocated()) {
+ return;
+ }
+
+ // Clear any remaining live entries (if not trivially destructible).
+ if (mOps->clearEntry) {
+ mEntryStore.ForEachSlot(Capacity(), mEntrySize, [&](const Slot& aSlot) {
+ if (aSlot.IsLive()) {
+ mOps->clearEntry(this, aSlot.ToEntry());
+ }
+ });
+ }
+
+ // Entry storage is freed last, by ~EntryStore().
+}
+
+void PLDHashTable::ClearAndPrepareForLength(uint32_t aLength) {
+ // Get these values before the destructor clobbers them.
+ const PLDHashTableOps* ops = mOps;
+ uint32_t entrySize = mEntrySize;
+
+ this->~PLDHashTable();
+ new (KnownNotNull, this) PLDHashTable(ops, entrySize, aLength);
+}
+
+void PLDHashTable::Clear() { ClearAndPrepareForLength(kDefaultInitialLength); }
+
+// If |Reason| is |ForAdd|, the return value is always non-null and it may be
+// a previously-removed entry. If |Reason| is |ForSearchOrRemove|, the return
+// value is null on a miss, and will never be a previously-removed entry on a
+// hit. This distinction is a bit grotty but this function is hot enough that
+// these differences are worthwhile. (It's also hot enough that
+// MOZ_ALWAYS_INLINE makes a significant difference.)
+template <PLDHashTable::SearchReason Reason, typename Success, typename Failure>
+MOZ_ALWAYS_INLINE auto PLDHashTable::SearchTable(const void* aKey,
+ PLDHashNumber aKeyHash,
+ Success&& aSuccess,
+ Failure&& aFailure) const {
+ MOZ_ASSERT(mEntryStore.IsAllocated());
+ NS_ASSERTION(!(aKeyHash & kCollisionFlag), "!(aKeyHash & kCollisionFlag)");
+
+ // Compute the primary hash address.
+ PLDHashNumber hash1 = Hash1(aKeyHash);
+ Slot slot = SlotForIndex(hash1);
+
+ // Miss: return space for a new entry.
+ if (slot.IsFree()) {
+ return (Reason == ForAdd) ? aSuccess(slot) : aFailure();
+ }
+
+ // Hit: return entry.
+ PLDHashMatchEntry matchEntry = mOps->matchEntry;
+ if (MatchSlotKeyhash(slot, aKeyHash)) {
+ PLDHashEntryHdr* e = slot.ToEntry();
+ if (matchEntry(e, aKey)) {
+ return aSuccess(slot);
+ }
+ }
+
+ // Collision: double hash.
+ PLDHashNumber hash2;
+ uint32_t sizeMask;
+ Hash2(aKeyHash, hash2, sizeMask);
+
+ // Save the first removed entry slot so Add() can recycle it. (Only used
+ // if Reason==ForAdd.)
+ Maybe<Slot> firstRemoved;
+
+ for (;;) {
+ if (Reason == ForAdd && !firstRemoved) {
+ if (MOZ_UNLIKELY(slot.IsRemoved())) {
+ firstRemoved.emplace(slot);
+ } else {
+ slot.MarkColliding();
+ }
+ }
+
+ hash1 -= hash2;
+ hash1 &= sizeMask;
+
+ slot = SlotForIndex(hash1);
+ if (slot.IsFree()) {
+ if (Reason != ForAdd) {
+ return aFailure();
+ }
+ return aSuccess(firstRemoved.refOr(slot));
+ }
+
+ if (MatchSlotKeyhash(slot, aKeyHash)) {
+ PLDHashEntryHdr* e = slot.ToEntry();
+ if (matchEntry(e, aKey)) {
+ return aSuccess(slot);
+ }
+ }
+ }
+
+ // NOTREACHED
+ return aFailure();
+}
+
+// This is a copy of SearchTable(), used by ChangeTable(), hardcoded to
+// 1. assume |Reason| is |ForAdd|,
+// 2. assume that |aKey| will never match an existing entry, and
+// 3. assume that no entries have been removed from the current table
+// structure.
+// Avoiding the need for |aKey| means we can avoid needing a way to map entries
+// to keys, which means callers can use complex key types more easily.
+MOZ_ALWAYS_INLINE auto PLDHashTable::FindFreeSlot(PLDHashNumber aKeyHash) const
+ -> Slot {
+ MOZ_ASSERT(mEntryStore.IsAllocated());
+ NS_ASSERTION(!(aKeyHash & kCollisionFlag), "!(aKeyHash & kCollisionFlag)");
+
+ // Compute the primary hash address.
+ PLDHashNumber hash1 = Hash1(aKeyHash);
+ Slot slot = SlotForIndex(hash1);
+
+ // Miss: return space for a new entry.
+ if (slot.IsFree()) {
+ return slot;
+ }
+
+ // Collision: double hash.
+ PLDHashNumber hash2;
+ uint32_t sizeMask;
+ Hash2(aKeyHash, hash2, sizeMask);
+
+ for (;;) {
+ MOZ_ASSERT(!slot.IsRemoved());
+ slot.MarkColliding();
+
+ hash1 -= hash2;
+ hash1 &= sizeMask;
+
+ slot = SlotForIndex(hash1);
+ if (slot.IsFree()) {
+ return slot;
+ }
+ }
+
+ // NOTREACHED
+}
+
+bool PLDHashTable::ChangeTable(int32_t aDeltaLog2) {
+ MOZ_ASSERT(mEntryStore.IsAllocated());
+
+ // Look, but don't touch, until we succeed in getting new entry store.
+ int32_t oldLog2 = kPLDHashNumberBits - mHashShift;
+ int32_t newLog2 = oldLog2 + aDeltaLog2;
+ uint32_t newCapacity = 1u << newLog2;
+ if (newCapacity > kMaxCapacity) {
+ return false;
+ }
+
+ uint32_t nbytes;
+ if (!SizeOfEntryStore(newCapacity, mEntrySize, &nbytes)) {
+ return false; // overflowed
+ }
+
+ char* newEntryStore = (char*)calloc(1, nbytes);
+ if (!newEntryStore) {
+ return false;
+ }
+
+ // We can't fail from here on, so update table parameters.
+ mHashShift = kPLDHashNumberBits - newLog2;
+ mRemovedCount = 0;
+
+ // Assign the new entry store to table.
+ char* oldEntryStore = mEntryStore.Get();
+ mEntryStore.Set(newEntryStore, &mGeneration);
+ PLDHashMoveEntry moveEntry = mOps->moveEntry;
+
+ // Copy only live entries, leaving removed ones behind.
+ uint32_t oldCapacity = 1u << oldLog2;
+ EntryStore::ForEachSlot(
+ oldEntryStore, oldCapacity, mEntrySize, [&](const Slot& slot) {
+ if (slot.IsLive()) {
+ const PLDHashNumber key = slot.KeyHash() & ~kCollisionFlag;
+ Slot newSlot = FindFreeSlot(key);
+ MOZ_ASSERT(newSlot.IsFree());
+ moveEntry(this, slot.ToEntry(), newSlot.ToEntry());
+ newSlot.SetKeyHash(key);
+ }
+ });
+
+ free(oldEntryStore);
+ return true;
+}
+
+MOZ_ALWAYS_INLINE PLDHashNumber
+PLDHashTable::ComputeKeyHash(const void* aKey) const {
+ MOZ_ASSERT(mEntryStore.IsAllocated());
+
+ PLDHashNumber keyHash = mozilla::ScrambleHashCode(mOps->hashKey(aKey));
+
+ // Avoid 0 and 1 hash codes, they indicate free and removed entries.
+ if (keyHash < 2) {
+ keyHash -= 2;
+ }
+ keyHash &= ~kCollisionFlag;
+
+ return keyHash;
+}
+
+PLDHashEntryHdr* PLDHashTable::Search(const void* aKey) const {
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ AutoReadOp op(mChecker);
+#endif
+
+ if (!mEntryStore.IsAllocated()) {
+ return nullptr;
+ }
+
+ return SearchTable<ForSearchOrRemove>(
+ aKey, ComputeKeyHash(aKey),
+ [&](Slot& slot) -> PLDHashEntryHdr* { return slot.ToEntry(); },
+ [&]() -> PLDHashEntryHdr* { return nullptr; });
+}
+
+PLDHashEntryHdr* PLDHashTable::Add(const void* aKey,
+ const mozilla::fallible_t& aFallible) {
+ auto maybeEntryHandle = MakeEntryHandle(aKey, aFallible);
+ if (!maybeEntryHandle) {
+ return nullptr;
+ }
+ return maybeEntryHandle->OrInsert([&aKey, this](PLDHashEntryHdr* entry) {
+ if (mOps->initEntry) {
+ mOps->initEntry(entry, aKey);
+ }
+ });
+}
+
+PLDHashEntryHdr* PLDHashTable::Add(const void* aKey) {
+ return MakeEntryHandle(aKey).OrInsert([&aKey, this](PLDHashEntryHdr* entry) {
+ if (mOps->initEntry) {
+ mOps->initEntry(entry, aKey);
+ }
+ });
+}
+
+void PLDHashTable::Remove(const void* aKey) {
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ AutoWriteOp op(mChecker);
+#endif
+
+ if (!mEntryStore.IsAllocated()) {
+ return;
+ }
+
+ PLDHashNumber keyHash = ComputeKeyHash(aKey);
+ SearchTable<ForSearchOrRemove>(
+ aKey, keyHash,
+ [&](Slot& slot) {
+ RawRemove(slot);
+ ShrinkIfAppropriate();
+ },
+ [&]() {
+ // Do nothing.
+ });
+}
+
+void PLDHashTable::RemoveEntry(PLDHashEntryHdr* aEntry) {
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ AutoWriteOp op(mChecker);
+#endif
+
+ RawRemove(aEntry);
+ ShrinkIfAppropriate();
+}
+
+void PLDHashTable::RawRemove(PLDHashEntryHdr* aEntry) {
+ Slot slot(mEntryStore.SlotForPLDHashEntry(aEntry, Capacity(), mEntrySize));
+ RawRemove(slot);
+}
+
+void PLDHashTable::RawRemove(Slot& aSlot) {
+ // Unfortunately, we can only do weak checking here. That's because
+ // RawRemove() can be called legitimately while an Enumerate() call is
+ // active, which doesn't fit well into how Checker's mState variable works.
+ MOZ_ASSERT(mChecker.IsWritable());
+
+ MOZ_ASSERT(mEntryStore.IsAllocated());
+
+ MOZ_ASSERT(aSlot.IsLive());
+
+ // Load keyHash first in case clearEntry() goofs it.
+ PLDHashNumber keyHash = aSlot.KeyHash();
+ if (mOps->clearEntry) {
+ PLDHashEntryHdr* entry = aSlot.ToEntry();
+ mOps->clearEntry(this, entry);
+ }
+ if (keyHash & kCollisionFlag) {
+ aSlot.MarkRemoved();
+ mRemovedCount++;
+ } else {
+ aSlot.MarkFree();
+ }
+ mEntryCount--;
+}
+
+// Shrink or compress if a quarter or more of all entries are removed, or if the
+// table is underloaded according to the minimum alpha, and is not minimal-size
+// already.
+void PLDHashTable::ShrinkIfAppropriate() {
+ uint32_t capacity = Capacity();
+ if (mRemovedCount >= capacity >> 2 ||
+ (capacity > kMinCapacity && mEntryCount <= MinLoad(capacity))) {
+ uint32_t log2;
+ BestCapacity(mEntryCount, &capacity, &log2);
+
+ int32_t deltaLog2 = log2 - (kPLDHashNumberBits - mHashShift);
+ MOZ_ASSERT(deltaLog2 <= 0);
+
+ (void)ChangeTable(deltaLog2);
+ }
+}
+
+size_t PLDHashTable::ShallowSizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ AutoReadOp op(mChecker);
+#endif
+
+ return aMallocSizeOf(mEntryStore.Get());
+}
+
+size_t PLDHashTable::ShallowSizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf);
+}
+
+mozilla::Maybe<PLDHashTable::EntryHandle> PLDHashTable::MakeEntryHandle(
+ const void* aKey, const mozilla::fallible_t&) {
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ mChecker.StartWriteOp();
+ auto endWriteOp = MakeScopeExit([&] { mChecker.EndWriteOp(); });
+#endif
+
+ // Allocate the entry storage if it hasn't already been allocated.
+ if (!mEntryStore.IsAllocated()) {
+ uint32_t nbytes;
+ // We already checked this in the constructor, so it must still be true.
+ MOZ_RELEASE_ASSERT(
+ SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes));
+ mEntryStore.Set((char*)calloc(1, nbytes), &mGeneration);
+ if (!mEntryStore.IsAllocated()) {
+ return Nothing();
+ }
+ }
+
+ // If alpha is >= .75, grow or compress the table. If aKey is already in the
+ // table, we may grow once more than necessary, but only if we are on the
+ // edge of being overloaded.
+ uint32_t capacity = Capacity();
+ if (mEntryCount + mRemovedCount >= MaxLoad(capacity)) {
+ // Compress if a quarter or more of all entries are removed.
+ int deltaLog2 = 1;
+ if (mRemovedCount >= capacity >> 2) {
+ deltaLog2 = 0;
+ }
+
+ // Grow or compress the table. If ChangeTable() fails, allow overloading up
+ // to the secondary max. Once we hit the secondary max, return null.
+ if (!ChangeTable(deltaLog2) &&
+ mEntryCount + mRemovedCount >= MaxLoadOnGrowthFailure(capacity)) {
+ return Nothing();
+ }
+ }
+
+ // Look for entry after possibly growing, so we don't have to add it,
+ // then skip it while growing the table and re-add it after.
+ PLDHashNumber keyHash = ComputeKeyHash(aKey);
+ Slot slot = SearchTable<ForAdd>(
+ aKey, keyHash, [](Slot& found) -> Slot { return found; },
+ []() -> Slot {
+ MOZ_CRASH("Nope");
+ return Slot(nullptr, nullptr);
+ });
+
+ // The `EntryHandle` will handle ending the write op when it is destroyed.
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ endWriteOp.release();
+#endif
+
+ return Some(EntryHandle{this, keyHash, slot});
+}
+
+PLDHashTable::EntryHandle PLDHashTable::MakeEntryHandle(const void* aKey) {
+ auto res = MakeEntryHandle(aKey, fallible);
+ if (!res) {
+ if (!mEntryStore.IsAllocated()) {
+ // We OOM'd while allocating the initial entry storage.
+ uint32_t nbytes;
+ (void)SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes);
+ NS_ABORT_OOM(nbytes);
+ } else {
+ // We failed to resize the existing entry storage, either due to OOM or
+ // because we exceeded the maximum table capacity or size; report it as
+ // an OOM. The multiplication by 2 gets us the size we tried to allocate,
+ // which is double the current size.
+ NS_ABORT_OOM(2 * EntrySize() * EntryCount());
+ }
+ }
+ return res.extract();
+}
+
+PLDHashTable::EntryHandle::EntryHandle(PLDHashTable* aTable,
+ PLDHashNumber aKeyHash, Slot aSlot)
+ : mTable(aTable), mKeyHash(aKeyHash), mSlot(aSlot) {}
+
+PLDHashTable::EntryHandle::EntryHandle(EntryHandle&& aOther) noexcept
+ : mTable(std::exchange(aOther.mTable, nullptr)),
+ mKeyHash(aOther.mKeyHash),
+ mSlot(aOther.mSlot) {}
+
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+PLDHashTable::EntryHandle::~EntryHandle() {
+ if (!mTable) {
+ return;
+ }
+
+ mTable->mChecker.EndWriteOp();
+}
+#endif
+
+void PLDHashTable::EntryHandle::Remove() {
+ MOZ_ASSERT(HasEntry());
+
+ mTable->RawRemove(mSlot);
+}
+
+void PLDHashTable::EntryHandle::OrRemove() {
+ if (HasEntry()) {
+ Remove();
+ }
+}
+
+void PLDHashTable::EntryHandle::OccupySlot() {
+ MOZ_ASSERT(!HasEntry());
+
+ PLDHashNumber keyHash = mKeyHash;
+ if (mSlot.IsRemoved()) {
+ mTable->mRemovedCount--;
+ keyHash |= kCollisionFlag;
+ }
+ mSlot.SetKeyHash(keyHash);
+ mTable->mEntryCount++;
+}
+
+PLDHashTable::Iterator::Iterator(Iterator&& aOther)
+ : mTable(aOther.mTable),
+ mCurrent(aOther.mCurrent),
+ mNexts(aOther.mNexts),
+ mNextsLimit(aOther.mNextsLimit),
+ mHaveRemoved(aOther.mHaveRemoved),
+ mEntrySize(aOther.mEntrySize) {
+ // No need to change |mChecker| here.
+ aOther.mTable = nullptr;
+ // We don't really have the concept of a null slot, so leave mCurrent.
+ aOther.mNexts = 0;
+ aOther.mNextsLimit = 0;
+ aOther.mHaveRemoved = false;
+ aOther.mEntrySize = 0;
+}
+
+PLDHashTable::Iterator::Iterator(PLDHashTable* aTable)
+ : mTable(aTable),
+ mCurrent(mTable->mEntryStore.SlotForIndex(0, mTable->mEntrySize,
+ mTable->Capacity())),
+ mNexts(0),
+ mNextsLimit(mTable->EntryCount()),
+ mHaveRemoved(false),
+ mEntrySize(aTable->mEntrySize) {
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ mTable->mChecker.StartReadOp();
+#endif
+
+ if (ChaosMode::isActive(ChaosFeature::HashTableIteration) &&
+ mTable->Capacity() > 0) {
+ // Start iterating at a random entry. It would be even more chaotic to
+ // iterate in fully random order, but that's harder.
+ uint32_t capacity = mTable->CapacityFromHashShift();
+ uint32_t i = ChaosMode::randomUint32LessThan(capacity);
+ mCurrent =
+ mTable->mEntryStore.SlotForIndex(i, mTable->mEntrySize, capacity);
+ }
+
+ // Advance to the first live entry, if there is one.
+ if (!Done() && IsOnNonLiveEntry()) {
+ MoveToNextLiveEntry();
+ }
+}
+
+PLDHashTable::Iterator::Iterator(PLDHashTable* aTable, EndIteratorTag aTag)
+ : mTable(aTable),
+ mCurrent(mTable->mEntryStore.SlotForIndex(0, mTable->mEntrySize,
+ mTable->Capacity())),
+ mNexts(mTable->EntryCount()),
+ mNextsLimit(mTable->EntryCount()),
+ mHaveRemoved(false),
+ mEntrySize(aTable->mEntrySize) {
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ mTable->mChecker.StartReadOp();
+#endif
+
+ MOZ_ASSERT(Done());
+}
+
+PLDHashTable::Iterator::Iterator(const Iterator& aOther)
+ : mTable(aOther.mTable),
+ mCurrent(aOther.mCurrent),
+ mNexts(aOther.mNexts),
+ mNextsLimit(aOther.mNextsLimit),
+ mHaveRemoved(aOther.mHaveRemoved),
+ mEntrySize(aOther.mEntrySize) {
+ // TODO: Is this necessary?
+ MOZ_ASSERT(!mHaveRemoved);
+
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ mTable->mChecker.StartReadOp();
+#endif
+}
+
+PLDHashTable::Iterator::~Iterator() {
+ if (mTable) {
+ if (mHaveRemoved) {
+ mTable->ShrinkIfAppropriate();
+ }
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ mTable->mChecker.EndReadOp();
+#endif
+ }
+}
+
+MOZ_ALWAYS_INLINE bool PLDHashTable::Iterator::IsOnNonLiveEntry() const {
+ MOZ_ASSERT(!Done());
+ return !mCurrent.IsLive();
+}
+
+void PLDHashTable::Iterator::Next() {
+ MOZ_ASSERT(!Done());
+
+ mNexts++;
+
+ // Advance to the next live entry, if there is one.
+ if (!Done()) {
+ MoveToNextLiveEntry();
+ }
+}
+
+MOZ_ALWAYS_INLINE void PLDHashTable::Iterator::MoveToNextLiveEntry() {
+ // Chaos mode requires wraparound to cover all possible entries, so we can't
+ // simply move to the next live entry and stop when we hit the end of the
+ // entry store. But we don't want to introduce extra branches into our inner
+ // loop. So we are going to exploit the structure of the entry store in this
+ // method to implement an efficient inner loop.
+ //
+ // The idea is that since we are really only iterating through the stored
+ // hashes and because we know that there are a power-of-two number of
+ // hashes, we can use masking to implement the wraparound for us. This
+ // method does have the downside of needing to recalculate where the
+ // associated entry is once we've found it, but that seems OK.
+
+ // Our current slot and its associated hash.
+ Slot slot = mCurrent;
+ PLDHashNumber* p = slot.HashPtr();
+ const uint32_t capacity = mTable->CapacityFromHashShift();
+ const uint32_t mask = capacity - 1;
+ auto hashes = reinterpret_cast<PLDHashNumber*>(mTable->mEntryStore.Get());
+ uint32_t slotIndex = p - hashes;
+
+ do {
+ slotIndex = (slotIndex + 1) & mask;
+ } while (!Slot::IsLiveHash(hashes[slotIndex]));
+
+ // slotIndex now indicates where a live slot is. Rematerialize the entry
+ // and the slot.
+ mCurrent = mTable->mEntryStore.SlotForIndex(slotIndex, mEntrySize, capacity);
+}
+
+void PLDHashTable::Iterator::Remove() {
+ mTable->RawRemove(mCurrent);
+ mHaveRemoved = true;
+}
diff --git a/xpcom/ds/PLDHashTable.h b/xpcom/ds/PLDHashTable.h
new file mode 100644
index 0000000000..af17ee6fd6
--- /dev/null
+++ b/xpcom/ds/PLDHashTable.h
@@ -0,0 +1,805 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// See the comment at the top of mfbt/HashTable.h for a comparison between
+// PLDHashTable and mozilla::HashTable.
+
+#ifndef PLDHashTable_h
+#define PLDHashTable_h
+
+#include <utility>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/fallible.h"
+#include "nscore.h"
+
+using PLDHashNumber = mozilla::HashNumber;
+static const uint32_t kPLDHashNumberBits = mozilla::kHashNumberBits;
+
+#if defined(DEBUG) || defined(FUZZING)
+# define MOZ_HASH_TABLE_CHECKS_ENABLED 1
+#endif
+
+class PLDHashTable;
+struct PLDHashTableOps;
+
+// Table entry header structure.
+//
+// In order to allow in-line allocation of key and value, we do not declare
+// either here. Instead, the API uses const void *key as a formal parameter.
+// The key need not be stored in the entry; it may be part of the value, but
+// need not be stored at all.
+//
+// Callback types are defined below and grouped into the PLDHashTableOps
+// structure, for single static initialization per hash table sub-type.
+//
+// Each hash table sub-type should make its entry type a subclass of
+// PLDHashEntryHdr. PLDHashEntryHdr is merely a common superclass to present a
+// uniform interface to PLDHashTable clients. The zero-sized base class
+// optimization, employed by all of our supported C++ compilers, will ensure
+// that this abstraction does not make objects needlessly larger.
+struct PLDHashEntryHdr {
+ PLDHashEntryHdr() = default;
+ PLDHashEntryHdr(const PLDHashEntryHdr&) = delete;
+ PLDHashEntryHdr& operator=(const PLDHashEntryHdr&) = delete;
+ PLDHashEntryHdr(PLDHashEntryHdr&&) = default;
+ PLDHashEntryHdr& operator=(PLDHashEntryHdr&&) = default;
+
+ private:
+ friend class PLDHashTable;
+};
+
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+
+// This class does three kinds of checking:
+//
+// - that calls to one of |mOps| or to an enumerator do not cause re-entry into
+// the table in an unsafe way;
+//
+// - that multiple threads do not access the table in an unsafe way;
+//
+// - that a table marked as immutable is not modified.
+//
+// "Safe" here means that multiple concurrent read operations are ok, but a
+// write operation (i.e. one that can cause the entry storage to be reallocated
+// or destroyed) cannot safely run concurrently with another read or write
+// operation. This meaning of "safe" is only partial; for example, it does not
+// cover whether a single entry in the table is modified by two separate
+// threads. (Doing such checking would be much harder.)
+//
+// It does this with two variables:
+//
+// - mState, which embodies a tri-stage tagged union with the following
+// variants:
+// - Idle
+// - Read(n), where 'n' is the number of concurrent read operations
+// - Write
+//
+// - mIsWritable, which indicates if the table is mutable.
+//
+class Checker {
+ public:
+ constexpr Checker() : mState(kIdle), mIsWritable(true) {}
+
+ Checker& operator=(Checker&& aOther) {
+ // Atomic<> doesn't have an |operator=(Atomic<>&&)|.
+ mState = uint32_t(aOther.mState);
+ mIsWritable = bool(aOther.mIsWritable);
+
+ aOther.mState = kIdle;
+ // XXX Shouldn't we set mIsWritable to true here for consistency?
+
+ return *this;
+ }
+
+ static bool IsIdle(uint32_t aState) { return aState == kIdle; }
+ static bool IsRead(uint32_t aState) {
+ return kRead1 <= aState && aState <= kReadMax;
+ }
+ static bool IsRead1(uint32_t aState) { return aState == kRead1; }
+ static bool IsWrite(uint32_t aState) { return aState == kWrite; }
+
+ bool IsIdle() const { return mState == kIdle; }
+
+ bool IsWritable() const { return mIsWritable; }
+
+ void SetNonWritable() { mIsWritable = false; }
+
+ // NOTE: the obvious way to implement these functions is to (a) check
+ // |mState| is reasonable, and then (b) update |mState|. But the lack of
+ // atomicity in such an implementation can cause problems if we get unlucky
+ // thread interleaving between (a) and (b).
+ //
+ // So instead for |mState| we are careful to (a) first get |mState|'s old
+ // value and assign it a new value in single atomic operation, and only then
+ // (b) check the old value was reasonable. This ensures we don't have
+ // interleaving problems.
+ //
+ // For |mIsWritable| we don't need to be as careful because it can only in
+ // transition in one direction (from writable to non-writable).
+
+ void StartReadOp() {
+ uint32_t oldState = mState++; // this is an atomic increment
+ MOZ_RELEASE_ASSERT(IsIdle(oldState) || IsRead(oldState));
+ MOZ_RELEASE_ASSERT(oldState < kReadMax); // check for overflow
+ }
+
+ void EndReadOp() {
+ uint32_t oldState = mState--; // this is an atomic decrement
+ MOZ_RELEASE_ASSERT(IsRead(oldState));
+ }
+
+ void StartWriteOp() {
+ MOZ_RELEASE_ASSERT(IsWritable());
+ uint32_t oldState = mState.exchange(kWrite);
+ MOZ_RELEASE_ASSERT(IsIdle(oldState));
+ }
+
+ void EndWriteOp() {
+ // Check again that the table is writable, in case it was marked as
+ // non-writable just after the IsWritable() assertion in StartWriteOp()
+ // occurred.
+ MOZ_RELEASE_ASSERT(IsWritable());
+ uint32_t oldState = mState.exchange(kIdle);
+ MOZ_RELEASE_ASSERT(IsWrite(oldState));
+ }
+
+ void StartIteratorRemovalOp() {
+ // When doing removals at the end of iteration, we go from Read1 state to
+ // Write and then back.
+ MOZ_RELEASE_ASSERT(IsWritable());
+ uint32_t oldState = mState.exchange(kWrite);
+ MOZ_RELEASE_ASSERT(IsRead1(oldState));
+ }
+
+ void EndIteratorRemovalOp() {
+ // Check again that the table is writable, in case it was marked as
+ // non-writable just after the IsWritable() assertion in
+ // StartIteratorRemovalOp() occurred.
+ MOZ_RELEASE_ASSERT(IsWritable());
+ uint32_t oldState = mState.exchange(kRead1);
+ MOZ_RELEASE_ASSERT(IsWrite(oldState));
+ }
+
+ void StartDestructorOp() {
+ // A destructor op is like a write, but the table doesn't need to be
+ // writable.
+ uint32_t oldState = mState.exchange(kWrite);
+ MOZ_RELEASE_ASSERT(IsIdle(oldState));
+ }
+
+ void EndDestructorOp() {
+ uint32_t oldState = mState.exchange(kIdle);
+ MOZ_RELEASE_ASSERT(IsWrite(oldState));
+ }
+
+ private:
+ // Things of note about the representation of |mState|.
+ // - The values between kRead1..kReadMax represent valid Read(n) values.
+ // - kIdle and kRead1 are deliberately chosen so that incrementing the -
+ // former gives the latter.
+ // - 9999 concurrent readers should be enough for anybody.
+ static const uint32_t kIdle = 0;
+ static const uint32_t kRead1 = 1;
+ static const uint32_t kReadMax = 9999;
+ static const uint32_t kWrite = 10000;
+
+ mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent> mState;
+ mozilla::Atomic<bool, mozilla::SequentiallyConsistent> mIsWritable;
+};
+#endif
+
+// A PLDHashTable may be allocated on the stack or within another structure or
+// class. No entry storage is allocated until the first element is added. This
+// means that empty hash tables are cheap, which is good because they are
+// common.
+//
+// There used to be a long, math-heavy comment here about the merits of
+// double hashing vs. chaining; it was removed in bug 1058335. In short, double
+// hashing is more space-efficient unless the element size gets large (in which
+// case you should keep using double hashing but switch to using pointer
+// elements). Also, with double hashing, you can't safely hold an entry pointer
+// and use it after an add or remove operation, unless you sample Generation()
+// before adding or removing, and compare the sample after, dereferencing the
+// entry pointer only if Generation() has not changed.
+class PLDHashTable {
+ private:
+ // A slot represents a cached hash value and its associated entry stored in
+ // the hash table. The hash value and the entry are not stored contiguously.
+ struct Slot {
+ Slot(PLDHashEntryHdr* aEntry, PLDHashNumber* aKeyHash)
+ : mEntry(aEntry), mKeyHash(aKeyHash) {}
+
+ Slot(const Slot&) = default;
+ Slot(Slot&& aOther) = default;
+
+ Slot& operator=(Slot&& aOther) = default;
+
+ bool operator==(const Slot& aOther) { return mEntry == aOther.mEntry; }
+
+ PLDHashNumber KeyHash() const { return *HashPtr(); }
+ void SetKeyHash(PLDHashNumber aHash) { *HashPtr() = aHash; }
+
+ PLDHashEntryHdr* ToEntry() const { return mEntry; }
+
+ bool IsFree() const { return KeyHash() == 0; }
+ bool IsRemoved() const { return KeyHash() == 1; }
+ bool IsLive() const { return IsLiveHash(KeyHash()); }
+ static bool IsLiveHash(uint32_t aHash) { return aHash >= 2; }
+
+ void MarkFree() { *HashPtr() = 0; }
+ void MarkRemoved() { *HashPtr() = 1; }
+ void MarkColliding() { *HashPtr() |= kCollisionFlag; }
+
+ void Next(uint32_t aEntrySize) {
+ char* p = reinterpret_cast<char*>(mEntry);
+ p += aEntrySize;
+ mEntry = reinterpret_cast<PLDHashEntryHdr*>(p);
+ mKeyHash++;
+ }
+ PLDHashNumber* HashPtr() const { return mKeyHash; }
+
+ private:
+ PLDHashEntryHdr* mEntry;
+ PLDHashNumber* mKeyHash;
+ };
+
+ // This class maintains the invariant that every time the entry store is
+ // changed, the generation is updated.
+ //
+ // The data layout separates the cached hashes of entries and the entries
+ // themselves to save space. We could store the entries thusly:
+ //
+ // +--------+--------+---------+
+ // | entry0 | entry1 | ... |
+ // +--------+--------+---------+
+ //
+ // where the entries themselves contain the cached hash stored as their
+ // first member. PLDHashTable did this for a long time, with entries looking
+ // like:
+ //
+ // class PLDHashEntryHdr
+ // {
+ // PLDHashNumber mKeyHash;
+ // };
+ //
+ // class MyEntry : public PLDHashEntryHdr
+ // {
+ // ...
+ // };
+ //
+ // The problem with this setup is that, depending on the layout of
+ // `MyEntry`, there may be platform ABI-mandated padding between `mKeyHash`
+ // and the first member of `MyEntry`. This ABI-mandated padding is wasted
+ // space, and was surprisingly common, e.g. when MyEntry contained a single
+ // pointer on 64-bit platforms.
+ //
+ // As previously alluded to, the current setup stores things thusly:
+ //
+ // +-------+-------+-------+-------+--------+--------+---------+
+ // | hash0 | hash1 | ..... | hashN | entry0 | entry1 | ... |
+ // +-------+-------+-------+-------+--------+--------+---------+
+ //
+ // which contains no wasted space between the hashes themselves, and no
+ // wasted space between the entries themselves. malloc is guaranteed to
+ // return blocks of memory with at least word alignment on all of our major
+ // platforms. PLDHashTable mandates that the size of the hash table is
+ // always a power of two, so the alignment of the memory containing the
+ // first entry is always at least the alignment of the entire entry store.
+ // That means the alignment of `entry0` should be its natural alignment.
+ // Entries may have problems if they contain over-aligned members such as
+ // SIMD vector types, but this has not been a problem in practice.
+ //
+ // Note: It would be natural to store the generation within this class, but
+ // we can't do that without bloating sizeof(PLDHashTable) on 64-bit machines.
+ // So instead we store it outside this class, and Set() takes a pointer to it
+ // and ensures it is updated as necessary.
+ class EntryStore {
+ private:
+ char* mEntryStore;
+
+ static char* Entries(char* aStore, uint32_t aCapacity) {
+ return aStore + aCapacity * sizeof(PLDHashNumber);
+ }
+
+ char* Entries(uint32_t aCapacity) const {
+ return Entries(Get(), aCapacity);
+ }
+
+ public:
+ EntryStore() : mEntryStore(nullptr) {}
+
+ ~EntryStore() {
+ free(mEntryStore);
+ mEntryStore = nullptr;
+ }
+
+ char* Get() const { return mEntryStore; }
+ bool IsAllocated() const { return !!mEntryStore; }
+
+ Slot SlotForIndex(uint32_t aIndex, uint32_t aEntrySize,
+ uint32_t aCapacity) const {
+ char* entries = Entries(aCapacity);
+ auto entry =
+ reinterpret_cast<PLDHashEntryHdr*>(entries + aIndex * aEntrySize);
+ auto hashes = reinterpret_cast<PLDHashNumber*>(Get());
+ return Slot(entry, &hashes[aIndex]);
+ }
+
+ Slot SlotForPLDHashEntry(PLDHashEntryHdr* aEntry, uint32_t aCapacity,
+ uint32_t aEntrySize) {
+ char* entries = Entries(aCapacity);
+ char* entry = reinterpret_cast<char*>(aEntry);
+ uint32_t entryOffset = entry - entries;
+ uint32_t slotIndex = entryOffset / aEntrySize;
+ return SlotForIndex(slotIndex, aEntrySize, aCapacity);
+ }
+
+ template <typename F>
+ void ForEachSlot(uint32_t aCapacity, uint32_t aEntrySize, F&& aFunc) {
+ ForEachSlot(Get(), aCapacity, aEntrySize, std::move(aFunc));
+ }
+
+ template <typename F>
+ static void ForEachSlot(char* aStore, uint32_t aCapacity,
+ uint32_t aEntrySize, F&& aFunc) {
+ char* entries = Entries(aStore, aCapacity);
+ Slot slot(reinterpret_cast<PLDHashEntryHdr*>(entries),
+ reinterpret_cast<PLDHashNumber*>(aStore));
+ for (size_t i = 0; i < aCapacity; ++i) {
+ aFunc(slot);
+ slot.Next(aEntrySize);
+ }
+ }
+
+ void Set(char* aEntryStore, uint16_t* aGeneration) {
+ mEntryStore = aEntryStore;
+ *aGeneration += 1;
+ }
+ };
+
+ // These fields are packed carefully. On 32-bit platforms,
+ // sizeof(PLDHashTable) is 20. On 64-bit platforms, sizeof(PLDHashTable) is
+ // 32; 28 bytes of data followed by 4 bytes of padding for alignment.
+ const PLDHashTableOps* const mOps; // Virtual operations; see below.
+ EntryStore mEntryStore; // (Lazy) entry storage and generation.
+ uint16_t mGeneration; // The storage generation.
+ uint8_t mHashShift; // Multiplicative hash shift.
+ const uint8_t mEntrySize; // Number of bytes in an entry.
+ uint32_t mEntryCount; // Number of entries in table.
+ uint32_t mRemovedCount; // Removed entry sentinels in table.
+
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ mutable Checker mChecker;
+#endif
+
+ public:
+ // Table capacity limit; do not exceed. The max capacity used to be 1<<23 but
+ // that occasionally that wasn't enough. Making it much bigger than 1<<26
+ // probably isn't worthwhile -- tables that big are kind of ridiculous.
+ // Also, the growth operation will (deliberately) fail if |capacity *
+ // mEntrySize| overflows a uint32_t, and mEntrySize is always at least 8
+ // bytes.
+ static const uint32_t kMaxCapacity = ((uint32_t)1 << 26);
+
+ static const uint32_t kMinCapacity = 8;
+
+ // Making this half of kMaxCapacity ensures it'll fit. Nobody should need an
+ // initial length anywhere nearly this large, anyway.
+ static const uint32_t kMaxInitialLength = kMaxCapacity / 2;
+
+ // This gives a default initial capacity of 8.
+ static const uint32_t kDefaultInitialLength = 4;
+
+ // Initialize the table with |aOps| and |aEntrySize|. The table's initial
+ // capacity is chosen such that |aLength| elements can be inserted without
+ // rehashing; if |aLength| is a power-of-two, this capacity will be
+ // |2*length|. However, because entry storage is allocated lazily, this
+ // initial capacity won't be relevant until the first element is added; prior
+ // to that the capacity will be zero.
+ //
+ // This will crash if |aEntrySize| and/or |aLength| are too large.
+ PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize,
+ uint32_t aLength = kDefaultInitialLength);
+
+ PLDHashTable(PLDHashTable&& aOther)
+ // Initialize fields which are checked by the move assignment operator
+ // and the destructor (which the move assignment operator calls).
+ : mOps(nullptr), mEntryStore(), mGeneration(0), mEntrySize(0) {
+ *this = std::move(aOther);
+ }
+
+ PLDHashTable& operator=(PLDHashTable&& aOther);
+
+ ~PLDHashTable();
+
+ // This should be used rarely.
+ const PLDHashTableOps* Ops() const { return mOps; }
+
+ // Size in entries (gross, not net of free and removed sentinels) for table.
+ // This can be zero if no elements have been added yet, in which case the
+ // entry storage will not have yet been allocated.
+ uint32_t Capacity() const {
+ return mEntryStore.IsAllocated() ? CapacityFromHashShift() : 0;
+ }
+
+ uint32_t EntrySize() const { return mEntrySize; }
+ uint32_t EntryCount() const { return mEntryCount; }
+ uint32_t Generation() const { return mGeneration; }
+
+ // To search for a |key| in |table|, call:
+ //
+ // entry = table.Search(key);
+ //
+ // If |entry| is non-null, |key| was found. If |entry| is null, key was not
+ // found.
+ PLDHashEntryHdr* Search(const void* aKey) const;
+
+ // To add an entry identified by |key| to table, call:
+ //
+ // entry = table.Add(key, mozilla::fallible);
+ //
+ // If |entry| is null upon return, then the table is severely overloaded and
+ // memory can't be allocated for entry storage.
+ //
+ // Otherwise, if the initEntry hook was provided, |entry| will be
+ // initialized. If the initEntry hook was not provided, the caller
+ // should initialize |entry| as appropriate.
+ PLDHashEntryHdr* Add(const void* aKey, const mozilla::fallible_t&);
+
+ // This is like the other Add() function, but infallible, and so never
+ // returns null.
+ PLDHashEntryHdr* Add(const void* aKey);
+
+ // To remove an entry identified by |key| from table, call:
+ //
+ // table.Remove(key);
+ //
+ // If |key|'s entry is found, it is cleared (via table->mOps->clearEntry).
+ // The table's capacity may be reduced afterwards.
+ void Remove(const void* aKey);
+
+ // To remove an entry found by a prior search, call:
+ //
+ // table.RemoveEntry(entry);
+ //
+ // The entry, which must be present and in use, is cleared (via
+ // table->mOps->clearEntry). The table's capacity may be reduced afterwards.
+ void RemoveEntry(PLDHashEntryHdr* aEntry);
+
+ // Remove an entry already accessed via Search() or Add().
+ //
+ // NB: this is a "raw" or low-level method. It does not shrink the table if
+ // it is underloaded. Don't use it unless necessary and you know what you are
+ // doing, and if so, please explain in a comment why it is necessary instead
+ // of RemoveEntry().
+ void RawRemove(PLDHashEntryHdr* aEntry);
+
+ // This function is equivalent to
+ // ClearAndPrepareForLength(kDefaultInitialLength).
+ void Clear();
+
+ // This function clears the table's contents and frees its entry storage,
+ // leaving it in a empty state ready to be used again. Afterwards, when the
+ // first element is added the entry storage that gets allocated will have a
+ // capacity large enough to fit |aLength| elements without rehashing.
+ //
+ // It's conceptually the same as calling the destructor and then re-calling
+ // the constructor with the original |aOps| and |aEntrySize| arguments, and
+ // a new |aLength| argument.
+ void ClearAndPrepareForLength(uint32_t aLength);
+
+ // Measure the size of the table's entry storage. If the entries contain
+ // pointers to other heap blocks, you have to iterate over the table and
+ // measure those separately; hence the "Shallow" prefix.
+ size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // Like ShallowSizeOfExcludingThis(), but includes sizeof(*this).
+ size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // Mark a table as immutable for the remainder of its lifetime. This
+ // changes the implementation from asserting one set of invariants to
+ // asserting a different set.
+ void MarkImmutable() {
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ mChecker.SetNonWritable();
+#endif
+ }
+
+ // If you use PLDHashEntryStub or a subclass of it as your entry struct, and
+ // if your entries move via memcpy and clear via memset(0), you can use these
+ // stub operations.
+ static const PLDHashTableOps* StubOps();
+
+ // The individual stub operations in StubOps().
+ static PLDHashNumber HashVoidPtrKeyStub(const void* aKey);
+ static bool MatchEntryStub(const PLDHashEntryHdr* aEntry, const void* aKey);
+ static void MoveEntryStub(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom,
+ PLDHashEntryHdr* aTo);
+ static void ClearEntryStub(PLDHashTable* aTable, PLDHashEntryHdr* aEntry);
+
+ // Hash/match operations for tables holding C strings.
+ static PLDHashNumber HashStringKey(const void* aKey);
+ static bool MatchStringKey(const PLDHashEntryHdr* aEntry, const void* aKey);
+
+ class EntryHandle {
+ public:
+ EntryHandle(EntryHandle&& aOther) noexcept;
+#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
+ ~EntryHandle();
+#endif
+
+ EntryHandle(const EntryHandle&) = delete;
+ EntryHandle& operator=(const EntryHandle&) = delete;
+ EntryHandle& operator=(EntryHandle&& aOther) = delete;
+
+ // Is this slot currently occupied?
+ bool HasEntry() const { return mSlot.IsLive(); }
+
+ explicit operator bool() const { return HasEntry(); }
+
+ // Get the entry stored in this slot. May not be called unless the slot is
+ // currently occupied.
+ PLDHashEntryHdr* Entry() {
+ MOZ_ASSERT(HasEntry());
+ return mSlot.ToEntry();
+ }
+
+ template <class F>
+ void Insert(F&& aInitEntry) {
+ MOZ_ASSERT(!HasEntry());
+ OccupySlot();
+ std::forward<F>(aInitEntry)(Entry());
+ }
+
+ // If the slot is currently vacant, the slot is occupied and `initEntry` is
+ // invoked to initialize the entry. Returns the entry stored in now-occupied
+ // slot.
+ template <class F>
+ PLDHashEntryHdr* OrInsert(F&& aInitEntry) {
+ if (!HasEntry()) {
+ Insert(std::forward<F>(aInitEntry));
+ }
+ return Entry();
+ }
+
+ /** Removes the entry. Note that the table won't shrink on destruction of
+ * the EntryHandle.
+ *
+ * \pre HasEntry()
+ * \post !HasEntry()
+ */
+ void Remove();
+
+ /** Removes the entry, if it exists. Note that the table won't shrink on
+ * destruction of the EntryHandle.
+ *
+ * \post !HasEntry()
+ */
+ void OrRemove();
+
+ private:
+ friend class PLDHashTable;
+
+ EntryHandle(PLDHashTable* aTable, PLDHashNumber aKeyHash, Slot aSlot);
+
+ void OccupySlot();
+
+ PLDHashTable* mTable;
+ PLDHashNumber mKeyHash;
+ Slot mSlot;
+ };
+
+ template <class F>
+ auto WithEntryHandle(const void* aKey, F&& aFunc)
+ -> std::invoke_result_t<F, EntryHandle&&> {
+ return std::forward<F>(aFunc)(MakeEntryHandle(aKey));
+ }
+
+ template <class F>
+ auto WithEntryHandle(const void* aKey, const mozilla::fallible_t& aFallible,
+ F&& aFunc)
+ -> std::invoke_result_t<F, mozilla::Maybe<EntryHandle>&&> {
+ return std::forward<F>(aFunc)(MakeEntryHandle(aKey, aFallible));
+ }
+
+ // This is an iterator for PLDHashtable. Assertions will detect some, but not
+ // all, mid-iteration table modifications that might invalidate (e.g.
+ // reallocate) the entry storage.
+ //
+ // Any element can be removed during iteration using Remove(). If any
+ // elements are removed, the table may be resized once iteration ends.
+ //
+ // Example usage:
+ //
+ // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
+ // auto entry = static_cast<FooEntry*>(iter.Get());
+ // // ... do stuff with |entry| ...
+ // // ... possibly call iter.Remove() once ...
+ // }
+ //
+ // or:
+ //
+ // for (PLDHashTable::Iterator iter(&table); !iter.Done(); iter.Next()) {
+ // auto entry = static_cast<FooEntry*>(iter.Get());
+ // // ... do stuff with |entry| ...
+ // // ... possibly call iter.Remove() once ...
+ // }
+ //
+ // The latter form is more verbose but is easier to work with when
+ // making subclasses of Iterator.
+ //
+ class Iterator {
+ public:
+ explicit Iterator(PLDHashTable* aTable);
+ struct EndIteratorTag {};
+ Iterator(PLDHashTable* aTable, EndIteratorTag aTag);
+ Iterator(Iterator&& aOther);
+ ~Iterator();
+
+ // Have we finished?
+ bool Done() const { return mNexts == mNextsLimit; }
+
+ // Get the current entry.
+ PLDHashEntryHdr* Get() const {
+ MOZ_ASSERT(!Done());
+ MOZ_ASSERT(mCurrent.IsLive());
+ return mCurrent.ToEntry();
+ }
+
+ // Advance to the next entry.
+ void Next();
+
+ // Remove the current entry. Must only be called once per entry, and Get()
+ // must not be called on that entry afterwards.
+ void Remove();
+
+ bool operator==(const Iterator& aOther) const {
+ MOZ_ASSERT(mTable == aOther.mTable);
+ return mNexts == aOther.mNexts;
+ }
+
+ Iterator Clone() const { return {*this}; }
+
+ protected:
+ PLDHashTable* mTable; // Main table pointer.
+
+ private:
+ Slot mCurrent; // Pointer to the current entry.
+ uint32_t mNexts; // Number of Next() calls.
+ uint32_t mNextsLimit; // Next() call limit.
+
+ bool mHaveRemoved; // Have any elements been removed?
+ uint8_t mEntrySize; // Size of entries.
+
+ bool IsOnNonLiveEntry() const;
+
+ void MoveToNextLiveEntry();
+
+ Iterator() = delete;
+ Iterator(const Iterator&);
+ Iterator& operator=(const Iterator&) = delete;
+ Iterator& operator=(const Iterator&&) = delete;
+ };
+
+ Iterator Iter() { return Iterator(this); }
+
+ // Use this if you need to initialize an Iterator in a const method. If you
+ // use this case, you should not call Remove() on the iterator.
+ Iterator ConstIter() const {
+ return Iterator(const_cast<PLDHashTable*>(this));
+ }
+
+ private:
+ static uint32_t HashShift(uint32_t aEntrySize, uint32_t aLength);
+
+ static const PLDHashNumber kCollisionFlag = 1;
+
+ PLDHashNumber Hash1(PLDHashNumber aHash0) const;
+ void Hash2(PLDHashNumber aHash, uint32_t& aHash2Out,
+ uint32_t& aSizeMaskOut) const;
+
+ static bool MatchSlotKeyhash(Slot& aSlot, const PLDHashNumber aHash);
+ Slot SlotForIndex(uint32_t aIndex) const;
+
+ // We store mHashShift rather than sizeLog2 to optimize the collision-free
+ // case in SearchTable.
+ uint32_t CapacityFromHashShift() const {
+ return ((uint32_t)1 << (kPLDHashNumberBits - mHashShift));
+ }
+
+ PLDHashNumber ComputeKeyHash(const void* aKey) const;
+
+ enum SearchReason { ForSearchOrRemove, ForAdd };
+
+ // Avoid using bare `Success` and `Failure`, as those names are commonly
+ // defined as macros.
+ template <SearchReason Reason, typename PLDSuccess, typename PLDFailure>
+ auto SearchTable(const void* aKey, PLDHashNumber aKeyHash,
+ PLDSuccess&& aSucess, PLDFailure&& aFailure) const;
+
+ Slot FindFreeSlot(PLDHashNumber aKeyHash) const;
+
+ bool ChangeTable(int aDeltaLog2);
+
+ void RawRemove(Slot& aSlot);
+ void ShrinkIfAppropriate();
+
+ mozilla::Maybe<EntryHandle> MakeEntryHandle(const void* aKey,
+ const mozilla::fallible_t&);
+
+ EntryHandle MakeEntryHandle(const void* aKey);
+
+ PLDHashTable(const PLDHashTable& aOther) = delete;
+ PLDHashTable& operator=(const PLDHashTable& aOther) = delete;
+};
+
+// Compute the hash code for a given key to be looked up, added, or removed.
+// A hash code may have any PLDHashNumber value.
+typedef PLDHashNumber (*PLDHashHashKey)(const void* aKey);
+
+// Compare the key identifying aEntry with the provided key parameter. Return
+// true if keys match, false otherwise.
+typedef bool (*PLDHashMatchEntry)(const PLDHashEntryHdr* aEntry,
+ const void* aKey);
+
+// Copy the data starting at aFrom to the new entry storage at aTo. Do not add
+// reference counts for any strong references in the entry, however, as this
+// is a "move" operation: the old entry storage at from will be freed without
+// any reference-decrementing callback shortly.
+typedef void (*PLDHashMoveEntry)(PLDHashTable* aTable,
+ const PLDHashEntryHdr* aFrom,
+ PLDHashEntryHdr* aTo);
+
+// Clear the entry and drop any strong references it holds. This callback is
+// invoked by Remove(), but only if the given key is found in the table.
+typedef void (*PLDHashClearEntry)(PLDHashTable* aTable,
+ PLDHashEntryHdr* aEntry);
+
+// Initialize a new entry. This function is called when
+// Add() finds no existing entry for the given key, and must add a new one.
+typedef void (*PLDHashInitEntry)(PLDHashEntryHdr* aEntry, const void* aKey);
+
+// Finally, the "vtable" structure for PLDHashTable. The first four hooks
+// must be provided by implementations; they're called unconditionally by the
+// generic PLDHashTable.cpp code. Hooks after these may be null.
+//
+// Summary of allocation-related hook usage with C++ placement new emphasis:
+// initEntry Call placement new using default key-based ctor.
+// moveEntry Call placement new using copy ctor, run dtor on old
+// entry storage.
+// clearEntry Run dtor on entry.
+//
+// Note the reason why initEntry is optional: the default hooks (stubs) clear
+// entry storage. On a successful Add(tbl, key), the returned entry pointer
+// addresses an entry struct whose entry members are still clear (null). Add()
+// callers can test such members to see whether the entry was newly created by
+// the Add() call that just succeeded. If placement new or similar
+// initialization is required, define an |initEntry| hook. Of course, the
+// |clearEntry| hook must zero or null appropriately.
+//
+// XXX assumes 0 is null for pointer types.
+struct PLDHashTableOps {
+ // Mandatory hooks. All implementations must provide these.
+ PLDHashHashKey hashKey;
+ PLDHashMatchEntry matchEntry;
+ PLDHashMoveEntry moveEntry;
+
+ // Optional hooks start here. If null, these are not called.
+ PLDHashClearEntry clearEntry;
+ PLDHashInitEntry initEntry;
+};
+
+// A minimal entry is a subclass of PLDHashEntryHdr and has a void* key pointer.
+struct PLDHashEntryStub : public PLDHashEntryHdr {
+ const void* key;
+};
+
+#endif /* PLDHashTable_h */
diff --git a/xpcom/ds/PerfectHash.h b/xpcom/ds/PerfectHash.h
new file mode 100644
index 0000000000..1e75855462
--- /dev/null
+++ b/xpcom/ds/PerfectHash.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Helper routines for perfecthash.py. Not to be used directly. */
+
+#ifndef mozilla_PerfectHash_h
+#define mozilla_PerfectHash_h
+
+#include <type_traits>
+
+namespace mozilla {
+namespace perfecthash {
+
+// 32-bit FNV offset basis and prime value.
+// NOTE: Must match values in |perfecthash.py|
+constexpr uint32_t FNV_OFFSET_BASIS = 0x811C9DC5;
+constexpr uint32_t FNV_PRIME = 16777619;
+
+/**
+ * Basic FNV hasher function used by perfecthash. Generic over the unit type.
+ */
+template <typename C>
+inline uint32_t Hash(uint32_t aBasis, const C* aKey, size_t aLen) {
+ for (size_t i = 0; i < aLen; ++i) {
+ aBasis =
+ (aBasis ^ static_cast<std::make_unsigned_t<C>>(aKey[i])) * FNV_PRIME;
+ }
+ return aBasis;
+}
+
+/**
+ * Helper method for getting the index from a perfect hash.
+ * Called by code generated from |perfecthash.py|.
+ */
+template <typename C, typename Base, size_t NBases, typename Entry,
+ size_t NEntries>
+inline const Entry& Lookup(const C* aKey, size_t aLen,
+ const Base (&aTable)[NBases],
+ const Entry (&aEntries)[NEntries]) {
+ uint32_t basis = aTable[Hash(FNV_OFFSET_BASIS, aKey, aLen) % NBases];
+ return aEntries[Hash(basis, aKey, aLen) % NEntries];
+}
+
+} // namespace perfecthash
+} // namespace mozilla
+
+#endif // !defined(mozilla_PerfectHash_h)
diff --git a/xpcom/ds/SimpleEnumerator.h b/xpcom/ds/SimpleEnumerator.h
new file mode 100644
index 0000000000..463b9ecaed
--- /dev/null
+++ b/xpcom/ds/SimpleEnumerator.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SimpleEnumerator_h
+#define mozilla_SimpleEnumerator_h
+
+#include "nsCOMPtr.h"
+#include "nsISimpleEnumerator.h"
+
+namespace mozilla {
+
+/**
+ * A wrapper class around nsISimpleEnumerator to support ranged iteration. This
+ * requires every element in the enumeration to implement the same interface, T.
+ * If any element does not implement this interface, the enumeration ends at
+ * that element, and triggers an assertion in debug builds.
+ *
+ * Typical usage looks something like:
+ *
+ * for (auto& docShell : SimpleEnumerator<nsIDocShell>(docShellEnum)) {
+ * docShell.LoadURI(...);
+ * }
+ */
+
+template <typename T>
+class SimpleEnumerator final {
+ public:
+ explicit SimpleEnumerator(nsISimpleEnumerator* aEnum) : mEnum(aEnum) {}
+
+ class Entry {
+ public:
+ explicit Entry(T* aPtr) : mPtr(aPtr) {}
+
+ explicit Entry(nsISimpleEnumerator& aEnum) : mEnum(&aEnum) { ++*this; }
+
+ const nsCOMPtr<T>& operator*() {
+ MOZ_ASSERT(mPtr);
+ return mPtr;
+ }
+
+ Entry& operator++() {
+ MOZ_ASSERT(mEnum);
+ nsCOMPtr<nsISupports> next;
+ if (NS_SUCCEEDED(mEnum->GetNext(getter_AddRefs(next)))) {
+ mPtr = do_QueryInterface(next);
+ MOZ_ASSERT(mPtr);
+ } else {
+ mPtr = nullptr;
+ }
+ return *this;
+ }
+
+ bool operator!=(const Entry& aOther) const { return mPtr != aOther.mPtr; }
+
+ private:
+ nsCOMPtr<T> mPtr;
+ nsCOMPtr<nsISimpleEnumerator> mEnum;
+ };
+
+ Entry begin() { return Entry(*mEnum); }
+
+ Entry end() { return Entry(nullptr); }
+
+ private:
+ nsCOMPtr<nsISimpleEnumerator> mEnum;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SimpleEnumerator_h
diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
new file mode 100644
index 0000000000..1b49ddc99b
--- /dev/null
+++ b/xpcom/ds/StaticAtoms.py
@@ -0,0 +1,2643 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# flake8: noqa
+
+import sys
+
+from Atom import (
+ Atom,
+ InheritingAnonBoxAtom,
+ NonInheritingAnonBoxAtom,
+ PseudoElementAtom,
+)
+from HTMLAtoms import HTML_PARSER_ATOMS
+
+# Static atom definitions, used to generate nsGkAtomList.h.
+#
+# Each atom is defined by a call to Atom, PseudoElementAtom,
+# NonInheritingAnonBoxAtom or InheritingAnonBoxAtom.
+#
+# The first argument is the atom's identifier.
+# The second argument is the atom's string value.
+#
+# Please keep the Atom() definitions on one line as this is parsed by the
+# htmlparser: parser/html/java/htmlparser
+# Please keep "START ATOMS" and "END ATOMS" comments as the parser uses them.
+#
+# It is not possible to conditionally define static atoms with #ifdef etc.
+# fmt: off
+STATIC_ATOMS = [
+ # START ATOMS
+ # --------------------------------------------------------------------------
+ # Generic atoms
+ # --------------------------------------------------------------------------
+ Atom("SystemPrincipal", "[System Principal]"),
+ Atom("_empty", ""),
+ Atom("_0", "0"),
+ Atom("_1", "1"),
+ Atom("mozframetype", "mozframetype"),
+ Atom("_moz_abspos", "_moz_abspos"),
+ Atom("_moz_activated", "_moz_activated"),
+ Atom("_moz_anonclass", "_moz_anonclass"),
+ Atom("_moz_resizing", "_moz_resizing"),
+ Atom("moztype", "_moz-type"),
+ Atom("mozdirty", "_moz_dirty"),
+ Atom("mozdisallowselectionprint", "mozdisallowselectionprint"),
+ Atom("mozdonotsend", "moz-do-not-send"),
+ Atom("mozfwcontainer", "moz-forward-container"), # Used by MailNews.
+ Atom("mozgeneratedcontentbefore", "_moz_generated_content_before"),
+ Atom("mozgeneratedcontentafter", "_moz_generated_content_after"),
+ Atom("mozgeneratedcontentmarker", "_moz_generated_content_marker"),
+ Atom("mozgeneratedcontentimage", "_moz_generated_content_image"),
+ Atom("mozquote", "_moz_quote"),
+ Atom("mozsignature", "moz-signature"), # Used by MailNews.
+ Atom("_moz_bullet_font", "-moz-bullet-font"),
+ Atom("_moz_is_glyph", "-moz-is-glyph"),
+ Atom("_moz_original_size", "_moz_original_size"),
+ Atom("_moz_print_preview", "-moz-print-preview"),
+ Atom("_moz_non_native_content_theme", "-moz-non-native-content-theme"),
+ Atom("menuactive", "_moz-menuactive"),
+ Atom("_poundDefault", "#default"),
+ Atom("_asterisk", "*"),
+ Atom("a", "a"),
+ Atom("abbr", "abbr"),
+ Atom("abort", "abort"),
+ Atom("above", "above"),
+ Atom("acceltext", "acceltext"),
+ Atom("accept", "accept"),
+ Atom("acceptcharset", "accept-charset"),
+ Atom("accessiblenode", "accessible-node"),
+ Atom("accesskey", "accesskey"),
+ Atom("acronym", "acronym"),
+ Atom("action", "action"),
+ Atom("active", "active"),
+ Atom("activateontab", "activateontab"),
+ Atom("actuate", "actuate"),
+ Atom("address", "address"),
+ Atom("adoptedsheetclones", "adoptedsheetclones"),
+ Atom("after", "after"),
+ Atom("align", "align"),
+ Atom("alink", "alink"),
+ Atom("all", "all"),
+ Atom("allow", "allow"),
+ Atom("allowdownloads", "allow-downloads"),
+ Atom("allowevents", "allowevents"),
+ Atom("allowforms", "allow-forms"),
+ Atom("allowfullscreen", "allowfullscreen"),
+ Atom("allowmodals", "allow-modals"),
+ Atom("alloworientationlock", "allow-orientation-lock"),
+ Atom("allowpointerlock", "allow-pointer-lock"),
+ Atom("allowpopupstoescapesandbox", "allow-popups-to-escape-sandbox"),
+ Atom("allowpopups", "allow-popups"),
+ Atom("allowpresentation", "allow-presentation"),
+ Atom("allowstorageaccessbyuseractivatetion", "allow-storage-access-by-user-activation"),
+ Atom("allowsameorigin", "allow-same-origin"),
+ Atom("allowscripts", "allow-scripts"),
+ Atom("allowscriptstoclose", "allowscriptstoclose"),
+ Atom("allowtopnavigation", "allow-top-navigation"),
+ Atom("allowtopnavigationbyuseractivation", "allow-top-navigation-by-user-activation"),
+ Atom("allowtopnavigationcustomprotocols", "allow-top-navigation-to-custom-protocols"),
+ Atom("allowuntrusted", "allowuntrusted"),
+ Atom("alt", "alt"),
+ Atom("alternate", "alternate"),
+ Atom("always", "always"),
+ Atom("ancestor", "ancestor"),
+ Atom("ancestorOrSelf", "ancestor-or-self"),
+ Atom("anchor", "anchor"),
+ Atom("_and", "and"),
+ Atom("animations", "animations"),
+ Atom("anonid", "anonid"),
+ Atom("anonlocation", "anonlocation"),
+ Atom("any", "any"),
+ Atom("any_hover", "any-hover"),
+ Atom("any_pointer", "any-pointer"),
+ Atom("applet", "applet"),
+ Atom("applyImports", "apply-imports"),
+ Atom("applyTemplates", "apply-templates"),
+ Atom("archive", "archive"),
+ Atom("area", "area"),
+ Atom("aria", "aria"),
+ Atom("aria_activedescendant", "aria-activedescendant"),
+ Atom("aria_atomic", "aria-atomic"),
+ Atom("aria_autocomplete", "aria-autocomplete"),
+ Atom("aria_busy", "aria-busy"),
+ Atom("aria_checked", "aria-checked"),
+ Atom("aria_controls", "aria-controls"),
+ Atom("aria_current", "aria-current"),
+ Atom("aria_describedby", "aria-describedby"),
+ Atom("aria_description", "aria-description"),
+ Atom("aria_disabled", "aria-disabled"),
+ Atom("aria_dropeffect", "aria-dropeffect"),
+ Atom("aria_expanded", "aria-expanded"),
+ Atom("aria_flowto", "aria-flowto"),
+ Atom("aria_haspopup", "aria-haspopup"),
+ Atom("aria_hidden", "aria-hidden"),
+ Atom("aria_invalid", "aria-invalid"),
+ Atom("aria_labelledby", "aria-labelledby"),
+ Atom("aria_level", "aria-level"),
+ Atom("aria_live", "aria-live"),
+ Atom("aria_multiline", "aria-multiline"),
+ Atom("aria_multiselectable", "aria-multiselectable"),
+ Atom("aria_owns", "aria-owns"),
+ Atom("aria_posinset", "aria-posinset"),
+ Atom("aria_pressed", "aria-pressed"),
+ Atom("aria_readonly", "aria-readonly"),
+ Atom("aria_relevant", "aria-relevant"),
+ Atom("aria_required", "aria-required"),
+ Atom("aria_selected", "aria-selected"),
+ Atom("aria_setsize", "aria-setsize"),
+ Atom("aria_sort", "aria-sort"),
+ Atom("aria_valuemax", "aria-valuemax"),
+ Atom("aria_valuemin", "aria-valuemin"),
+ Atom("aria_valuenow", "aria-valuenow"),
+ Atom("arrow", "arrow"),
+ Atom("article", "article"),
+ Atom("as", "as"),
+ Atom("ascending", "ascending"),
+ Atom("aside", "aside"),
+ Atom("aspectRatio", "aspect-ratio"),
+ Atom("async", "async"),
+ Atom("attribute", "attribute"),
+ Atom("attributes", "attributes"),
+ Atom("attributeSet", "attribute-set"),
+ Atom("_auto", "auto"),
+ Atom("autocapitalize", "autocapitalize"),
+ Atom("autocheck", "autocheck"),
+ Atom("autocomplete", "autocomplete"),
+ Atom("autocomplete_richlistbox", "autocomplete-richlistbox"),
+ Atom("autofocus", "autofocus"),
+ Atom("autoplay", "autoplay"),
+ Atom("axis", "axis"),
+ Atom("b", "b"),
+ Atom("background", "background"),
+ Atom("bar", "bar"),
+ Atom("base", "base"),
+ Atom("basefont", "basefont"),
+ Atom("baseline", "baseline"),
+ Atom("bdi", "bdi"),
+ Atom("bdo", "bdo"),
+ Atom("before", "before"),
+ Atom("behavior", "behavior"),
+ Atom("below", "below"),
+ Atom("bgcolor", "bgcolor"),
+ Atom("bgsound", "bgsound"),
+ Atom("big", "big"),
+ Atom("binding", "binding"),
+ Atom("bindings", "bindings"),
+ Atom("bindToUntrustedContent", "bindToUntrustedContent"),
+ Atom("black", "black"),
+ Atom("block", "block"),
+ Atom("block_size", "block-size"),
+ Atom("blockquote", "blockquote"),
+ Atom("blur", "blur"),
+ Atom("body", "body"),
+ Atom("boolean", "boolean"),
+ Atom("border", "border"),
+ Atom("bordercolor", "bordercolor"),
+ Atom("both", "both"),
+ Atom("bottom", "bottom"),
+ Atom("bottomend", "bottomend"),
+ Atom("bottomstart", "bottomstart"),
+ Atom("bottomleft", "bottomleft"),
+ Atom("bottommargin", "bottommargin"),
+ Atom("bottomright", "bottomright"),
+ Atom("box", "box"),
+ Atom("br", "br"),
+ Atom("browser", "browser"),
+ Atom("mozbrowser", "mozbrowser"),
+ Atom("button", "button"),
+ Atom("callTemplate", "call-template"),
+ Atom("canvas", "canvas"),
+ Atom("caption", "caption"),
+ Atom("captionBox", "caption-box"),
+ Atom("capture", "capture"),
+ Atom("caseOrder", "case-order"),
+ Atom("cdataSectionElements", "cdata-section-elements"),
+ Atom("ceiling", "ceiling"),
+ Atom("cell", "cell"),
+ Atom("cellpadding", "cellpadding"),
+ Atom("cellspacing", "cellspacing"),
+ Atom("center", "center"),
+ Atom("change", "change"),
+ Atom("_char", "char"),
+ Atom("characterData", "characterData"),
+ Atom("charcode", "charcode"),
+ Atom("charoff", "charoff"),
+ Atom("charset", "charset"),
+ Atom("checkbox", "checkbox"),
+ Atom("checkboxLabel", "checkbox-label"),
+ Atom("checked", "checked"),
+ Atom("child", "child"),
+ Atom("children", "children"),
+ Atom("childList", "childList"),
+ Atom("child_item_count", "child-item-count"),
+ Atom("choose", "choose"),
+ Atom("chromemargin", "chromemargin"),
+ Atom("exposeToUntrustedContent", "exposeToUntrustedContent"),
+ Atom("circ", "circ"),
+ Atom("circle", "circle"),
+ Atom("cite", "cite"),
+ Atom("_class", "class"),
+ Atom("classid", "classid"),
+ Atom("clear", "clear"),
+ Atom("click", "click"),
+ Atom("clickcount", "clickcount"),
+ Atom("clickthrough", "clickthrough"),
+ Atom("movetoclick", "movetoclick"),
+ Atom("clip", "clip"),
+ Atom("close", "close"),
+ Atom("closed", "closed"),
+ Atom("closemenu", "closemenu"),
+ Atom("code", "code"),
+ Atom("codebase", "codebase"),
+ Atom("codetype", "codetype"),
+ Atom("col", "col"),
+ Atom("colgroup", "colgroup"),
+ Atom("collapse", "collapse"),
+ Atom("collapsed", "collapsed"),
+ Atom("color", "color"),
+ Atom("color_gamut", "color-gamut"),
+ Atom("color_index", "color-index"),
+ Atom("color_scheme", "color-scheme"),
+ Atom("cols", "cols"),
+ Atom("colspan", "colspan"),
+ Atom("combobox", "combobox"),
+ Atom("command", "command"),
+ Atom("commandupdater", "commandupdater"),
+ Atom("comment", "comment"),
+ Atom("compact", "compact"),
+ Atom("concat", "concat"),
+ Atom("constructor", "constructor"),
+ Atom("consumeoutsideclicks", "consumeoutsideclicks"),
+ Atom("container", "container"),
+ Atom("contains", "contains"),
+ Atom("content", "content"),
+ Atom("contenteditable", "contenteditable"),
+ Atom("headerContentDisposition", "content-disposition"),
+ Atom("headerContentLanguage", "content-language"),
+ Atom("contentLocation", "content-location"),
+ Atom("headerContentScriptType", "content-script-type"),
+ Atom("headerContentStyleType", "content-style-type"),
+ Atom("headerContentType", "content-type"),
+ Atom("consumeanchor", "consumeanchor"),
+ Atom("context", "context"),
+ Atom("contextmenu", "contextmenu"),
+ Atom("control", "control"),
+ Atom("controls", "controls"),
+ Atom("coords", "coords"),
+ Atom("copy", "copy"),
+ Atom("copyOf", "copy-of"),
+ Atom("count", "count"),
+ Atom("crop", "crop"),
+ Atom("crossorigin", "crossorigin"),
+ Atom("curpos", "curpos"),
+ Atom("current", "current"),
+ Atom("cutoutregion", "cutoutregion"),
+ Atom("cycler", "cycler"),
+ Atom("dashed", "dashed"),
+ Atom("data", "data"),
+ Atom("dataAtShortcutkeys", "data-at-shortcutkeys"),
+ Atom("datalist", "datalist"),
+ Atom("datal10nid", "data-l10n-id"),
+ Atom("datal10nargs", "data-l10n-args"),
+ Atom("datal10nattrs", "data-l10n-attrs"),
+ Atom("datal10nname", "data-l10n-name"),
+ Atom("datal10nsync", "data-l10n-sync"),
+ Atom("dataType", "data-type"),
+ Atom("dateTime", "date-time"),
+ Atom("date", "date"),
+ Atom("datetime", "datetime"),
+ Atom("datetime_local", "datetime-local"),
+ Atom("datetimeInputBoxWrapper", "datetime-input-box-wrapper"),
+ Atom("dd", "dd"),
+ Atom("decimal", "decimal"),
+ Atom("decimalFormat", "decimal-format"),
+ Atom("decimalSeparator", "decimal-separator"),
+ Atom("declare", "declare"),
+ Atom("decoderDoctor", "decoder-doctor"),
+ Atom("decoding", "decoding"),
+ Atom("decrement", "decrement"),
+ Atom("_default", "default"),
+ Atom("headerDefaultStyle", "default-style"),
+ Atom("defer", "defer"),
+ Atom("del", "del"),
+ Atom("delegatesanchor", "delegatesanchor"),
+ Atom("deletion", "deletion"),
+ Atom("deprecation", "deprecation"),
+ Atom("descendant", "descendant"),
+ Atom("descendantOrSelf", "descendant-or-self"),
+ Atom("descending", "descending"),
+ Atom("description", "description"),
+ Atom("destructor", "destructor"),
+ Atom("details", "details"),
+ Atom("deviceAspectRatio", "device-aspect-ratio"),
+ Atom("deviceHeight", "device-height"),
+ Atom("devicePixelRatio", "device-pixel-ratio"),
+ Atom("deviceWidth", "device-width"),
+ Atom("dfn", "dfn"),
+ Atom("dialog", "dialog"),
+ Atom("difference", "difference"),
+ Atom("digit", "digit"),
+ Atom("dir", "dir"),
+ Atom("dirAutoSetBy", "dirAutoSetBy"),
+ Atom("directory", "directory"),
+ Atom("disableOutputEscaping", "disable-output-escaping"),
+ Atom("disabled", "disabled"),
+ Atom("disableglobalhistory", "disableglobalhistory"),
+ Atom("disablehistory", "disablehistory"),
+ Atom("disablefullscreen", "disablefullscreen"),
+ Atom("disclosure_closed", "disclosure-closed"),
+ Atom("disclosure_open", "disclosure-open"),
+ Atom("display", "display"),
+ Atom("displayMode", "display-mode"),
+ Atom("distinct", "distinct"),
+ Atom("div", "div"),
+ Atom("dl", "dl"),
+ Atom("docAbstract", "doc-abstract"),
+ Atom("docAcknowledgments", "doc-acknowledgments"),
+ Atom("docAfterword", "doc-afterword"),
+ Atom("docAppendix", "doc-appendix"),
+ Atom("docBacklink", "doc-backlink"),
+ Atom("docBiblioentry", "doc-biblioentry"),
+ Atom("docBibliography", "doc-bibliography"),
+ Atom("docBiblioref", "doc-biblioref"),
+ Atom("docChapter", "doc-chapter"),
+ Atom("docColophon", "doc-colophon"),
+ Atom("docConclusion", "doc-conclusion"),
+ Atom("docCover", "doc-cover"),
+ Atom("docCredit", "doc-credit"),
+ Atom("docCredits", "doc-credits"),
+ Atom("docDedication", "doc-dedication"),
+ Atom("docEndnote", "doc-endnote"),
+ Atom("docEndnotes", "doc-endnotes"),
+ Atom("docEpigraph", "doc-epigraph"),
+ Atom("docEpilogue", "doc-epilogue"),
+ Atom("docErrata", "doc-errata"),
+ Atom("docExample", "doc-example"),
+ Atom("docFootnote", "doc-footnote"),
+ Atom("docForeword", "doc-foreword"),
+ Atom("docGlossary", "doc-glossary"),
+ Atom("docGlossref", "doc-glossref"),
+ Atom("docIndex", "doc-index"),
+ Atom("docIntroduction", "doc-introduction"),
+ Atom("docNoteref", "doc-noteref"),
+ Atom("docNotice", "doc-notice"),
+ Atom("docPagebreak", "doc-pagebreak"),
+ Atom("docPagelist", "doc-pagelist"),
+ Atom("docPart", "doc-part"),
+ Atom("docPreface", "doc-preface"),
+ Atom("docPrologue", "doc-prologue"),
+ Atom("docPullquote", "doc-pullquote"),
+ Atom("docQna", "doc-qna"),
+ Atom("docSubtitle", "doc-subtitle"),
+ Atom("docTip", "doc-tip"),
+ Atom("docToc", "doc-toc"),
+ Atom("doctypePublic", "doctype-public"),
+ Atom("doctypeSystem", "doctype-system"),
+ Atom("document", "document"),
+ Atom("down", "down"),
+ Atom("download", "download"),
+ Atom("drag", "drag"),
+ Atom("draggable", "draggable"),
+ Atom("dragging", "dragging"),
+ Atom("dragSession", "dragSession"),
+ Atom("drawintitlebar", "drawintitlebar"),
+ Atom("drawtitle", "drawtitle"),
+ Atom("dropAfter", "dropAfter"),
+ Atom("dropBefore", "dropBefore"),
+ Atom("dropOn", "dropOn"),
+ Atom("dropMarker", "dropmarker"),
+ Atom("dt", "dt"),
+ Atom("e", "e"),
+ Atom("editable", "editable"),
+ Atom("editing", "editing"),
+ Atom("editor", "editor"),
+ Atom("element", "element"),
+ Atom("elementAvailable", "element-available"),
+ Atom("elements", "elements"),
+ Atom("em", "em"),
+ Atom("embed", "embed"),
+ Atom("empty", "empty"),
+ Atom("encoding", "encoding"),
+ Atom("enctype", "enctype"),
+ Atom("end", "end"),
+ Atom("endEvent", "endEvent"),
+ Atom("enterkeyhint", "enterkeyhint"),
+ Atom("error", "error"),
+ Atom("ethiopic_numeric", "ethiopic-numeric"),
+ Atom("even", "even"),
+ Atom("event", "event"),
+ Atom("events", "events"),
+ Atom("excludeResultPrefixes", "exclude-result-prefixes"),
+ Atom("exportparts", "exportparts"),
+ Atom("explicit_name", "explicit-name"),
+ Atom("extends", "extends"),
+ Atom("extensionElementPrefixes", "extension-element-prefixes"),
+ Atom("face", "face"),
+ Atom("fallback", "fallback"),
+ Atom("_false", "false"),
+ Atom("farthest", "farthest"),
+ Atom("featurePolicyViolation", "feature-policy-violation"),
+ Atom("field", "field"),
+ Atom("fieldset", "fieldset"),
+ Atom("file", "file"),
+ Atom("figcaption", "figcaption"),
+ Atom("figure", "figure"),
+ Atom("findbar", "findbar"),
+ Atom("firstInput", "first-input"),
+ Atom("fixed", "fixed"),
+ Atom("flags", "flags"),
+ Atom("flex", "flex"),
+ Atom("flip", "flip"),
+ Atom("floating", "floating"),
+ Atom("floor", "floor"),
+ Atom("flowlength", "flowlength"),
+ Atom("focus", "focus"),
+ Atom("focused", "focused"),
+ Atom("followanchor", "followanchor"),
+ Atom("following", "following"),
+ Atom("followingSibling", "following-sibling"),
+ Atom("font", "font"),
+ Atom("fontWeight", "font-weight"),
+ Atom("footer", "footer"),
+ Atom("_for", "for"),
+ Atom("forEach", "for-each"),
+ Atom("forcedColors", "forced-colors"),
+ Atom("forceOwnRefreshDriver", "forceOwnRefreshDriver"),
+ Atom("form", "form"),
+ Atom("formaction", "formaction"),
+ Atom("format", "format"),
+ Atom("formatNumber", "format-number"),
+ Atom("formenctype", "formenctype"),
+ Atom("formmethod", "formmethod"),
+ Atom("formnovalidate", "formnovalidate"),
+ Atom("formtarget", "formtarget"),
+ Atom("frame", "frame"),
+ Atom("frameborder", "frameborder"),
+ Atom("frameset", "frameset"),
+ Atom("from", "from"),
+ Atom("fullscreenchange", "fullscreenchange"),
+ Atom("fullscreenerror", "fullscreenerror"),
+ Atom("functionAvailable", "function-available"),
+ Atom("generateId", "generate-id"),
+ Atom("getter", "getter"),
+ Atom("graphicsDocument", "graphics-document"),
+ Atom("graphicsObject", "graphics-object"),
+ Atom("graphicsSymbol", "graphics-symbol"),
+ Atom("grid", "grid"),
+ Atom("group", "group"),
+ Atom("groups", "groups"),
+ Atom("groupbox", "groupbox"),
+ Atom("groupingSeparator", "grouping-separator"),
+ Atom("groupingSize", "grouping-size"),
+ Atom("grow", "grow"),
+ Atom("h1", "h1"),
+ Atom("h2", "h2"),
+ Atom("h3", "h3"),
+ Atom("h4", "h4"),
+ Atom("h5", "h5"),
+ Atom("h6", "h6"),
+ Atom("handheldFriendly", "HandheldFriendly"),
+ Atom("handler", "handler"),
+ Atom("handlers", "handlers"),
+ Atom("HARD", "HARD"),
+ Atom("hasSameNode", "has-same-node"),
+ Atom("hbox", "hbox"),
+ Atom("head", "head"),
+ Atom("header", "header"),
+ Atom("headers", "headers"),
+ Atom("hebrew", "hebrew"),
+ Atom("height", "height"),
+ Atom("hgroup", "hgroup"),
+ Atom("hidden", "hidden"),
+ Atom("hidechrome", "hidechrome"),
+ Atom("hidecolumnpicker", "hidecolumnpicker"),
+ Atom("high", "high"),
+ Atom("highest", "highest"),
+ Atom("horizontal", "horizontal"),
+ Atom("hover", "hover"),
+ Atom("hr", "hr"),
+ Atom("href", "href"),
+ Atom("hreflang", "hreflang"),
+ Atom("hsides", "hsides"),
+ Atom("hspace", "hspace"),
+ Atom("html", "html"),
+ Atom("httpEquiv", "http-equiv"),
+ Atom("i", "i"),
+ Atom("icon", "icon"),
+ Atom("id", "id"),
+ Atom("_if", "if"),
+ Atom("iframe", "iframe"),
+ Atom("ignorekeys", "ignorekeys"),
+ Atom("ignoreuserfocus", "ignoreuserfocus"),
+ Atom("image", "image"),
+ Atom("imageClickedPoint", "image-clicked-point"),
+ Atom("imagesizes", "imagesizes"),
+ Atom("imagesrcset", "imagesrcset"),
+ Atom("img", "img"),
+ Atom("implementation", "implementation"),
+ Atom("implements", "implements"),
+ Atom("import", "import"),
+ Atom("include", "include"),
+ Atom("includes", "includes"),
+ Atom("incontentshell", "incontentshell"),
+ Atom("increment", "increment"),
+ Atom("indent", "indent"),
+ Atom("indeterminate", "indeterminate"),
+ Atom("index", "index"),
+ Atom("inert", "inert"),
+ Atom("infinity", "infinity"),
+ Atom("inherits", "inherits"),
+ Atom("inheritOverflow", "inherit-overflow"),
+ Atom("inheritstyle", "inheritstyle"),
+ Atom("initial_scale", "initial-scale"),
+ Atom("input", "input"),
+ Atom("inputmode", "inputmode"),
+ Atom("ins", "ins"),
+ Atom("insertafter", "insertafter"),
+ Atom("insertbefore", "insertbefore"),
+ Atom("insertion", "insertion"),
+ Atom("integer", "integer"),
+ Atom("integrity", "integrity"),
+ Atom("internal", "internal"),
+ Atom("internals", "internals"),
+ Atom("intersection", "intersection"),
+ Atom("intersectionobserverlist", "intersectionobserverlist"),
+ Atom("is", "is"),
+ Atom("ismap", "ismap"),
+ Atom("itemid", "itemid"),
+ Atom("itemprop", "itemprop"),
+ Atom("itemref", "itemref"),
+ Atom("itemscope", "itemscope"),
+ Atom("itemtype", "itemtype"),
+ Atom("japanese_formal", "japanese-formal"),
+ Atom("japanese_informal", "japanese-informal"),
+ Atom("kbd", "kbd"),
+ Atom("keepcurrentinview", "keepcurrentinview"),
+ Atom("keepobjectsalive", "keepobjectsalive"),
+ Atom("key", "key"),
+ Atom("keycode", "keycode"),
+ Atom("keydown", "keydown"),
+ Atom("keygen", "keygen"),
+ Atom("keypress", "keypress"),
+ Atom("keyset", "keyset"),
+ Atom("keysystem", "keysystem"),
+ Atom("keyup", "keyup"),
+ Atom("kind", "kind"),
+ Atom("korean_hangul_formal", "korean-hangul-formal"),
+ Atom("korean_hanja_formal", "korean-hanja-formal"),
+ Atom("korean_hanja_informal", "korean-hanja-informal"),
+ Atom("label", "label"),
+ Atom("lang", "lang"),
+ Atom("language", "language"),
+ Atom("last", "last"),
+ Atom("layer", "layer"),
+ Atom("LayerActivity", "LayerActivity"),
+ Atom("layout_guess", "layout-guess"),
+ Atom("leading", "leading"),
+ Atom("leaf", "leaf"),
+ Atom("left", "left"),
+ Atom("leftmargin", "leftmargin"),
+ Atom("legend", "legend"),
+ Atom("length", "length"),
+ Atom("letterValue", "letter-value"),
+ Atom("level", "level"),
+ Atom("lhs", "lhs"),
+ Atom("li", "li"),
+ Atom("line", "line"),
+ Atom("link", "link"),
+ Atom("linkset", "linkset"),
+ # Atom("list", "list"), # "list" is present below
+ Atom("listbox", "listbox"),
+ Atom("listener", "listener"),
+ Atom("listheader", "listheader"),
+ Atom("listing", "listing"),
+ Atom("listitem", "listitem"),
+ Atom("load", "load"),
+ Atom("loading", "loading"),
+ Atom("triggeringprincipal", "triggeringprincipal"),
+ Atom("localedir", "localedir"),
+ Atom("localName", "local-name"),
+ Atom("localization", "localization"),
+ Atom("longdesc", "longdesc"),
+ Atom("loop", "loop"),
+ Atom("low", "low"),
+ Atom("lowerFirst", "lower-first"),
+ Atom("lowest", "lowest"),
+ Atom("lowsrc", "lowsrc"),
+ Atom("ltr", "ltr"),
+ Atom("lwtheme", "lwtheme"),
+ Atom("main", "main"),
+ Atom("map", "map"),
+ Atom("manifest", "manifest"),
+ Atom("marginBottom", "margin-bottom"),
+ Atom("marginLeft", "margin-left"),
+ Atom("marginRight", "margin-right"),
+ Atom("marginTop", "margin-top"),
+ Atom("marginheight", "marginheight"),
+ Atom("marginwidth", "marginwidth"),
+ Atom("mark", "mark"),
+ Atom("marquee", "marquee"),
+ Atom("match", "match"),
+ Atom("max", "max"),
+ Atom("maxheight", "maxheight"),
+ Atom("maximum_scale", "maximum-scale"),
+ Atom("maxlength", "maxlength"),
+ Atom("maxpos", "maxpos"),
+ Atom("maxwidth", "maxwidth"),
+ Atom("measure", "measure"),
+ Atom("media", "media"),
+ Atom("mediaType", "media-type"),
+ Atom("menu", "menu"),
+ Atom("menubar", "menubar"),
+ Atom("menucaption", "menucaption"),
+ Atom("menugroup", "menugroup"),
+ Atom("menuitem", "menuitem"),
+ Atom("menulist", "menulist"),
+ Atom("menupopup", "menupopup"),
+ Atom("menuseparator", "menuseparator"),
+ Atom("mesh", "mesh"),
+ Atom("message", "message"),
+ Atom("meta", "meta"),
+ Atom("referrer", "referrer"),
+ Atom("referrerpolicy", "referrerpolicy"),
+ Atom("renderroot", "renderroot"),
+ Atom("headerReferrerPolicy", "referrer-policy"),
+ Atom("meter", "meter"),
+ Atom("method", "method"),
+ Atom("middle", "middle"),
+ Atom("min", "min"),
+ Atom("minheight", "minheight"),
+ Atom("minimum_scale", "minimum-scale"),
+ Atom("minlength", "minlength"),
+ Atom("minpos", "minpos"),
+ Atom("minusSign", "minus-sign"),
+ Atom("minwidth", "minwidth"),
+ Atom("mixed", "mixed"),
+ Atom("messagemanagergroup", "messagemanagergroup"),
+ Atom("mod", "mod"),
+ Atom("mode", "mode"),
+ Atom("modifiers", "modifiers"),
+ Atom("monochrome", "monochrome"),
+ Atom("mouseover", "mouseover"),
+ Atom("mozAccessiblecaret", "moz-accessiblecaret"),
+ Atom("mozCustomContentContainer", "moz-custom-content-container"),
+ Atom("mozGrabber", "mozGrabber"),
+ Atom("mozNativeAnonymous", "-moz-native-anonymous"),
+ Atom("mozprivatebrowsing", "mozprivatebrowsing"),
+ Atom("mozResizer", "mozResizer"),
+ Atom("mozResizingInfo", "mozResizingInfo"),
+ Atom("mozResizingShadow", "mozResizingShadow"),
+ Atom("mozTableAddColumnAfter", "mozTableAddColumnAfter"),
+ Atom("mozTableAddColumnBefore", "mozTableAddColumnBefore"),
+ Atom("mozTableAddRowAfter", "mozTableAddRowAfter"),
+ Atom("mozTableAddRowBefore", "mozTableAddRowBefore"),
+ Atom("mozTableRemoveRow", "mozTableRemoveRow"),
+ Atom("mozTableRemoveColumn", "mozTableRemoveColumn"),
+ Atom("moz_opaque", "moz-opaque"),
+ Atom("moz_action_hint", "mozactionhint"),
+ Atom("multicol", "multicol"),
+ Atom("multiple", "multiple"),
+ Atom("muted", "muted"),
+ Atom("name", "name"),
+ Atom("_namespace", "namespace"),
+ Atom("namespaceAlias", "namespace-alias"),
+ Atom("namespaceUri", "namespace-uri"),
+ Atom("NaN", "NaN"),
+ Atom("n", "n"),
+ Atom("nativeAnonymousChildList", "nativeAnonymousChildList"),
+ Atom("nav", "nav"),
+ Atom("ne", "ne"),
+ Atom("never", "never"),
+ Atom("_new", "new"),
+ Atom("newline", "newline"),
+ Atom("nextRemoteTabId", "nextRemoteTabId"),
+ Atom("no", "no"),
+ Atom("noautofocus", "noautofocus"),
+ Atom("noautohide", "noautohide"),
+ Atom("norolluponanchor", "norolluponanchor"),
+ Atom("noBar", "no-bar"),
+ Atom("nobr", "nobr"),
+ Atom("nodefaultsrc", "nodefaultsrc"),
+ Atom("nodeSet", "node-set"),
+ Atom("noembed", "noembed"),
+ Atom("noframes", "noframes"),
+ Atom("nohref", "nohref"),
+ Atom("nomodule", "nomodule"),
+ Atom("nonce", "nonce"),
+ Atom("none", "none"),
+ Atom("noresize", "noresize"),
+ Atom("normal", "normal"),
+ Atom("normalizeSpace", "normalize-space"),
+ Atom("noscript", "noscript"),
+ Atom("noshade", "noshade"),
+ Atom("notification", "notification"),
+ Atom("novalidate", "novalidate"),
+ Atom("_not", "not"),
+ Atom("nowrap", "nowrap"),
+ Atom("number", "number"),
+ Atom("nw", "nw"),
+ Atom("object", "object"),
+ Atom("objectType", "object-type"),
+ Atom("observes", "observes"),
+ Atom("odd", "odd"),
+ Atom("OFF", "OFF"),
+ Atom("ol", "ol"),
+ Atom("omitXmlDeclaration", "omit-xml-declaration"),
+ Atom("onabort", "onabort"),
+ Atom("onmozaccesskeynotfound", "onmozaccesskeynotfound"),
+ Atom("onactivate", "onactivate"),
+ Atom("onafterprint", "onafterprint"),
+ Atom("onafterscriptexecute", "onafterscriptexecute"),
+ Atom("onanimationcancel", "onanimationcancel"),
+ Atom("onanimationend", "onanimationend"),
+ Atom("onanimationiteration", "onanimationiteration"),
+ Atom("onanimationstart", "onanimationstart"),
+ Atom("onAppCommand", "onAppCommand"),
+ Atom("onaudioprocess", "onaudioprocess"),
+ Atom("onauxclick", "onauxclick"),
+ Atom("onbeforecopy", "onbeforecopy"),
+ Atom("onbeforecut", "onbeforecut"),
+ Atom("onbeforeinput", "onbeforeinput"),
+ Atom("onbeforepaste", "onbeforepaste"),
+ Atom("onbeforeprint", "onbeforeprint"),
+ Atom("onbeforescriptexecute", "onbeforescriptexecute"),
+ Atom("onbeforeunload", "onbeforeunload"),
+ Atom("onblocked", "onblocked"),
+ Atom("onblur", "onblur"),
+ Atom("onbounce", "onbounce"),
+ Atom("onboundschange", "onboundschange"),
+ Atom("onbroadcast", "onbroadcast"),
+ Atom("onbufferedamountlow", "onbufferedamountlow"),
+ Atom("oncached", "oncached"),
+ Atom("oncancel", "oncancel"),
+ Atom("onchange", "onchange"),
+ Atom("onchargingchange", "onchargingchange"),
+ Atom("onchargingtimechange", "onchargingtimechange"),
+ Atom("onchecking", "onchecking"),
+ Atom("onCheckboxStateChange", "onCheckboxStateChange"),
+ Atom("onCheckKeyPressEventModel", "onCheckKeyPressEventModel"),
+ Atom("onclick", "onclick"),
+ Atom("onclose", "onclose"),
+ Atom("oncommand", "oncommand"),
+ Atom("oncommandupdate", "oncommandupdate"),
+ Atom("oncomplete", "oncomplete"),
+ Atom("oncompositionend", "oncompositionend"),
+ Atom("oncompositionstart", "oncompositionstart"),
+ Atom("oncompositionupdate", "oncompositionupdate"),
+ Atom("onconnect", "onconnect"),
+ Atom("onconnectionavailable", "onconnectionavailable"),
+ Atom("oncontextmenu", "oncontextmenu"),
+ Atom("oncontextlost", "oncontextlost"),
+ Atom("oncontextrestored", "oncontextrestored"),
+ Atom("oncopy", "oncopy"),
+ Atom("oncut", "oncut"),
+ Atom("ondblclick", "ondblclick"),
+ Atom("ondischargingtimechange", "ondischargingtimechange"),
+ Atom("ondownloading", "ondownloading"),
+ Atom("onDOMActivate", "onDOMActivate"),
+ Atom("onDOMAttrModified", "onDOMAttrModified"),
+ Atom("onDOMCharacterDataModified", "onDOMCharacterDataModified"),
+ Atom("onDOMFocusIn", "onDOMFocusIn"),
+ Atom("onDOMFocusOut", "onDOMFocusOut"),
+ Atom("onDOMMouseScroll", "onDOMMouseScroll"),
+ Atom("onDOMNodeInserted", "onDOMNodeInserted"),
+ Atom("onDOMNodeInsertedIntoDocument", "onDOMNodeInsertedIntoDocument"),
+ Atom("onDOMNodeRemoved", "onDOMNodeRemoved"),
+ Atom("onDOMNodeRemovedFromDocument", "onDOMNodeRemovedFromDocument"),
+ Atom("onDOMSubtreeModified", "onDOMSubtreeModified"),
+ Atom("ondata", "ondata"),
+ Atom("ondrag", "ondrag"),
+ Atom("ondragdrop", "ondragdrop"),
+ Atom("ondragend", "ondragend"),
+ Atom("ondragenter", "ondragenter"),
+ Atom("ondragexit", "ondragexit"),
+ Atom("ondragleave", "ondragleave"),
+ Atom("ondragover", "ondragover"),
+ Atom("ondragstart", "ondragstart"),
+ Atom("ondrain", "ondrain"),
+ Atom("ondrop", "ondrop"),
+ Atom("onerror", "onerror"),
+ Atom("onfinish", "onfinish"),
+ Atom("onfocus", "onfocus"),
+ Atom("onfocusin", "onfocusin"),
+ Atom("onfocusout", "onfocusout"),
+ Atom("onfullscreenchange", "onfullscreenchange"),
+ Atom("onfullscreenerror", "onfullscreenerror"),
+ Atom("onget", "onget"),
+ Atom("onhashchange", "onhashchange"),
+ Atom("oninput", "oninput"),
+ Atom("oninputsourceschange", "oninputsourceschange"),
+ Atom("oninstall", "oninstall"),
+ Atom("oninvalid", "oninvalid"),
+ Atom("onkeydown", "onkeydown"),
+ Atom("onkeypress", "onkeypress"),
+ Atom("onkeyup", "onkeyup"),
+ Atom("onlanguagechange", "onlanguagechange"),
+ Atom("onlevelchange", "onlevelchange"),
+ Atom("onload", "onload"),
+ Atom("onloading", "onloading"),
+ Atom("onloadingdone", "onloadingdone"),
+ Atom("onloadingerror", "onloadingerror"),
+ Atom("onpopstate", "onpopstate"),
+ Atom("only", "only"), # this one is not an event
+ Atom("onmerchantvalidation", "onmerchantvalidation"),
+ Atom("onmessage", "onmessage"),
+ Atom("onmessageerror", "onmessageerror"),
+ Atom("onmidimessage", "onmidimessage"),
+ Atom("onmousedown", "onmousedown"),
+ Atom("onmouseenter", "onmouseenter"),
+ Atom("onmouseleave", "onmouseleave"),
+ Atom("onmouselongtap", "onmouselongtap"),
+ Atom("onmousemove", "onmousemove"),
+ Atom("onmouseout", "onmouseout"),
+ Atom("onmouseover", "onmouseover"),
+ Atom("onMozMouseHittest", "onMozMouseHittest"),
+ Atom("onMozMouseExploreByTouch", "onMozMouseExploreByTouch"),
+ Atom("onmouseup", "onmouseup"),
+ Atom("onMozAfterPaint", "onMozAfterPaint"),
+ Atom("onmozfullscreenchange", "onmozfullscreenchange"),
+ Atom("onmozfullscreenerror", "onmozfullscreenerror"),
+ Atom("onmozpointerlockchange", "onmozpointerlockchange"),
+ Atom("onmozpointerlockerror", "onmozpointerlockerror"),
+ Atom("onMozMousePixelScroll", "onMozMousePixelScroll"),
+ Atom("onMozScrolledAreaChanged", "onMozScrolledAreaChanged"),
+ Atom("onmute", "onmute"),
+ Atom("onnotificationclick", "onnotificationclick"),
+ Atom("onnotificationclose", "onnotificationclose"),
+ Atom("onnoupdate", "onnoupdate"),
+ Atom("onobsolete", "onobsolete"),
+ Atom("ononline", "ononline"),
+ Atom("onoffline", "onoffline"),
+ Atom("onopen", "onopen"),
+ Atom("onorientationchange", "onorientationchange"),
+ Atom("onoverflow", "onoverflow"),
+ Atom("onpagehide", "onpagehide"),
+ Atom("onpageshow", "onpageshow"),
+ Atom("onpaste", "onpaste"),
+ Atom("onpayerdetailchange", "onpayerdetailchange"),
+ Atom("onpaymentmethodchange", "onpaymentmethodchange"),
+ Atom("onpointerlockchange", "onpointerlockchange"),
+ Atom("onpointerlockerror", "onpointerlockerror"),
+ Atom("onpopuphidden", "onpopuphidden"),
+ Atom("onpopuphiding", "onpopuphiding"),
+ Atom("onpopuppositioned", "onpopuppositioned"),
+ Atom("onpopupshowing", "onpopupshowing"),
+ Atom("onpopupshown", "onpopupshown"),
+ Atom("onprocessorerror", "onprocessorerror"),
+ Atom("onprioritychange", "onprioritychange"),
+ Atom("onpush", "onpush"),
+ Atom("onpushsubscriptionchange", "onpushsubscriptionchange"),
+ Atom("onRadioStateChange", "onRadioStateChange"),
+ Atom("onreadystatechange", "onreadystatechange"),
+ Atom("onrejectionhandled", "onrejectionhandled"),
+ Atom("onremove", "onremove"),
+ Atom("onrequestprogress", "onrequestprogress"),
+ Atom("onresourcetimingbufferfull", "onresourcetimingbufferfull"),
+ Atom("onresponseprogress", "onresponseprogress"),
+ Atom("onRequest", "onRequest"),
+ Atom("onreset", "onreset"),
+ Atom("onresize", "onresize"),
+ Atom("onscroll", "onscroll"),
+ Atom("onsecuritypolicyviolation", "onsecuritypolicyviolation"),
+ Atom("onselect", "onselect"),
+ Atom("onselectionchange", "onselectionchange"),
+ Atom("onselectend", "onselectend"),
+ Atom("onselectstart", "onselectstart"),
+ Atom("onset", "onset"),
+ Atom("onshippingaddresschange", "onshippingaddresschange"),
+ Atom("onshippingoptionchange", "onshippingoptionchange"),
+ Atom("onshow", "onshow"),
+ Atom("onslotchange", "onslotchange"),
+ Atom("onsqueeze", "onsqueeze"),
+ Atom("onsqueezeend", "onsqueezeend"),
+ Atom("onsqueezestart", "onsqueezestart"),
+ Atom("onstatechange", "onstatechange"),
+ Atom("onstorage", "onstorage"),
+ Atom("onsubmit", "onsubmit"),
+ Atom("onsuccess", "onsuccess"),
+ Atom("onsystemstatusbarclick", "onsystemstatusbarclick"),
+ Atom("ontypechange", "ontypechange"),
+ Atom("onterminate", "onterminate"),
+ Atom("ontext", "ontext"),
+ Atom("ontoggle", "ontoggle"),
+ Atom("ontonechange", "ontonechange"),
+ Atom("ontouchstart", "ontouchstart"),
+ Atom("ontouchend", "ontouchend"),
+ Atom("ontouchmove", "ontouchmove"),
+ Atom("ontouchcancel", "ontouchcancel"),
+ Atom("ontransitioncancel", "ontransitioncancel"),
+ Atom("ontransitionend", "ontransitionend"),
+ Atom("ontransitionrun", "ontransitionrun"),
+ Atom("ontransitionstart", "ontransitionstart"),
+ Atom("onuncapturederror", "onuncapturederror"),
+ Atom("onunderflow", "onunderflow"),
+ Atom("onunhandledrejection", "onunhandledrejection"),
+ Atom("onunload", "onunload"),
+ Atom("onunmute", "onunmute"),
+ Atom("onupdatefound", "onupdatefound"),
+ Atom("onupdateready", "onupdateready"),
+ Atom("onupgradeneeded", "onupgradeneeded"),
+ Atom("onversionchange", "onversionchange"),
+ Atom("onvisibilitychange", "onvisibilitychange"),
+ Atom("onvoiceschanged", "onvoiceschanged"),
+ Atom("onvrdisplayactivate", "onvrdisplayactivate"),
+ Atom("onvrdisplayconnect", "onvrdisplayconnect"),
+ Atom("onvrdisplaydeactivate", "onvrdisplaydeactivate"),
+ Atom("onvrdisplaydisconnect", "onvrdisplaydisconnect"),
+ Atom("onvrdisplaypresentchange", "onvrdisplaypresentchange"),
+ Atom("onwebkitAnimationEnd", "onwebkitAnimationEnd"),
+ Atom("onwebkitAnimationIteration", "onwebkitAnimationIteration"),
+ Atom("onwebkitAnimationStart", "onwebkitAnimationStart"),
+ Atom("onwebkitTransitionEnd", "onwebkitTransitionEnd"),
+ Atom("onwebkitanimationend", "onwebkitanimationend"),
+ Atom("onwebkitanimationiteration", "onwebkitanimationiteration"),
+ Atom("onwebkitanimationstart", "onwebkitanimationstart"),
+ Atom("onwebkittransitionend", "onwebkittransitionend"),
+ Atom("onwheel", "onwheel"),
+ Atom("open", "open"),
+ Atom("optgroup", "optgroup"),
+ Atom("optimum", "optimum"),
+ Atom("option", "option"),
+ Atom("_or", "or"),
+ Atom("order", "order"),
+ Atom("orient", "orient"),
+ Atom("orientation", "orientation"),
+ Atom("origin_trial", "origin-trial"),
+ Atom("otherwise", "otherwise"),
+ Atom("output", "output"),
+ Atom("overflow", "overflow"),
+ Atom("overflowBlock", "overflow-block"),
+ Atom("overflowInline", "overflow-inline"),
+ Atom("overlay", "overlay"),
+ Atom("p", "p"),
+ Atom("pack", "pack"),
+ Atom("page", "page"),
+ Atom("pageincrement", "pageincrement"),
+ Atom("paint", "paint"),
+ Atom("paint_order", "paint-order"),
+ Atom("panel", "panel"),
+ Atom("paragraph", "paragraph"),
+ Atom("param", "param"),
+ Atom("parameter", "parameter"),
+ Atom("parent", "parent"),
+ Atom("parentfocused", "parentfocused"),
+ Atom("parsererror", "parsererror"),
+ Atom("part", "part"),
+ Atom("password", "password"),
+ Atom("pattern", "pattern"),
+ Atom("patternSeparator", "pattern-separator"),
+ Atom("perMille", "per-mille"),
+ Atom("percent", "percent"),
+ Atom("persist", "persist"),
+ Atom("phase", "phase"),
+ Atom("picture", "picture"),
+ Atom("ping", "ping"),
+ Atom("pinned", "pinned"),
+ Atom("placeholder", "placeholder"),
+ Atom("plaintext", "plaintext"),
+ Atom("playbackrate", "playbackrate"),
+ Atom("pointSize", "point-size"),
+ Atom("poly", "poly"),
+ Atom("polygon", "polygon"),
+ Atom("popup", "popup"),
+ Atom("popupalign", "popupalign"),
+ Atom("popupanchor", "popupanchor"),
+ Atom("popupgroup", "popupgroup"),
+ Atom("popupset", "popupset"),
+ Atom("popupsinherittooltip", "popupsinherittooltip"),
+ Atom("portal", "portal"),
+ Atom("position", "position"),
+ Atom("poster", "poster"),
+ Atom("pre", "pre"),
+ Atom("preceding", "preceding"),
+ Atom("precedingSibling", "preceding-sibling"),
+ Atom("prefersReducedMotion", "prefers-reduced-motion"),
+ Atom("prefersColorScheme", "prefers-color-scheme"),
+ Atom("prefersContrast", "prefers-contrast"),
+ Atom("prefix", "prefix"),
+ Atom("prefwidth", "prefwidth"),
+ Atom("dynamicRange", "dynamic-range"),
+ Atom("videoDynamicRange", "video-dynamic-range"),
+ Atom("preload", "preload"),
+ Atom("preserve", "preserve"),
+ Atom("preserveSpace", "preserve-space"),
+ Atom("preventdefault", "preventdefault"),
+ Atom("previewDiv", "preview-div"),
+ Atom("primary", "primary"),
+ Atom("print", "print"),
+ Atom("printisfocuseddoc", "printisfocuseddoc"),
+ Atom("printselectionranges", "printselectionranges"),
+ Atom("priority", "priority"),
+ Atom("processingInstruction", "processing-instruction"),
+ Atom("profile", "profile"),
+ Atom("progress", "progress"),
+ Atom("prompt", "prompt"),
+ Atom("properties", "properties"),
+ Atom("property", "property"),
+ Atom("pubdate", "pubdate"),
+ Atom("q", "q"),
+ Atom("radio", "radio"),
+ Atom("radioLabel", "radio-label"),
+ Atom("radiogroup", "radiogroup"),
+ Atom("range", "range"),
+ Atom("readonly", "readonly"),
+ Atom("rect", "rect"),
+ Atom("rectangle", "rectangle"),
+ Atom("refresh", "refresh"),
+ Atom("rel", "rel"),
+ Atom("relativeBounds", "relative-bounds"),
+ Atom("rem", "rem"),
+ Atom("remote", "remote"),
+ Atom("removeelement", "removeelement"),
+ Atom("renderingobserverset", "renderingobserverset"),
+ Atom("repeat", "repeat"),
+ Atom("replace", "replace"),
+ Atom("requestcontextid", "requestcontextid"),
+ Atom("required", "required"),
+ Atom("reserved", "reserved"),
+ Atom("reset", "reset"),
+ Atom("resizeafter", "resizeafter"),
+ Atom("resizebefore", "resizebefore"),
+ Atom("resizer", "resizer"),
+ Atom("resolution", "resolution"),
+ Atom("resources", "resources"),
+ Atom("result", "result"),
+ Atom("resultPrefix", "result-prefix"),
+ Atom("retargetdocumentfocus", "retargetdocumentfocus"),
+ Atom("rev", "rev"),
+ Atom("reverse", "reverse"),
+ Atom("reversed", "reversed"),
+ Atom("rhs", "rhs"),
+ Atom("richlistbox", "richlistbox"),
+ Atom("richlistitem", "richlistitem"),
+ Atom("right", "right"),
+ Atom("rightmargin", "rightmargin"),
+ Atom("role", "role"),
+ Atom("rolluponmousewheel", "rolluponmousewheel"),
+ Atom("round", "round"),
+ Atom("row", "row"),
+ Atom("rows", "rows"),
+ Atom("rowspan", "rowspan"),
+ Atom("rb", "rb"),
+ Atom("rp", "rp"),
+ Atom("rt", "rt"),
+ Atom("rtc", "rtc"),
+ Atom("rtl", "rtl"),
+ Atom("ruby", "ruby"),
+ Atom("rubyBase", "ruby-base"),
+ Atom("rubyBaseContainer", "ruby-base-container"),
+ Atom("rubyText", "ruby-text"),
+ Atom("rubyTextContainer", "ruby-text-container"),
+ Atom("rules", "rules"),
+ Atom("s", "s"),
+ Atom("safe_area_inset_top", "safe-area-inset-top"),
+ Atom("safe_area_inset_bottom", "safe-area-inset-bottom"),
+ Atom("safe_area_inset_left", "safe-area-inset-left"),
+ Atom("safe_area_inset_right", "safe-area-inset-right"),
+ Atom("samp", "samp"),
+ Atom("sandbox", "sandbox"),
+ Atom("sbattr", "sbattr"),
+ Atom("scale", "scale"),
+ Atom("scan", "scan"),
+ Atom("scheme", "scheme"),
+ Atom("scope", "scope"),
+ Atom("scoped", "scoped"),
+ Atom("screen", "screen"),
+ Atom("screenX", "screenX"),
+ Atom("screenx", "screenx"),
+ Atom("screenY", "screenY"),
+ Atom("screeny", "screeny"),
+ Atom("script", "script"),
+ Atom("scrollbar", "scrollbar"),
+ Atom("scrollbarThumb", "scrollbar-thumb"),
+ Atom("scrollamount", "scrollamount"),
+ Atom("scrollbarbutton", "scrollbarbutton"),
+ Atom("scrollbarDownBottom", "scrollbar-down-bottom"),
+ Atom("scrollbarDownTop", "scrollbar-down-top"),
+ Atom("scrollbarUpBottom", "scrollbar-up-bottom"),
+ Atom("scrollbarUpTop", "scrollbar-up-top"),
+ Atom("scrollbox", "scrollbox"),
+ Atom("scrollcorner", "scrollcorner"),
+ Atom("scrolldelay", "scrolldelay"),
+ Atom("scrolling", "scrolling"),
+ Atom("scrollPosition", "scroll-position"),
+ Atom("se", "se"),
+ Atom("section", "section"),
+ Atom("select", "select"),
+ Atom("selected", "selected"),
+ Atom("selectedIndex", "selectedIndex"),
+ Atom("selectedindex", "selectedindex"),
+ Atom("selectmenu", "selectmenu"),
+ Atom("self", "self"),
+ Atom("seltype", "seltype"),
+ Atom("setcookie", "set-cookie"),
+ Atom("setter", "setter"),
+ Atom("shadow", "shadow"),
+ Atom("shape", "shape"),
+ Atom("show", "show"),
+ Atom("showcaret", "showcaret"),
+ Atom("sibling", "sibling"),
+ Atom("simple", "simple"),
+ Atom("simp_chinese_formal", "simp-chinese-formal"),
+ Atom("simp_chinese_informal", "simp-chinese-informal"),
+ Atom("single", "single"),
+ Atom("size", "size"),
+ Atom("sizes", "sizes"),
+ Atom("sizemode", "sizemode"),
+ Atom("sizetopopup", "sizetopopup"),
+ Atom("slider", "slider"),
+ Atom("small", "small"),
+ Atom("smooth", "smooth"),
+ Atom("snap", "snap"),
+ Atom("solid", "solid"),
+ Atom("sort", "sort"),
+ Atom("sortActive", "sortActive"),
+ Atom("sortDirection", "sortDirection"),
+ Atom("sorted", "sorted"),
+ Atom("sorthints", "sorthints"),
+ Atom("source", "source"),
+ Atom("sourcetext", "sourcetext"),
+ Atom("space", "space"),
+ Atom("spacer", "spacer"),
+ Atom("span", "span"),
+ Atom("spellcheck", "spellcheck"),
+ Atom("split", "split"),
+ Atom("splitter", "splitter"),
+ Atom("square", "square"),
+ Atom("src", "src"),
+ Atom("srcdoc", "srcdoc"),
+ Atom("srclang", "srclang"),
+ Atom("srcset", "srcset"),
+ Atom("standalone", "standalone"),
+ Atom("standby", "standby"),
+ Atom("start", "start"),
+ Atom("startsWith", "starts-with"),
+ Atom("state", "state"),
+ Atom("statusbar", "statusbar"),
+ Atom("step", "step"),
+ Atom("stop", "stop"),
+ Atom("stretch", "stretch"),
+ Atom("strike", "strike"),
+ Atom("string", "string"),
+ Atom("stringLength", "string-length"),
+ Atom("stripSpace", "strip-space"),
+ Atom("strong", "strong"),
+ Atom("style", "style"),
+ Atom("stylesheet", "stylesheet"),
+ Atom("stylesheetPrefix", "stylesheet-prefix"),
+ Atom("submit", "submit"),
+ Atom("substate", "substate"),
+ Atom("substring", "substring"),
+ Atom("substringAfter", "substring-after"),
+ Atom("substringBefore", "substring-before"),
+ Atom("sub", "sub"),
+ Atom("suggestion", "suggestion"),
+ Atom("sum", "sum"),
+ Atom("sup", "sup"),
+ Atom("summary", "summary"),
+ Atom("sw", "sw"),
+ # Atom("_switch", "switch"), # "switch" is present below
+ Atom("systemProperty", "system-property"),
+ Atom("tab", "tab"),
+ Atom("tabindex", "tabindex"),
+ Atom("table", "table"),
+ Atom("tabpanel", "tabpanel"),
+ Atom("tabpanels", "tabpanels"),
+ Atom("tag", "tag"),
+ Atom("target", "target"),
+ Atom("targets", "targets"),
+ Atom("tbody", "tbody"),
+ Atom("td", "td"),
+ Atom("tel", "tel"),
+ Atom("_template", "template"),
+ Atom("text_decoration", "text-decoration"),
+ Atom("terminate", "terminate"),
+ Atom("term", "term"),
+ Atom("test", "test"),
+ Atom("text", "text"),
+ Atom("textAlign", "text-align"),
+ Atom("textarea", "textarea"),
+ Atom("textbox", "textbox"),
+ Atom("textLink", "text-link"),
+ Atom("textNodeDirectionalityMap", "textNodeDirectionalityMap"),
+ Atom("textOverlay", "text-overlay"),
+ Atom("tfoot", "tfoot"),
+ Atom("th", "th"),
+ Atom("thead", "thead"),
+ Atom("thumb", "thumb"),
+ Atom("time", "time"),
+ Atom("title", "title"),
+ Atom("titlebar", "titlebar"),
+ Atom("titletip", "titletip"),
+ Atom("token", "token"),
+ Atom("tokenize", "tokenize"),
+ Atom("toolbar", "toolbar"),
+ Atom("toolbarbutton", "toolbarbutton"),
+ Atom("toolbaritem", "toolbaritem"),
+ Atom("toolbarpaletteitem", "toolbarpaletteitem"),
+ Atom("toolbox", "toolbox"),
+ Atom("tooltip", "tooltip"),
+ Atom("tooltiptext", "tooltiptext"),
+ Atom("top", "top"),
+ Atom("topleft", "topleft"),
+ Atom("topmargin", "topmargin"),
+ Atom("topright", "topright"),
+ Atom("tr", "tr"),
+ Atom("track", "track"),
+ Atom("trad_chinese_formal", "trad-chinese-formal"),
+ Atom("trad_chinese_informal", "trad-chinese-informal"),
+ Atom("trailing", "trailing"),
+ Atom("transform", "transform"),
+ Atom("transform_3d", "transform-3d"),
+ Atom("transformiix", "transformiix"),
+ Atom("translate", "translate"),
+ Atom("transparent", "transparent"),
+ Atom("tree", "tree"),
+ Atom("treecell", "treecell"),
+ Atom("treechildren", "treechildren"),
+ Atom("treecol", "treecol"),
+ Atom("treecolpicker", "treecolpicker"),
+ Atom("treecols", "treecols"),
+ Atom("treeitem", "treeitem"),
+ Atom("treerow", "treerow"),
+ Atom("treeseparator", "treeseparator"),
+ Atom("_true", "true"),
+ Atom("truespeed", "truespeed"),
+ Atom("tt", "tt"),
+ Atom("type", "type"),
+ Atom("u", "u"),
+ Atom("ul", "ul"),
+ Atom("unparsedEntityUri", "unparsed-entity-uri"),
+ Atom("up", "up"),
+ Atom("update", "update"),
+ Atom("upperFirst", "upper-first"),
+ Atom("use", "use"),
+ Atom("useAttributeSets", "use-attribute-sets"),
+ Atom("usemap", "usemap"),
+ Atom("user_scalable", "user-scalable"),
+ Atom("validate", "validate"),
+ Atom("valign", "valign"),
+ Atom("value", "value"),
+ Atom("values", "values"),
+ Atom("valueOf", "value-of"),
+ Atom("valuetype", "valuetype"),
+ Atom("var", "var"),
+ Atom("variable", "variable"),
+ Atom("vendor", "vendor"),
+ Atom("vendorUrl", "vendor-url"),
+ Atom("version", "version"),
+ Atom("vertical", "vertical"),
+ Atom("audio", "audio"),
+ Atom("video", "video"),
+ Atom("viewport", "viewport"),
+ Atom("viewport_fit", "viewport-fit"),
+ Atom("viewport_height", "viewport-height"),
+ Atom("viewport_initial_scale", "viewport-initial-scale"),
+ Atom("viewport_maximum_scale", "viewport-maximum-scale"),
+ Atom("viewport_minimum_scale", "viewport-minimum-scale"),
+ Atom("viewport_user_scalable", "viewport-user-scalable"),
+ Atom("viewport_width", "viewport-width"),
+ Atom("visibility", "visibility"),
+ Atom("visuallyselected", "visuallyselected"),
+ Atom("vlink", "vlink"),
+ Atom("_void", "void"),
+ Atom("vsides", "vsides"),
+ Atom("vspace", "vspace"),
+ Atom("w", "w"),
+ Atom("wbr", "wbr"),
+ Atom("webkitdirectory", "webkitdirectory"),
+ Atom("when", "when"),
+ Atom("white", "white"),
+ Atom("width", "width"),
+ Atom("willChange", "will-change"),
+ Atom("window", "window"),
+ Atom("headerWindowTarget", "window-target"),
+ Atom("windowtype", "windowtype"),
+ Atom("withParam", "with-param"),
+ Atom("wrap", "wrap"),
+ Atom("headerDNSPrefetchControl", "x-dns-prefetch-control"),
+ Atom("headerCSP", "content-security-policy"),
+ Atom("headerCSPReportOnly", "content-security-policy-report-only"),
+ Atom("headerXFO", "x-frame-options"),
+ Atom("x_western", "x-western"),
+ Atom("xml", "xml"),
+ Atom("xml_stylesheet", "xml-stylesheet"),
+ Atom("xmlns", "xmlns"),
+ Atom("xmp", "xmp"),
+ Atom("xul", "xul"),
+ Atom("yes", "yes"),
+ Atom("z_index", "z-index"),
+ Atom("zeroDigit", "zero-digit"),
+ Atom("zlevel", "zlevel"),
+ Atom("percentage", "%"),
+ Atom("A", "A"),
+ Atom("alignment_baseline", "alignment-baseline"),
+ Atom("amplitude", "amplitude"),
+ Atom("animate", "animate"),
+ Atom("animateColor", "animateColor"),
+ Atom("animateMotion", "animateMotion"),
+ Atom("animateTransform", "animateTransform"),
+ Atom("arithmetic", "arithmetic"),
+ Atom("atop", "atop"),
+ Atom("azimuth", "azimuth"),
+ Atom("B", "B"),
+ Atom("backgroundColor", "background-color"),
+ Atom("background_image", "background-image"),
+ Atom("baseFrequency", "baseFrequency"),
+ Atom("baseline_shift", "baseline-shift"),
+ Atom("bias", "bias"),
+ Atom("caption_side", "caption-side"),
+ Atom("clip_path", "clip-path"),
+ Atom("clip_rule", "clip-rule"),
+ Atom("clipPath", "clipPath"),
+ Atom("clipPathUnits", "clipPathUnits"),
+ Atom("cm", "cm"),
+ Atom("colorBurn", "color-burn"),
+ Atom("colorDodge", "color-dodge"),
+ Atom("colorInterpolation", "color-interpolation"),
+ Atom("colorInterpolationFilters", "color-interpolation-filters"),
+ Atom("colorProfile", "color-profile"),
+ Atom("cursor", "cursor"),
+ Atom("cx", "cx"),
+ Atom("cy", "cy"),
+ Atom("d", "d"),
+ Atom("darken", "darken"),
+ Atom("defs", "defs"),
+ Atom("deg", "deg"),
+ Atom("desc", "desc"),
+ Atom("diffuseConstant", "diffuseConstant"),
+ Atom("dilate", "dilate"),
+ Atom("direction", "direction"),
+ Atom("disable", "disable"),
+ Atom("disc", "disc"),
+ Atom("discrete", "discrete"),
+ Atom("divisor", "divisor"),
+ Atom("dominant_baseline", "dominant-baseline"),
+ Atom("duplicate", "duplicate"),
+ Atom("dx", "dx"),
+ Atom("dy", "dy"),
+ Atom("edgeMode", "edgeMode"),
+ Atom("ellipse", "ellipse"),
+ Atom("elevation", "elevation"),
+ Atom("erode", "erode"),
+ Atom("ex", "ex"),
+ Atom("exact", "exact"),
+ Atom("exclusion", "exclusion"),
+ Atom("exponent", "exponent"),
+ Atom("feBlend", "feBlend"),
+ Atom("feColorMatrix", "feColorMatrix"),
+ Atom("feComponentTransfer", "feComponentTransfer"),
+ Atom("feComposite", "feComposite"),
+ Atom("feConvolveMatrix", "feConvolveMatrix"),
+ Atom("feDiffuseLighting", "feDiffuseLighting"),
+ Atom("feDisplacementMap", "feDisplacementMap"),
+ Atom("feDistantLight", "feDistantLight"),
+ Atom("feDropShadow", "feDropShadow"),
+ Atom("feFlood", "feFlood"),
+ Atom("feFuncA", "feFuncA"),
+ Atom("feFuncB", "feFuncB"),
+ Atom("feFuncG", "feFuncG"),
+ Atom("feFuncR", "feFuncR"),
+ Atom("feGaussianBlur", "feGaussianBlur"),
+ Atom("feImage", "feImage"),
+ Atom("feMerge", "feMerge"),
+ Atom("feMergeNode", "feMergeNode"),
+ Atom("feMorphology", "feMorphology"),
+ Atom("feOffset", "feOffset"),
+ Atom("fePointLight", "fePointLight"),
+ Atom("feSpecularLighting", "feSpecularLighting"),
+ Atom("feSpotLight", "feSpotLight"),
+ Atom("feTile", "feTile"),
+ Atom("feTurbulence", "feTurbulence"),
+ Atom("fill", "fill"),
+ Atom("fill_opacity", "fill-opacity"),
+ Atom("fill_rule", "fill-rule"),
+ Atom("filter", "filter"),
+ Atom("filterUnits", "filterUnits"),
+ Atom("_float", "float"),
+ Atom("flood_color", "flood-color"),
+ Atom("flood_opacity", "flood-opacity"),
+ Atom("font_face", "font-face"),
+ Atom("font_face_format", "font-face-format"),
+ Atom("font_face_name", "font-face-name"),
+ Atom("font_face_src", "font-face-src"),
+ Atom("font_face_uri", "font-face-uri"),
+ Atom("font_family", "font-family"),
+ Atom("font_size", "font-size"),
+ Atom("font_size_adjust", "font-size-adjust"),
+ Atom("font_stretch", "font-stretch"),
+ Atom("font_style", "font-style"),
+ Atom("font_variant", "font-variant"),
+ Atom("formatting", "formatting"),
+ Atom("foreignObject", "foreignObject"),
+ Atom("fractalNoise", "fractalNoise"),
+ Atom("fr", "fr"),
+ Atom("fx", "fx"),
+ Atom("fy", "fy"),
+ Atom("G", "G"),
+ Atom("g", "g"),
+ Atom("gamma", "gamma"),
+ Atom("glyphRef", "glyphRef"),
+ Atom("grad", "grad"),
+ Atom("gradientTransform", "gradientTransform"),
+ Atom("gradientUnits", "gradientUnits"),
+ Atom("hardLight", "hard-light"),
+ Atom("hue", "hue"),
+ Atom("hueRotate", "hueRotate"),
+ Atom("identity", "identity"),
+ Atom("image_rendering", "image-rendering"),
+ Atom("in", "in"),
+ Atom("in2", "in2"),
+ Atom("intercept", "intercept"),
+ Atom("k1", "k1"),
+ Atom("k2", "k2"),
+ Atom("k3", "k3"),
+ Atom("k4", "k4"),
+ Atom("kernelMatrix", "kernelMatrix"),
+ Atom("kernelUnitLength", "kernelUnitLength"),
+ Atom("lengthAdjust", "lengthAdjust"),
+ Atom("letter_spacing", "letter-spacing"),
+ Atom("lighten", "lighten"),
+ Atom("lighter", "lighter"),
+ Atom("lighting_color", "lighting-color"),
+ Atom("limitingConeAngle", "limitingConeAngle"),
+ Atom("linear", "linear"),
+ Atom("linearGradient", "linearGradient"),
+ Atom("list_item", "list-item"),
+ Atom("list_style_type", "list-style-type"),
+ Atom("luminanceToAlpha", "luminanceToAlpha"),
+ Atom("luminosity", "luminosity"),
+ Atom("magnify", "magnify"),
+ Atom("marker", "marker"),
+ Atom("marker_end", "marker-end"),
+ Atom("marker_mid", "marker-mid"),
+ Atom("marker_start", "marker-start"),
+ Atom("markerHeight", "markerHeight"),
+ Atom("markerUnits", "markerUnits"),
+ Atom("markerWidth", "markerWidth"),
+ Atom("mask", "mask"),
+ Atom("maskContentUnits", "maskContentUnits"),
+ Atom("mask_type", "mask-type"),
+ Atom("maskUnits", "maskUnits"),
+ Atom("matrix", "matrix"),
+ Atom("metadata", "metadata"),
+ Atom("missingGlyph", "missing-glyph"),
+ Atom("mm", "mm"),
+ Atom("mpath", "mpath"),
+ Atom("noStitch", "noStitch"),
+ Atom("numOctaves", "numOctaves"),
+ Atom("multiply", "multiply"),
+ Atom("objectBoundingBox", "objectBoundingBox"),
+ Atom("offset", "offset"),
+ Atom("onSVGLoad", "onSVGLoad"),
+ Atom("onSVGScroll", "onSVGScroll"),
+ Atom("onzoom", "onzoom"),
+ Atom("opacity", "opacity"),
+ Atom("_operator", "operator"),
+ Atom("out", "out"),
+ Atom("over", "over"),
+ Atom("overridePreserveAspectRatio", "overridePreserveAspectRatio"),
+ Atom("pad", "pad"),
+ Atom("path", "path"),
+ Atom("pathLength", "pathLength"),
+ Atom("patternContentUnits", "patternContentUnits"),
+ Atom("patternTransform", "patternTransform"),
+ Atom("patternUnits", "patternUnits"),
+ Atom("pc", "pc"),
+ Atom("pointer", "pointer"),
+ Atom("pointer_events", "pointer-events"),
+ Atom("points", "points"),
+ Atom("pointsAtX", "pointsAtX"),
+ Atom("pointsAtY", "pointsAtY"),
+ Atom("pointsAtZ", "pointsAtZ"),
+ Atom("polyline", "polyline"),
+ Atom("preserveAlpha", "preserveAlpha"),
+ Atom("preserveAspectRatio", "preserveAspectRatio"),
+ Atom("primitiveUnits", "primitiveUnits"),
+ Atom("pt", "pt"),
+ Atom("px", "px"),
+ Atom("R", "R"),
+ Atom("r", "r"),
+ Atom("rad", "rad"),
+ Atom("radialGradient", "radialGradient"),
+ Atom("radius", "radius"),
+ Atom("reflect", "reflect"),
+ Atom("refX", "refX"),
+ Atom("refY", "refY"),
+ Atom("requiredExtensions", "requiredExtensions"),
+ Atom("requiredFeatures", "requiredFeatures"),
+ Atom("rotate", "rotate"),
+ Atom("rx", "rx"),
+ Atom("ry", "ry"),
+ Atom("saturate", "saturate"),
+ Atom("saturation", "saturation"),
+ Atom("set", "set"),
+ Atom("seed", "seed"),
+ Atom("shape_rendering", "shape-rendering"),
+ Atom("simpleScopeChain", "simpleScopeChain"),
+ Atom("skewX", "skewX"),
+ Atom("skewY", "skewY"),
+ Atom("slope", "slope"),
+ Atom("slot", "slot"),
+ Atom("softLight", "soft-light"),
+ Atom("spacing", "spacing"),
+ Atom("spacingAndGlyphs", "spacingAndGlyphs"),
+ Atom("specularConstant", "specularConstant"),
+ Atom("specularExponent", "specularExponent"),
+ Atom("spreadMethod", "spreadMethod"),
+ Atom("startOffset", "startOffset"),
+ Atom("stdDeviation", "stdDeviation"),
+ Atom("stitch", "stitch"),
+ Atom("stitchTiles", "stitchTiles"),
+ Atom("stop_color", "stop-color"),
+ Atom("stop_opacity", "stop-opacity"),
+ Atom("stroke", "stroke"),
+ Atom("stroke_dasharray", "stroke-dasharray"),
+ Atom("stroke_dashoffset", "stroke-dashoffset"),
+ Atom("stroke_linecap", "stroke-linecap"),
+ Atom("stroke_linejoin", "stroke-linejoin"),
+ Atom("stroke_miterlimit", "stroke-miterlimit"),
+ Atom("stroke_opacity", "stroke-opacity"),
+ Atom("stroke_width", "stroke-width"),
+ Atom("strokeWidth", "strokeWidth"),
+ Atom("surfaceScale", "surfaceScale"),
+ Atom("svg", "svg"),
+ Atom("svgSwitch", "switch"),
+ Atom("symbol", "symbol"),
+ Atom("systemLanguage", "systemLanguage"),
+ Atom("tableValues", "tableValues"),
+ Atom("targetX", "targetX"),
+ Atom("targetY", "targetY"),
+ Atom("text_anchor", "text-anchor"),
+ Atom("text_rendering", "text-rendering"),
+ Atom("textLength", "textLength"),
+ Atom("textPath", "textPath"),
+ Atom("transform_origin", "transform-origin"),
+ Atom("tref", "tref"),
+ Atom("tspan", "tspan"),
+ Atom("turbulence", "turbulence"),
+ Atom("unicode_bidi", "unicode-bidi"),
+ Atom("userSpaceOnUse", "userSpaceOnUse"),
+ Atom("view", "view"),
+ Atom("viewBox", "viewBox"),
+ Atom("viewTarget", "viewTarget"),
+ Atom("white_space", "white-space"),
+ Atom("word_spacing", "word-spacing"),
+ Atom("writing_mode", "writing-mode"),
+ Atom("x", "x"),
+ Atom("x1", "x1"),
+ Atom("x2", "x2"),
+ Atom("xChannelSelector", "xChannelSelector"),
+ Atom("xor_", "xor"),
+ Atom("y", "y"),
+ Atom("y1", "y1"),
+ Atom("y2", "y2"),
+ Atom("yChannelSelector", "yChannelSelector"),
+ Atom("z", "z"),
+ Atom("zoomAndPan", "zoomAndPan"),
+ Atom("vector_effect", "vector-effect"),
+ Atom("vertical_align", "vertical-align"),
+ Atom("accumulate", "accumulate"),
+ Atom("additive", "additive"),
+ Atom("attributeName", "attributeName"),
+ Atom("attributeType", "attributeType"),
+ Atom("auto_reverse", "auto-reverse"),
+ Atom("begin", "begin"),
+ Atom("beginEvent", "beginEvent"),
+ Atom("by", "by"),
+ Atom("calcMode", "calcMode"),
+ Atom("dur", "dur"),
+ Atom("keyPoints", "keyPoints"),
+ Atom("keySplines", "keySplines"),
+ Atom("keyTimes", "keyTimes"),
+ Atom("mozAnimateMotionDummyAttr", "_mozAnimateMotionDummyAttr"),
+ Atom("onbegin", "onbegin"),
+ Atom("onbeginEvent", "onbeginEvent"),
+ Atom("onend", "onend"),
+ Atom("onendEvent", "onendEvent"),
+ Atom("onrepeat", "onrepeat"),
+ Atom("onrepeatEvent", "onrepeatEvent"),
+ Atom("repeatCount", "repeatCount"),
+ Atom("repeatDur", "repeatDur"),
+ Atom("repeatEvent", "repeatEvent"),
+ Atom("restart", "restart"),
+ Atom("to", "to"),
+ Atom("abs_", "abs"),
+ Atom("accent_", "accent"),
+ Atom("accentunder_", "accentunder"),
+ Atom("actiontype_", "actiontype"),
+ Atom("alignmentscope_", "alignmentscope"),
+ Atom("altimg_", "altimg"),
+ Atom("altimg_height_", "altimg-height"),
+ Atom("altimg_valign_", "altimg-valign"),
+ Atom("altimg_width_", "altimg-width"),
+ Atom("annotation_", "annotation"),
+ Atom("annotation_xml_", "annotation-xml"),
+ Atom("apply_", "apply"),
+ Atom("approx_", "approx"),
+ Atom("arccos_", "arccos"),
+ Atom("arccosh_", "arccosh"),
+ Atom("arccot_", "arccot"),
+ Atom("arccoth_", "arccoth"),
+ Atom("arccsc_", "arccsc"),
+ Atom("arccsch_", "arccsch"),
+ Atom("arcsec_", "arcsec"),
+ Atom("arcsech_", "arcsech"),
+ Atom("arcsin_", "arcsin"),
+ Atom("arcsinh_", "arcsinh"),
+ Atom("arctan_", "arctan"),
+ Atom("arctanh_", "arctanh"),
+ Atom("arg_", "arg"),
+ Atom("bevelled_", "bevelled"),
+ Atom("bind_", "bind"),
+ Atom("bvar_", "bvar"),
+ Atom("card_", "card"),
+ Atom("cartesianproduct_", "cartesianproduct"),
+ Atom("cbytes_", "cbytes"),
+ Atom("cd_", "cd"),
+ Atom("cdgroup_", "cdgroup"),
+ Atom("cerror_", "cerror"),
+ Atom("charalign_", "charalign"),
+ Atom("ci_", "ci"),
+ Atom("closure_", "closure"),
+ Atom("cn_", "cn"),
+ Atom("codomain_", "codomain"),
+ Atom("columnalign_", "columnalign"),
+ Atom("columnalignment_", "columnalignment"),
+ Atom("columnlines_", "columnlines"),
+ Atom("columnspacing_", "columnspacing"),
+ Atom("columnspan_", "columnspan"),
+ Atom("columnwidth_", "columnwidth"),
+ Atom("complexes_", "complexes"),
+ Atom("compose_", "compose"),
+ Atom("condition_", "condition"),
+ Atom("conjugate_", "conjugate"),
+ Atom("cos_", "cos"),
+ Atom("cosh_", "cosh"),
+ Atom("cot_", "cot"),
+ Atom("coth_", "coth"),
+ Atom("crossout_", "crossout"),
+ Atom("csc_", "csc"),
+ Atom("csch_", "csch"),
+ Atom("cs_", "cs"),
+ Atom("csymbol_", "csymbol"),
+ Atom("csp", "csp"),
+ Atom("curl_", "curl"),
+ Atom("decimalpoint_", "decimalpoint"),
+ Atom("definitionURL_", "definitionURL"),
+ Atom("degree_", "degree"),
+ Atom("denomalign_", "denomalign"),
+ Atom("depth_", "depth"),
+ Atom("determinant_", "determinant"),
+ Atom("diff_", "diff"),
+ Atom("displaystyle_", "displaystyle"),
+ Atom("divergence_", "divergence"),
+ Atom("divide_", "divide"),
+ Atom("domain_", "domain"),
+ Atom("domainofapplication_", "domainofapplication"),
+ Atom("edge_", "edge"),
+ Atom("el", "el"),
+ Atom("emptyset_", "emptyset"),
+ Atom("eq_", "eq"),
+ Atom("equalcolumns_", "equalcolumns"),
+ Atom("equalrows_", "equalrows"),
+ Atom("equivalent_", "equivalent"),
+ Atom("eulergamma_", "eulergamma"),
+ Atom("exists_", "exists"),
+ Atom("exp_", "exp"),
+ Atom("exponentiale_", "exponentiale"),
+ Atom("factorial_", "factorial"),
+ Atom("factorof_", "factorof"),
+ Atom("fence_", "fence"),
+ Atom("fn_", "fn"),
+ Atom("fontfamily_", "fontfamily"),
+ Atom("fontsize_", "fontsize"),
+ Atom("fontstyle_", "fontstyle"),
+ Atom("fontweight_", "fontweight"),
+ Atom("forall_", "forall"),
+ Atom("framespacing_", "framespacing"),
+ Atom("gcd_", "gcd"),
+ Atom("geq_", "geq"),
+ Atom("groupalign_", "groupalign"),
+ Atom("gt_", "gt"),
+ Atom("ident_", "ident"),
+ Atom("imaginaryi_", "imaginaryi"),
+ Atom("imaginary_", "imaginary"),
+ Atom("implies_", "implies"),
+ Atom("indentalignfirst_", "indentalignfirst"),
+ Atom("indentalign_", "indentalign"),
+ Atom("indentalignlast_", "indentalignlast"),
+ Atom("indentshiftfirst_", "indentshiftfirst"),
+ Atom("indentshift_", "indentshift"),
+ Atom("indenttarget_", "indenttarget"),
+ Atom("integers_", "integers"),
+ Atom("intersect_", "intersect"),
+ Atom("interval_", "interval"),
+ Atom("int_", "int"),
+ Atom("inverse_", "inverse"),
+ Atom("lambda_", "lambda"),
+ Atom("laplacian_", "laplacian"),
+ Atom("largeop_", "largeop"),
+ Atom("lcm_", "lcm"),
+ Atom("leq_", "leq"),
+ Atom("limit_", "limit"),
+ Atom("linebreak_", "linebreak"),
+ Atom("linebreakmultchar_", "linebreakmultchar"),
+ Atom("linebreakstyle_", "linebreakstyle"),
+ Atom("linethickness_", "linethickness"),
+ Atom("list_", "list"),
+ Atom("ln_", "ln"),
+ Atom("location_", "location"),
+ Atom("logbase_", "logbase"),
+ Atom("log_", "log"),
+ Atom("longdivstyle_", "longdivstyle"),
+ Atom("lowlimit_", "lowlimit"),
+ Atom("lquote_", "lquote"),
+ Atom("lspace_", "lspace"),
+ Atom("lt_", "lt"),
+ Atom("maction_", "maction"),
+ Atom("maligngroup_", "maligngroup"),
+ Atom("malignmark_", "malignmark"),
+ Atom("mathbackground_", "mathbackground"),
+ Atom("mathcolor_", "mathcolor"),
+ Atom("mathsize_", "mathsize"),
+ Atom("mathvariant_", "mathvariant"),
+ Atom("matrixrow_", "matrixrow"),
+ Atom("maxsize_", "maxsize"),
+ Atom("mean_", "mean"),
+ Atom("median_", "median"),
+ Atom("menclose_", "menclose"),
+ Atom("merror_", "merror"),
+ Atom("mfenced_", "mfenced"),
+ Atom("mfrac_", "mfrac"),
+ Atom("mglyph_", "mglyph"),
+ Atom("mi_", "mi"),
+ Atom("minlabelspacing_", "minlabelspacing"),
+ Atom("minsize_", "minsize"),
+ Atom("minus_", "minus"),
+ Atom("mlabeledtr_", "mlabeledtr"),
+ Atom("mlongdiv_", "mlongdiv"),
+ Atom("mmultiscripts_", "mmultiscripts"),
+ Atom("mn_", "mn"),
+ Atom("momentabout_", "momentabout"),
+ Atom("moment_", "moment"),
+ Atom("mo_", "mo"),
+ Atom("movablelimits_", "movablelimits"),
+ Atom("mover_", "mover"),
+ Atom("mpadded_", "mpadded"),
+ Atom("mphantom_", "mphantom"),
+ Atom("mprescripts_", "mprescripts"),
+ Atom("mroot_", "mroot"),
+ Atom("mrow_", "mrow"),
+ Atom("mscarries_", "mscarries"),
+ Atom("mscarry_", "mscarry"),
+ Atom("msgroup_", "msgroup"),
+ Atom("msline_", "msline"),
+ Atom("ms_", "ms"),
+ Atom("mspace_", "mspace"),
+ Atom("msqrt_", "msqrt"),
+ Atom("msrow_", "msrow"),
+ Atom("mstack_", "mstack"),
+ Atom("mstyle_", "mstyle"),
+ Atom("msub_", "msub"),
+ Atom("msubsup_", "msubsup"),
+ Atom("msup_", "msup"),
+ Atom("mtable_", "mtable"),
+ Atom("mtd_", "mtd"),
+ Atom("mtext_", "mtext"),
+ Atom("mtr_", "mtr"),
+ Atom("munder_", "munder"),
+ Atom("munderover_", "munderover"),
+ Atom("naturalnumbers_", "naturalnumbers"),
+ Atom("neq_", "neq"),
+ Atom("notanumber_", "notanumber"),
+ Atom("notation_", "notation"),
+ Atom("note_", "note"),
+ Atom("notin_", "notin"),
+ Atom("notprsubset_", "notprsubset"),
+ Atom("notsubset_", "notsubset"),
+ Atom("numalign_", "numalign"),
+ Atom("other", "other"),
+ Atom("outerproduct_", "outerproduct"),
+ Atom("partialdiff_", "partialdiff"),
+ Atom("piece_", "piece"),
+ Atom("piecewise_", "piecewise"),
+ Atom("pi_", "pi"),
+ Atom("plus_", "plus"),
+ Atom("power_", "power"),
+ Atom("primes_", "primes"),
+ Atom("product_", "product"),
+ Atom("prsubset_", "prsubset"),
+ Atom("quotient_", "quotient"),
+ Atom("rationals_", "rationals"),
+ Atom("real_", "real"),
+ Atom("reals_", "reals"),
+ Atom("reln_", "reln"),
+ Atom("root_", "root"),
+ Atom("rowalign_", "rowalign"),
+ Atom("rowlines_", "rowlines"),
+ Atom("rowspacing_", "rowspacing"),
+ Atom("rquote_", "rquote"),
+ Atom("rspace_", "rspace"),
+ Atom("scalarproduct_", "scalarproduct"),
+ Atom("schemaLocation_", "schemaLocation"),
+ Atom("scriptlevel_", "scriptlevel"),
+ Atom("scriptminsize_", "scriptminsize"),
+ Atom("scriptsizemultiplier_", "scriptsizemultiplier"),
+ Atom("scriptsize_", "scriptsize"),
+ Atom("sdev_", "sdev"),
+ Atom("sech_", "sech"),
+ Atom("sec_", "sec"),
+ Atom("selection_", "selection"),
+ Atom("selector_", "selector"),
+ Atom("semantics_", "semantics"),
+ Atom("separator_", "separator"),
+ Atom("separators_", "separators"),
+ Atom("sep_", "sep"),
+ Atom("setdiff_", "setdiff"),
+ # Atom("set_", "set"), # "set" is present above
+ Atom("share_", "share"),
+ Atom("shift_", "shift"),
+ Atom("side_", "side"),
+ Atom("sinh_", "sinh"),
+ Atom("sin_", "sin"),
+ Atom("stackalign_", "stackalign"),
+ Atom("stretchy_", "stretchy"),
+ Atom("subscriptshift_", "subscriptshift"),
+ Atom("subset_", "subset"),
+ Atom("superscriptshift_", "superscriptshift"),
+ Atom("symmetric_", "symmetric"),
+ Atom("tanh_", "tanh"),
+ Atom("tan_", "tan"),
+ Atom("tendsto_", "tendsto"),
+ Atom("times_", "times"),
+ Atom("transpose_", "transpose"),
+ Atom("union_", "union"),
+ Atom("uplimit_", "uplimit"),
+ Atom("variance_", "variance"),
+ Atom("vectorproduct_", "vectorproduct"),
+ Atom("vector_", "vector"),
+ Atom("voffset_", "voffset"),
+ Atom("xref_", "xref"),
+ Atom("math", "math"), # the only one without an underscore
+ Atom("booleanFromString", "boolean-from-string"),
+ Atom("countNonEmpty", "count-non-empty"),
+ Atom("daysFromDate", "days-from-date"),
+ Atom("secondsFromDateTime", "seconds-from-dateTime"),
+ Atom("tabbrowser_arrowscrollbox", "tabbrowser-arrowscrollbox"),
+ # Simple gestures support
+ Atom("onMozSwipeGestureMayStart", "onMozSwipeGestureMayStart"),
+ Atom("onMozSwipeGestureStart", "onMozSwipeGestureStart"),
+ Atom("onMozSwipeGestureUpdate", "onMozSwipeGestureUpdate"),
+ Atom("onMozSwipeGestureEnd", "onMozSwipeGestureEnd"),
+ Atom("onMozSwipeGesture", "onMozSwipeGesture"),
+ Atom("onMozMagnifyGestureStart", "onMozMagnifyGestureStart"),
+ Atom("onMozMagnifyGestureUpdate", "onMozMagnifyGestureUpdate"),
+ Atom("onMozMagnifyGesture", "onMozMagnifyGesture"),
+ Atom("onMozRotateGestureStart", "onMozRotateGestureStart"),
+ Atom("onMozRotateGestureUpdate", "onMozRotateGestureUpdate"),
+ Atom("onMozRotateGesture", "onMozRotateGesture"),
+ Atom("onMozTapGesture", "onMozTapGesture"),
+ Atom("onMozPressTapGesture", "onMozPressTapGesture"),
+ Atom("onMozEdgeUIStarted", "onMozEdgeUIStarted"),
+ Atom("onMozEdgeUICanceled", "onMozEdgeUICanceled"),
+ Atom("onMozEdgeUICompleted", "onMozEdgeUICompleted"),
+ # Pointer events
+ Atom("onpointerdown", "onpointerdown"),
+ Atom("onpointermove", "onpointermove"),
+ Atom("onpointerup", "onpointerup"),
+ Atom("onpointercancel", "onpointercancel"),
+ Atom("onpointerover", "onpointerover"),
+ Atom("onpointerout", "onpointerout"),
+ Atom("onpointerenter", "onpointerenter"),
+ Atom("onpointerleave", "onpointerleave"),
+ Atom("ongotpointercapture", "ongotpointercapture"),
+ Atom("onlostpointercapture", "onlostpointercapture"),
+ # orientation support
+ Atom("ondevicemotion", "ondevicemotion"),
+ Atom("ondeviceorientation", "ondeviceorientation"),
+ Atom("ondeviceorientationabsolute", "ondeviceorientationabsolute"),
+ Atom("onmozorientationchange", "onmozorientationchange"),
+ Atom("onuserproximity", "onuserproximity"),
+ # light sensor support
+ Atom("ondevicelight", "ondevicelight"),
+ # MediaDevices device change event
+ Atom("ondevicechange", "ondevicechange"),
+ # Internal Visual Viewport events
+ Atom("onmozvisualresize", "onmozvisualresize"),
+ Atom("onmozvisualscroll", "onmozvisualscroll"),
+ # Miscellaneous events included for memory usage optimization (see bug 1542885)
+ Atom("onDOMAutoComplete", "onDOMAutoComplete"),
+ Atom("onDOMContentLoaded", "onDOMContentLoaded"),
+ Atom("onDOMDocElementInserted", "onDOMDocElementInserted"),
+ Atom("onDOMFormBeforeSubmit", "onDOMFormBeforeSubmit"),
+ Atom("onDOMFormHasPassword", "onDOMFormHasPassword"),
+ Atom("onDOMFrameContentLoaded", "onDOMFrameContentLoaded"),
+ Atom("onDOMHeadElementParsed", "onDOMHeadElementParsed"),
+ Atom("onDOMInputPasswordAdded", "onDOMInputPasswordAdded"),
+ Atom("onDOMLinkAdded", "onDOMLinkAdded"),
+ Atom("onDOMLinkChanged", "onDOMLinkChanged"),
+ Atom("onDOMMetaAdded", "onDOMMetaAdded"),
+ Atom("onDOMMetaChanged", "onDOMMetaChanged"),
+ Atom("onDOMMetaRemoved", "onDOMMetaRemoved"),
+ Atom("onDOMPopupBlocked", "onDOMPopupBlocked"),
+ Atom("onDOMTitleChanged", "onDOMTitleChanged"),
+ Atom("onDOMWindowClose", "onDOMWindowClose"),
+ Atom("onDOMWindowCreated", "onDOMWindowCreated"),
+ Atom("onDOMWindowFocus", "onDOMWindowFocus"),
+ Atom("onFullZoomChange", "onFullZoomChange"),
+ Atom("onGloballyAutoplayBlocked", "onGloballyAutoplayBlocked"),
+ Atom("onMozApplicationManifest", "onMozApplicationManifest"),
+ Atom("onMozDOMFullscreen_Entered", "onMozDOMFullscreen:Entered"),
+ Atom("onMozDOMFullscreen_Exit", "onMozDOMFullscreen:Exit"),
+ Atom("onMozDOMFullscreen_Exited", "onMozDOMFullscreen:Exited"),
+ Atom("onMozDOMFullscreen_NewOrigin", "onMozDOMFullscreen:NewOrigin"),
+ Atom("onMozDOMFullscreen_Request", "onMozDOMFullscreen:Request"),
+ Atom("onMozDOMPointerLock_Entered", "onMozDOMPointerLock:Entered"),
+ Atom("onMozDOMPointerLock_Exited", "onMozDOMPointerLock:Exited"),
+ Atom("onMozInvalidForm", "onMozInvalidForm"),
+ Atom("onMozLocalStorageChanged", "onMozLocalStorageChanged"),
+ Atom("onMozOpenDateTimePicker", "onMozOpenDateTimePicker"),
+ Atom("onMozSessionStorageChanged", "onMozSessionStorageChanged"),
+ Atom("onMozTogglePictureInPicture", "onMozTogglePictureInPicture"),
+ Atom("onPluginCrashed", "onPluginCrashed"),
+ Atom("onPrintingError", "onPrintingError"),
+ Atom("onTextZoomChange", "onTextZoomChange"),
+ Atom("onUAWidgetSetupOrChange", "onUAWidgetSetupOrChange"),
+ Atom("onUAWidgetTeardown", "onUAWidgetTeardown"),
+ Atom("onUnselectedTabHover_Disable", "onUnselectedTabHover:Disable"),
+ Atom("onUnselectedTabHover_Enable", "onUnselectedTabHover:Enable"),
+ Atom("onmozshowdropdown", "onmozshowdropdown"),
+ Atom("onmozshowdropdown_sourcetouch", "onmozshowdropdown-sourcetouch"),
+ Atom("onprintPreviewUpdate", "onprintPreviewUpdate"),
+ Atom("onscrollend", "onscrollend"),
+ # WebExtensions
+ Atom("moz_extension", "moz-extension"),
+ Atom("all_urlsPermission", "<all_urls>"),
+ Atom("clipboardRead", "clipboardRead"),
+ Atom("clipboardWrite", "clipboardWrite"),
+ Atom("debugger", "debugger"),
+ Atom("mozillaAddons", "mozillaAddons"),
+ Atom("tabs", "tabs"),
+ Atom("webRequestBlocking", "webRequestBlocking"),
+ Atom("webRequestFilterResponse_serviceWorkerScript", "webRequestFilterResponse.serviceWorkerScript"),
+ Atom("http", "http"),
+ Atom("https", "https"),
+ Atom("ws", "ws"),
+ Atom("wss", "wss"),
+ Atom("ftp", "ftp"),
+ Atom("chrome", "chrome"),
+ Atom("moz", "moz"),
+ Atom("moz_icon", "moz-icon"),
+ Atom("moz_gio", "moz-gio"),
+ Atom("proxy", "proxy"),
+ Atom("privateBrowsingAllowedPermission", "internal:privateBrowsingAllowed"),
+ Atom("svgContextPropertiesAllowedPermission", "internal:svgContextPropertiesAllowed"),
+ Atom("theme", "theme"),
+ # CSS Counter Styles
+ Atom("decimal_leading_zero", "decimal-leading-zero"),
+ Atom("arabic_indic", "arabic-indic"),
+ Atom("armenian", "armenian"),
+ Atom("upper_armenian", "upper-armenian"),
+ Atom("lower_armenian", "lower-armenian"),
+ Atom("bengali", "bengali"),
+ Atom("cambodian", "cambodian"),
+ Atom("khmer", "khmer"),
+ Atom("cjk_decimal", "cjk-decimal"),
+ Atom("devanagari", "devanagari"),
+ Atom("georgian", "georgian"),
+ Atom("gujarati", "gujarati"),
+ Atom("gurmukhi", "gurmukhi"),
+ Atom("kannada", "kannada"),
+ Atom("lao", "lao"),
+ Atom("malayalam", "malayalam"),
+ Atom("mongolian", "mongolian"),
+ Atom("myanmar", "myanmar"),
+ Atom("oriya", "oriya"),
+ Atom("persian", "persian"),
+ Atom("lower_roman", "lower-roman"),
+ Atom("upper_roman", "upper-roman"),
+ Atom("tamil", "tamil"),
+ Atom("telugu", "telugu"),
+ Atom("thai", "thai"),
+ Atom("tibetan", "tibetan"),
+ Atom("lower_alpha", "lower-alpha"),
+ Atom("lower_latin", "lower-latin"),
+ Atom("upper_alpha", "upper-alpha"),
+ Atom("upper_latin", "upper-latin"),
+ Atom("cjk_heavenly_stem", "cjk-heavenly-stem"),
+ Atom("cjk_earthly_branch", "cjk-earthly-branch"),
+ Atom("lower_greek", "lower-greek"),
+ Atom("hiragana", "hiragana"),
+ Atom("hiragana_iroha", "hiragana-iroha"),
+ Atom("katakana", "katakana"),
+ Atom("katakana_iroha", "katakana-iroha"),
+ Atom("cjk_ideographic", "cjk-ideographic"),
+ Atom("_moz_arabic_indic", "-moz-arabic-indic"),
+ Atom("_moz_persian", "-moz-persian"),
+ Atom("_moz_urdu", "-moz-urdu"),
+ Atom("_moz_devanagari", "-moz-devanagari"),
+ Atom("_moz_bengali", "-moz-bengali"),
+ Atom("_moz_gurmukhi", "-moz-gurmukhi"),
+ Atom("_moz_gujarati", "-moz-gujarati"),
+ Atom("_moz_oriya", "-moz-oriya"),
+ Atom("_moz_tamil", "-moz-tamil"),
+ Atom("_moz_telugu", "-moz-telugu"),
+ Atom("_moz_kannada", "-moz-kannada"),
+ Atom("_moz_malayalam", "-moz-malayalam"),
+ Atom("_moz_thai", "-moz-thai"),
+ Atom("_moz_lao", "-moz-lao"),
+ Atom("_moz_myanmar", "-moz-myanmar"),
+ Atom("_moz_khmer", "-moz-khmer"),
+ Atom("_moz_cjk_heavenly_stem", "-moz-cjk-heavenly-stem"),
+ Atom("_moz_cjk_earthly_branch", "-moz-cjk-earthly-branch"),
+ Atom("_moz_hangul", "-moz-hangul"),
+ Atom("_moz_hangul_consonant", "-moz-hangul-consonant"),
+ Atom("_moz_ethiopic_halehame", "-moz-ethiopic-halehame"),
+ Atom("_moz_ethiopic_halehame_am", "-moz-ethiopic-halehame-am"),
+ Atom("_moz_ethiopic_halehame_ti_er", "-moz-ethiopic-halehame-ti-er"),
+ Atom("_moz_ethiopic_halehame_ti_et", "-moz-ethiopic-halehame-ti-et"),
+ Atom("_moz_trad_chinese_informal", "-moz-trad-chinese-informal"),
+ Atom("_moz_trad_chinese_formal", "-moz-trad-chinese-formal"),
+ Atom("_moz_simp_chinese_informal", "-moz-simp-chinese-informal"),
+ Atom("_moz_simp_chinese_formal", "-moz-simp-chinese-formal"),
+ Atom("_moz_japanese_informal", "-moz-japanese-informal"),
+ Atom("_moz_japanese_formal", "-moz-japanese-formal"),
+ Atom("_moz_ethiopic_numeric", "-moz-ethiopic-numeric"),
+ # --------------------------------------------------------------------------
+ # Special atoms
+ # --------------------------------------------------------------------------
+ # Node types
+ Atom("cdataTagName", "#cdata-section"),
+ Atom("commentTagName", "#comment"),
+ Atom("documentNodeName", "#document"),
+ Atom("documentFragmentNodeName", "#document-fragment"),
+ Atom("documentTypeNodeName", "#document-type"),
+ Atom("processingInstructionTagName", "#processing-instruction"),
+ Atom("textTagName", "#text"),
+ # Frame types
+ #
+ # TODO(emilio): Rename this? This is only used now to mark the style context of
+ # the placeholder with a dummy pseudo.
+ Atom("placeholderFrame", "PlaceholderFrame"),
+ Atom("onloadend", "onloadend"),
+ Atom("onloadstart", "onloadstart"),
+ Atom("onprogress", "onprogress"),
+ Atom("onsuspend", "onsuspend"),
+ Atom("onemptied", "onemptied"),
+ Atom("onstalled", "onstalled"),
+ Atom("onplay", "onplay"),
+ Atom("onpause", "onpause"),
+ Atom("onloadedmetadata", "onloadedmetadata"),
+ Atom("onloadeddata", "onloadeddata"),
+ Atom("onwaiting", "onwaiting"),
+ Atom("onplaying", "onplaying"),
+ Atom("oncanplay", "oncanplay"),
+ Atom("oncanplaythrough", "oncanplaythrough"),
+ Atom("onseeking", "onseeking"),
+ Atom("onseeked", "onseeked"),
+ Atom("ontimeout", "ontimeout"),
+ Atom("ontimeupdate", "ontimeupdate"),
+ Atom("onended", "onended"),
+ Atom("onformdata", "onformdata"),
+ Atom("onratechange", "onratechange"),
+ Atom("ondurationchange", "ondurationchange"),
+ Atom("onvolumechange", "onvolumechange"),
+ Atom("onaddtrack", "onaddtrack"),
+ Atom("oncontrollerchange", "oncontrollerchange"),
+ Atom("oncuechange", "oncuechange"),
+ Atom("onenter", "onenter"),
+ Atom("onexit", "onexit"),
+ Atom("onencrypted", "onencrypted"),
+ Atom("onwaitingforkey", "onwaitingforkey"),
+ Atom("onkeystatuseschange", "onkeystatuseschange"),
+ Atom("onremovetrack", "onremovetrack"),
+ Atom("loadstart", "loadstart"),
+ Atom("suspend", "suspend"),
+ Atom("emptied", "emptied"),
+ Atom("play", "play"),
+ Atom("pause", "pause"),
+ Atom("loadedmetadata", "loadedmetadata"),
+ Atom("loadeddata", "loadeddata"),
+ Atom("waiting", "waiting"),
+ Atom("playing", "playing"),
+ Atom("timeupdate", "timeupdate"),
+ Atom("canplay", "canplay"),
+ Atom("canplaythrough", "canplaythrough"),
+ Atom("ondataavailable", "ondataavailable"),
+ Atom("onwarning", "onwarning"),
+ Atom("onstart", "onstart"),
+ Atom("onstop", "onstop"),
+ Atom("onphoto", "onphoto"),
+ Atom("ongamepadbuttondown", "ongamepadbuttondown"),
+ Atom("ongamepadbuttonup", "ongamepadbuttonup"),
+ Atom("ongamepadaxismove", "ongamepadaxismove"),
+ Atom("ongamepadconnected", "ongamepadconnected"),
+ Atom("ongamepaddisconnected", "ongamepaddisconnected"),
+ Atom("onfetch", "onfetch"),
+ # Content property names
+ Atom("afterPseudoProperty", "afterPseudoProperty"), # nsXMLElement*
+ Atom("animationsProperty", "AnimationsProperty"), # FrameAnimations*
+ Atom("animationsOfBeforeProperty", "AnimationsOfBeforeProperty"), # FrameAnimations*
+ Atom("animationsOfAfterProperty", "AnimationsOfAfterProperty"), # FrameAnimations*
+ Atom("animationsOfMarkerProperty", "AnimationsOfMarkerProperty"), # FrameAnimations*
+ Atom("animationEffectsProperty", "AnimationEffectsProperty"), # EffectSet*
+ Atom("animationEffectsForBeforeProperty", "AnimationsEffectsForBeforeProperty"), # EffectSet*
+ Atom("animationEffectsForAfterProperty", "AnimationsEffectsForAfterProperty"), # EffectSet*
+ Atom("animationEffectsForMarkerProperty", "AnimationsEffectsForMarkerProperty"), # EffectSet*
+ Atom("beforePseudoProperty", "beforePseudoProperty"), # nsXMLElement*
+ Atom("cssPseudoElementBeforeProperty", "CSSPseudoElementBeforeProperty"), # CSSPseudoElement*
+ Atom("cssPseudoElementAfterProperty", "CSSPseudoElementAfterProperty"), # CSSPseudoElement*
+ Atom("cssPseudoElementMarkerProperty", "CSSPseudoElementMarkerProperty"), # CSSPseudoElement*
+ Atom("transitionsProperty", "TransitionsProperty"), # FrameTransitions*
+ Atom("transitionsOfBeforeProperty", "TransitionsOfBeforeProperty"), # FrameTransitions*
+ Atom("transitionsOfAfterProperty", "TransitionsOfAfterProperty"), # FrameTransitions*
+ Atom("transitionsOfMarkerProperty", "TransitionsOfMarkerProperty"), # FrameTransitions*
+ Atom("genConInitializerProperty", "QuoteNodeProperty"),
+ Atom("labelMouseDownPtProperty", "LabelMouseDownPtProperty"),
+ Atom("lockedStyleStates", "lockedStyleStates"),
+ Atom("apzCallbackTransform", "apzCallbackTransform"),
+ Atom("apzDisabled", "ApzDisabled"), # bool
+ Atom("restylableAnonymousNode", "restylableAnonymousNode"), # bool
+ Atom("docLevelNativeAnonymousContent", "docLevelNativeAnonymousContent"), # bool
+ Atom("paintRequestTime", "PaintRequestTime"),
+ Atom("pseudoProperty", "PseudoProperty"), # PseudoStyleType
+ Atom("scrollTimelinesProperty", "SrollTimelinesProperty"), # ScrollTimelineSet*
+ Atom("manualNACProperty", "ManualNACProperty"), # ManualNAC*
+ Atom("markerPseudoProperty", "markerPseudoProperty"), # nsXMLElement*
+ # Languages for lang-specific transforms
+ Atom("Japanese", "ja"),
+ Atom("Chinese", "zh-CN"),
+ Atom("Taiwanese", "zh-TW"),
+ Atom("HongKongChinese", "zh-HK"),
+ Atom("Unicode", "x-unicode"),
+ # language codes specifically referenced in the gfx code
+ Atom("ko", "ko"),
+ Atom("zh_cn", "zh-cn"),
+ Atom("zh_tw", "zh-tw"),
+ # additional codes used in nsUnicodeRange.cpp
+ Atom("x_cyrillic", "x-cyrillic"),
+ Atom("he", "he"),
+ Atom("ar", "ar"),
+ Atom("x_devanagari", "x-devanagari"),
+ Atom("x_tamil", "x-tamil"),
+ Atom("x_armn", "x-armn"),
+ Atom("x_beng", "x-beng"),
+ Atom("x_cans", "x-cans"),
+ Atom("x_ethi", "x-ethi"),
+ Atom("x_geor", "x-geor"),
+ Atom("x_gujr", "x-gujr"),
+ Atom("x_guru", "x-guru"),
+ Atom("x_khmr", "x-khmr"),
+ Atom("x_knda", "x-knda"),
+ Atom("x_mlym", "x-mlym"),
+ Atom("x_orya", "x-orya"),
+ Atom("x_sinh", "x-sinh"),
+ Atom("x_telu", "x-telu"),
+ Atom("x_tibt", "x-tibt"),
+ # additional languages that have special case transformations
+ Atom("az", "az"),
+ Atom("ba", "ba"),
+ Atom("crh", "crh"),
+ # Atom("el", "el"), # "el" is present above
+ Atom("ga", "ga"),
+ # Atom("lt", "lt"), # "lt" is present above (atom name "lt_")
+ Atom("nl", "nl"),
+ # mathematical language, used for MathML
+ Atom("x_math", "x-math"),
+ # other languages mentioned in :lang() rules in UA style sheets
+ Atom("zh", "zh"),
+ # Names for editor transactions
+ Atom("TypingTxnName", "Typing"),
+ Atom("IMETxnName", "IME"),
+ Atom("DeleteTxnName", "Deleting"),
+ # Font families
+ Atom("serif", "serif"),
+ Atom("sans_serif", "sans-serif"),
+ Atom("cursive", "cursive"),
+ Atom("fantasy", "fantasy"),
+ Atom("monospace", "monospace"),
+ Atom("mozfixed", "-moz-fixed"),
+ Atom("moz_fixed_pos_containing_block", "-moz-fixed-pos-containing-block"),
+ # Standard font-palette identifiers
+ Atom("light", "light"),
+ Atom("dark", "dark"),
+ # IPC stuff
+ # Atom("Remote", "remote"), # "remote" is present above
+ Atom("RemoteId", "_remote_id"),
+ Atom("RemoteType", "remoteType"),
+ Atom("DisplayPort", "_displayport"),
+ Atom("DisplayPortMargins", "_displayportmargins"),
+ Atom("DisplayPortBase", "_displayportbase"),
+ Atom("MinimalDisplayPort", "_minimaldisplayport"),
+ Atom("forceMousewheelAutodir", "_force_mousewheel_autodir"),
+ Atom("forceMousewheelAutodirHonourRoot", "_force_mousewheel_autodir_honourroot"),
+ Atom("forcemessagemanager", "forcemessagemanager"),
+ Atom("initialBrowsingContextGroupId", "initialBrowsingContextGroupId"),
+ Atom("initiallyactive", "initiallyactive"),
+ # windows media query names
+ Atom("windows_win7", "windows-win7"),
+ Atom("windows_win8", "windows-win8"),
+ Atom("windows_win10", "windows-win10"),
+ # Names for system metrics.
+ Atom("_moz_scrollbar_start_backward", "-moz-scrollbar-start-backward"),
+ Atom("_moz_scrollbar_start_forward", "-moz-scrollbar-start-forward"),
+ Atom("_moz_scrollbar_end_backward", "-moz-scrollbar-end-backward"),
+ Atom("_moz_scrollbar_end_forward", "-moz-scrollbar-end-forward"),
+ Atom("_moz_overlay_scrollbars", "-moz-overlay-scrollbars"),
+ Atom("_moz_windows_accent_color_in_titlebar", "-moz-windows-accent-color-in-titlebar"),
+ Atom("_moz_windows_default_theme", "-moz-windows-default-theme"),
+ Atom("_moz_mac_graphite_theme", "-moz-mac-graphite-theme"),
+ Atom("_moz_mac_big_sur_theme", "-moz-mac-big-sur-theme"),
+ Atom("_moz_mac_rtl", "-moz-mac-rtl"),
+ Atom("_moz_platform", "-moz-platform"),
+ Atom("_moz_windows_compositor", "-moz-windows-compositor"),
+ Atom("_moz_windows_classic", "-moz-windows-classic"),
+ Atom("_moz_windows_glass", "-moz-windows-glass"),
+ Atom("_moz_windows_non_native_menus", "-moz-windows-non-native-menus"),
+ Atom("_moz_menubar_drag", "-moz-menubar-drag"),
+ Atom("_moz_device_pixel_ratio", "-moz-device-pixel-ratio"),
+ Atom("_moz_device_orientation", "-moz-device-orientation"),
+ Atom("_moz_is_resource_document", "-moz-is-resource-document"),
+ Atom("_moz_swipe_animation_enabled", "-moz-swipe-animation-enabled"),
+ Atom("_moz_gtk_csd_available", "-moz-gtk-csd-available"),
+ Atom("_moz_gtk_csd_titlebar_radius", "-moz-gtk-csd-titlebar-radius"),
+ Atom("_moz_gtk_csd_minimize_button", "-moz-gtk-csd-minimize-button"),
+ Atom("_moz_gtk_csd_minimize_button_position", "-moz-gtk-csd-minimize-button-position"),
+ Atom("_moz_gtk_csd_maximize_button", "-moz-gtk-csd-maximize-button"),
+ Atom("_moz_gtk_csd_maximize_button_position", "-moz-gtk-csd-maximize-button-position"),
+ Atom("_moz_gtk_csd_close_button", "-moz-gtk-csd-close-button"),
+ Atom("_moz_gtk_csd_close_button_position", "-moz-gtk-csd-close-button-position"),
+ Atom("_moz_gtk_csd_reversed_placement", "-moz-gtk-csd-reversed-placement"),
+ Atom("_moz_gtk_csd_menu_radius", "-moz-gtk-csd-menu-radius"),
+ Atom("_moz_content_prefers_color_scheme", "-moz-content-prefers-color-scheme"),
+ Atom("_moz_content_preferred_color_scheme", "-moz-content-preferred-color-scheme"),
+ Atom("_moz_system_dark_theme", "-moz-system-dark-theme"),
+ Atom("_moz_box_flexbox_emulation", "-moz-box-flexbox-emulation"),
+ Atom("_moz_panel_animations", "-moz-panel-animations"),
+ # application commands
+ Atom("Back", "Back"),
+ Atom("Forward", "Forward"),
+ Atom("Reload", "Reload"),
+ Atom("Stop", "Stop"),
+ Atom("Search", "Search"),
+ Atom("Bookmarks", "Bookmarks"),
+ Atom("Home", "Home"),
+ Atom("NextTrack", "NextTrack"),
+ Atom("PreviousTrack", "PreviousTrack"),
+ Atom("MediaStop", "MediaStop"),
+ Atom("PlayPause", "PlayPause"),
+ Atom("New", "New"),
+ Atom("Open", "Open"),
+ Atom("Close", "Close"),
+ Atom("Save", "Save"),
+ Atom("Find", "Find"),
+ Atom("Help", "Help"),
+ Atom("Print", "Print"),
+ Atom("SendMail", "SendMail"),
+ Atom("ForwardMail", "ForwardMail"),
+ Atom("ReplyToMail", "ReplyToMail"),
+ Atom("alert", "alert"),
+ Atom("alertdialog", "alertdialog"),
+ Atom("application", "application"),
+ Atom("aria_colcount", "aria-colcount"),
+ Atom("aria_colindex", "aria-colindex"),
+ Atom("aria_colindextext", "aria-colindextext"),
+ Atom("aria_colspan", "aria-colspan"),
+ Atom("aria_details", "aria-details"),
+ Atom("aria_errormessage", "aria-errormessage"),
+ Atom("aria_grabbed", "aria-grabbed"),
+ Atom("aria_keyshortcuts", "aria-keyshortcuts"),
+ Atom("aria_label", "aria-label"),
+ Atom("aria_modal", "aria-modal"),
+ Atom("aria_orientation", "aria-orientation"),
+ Atom("aria_placeholder", "aria-placeholder"),
+ Atom("aria_roledescription", "aria-roledescription"),
+ Atom("aria_rowcount", "aria-rowcount"),
+ Atom("aria_rowindex", "aria-rowindex"),
+ Atom("aria_rowindextext", "aria-rowindextext"),
+ Atom("aria_rowspan", "aria-rowspan"),
+ Atom("aria_valuetext", "aria-valuetext"),
+ Atom("assertive", "assertive"),
+ Atom("auto_generated", "auto-generated"),
+ Atom("banner", "banner"),
+ Atom("checkable", "checkable"),
+ Atom("columnheader", "columnheader"),
+ Atom("complementary", "complementary"),
+ Atom("containerAtomic", "container-atomic"),
+ Atom("containerBusy", "container-busy"),
+ Atom("containerLive", "container-live"),
+ Atom("containerLiveRole", "container-live-role"),
+ Atom("containerRelevant", "container-relevant"),
+ Atom("contentinfo", "contentinfo"),
+ Atom("cycles", "cycles"),
+ Atom("datatable", "datatable"),
+ Atom("eventFromInput", "event-from-input"),
+ Atom("feed", "feed"),
+ Atom("grammar", "grammar"),
+ Atom("gridcell", "gridcell"),
+ Atom("heading", "heading"),
+ Atom("inlinevalue", "inline"),
+ Atom("inline_size", "inline-size"),
+ Atom("invalid", "invalid"),
+ Atom("lineNumber", "line-number"),
+ Atom("menuitemcheckbox", "menuitemcheckbox"),
+ Atom("menuitemradio", "menuitemradio"),
+ # Atom("mixed", "mixed"), # "mixed" is present above
+ Atom("navigation", "navigation"),
+ Atom("polite", "polite"),
+ Atom("posinset", "posinset"),
+ Atom("presentation", "presentation"),
+ Atom("progressbar", "progressbar"),
+ Atom("region", "region"),
+ Atom("rowgroup", "rowgroup"),
+ Atom("rowheader", "rowheader"),
+ Atom("search", "search"),
+ Atom("searchbox", "searchbox"),
+ Atom("setsize", "setsize"),
+ Atom("spelling", "spelling"),
+ Atom("spinbutton", "spinbutton"),
+ Atom("status", "status"),
+ Atom("tableCellIndex", "table-cell-index"),
+ Atom("tablist", "tablist"),
+ Atom("textIndent", "text-indent"),
+ Atom("textInputType", "text-input-type"),
+ Atom("textLineThroughColor", "text-line-through-color"),
+ Atom("textLineThroughStyle", "text-line-through-style"),
+ Atom("textPosition", "text-position"),
+ Atom("textUnderlineColor", "text-underline-color"),
+ Atom("textUnderlineStyle", "text-underline-style"),
+ Atom("timer", "timer"),
+ Atom("toolbarname", "toolbarname"),
+ Atom("toolbarseparator", "toolbarseparator"),
+ Atom("toolbarspacer", "toolbarspacer"),
+ Atom("toolbarspring", "toolbarspring"),
+ Atom("treegrid", "treegrid"),
+ Atom("_undefined", "undefined"),
+ Atom("xmlroles", "xml-roles"),
+ # MathML xml roles
+ Atom("close_fence", "close-fence"),
+ Atom("denominator", "denominator"),
+ Atom("numerator", "numerator"),
+ Atom("open_fence", "open-fence"),
+ Atom("overscript", "overscript"),
+ Atom("presubscript", "presubscript"),
+ Atom("presuperscript", "presuperscript"),
+ Atom("root_index", "root-index"),
+ Atom("subscript", "subscript"),
+ Atom("superscript", "superscript"),
+ Atom("underscript", "underscript"),
+ Atom("onaudiostart", "onaudiostart"),
+ Atom("onaudioend", "onaudioend"),
+ Atom("onsoundstart", "onsoundstart"),
+ Atom("onsoundend", "onsoundend"),
+ Atom("onspeechstart", "onspeechstart"),
+ Atom("onspeechend", "onspeechend"),
+ Atom("onresult", "onresult"),
+ Atom("onnomatch", "onnomatch"),
+ Atom("onresume", "onresume"),
+ Atom("onmark", "onmark"),
+ Atom("onboundary", "onboundary"),
+ # Media Controller
+ Atom("onactivated", "onactivated"),
+ Atom("ondeactivated", "ondeactivated"),
+ Atom("onmetadatachange", "onmetadatachange"),
+ Atom("onplaybackstatechange", "onplaybackstatechange"),
+ Atom("onpositionstatechange", "onpositionstatechange"),
+ Atom("onsupportedkeyschange", "onsupportedkeyschange"),
+ # media query for MathML Core's implementation of maction/semantics
+ Atom("_moz_mathml_core_maction_and_semantics", "-moz-mathml-core-maction-and-semantics"),
+ # media query for MathML Core's implementation of ms
+ Atom("_moz_mathml_core_ms", "-moz-mathml-core-ms"),
+ # Contextual Identity / Containers
+ Atom("usercontextid", "usercontextid"),
+ Atom("geckoViewSessionContextId", "geckoViewSessionContextId"),
+ # Namespaces
+ Atom("nsuri_xmlns", "http://www.w3.org/2000/xmlns/"),
+ Atom("nsuri_xml", "http://www.w3.org/XML/1998/namespace"),
+ Atom("nsuri_xhtml", "http://www.w3.org/1999/xhtml"),
+ Atom("nsuri_xlink", "http://www.w3.org/1999/xlink"),
+ Atom("nsuri_xslt", "http://www.w3.org/1999/XSL/Transform"),
+ Atom("nsuri_mathml", "http://www.w3.org/1998/Math/MathML"),
+ Atom("nsuri_rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
+ Atom("nsuri_xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"),
+ Atom("nsuri_svg", "http://www.w3.org/2000/svg"),
+ Atom("nsuri_parsererror", "http://www.mozilla.org/newlayout/xml/parsererror.xml"),
+ # MSE
+ Atom("onsourceopen", "onsourceopen"),
+ Atom("onsourceended", "onsourceended"),
+ Atom("onsourceclose", "onsourceclose"),
+ Atom("onupdatestart", "onupdatestart"),
+ Atom("onupdate", "onupdate"),
+ Atom("onupdateend", "onupdateend"),
+ Atom("onaddsourcebuffer", "onaddsourcebuffer"),
+ Atom("onremovesourcebuffer", "onremovesourcebuffer"),
+ # RDF (not used by mozilla-central, but still used by comm-central)
+ Atom("about", "about"),
+ Atom("ID", "ID"),
+ Atom("nodeID", "nodeID"),
+ Atom("aboutEach", "aboutEach"),
+ Atom("resource", "resource"),
+ Atom("RDF", "RDF"),
+ Atom("Description", "Description"),
+ Atom("Bag", "Bag"),
+ Atom("Seq", "Seq"),
+ Atom("Alt", "Alt"),
+ # Atom("kLiAtom", "li"), # "li" is present above
+ # Atom("kXMLNSAtom", "xmlns"), # "xmlns" is present above
+ Atom("parseType", "parseType"),
+ # Directory service
+ Atom("DirectoryService_CurrentProcess", "XCurProcD"),
+ Atom("DirectoryService_GRE_Directory", "GreD"),
+ Atom("DirectoryService_GRE_BinDirectory", "GreBinD"),
+ Atom("DirectoryService_OS_TemporaryDirectory", "TmpD"),
+ Atom("DirectoryService_OS_CurrentProcessDirectory", "CurProcD"),
+ Atom("DirectoryService_OS_CurrentWorkingDirectory", "CurWorkD"),
+ Atom("DirectoryService_OS_SystemConfigDir", "SysConfD"),
+ # Atom("DirectoryService_OS_HomeDirectory", "Home"), # "Home" is present above
+ Atom("DirectoryService_OS_DesktopDirectory", "Desk"),
+ Atom("DirectoryService_InitCurrentProcess_dummy", "MozBinD"),
+ Atom("DirectoryService_SystemDirectory", "SysD"),
+ Atom("DirectoryService_UserLibDirectory", "ULibDir"),
+ Atom("DirectoryService_DefaultDownloadDirectory", "DfltDwnld"),
+ Atom("DirectoryService_LocalApplicationsDirectory", "LocApp"),
+ Atom("DirectoryService_UserPreferencesDirectory", "UsrPrfs"),
+ Atom("DirectoryService_PictureDocumentsDirectory", "Pct"),
+ Atom("DirectoryService_WindowsDirectory", "WinD"),
+ Atom("DirectoryService_WindowsProgramFiles", "ProgF"),
+ Atom("DirectoryService_Programs", "Progs"),
+ Atom("DirectoryService_Favorites", "Favs"),
+ Atom("DirectoryService_Appdata", "AppData"),
+ Atom("DirectoryService_LocalAppdata", "LocalAppData"),
+ Atom("DirectoryService_LocalAppdataLow", "LocalAppDataLow"),
+ Atom("DirectoryService_LowIntegrityTempBase", "LowTmpDBase"),
+ Atom("DirectoryService_WinCookiesDirectory", "CookD"),
+ # CSS pseudo-elements -- these must appear in the same order as
+ # in nsCSSPseudoElementList.h
+ PseudoElementAtom("PseudoElement_after", ":after"),
+ PseudoElementAtom("PseudoElement_before", ":before"),
+ PseudoElementAtom("PseudoElement_marker", ":marker"),
+ PseudoElementAtom("PseudoElement_backdrop", ":backdrop"),
+ PseudoElementAtom("PseudoElement_cue", ":cue"),
+ PseudoElementAtom("PseudoElement_firstLetter", ":first-letter"),
+ PseudoElementAtom("PseudoElement_firstLine", ":first-line"),
+ PseudoElementAtom("PseudoElement_selection", ":selection"),
+ PseudoElementAtom("PseudoElement_mozFocusInner", ":-moz-focus-inner"),
+ PseudoElementAtom("PseudoElement_mozNumberSpinBox", ":-moz-number-spin-box"),
+ PseudoElementAtom("PseudoElement_mozNumberSpinUp", ":-moz-number-spin-up"),
+ PseudoElementAtom("PseudoElement_mozNumberSpinDown", ":-moz-number-spin-down"),
+ PseudoElementAtom("PseudoElement_mozSearchClearButton", ":-moz-search-clear-button"),
+ PseudoElementAtom("PseudoElement_mozProgressBar", ":-moz-progress-bar"),
+ PseudoElementAtom("PseudoElement_mozRangeTrack", ":-moz-range-track"),
+ PseudoElementAtom("PseudoElement_mozRangeProgress", ":-moz-range-progress"),
+ PseudoElementAtom("PseudoElement_mozRangeThumb", ":-moz-range-thumb"),
+ PseudoElementAtom("PseudoElement_mozMeterBar", ":-moz-meter-bar"),
+ PseudoElementAtom("PseudoElement_placeholder", ":placeholder"),
+ PseudoElementAtom("PseudoElement_mozColorSwatch", ":-moz-color-swatch"),
+ PseudoElementAtom("PseudoElement_mozTextControlEditingRoot", ":-moz-text-control-editing-root"),
+ PseudoElementAtom("PseudoElement_mozTextControlPreview", ":-moz-text-control-preview"),
+ PseudoElementAtom("PseudoElement_mozReveal", ":-moz-reveal"),
+ PseudoElementAtom("PseudoElement_fileSelectorButton", ":file-selector-button"),
+ # CSS anonymous boxes -- these must appear in the same order as
+ # in nsCSSAnonBoxList.h
+ NonInheritingAnonBoxAtom("AnonBox_oofPlaceholder", ":-moz-oof-placeholder"),
+ NonInheritingAnonBoxAtom("AnonBox_horizontalFramesetBorder", ":-moz-hframeset-border"),
+ NonInheritingAnonBoxAtom("AnonBox_verticalFramesetBorder", ":-moz-vframeset-border"),
+ NonInheritingAnonBoxAtom("AnonBox_framesetBlank", ":-moz-frameset-blank"),
+ NonInheritingAnonBoxAtom("AnonBox_tableColGroup", ":-moz-table-column-group"),
+ NonInheritingAnonBoxAtom("AnonBox_tableCol", ":-moz-table-column"),
+ NonInheritingAnonBoxAtom("AnonBox_page", ":-moz-page"),
+ NonInheritingAnonBoxAtom("AnonBox_pageBreak", ":-moz-page-break"),
+ NonInheritingAnonBoxAtom("AnonBox_pageContent", ":-moz-page-content"),
+ NonInheritingAnonBoxAtom("AnonBox_printedSheet", ":-moz-printed-sheet"),
+ NonInheritingAnonBoxAtom("AnonBox_columnSpanWrapper", ":-moz-column-span-wrapper"),
+ InheritingAnonBoxAtom("AnonBox_mozText", ":-moz-text"),
+ InheritingAnonBoxAtom("AnonBox_firstLetterContinuation", ":-moz-first-letter-continuation"),
+ InheritingAnonBoxAtom("AnonBox_mozBlockInsideInlineWrapper", ":-moz-block-inside-inline-wrapper"),
+ InheritingAnonBoxAtom("AnonBox_mozMathMLAnonymousBlock", ":-moz-mathml-anonymous-block"),
+ InheritingAnonBoxAtom("AnonBox_mozLineFrame", ":-moz-line-frame"),
+ InheritingAnonBoxAtom("AnonBox_buttonContent", ":-moz-button-content"),
+ InheritingAnonBoxAtom("AnonBox_cellContent", ":-moz-cell-content"),
+ InheritingAnonBoxAtom("AnonBox_dropDownList", ":-moz-dropdown-list"),
+ InheritingAnonBoxAtom("AnonBox_fieldsetContent", ":-moz-fieldset-content"),
+ InheritingAnonBoxAtom("AnonBox_mozDisplayComboboxControlFrame", ":-moz-display-comboboxcontrol-frame"),
+ InheritingAnonBoxAtom("AnonBox_htmlCanvasContent", ":-moz-html-canvas-content"),
+ InheritingAnonBoxAtom("AnonBox_inlineTable", ":-moz-inline-table"),
+ InheritingAnonBoxAtom("AnonBox_table", ":-moz-table"),
+ InheritingAnonBoxAtom("AnonBox_tableCell", ":-moz-table-cell"),
+ InheritingAnonBoxAtom("AnonBox_tableWrapper", ":-moz-table-wrapper"),
+ InheritingAnonBoxAtom("AnonBox_tableRowGroup", ":-moz-table-row-group"),
+ InheritingAnonBoxAtom("AnonBox_tableRow", ":-moz-table-row"),
+ InheritingAnonBoxAtom("AnonBox_canvas", ":-moz-canvas"),
+ InheritingAnonBoxAtom("AnonBox_pageSequence", ":-moz-page-sequence"),
+ InheritingAnonBoxAtom("AnonBox_scrolledContent", ":-moz-scrolled-content"),
+ InheritingAnonBoxAtom("AnonBox_scrolledCanvas", ":-moz-scrolled-canvas"),
+ InheritingAnonBoxAtom("AnonBox_columnSet", ":-moz-column-set"),
+ InheritingAnonBoxAtom("AnonBox_columnContent", ":-moz-column-content"),
+ InheritingAnonBoxAtom("AnonBox_viewport", ":-moz-viewport"),
+ InheritingAnonBoxAtom("AnonBox_viewportScroll", ":-moz-viewport-scroll"),
+ InheritingAnonBoxAtom("AnonBox_anonymousItem", ":-moz-anonymous-item"),
+ InheritingAnonBoxAtom("AnonBox_blockRubyContent", ":-moz-block-ruby-content"),
+ InheritingAnonBoxAtom("AnonBox_ruby", ":-moz-ruby"),
+ InheritingAnonBoxAtom("AnonBox_rubyBase", ":-moz-ruby-base"),
+ InheritingAnonBoxAtom("AnonBox_rubyBaseContainer", ":-moz-ruby-base-container"),
+ InheritingAnonBoxAtom("AnonBox_rubyText", ":-moz-ruby-text"),
+ InheritingAnonBoxAtom("AnonBox_rubyTextContainer", ":-moz-ruby-text-container"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeColumn", ":-moz-tree-column"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeRow", ":-moz-tree-row"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeSeparator", ":-moz-tree-separator"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeCell", ":-moz-tree-cell"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeIndentation", ":-moz-tree-indentation"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeLine", ":-moz-tree-line"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeTwisty", ":-moz-tree-twisty"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeImage", ":-moz-tree-image"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeCellText", ":-moz-tree-cell-text"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeCheckbox", ":-moz-tree-checkbox"),
+ InheritingAnonBoxAtom("AnonBox_mozTreeDropFeedback", ":-moz-tree-drop-feedback"),
+ InheritingAnonBoxAtom("AnonBox_mozSVGMarkerAnonChild", ":-moz-svg-marker-anon-child"),
+ InheritingAnonBoxAtom("AnonBox_mozSVGOuterSVGAnonChild", ":-moz-svg-outer-svg-anon-child"),
+ InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"),
+ InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"),
+ # END ATOMS
+] + HTML_PARSER_ATOMS
+# fmt: on
+
+
+def verify():
+ idents = set()
+ strings = set()
+ failed = False
+ for atom in STATIC_ATOMS:
+ if atom.ident in idents:
+ print("StaticAtoms.py: duplicate static atom ident: %s" % atom.ident)
+ failed = True
+ if atom.string in strings:
+ print('StaticAtoms.py: duplicate static atom string: "%s"' % atom.string)
+ failed = True
+ idents.add(atom.ident)
+ strings.add(atom.string)
+ if failed:
+ sys.exit(1)
+
+
+def generate_nsgkatomlist_h(output, *ignore):
+ verify()
+ output.write(
+ "/* THIS FILE IS AUTOGENERATED BY StaticAtoms.py. DO NOT EDIT */\n\n"
+ "#ifdef small\n"
+ "#undef small\n"
+ "#endif\n\n"
+ "// GK_ATOM(identifier, string, hash, is_ascii_lower, gecko_type, atom_type)\n"
+ + "".join(
+ [
+ 'GK_ATOM(%s, "%s", 0x%08x, %s, %s, %s)\n'
+ % (
+ a.ident,
+ a.string,
+ a.hash,
+ str(a.is_ascii_lowercase).lower(),
+ a.ty,
+ a.atom_type,
+ )
+ for a in STATIC_ATOMS
+ ]
+ )
+ )
+
+
+def generate_nsgkatomconsts_h(output, *ignore):
+ pseudo_index = None
+ anon_box_index = None
+ pseudo_count = 0
+ anon_box_count = 0
+ for i, atom in enumerate(STATIC_ATOMS):
+ if atom.atom_type == "PseudoElementAtom":
+ if pseudo_index is None:
+ pseudo_index = i
+ pseudo_count += 1
+ elif (
+ atom.atom_type == "NonInheritingAnonBoxAtom"
+ or atom.atom_type == "InheritingAnonBoxAtom"
+ ):
+ if anon_box_index is None:
+ anon_box_index = i
+ anon_box_count += 1
+ output.write(
+ "/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n"
+ "#ifndef nsGkAtomConsts_h\n"
+ "#define nsGkAtomConsts_h\n\n"
+ "namespace mozilla {\n"
+ " constexpr uint32_t kAtomIndex_PseudoElements = %d;\n"
+ " constexpr uint32_t kAtomCount_PseudoElements = %d;\n"
+ " constexpr uint32_t kAtomIndex_AnonBoxes = %d;\n"
+ " constexpr uint32_t kAtomCount_AnonBoxes = %d;\n"
+ "}\n\n"
+ "#endif\n" % (pseudo_index, pseudo_count, anon_box_index, anon_box_count)
+ )
+
+
+if __name__ == "__main__":
+ generate_nsgkatomlist_h(sys.stdout)
diff --git a/xpcom/ds/StickyTimeDuration.h b/xpcom/ds/StickyTimeDuration.h
new file mode 100644
index 0000000000..4d888dfbcd
--- /dev/null
+++ b/xpcom/ds/StickyTimeDuration.h
@@ -0,0 +1,238 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StickyTimeDuration_h
+#define mozilla_StickyTimeDuration_h
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/FloatingPoint.h"
+
+namespace mozilla {
+
+/**
+ * A ValueCalculator class that performs additional checks before performing
+ * arithmetic operations such that if either operand is Forever (or the
+ * negative equivalent) the result remains Forever (or the negative equivalent
+ * as appropriate).
+ *
+ * Currently this only checks if either argument to each operation is
+ * Forever/-Forever. However, it is possible that, for example,
+ * aA + aB > INT64_MAX (or < INT64_MIN).
+ *
+ * We currently don't check for that case since we don't expect that to
+ * happen often except under test conditions in which case the wrapping
+ * behavior is probably acceptable.
+ */
+class StickyTimeDurationValueCalculator {
+ public:
+ static int64_t Add(int64_t aA, int64_t aB) {
+ MOZ_ASSERT((aA != INT64_MAX || aB != INT64_MIN) &&
+ (aA != INT64_MIN || aB != INT64_MAX),
+ "'Infinity + -Infinity' and '-Infinity + Infinity'"
+ " are undefined");
+
+ // Forever + x = Forever
+ // x + Forever = Forever
+ if (aA == INT64_MAX || aB == INT64_MAX) {
+ return INT64_MAX;
+ }
+ // -Forever + x = -Forever
+ // x + -Forever = -Forever
+ if (aA == INT64_MIN || aB == INT64_MIN) {
+ return INT64_MIN;
+ }
+
+ return aA + aB;
+ }
+
+ // Note that we can't just define Add and have BaseTimeDuration call Add with
+ // negative arguments since INT64_MAX != -INT64_MIN so the saturating logic
+ // won't work.
+ static int64_t Subtract(int64_t aA, int64_t aB) {
+ MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) || aA != aB,
+ "'Infinity - Infinity' and '-Infinity - -Infinity'"
+ " are undefined");
+
+ // Forever - x = Forever
+ // x - -Forever = Forever
+ if (aA == INT64_MAX || aB == INT64_MIN) {
+ return INT64_MAX;
+ }
+ // -Forever - x = -Forever
+ // x - Forever = -Forever
+ if (aA == INT64_MIN || aB == INT64_MAX) {
+ return INT64_MIN;
+ }
+
+ return aA - aB;
+ }
+
+ template <typename T>
+ static int64_t Multiply(int64_t aA, T aB) {
+ // Specializations for double, float, and int64_t are provided following.
+ return Multiply(aA, static_cast<int64_t>(aB));
+ }
+
+ static int64_t Divide(int64_t aA, int64_t aB) {
+ MOZ_ASSERT(aB != 0, "Division by zero");
+ MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) ||
+ (aB != INT64_MAX && aB != INT64_MIN),
+ "Dividing +/-Infinity by +/-Infinity is undefined");
+
+ // Forever / +x = Forever
+ // Forever / -x = -Forever
+ // -Forever / +x = -Forever
+ // -Forever / -x = Forever
+ if (aA == INT64_MAX || aA == INT64_MIN) {
+ return (aA >= 0) ^ (aB >= 0) ? INT64_MIN : INT64_MAX;
+ }
+ // x / Forever = 0
+ // x / -Forever = 0
+ if (aB == INT64_MAX || aB == INT64_MIN) {
+ return 0;
+ }
+
+ return aA / aB;
+ }
+
+ static double DivideDouble(int64_t aA, int64_t aB) {
+ MOZ_ASSERT(aB != 0, "Division by zero");
+ MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) ||
+ (aB != INT64_MAX && aB != INT64_MIN),
+ "Dividing +/-Infinity by +/-Infinity is undefined");
+
+ // Forever / +x = Forever
+ // Forever / -x = -Forever
+ // -Forever / +x = -Forever
+ // -Forever / -x = Forever
+ if (aA == INT64_MAX || aA == INT64_MIN) {
+ return (aA >= 0) ^ (aB >= 0) ? NegativeInfinity<double>()
+ : PositiveInfinity<double>();
+ }
+ // x / Forever = 0
+ // x / -Forever = 0
+ if (aB == INT64_MAX || aB == INT64_MIN) {
+ return 0.0;
+ }
+
+ return static_cast<double>(aA) / aB;
+ }
+
+ static int64_t Modulo(int64_t aA, int64_t aB) {
+ MOZ_ASSERT(aA != INT64_MAX && aA != INT64_MIN,
+ "Infinity modulo x is undefined");
+
+ return aA % aB;
+ }
+};
+
+template <>
+inline int64_t StickyTimeDurationValueCalculator::Multiply<int64_t>(
+ int64_t aA, int64_t aB) {
+ MOZ_ASSERT((aA != 0 || (aB != INT64_MIN && aB != INT64_MAX)) &&
+ ((aA != INT64_MIN && aA != INT64_MAX) || aB != 0),
+ "Multiplication of infinity by zero");
+
+ // Forever * +x = Forever
+ // Forever * -x = -Forever
+ // -Forever * +x = -Forever
+ // -Forever * -x = Forever
+ //
+ // i.e. If one or more of the arguments is +/-Forever, then
+ // return -Forever if the signs differ, or +Forever otherwise.
+ if (aA == INT64_MAX || aA == INT64_MIN || aB == INT64_MAX ||
+ aB == INT64_MIN) {
+ return (aA >= 0) ^ (aB >= 0) ? INT64_MIN : INT64_MAX;
+ }
+
+ return aA * aB;
+}
+
+template <>
+inline int64_t StickyTimeDurationValueCalculator::Multiply<double>(int64_t aA,
+ double aB) {
+ MOZ_ASSERT((aA != 0 || (!IsInfinite(aB))) &&
+ ((aA != INT64_MIN && aA != INT64_MAX) || aB != 0.0),
+ "Multiplication of infinity by zero");
+
+ // As with Multiply<int64_t>, if one or more of the arguments is
+ // +/-Forever or +/-Infinity, then return -Forever if the signs differ,
+ // or +Forever otherwise.
+ if (aA == INT64_MAX || aA == INT64_MIN || IsInfinite(aB)) {
+ return (aA >= 0) ^ (aB >= 0.0) ? INT64_MIN : INT64_MAX;
+ }
+
+ return aA * aB;
+}
+
+template <>
+inline int64_t StickyTimeDurationValueCalculator::Multiply<float>(int64_t aA,
+ float aB) {
+ MOZ_ASSERT(IsInfinite(aB) == IsInfinite(static_cast<double>(aB)),
+ "Casting to float loses infinite-ness");
+
+ return Multiply(aA, static_cast<double>(aB));
+}
+
+/**
+ * Specialization of BaseTimeDuration that uses
+ * StickyTimeDurationValueCalculator for arithmetic on the mValue member.
+ *
+ * Use this class when you need a time duration that is expected to hold values
+ * of Forever (or the negative equivalent) *and* when you expect that
+ * time duration to be used in arithmetic operations (and not just value
+ * comparisons).
+ */
+typedef BaseTimeDuration<StickyTimeDurationValueCalculator> StickyTimeDuration;
+
+// Template specializations to allow arithmetic between StickyTimeDuration
+// and TimeDuration objects by falling back to the safe behavior.
+inline StickyTimeDuration operator+(const TimeDuration& aA,
+ const StickyTimeDuration& aB) {
+ return StickyTimeDuration(aA) + aB;
+}
+inline StickyTimeDuration operator+(const StickyTimeDuration& aA,
+ const TimeDuration& aB) {
+ return aA + StickyTimeDuration(aB);
+}
+
+inline StickyTimeDuration operator-(const TimeDuration& aA,
+ const StickyTimeDuration& aB) {
+ return StickyTimeDuration(aA) - aB;
+}
+inline StickyTimeDuration operator-(const StickyTimeDuration& aA,
+ const TimeDuration& aB) {
+ return aA - StickyTimeDuration(aB);
+}
+
+inline StickyTimeDuration& operator+=(StickyTimeDuration& aA,
+ const TimeDuration& aB) {
+ return aA += StickyTimeDuration(aB);
+}
+inline StickyTimeDuration& operator-=(StickyTimeDuration& aA,
+ const TimeDuration& aB) {
+ return aA -= StickyTimeDuration(aB);
+}
+
+inline double operator/(const TimeDuration& aA, const StickyTimeDuration& aB) {
+ return StickyTimeDuration(aA) / aB;
+}
+inline double operator/(const StickyTimeDuration& aA, const TimeDuration& aB) {
+ return aA / StickyTimeDuration(aB);
+}
+
+inline StickyTimeDuration operator%(const TimeDuration& aA,
+ const StickyTimeDuration& aB) {
+ return StickyTimeDuration(aA) % aB;
+}
+inline StickyTimeDuration operator%(const StickyTimeDuration& aA,
+ const TimeDuration& aB) {
+ return aA % StickyTimeDuration(aB);
+}
+
+} // namespace mozilla
+
+#endif /* mozilla_StickyTimeDuration_h */
diff --git a/xpcom/ds/Tokenizer.cpp b/xpcom/ds/Tokenizer.cpp
new file mode 100644
index 0000000000..3b0f6b02dd
--- /dev/null
+++ b/xpcom/ds/Tokenizer.cpp
@@ -0,0 +1,805 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Tokenizer.h"
+
+#include "nsUnicharUtils.h"
+#include <algorithm>
+
+namespace mozilla {
+
+template <>
+char const TokenizerBase<char>::sWhitespaces[] = {' ', '\t', 0};
+template <>
+char16_t const TokenizerBase<char16_t>::sWhitespaces[3] = {' ', '\t', 0};
+
+template <typename TChar>
+static bool contains(TChar const* const list, TChar const needle) {
+ for (TChar const* c = list; *c; ++c) {
+ if (needle == *c) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename TChar>
+TTokenizer<TChar>::TTokenizer(const typename base::TAString& aSource,
+ const TChar* aWhitespaces,
+ const TChar* aAdditionalWordChars)
+ : TokenizerBase<TChar>(aWhitespaces, aAdditionalWordChars) {
+ base::mInputFinished = true;
+ aSource.BeginReading(base::mCursor);
+ mRecord = mRollback = base::mCursor;
+ aSource.EndReading(base::mEnd);
+}
+
+template <typename TChar>
+TTokenizer<TChar>::TTokenizer(const TChar* aSource, const TChar* aWhitespaces,
+ const TChar* aAdditionalWordChars)
+ : TTokenizer(typename base::TDependentString(aSource), aWhitespaces,
+ aAdditionalWordChars) {}
+
+template <typename TChar>
+bool TTokenizer<TChar>::Next(typename base::Token& aToken) {
+ if (!base::HasInput()) {
+ base::mHasFailed = true;
+ return false;
+ }
+
+ mRollback = base::mCursor;
+ base::mCursor = base::Parse(aToken);
+
+ base::AssignFragment(aToken, mRollback, base::mCursor);
+
+ base::mPastEof = aToken.Type() == base::TOKEN_EOF;
+ base::mHasFailed = false;
+ return true;
+}
+
+template <typename TChar>
+bool TTokenizer<TChar>::Check(const typename base::TokenType aTokenType,
+ typename base::Token& aResult) {
+ if (!base::HasInput()) {
+ base::mHasFailed = true;
+ return false;
+ }
+
+ typename base::TAString::const_char_iterator next = base::Parse(aResult);
+ if (aTokenType != aResult.Type()) {
+ base::mHasFailed = true;
+ return false;
+ }
+
+ mRollback = base::mCursor;
+ base::mCursor = next;
+
+ base::AssignFragment(aResult, mRollback, base::mCursor);
+
+ base::mPastEof = aResult.Type() == base::TOKEN_EOF;
+ base::mHasFailed = false;
+ return true;
+}
+
+template <typename TChar>
+bool TTokenizer<TChar>::Check(const typename base::Token& aToken) {
+#ifdef DEBUG
+ base::Validate(aToken);
+#endif
+
+ if (!base::HasInput()) {
+ base::mHasFailed = true;
+ return false;
+ }
+
+ typename base::Token parsed;
+ typename base::TAString::const_char_iterator next = base::Parse(parsed);
+ if (!aToken.Equals(parsed)) {
+ base::mHasFailed = true;
+ return false;
+ }
+
+ mRollback = base::mCursor;
+ base::mCursor = next;
+ base::mPastEof = parsed.Type() == base::TOKEN_EOF;
+ base::mHasFailed = false;
+ return true;
+}
+
+template <typename TChar>
+void TTokenizer<TChar>::SkipWhites(WhiteSkipping aIncludeNewLines) {
+ if (!CheckWhite() &&
+ (aIncludeNewLines == DONT_INCLUDE_NEW_LINE || !CheckEOL())) {
+ return;
+ }
+
+ typename base::TAString::const_char_iterator rollback = mRollback;
+ while (CheckWhite() || (aIncludeNewLines == INCLUDE_NEW_LINE && CheckEOL())) {
+ }
+
+ base::mHasFailed = false;
+ mRollback = rollback;
+}
+
+template <typename TChar>
+void TTokenizer<TChar>::SkipUntil(typename base::Token const& aToken) {
+ typename base::TAString::const_char_iterator rollback = base::mCursor;
+ const typename base::Token eof = base::Token::EndOfFile();
+
+ typename base::Token t;
+ while (Next(t)) {
+ if (aToken.Equals(t) || eof.Equals(t)) {
+ Rollback();
+ break;
+ }
+ }
+
+ mRollback = rollback;
+}
+
+template <typename TChar>
+bool TTokenizer<TChar>::CheckChar(bool (*aClassifier)(const TChar aChar)) {
+ if (!aClassifier) {
+ MOZ_ASSERT(false);
+ return false;
+ }
+
+ if (!base::HasInput() || base::mCursor == base::mEnd) {
+ base::mHasFailed = true;
+ return false;
+ }
+
+ if (!aClassifier(*base::mCursor)) {
+ base::mHasFailed = true;
+ return false;
+ }
+
+ mRollback = base::mCursor;
+ ++base::mCursor;
+ base::mHasFailed = false;
+ return true;
+}
+
+template <typename TChar>
+bool TTokenizer<TChar>::CheckPhrase(const typename base::TAString& aPhrase) {
+ if (!base::HasInput()) {
+ return false;
+ }
+
+ typedef typename base::TAString::const_char_iterator Cursor;
+
+ TTokenizer<TChar> pattern(aPhrase);
+ MOZ_ASSERT(!pattern.CheckEOF(),
+ "This will return true but won't shift the Tokenizer's cursor");
+
+ return [&](Cursor cursor, Cursor rollback) mutable {
+ while (true) {
+ if (pattern.CheckEOF()) {
+ base::mHasFailed = false;
+ mRollback = cursor;
+ return true;
+ }
+
+ typename base::Token t1, t2;
+ Unused << Next(t1);
+ Unused << pattern.Next(t2);
+ if (t1.Type() == t2.Type() && t1.Fragment().Equals(t2.Fragment())) {
+ continue;
+ }
+
+ break;
+ }
+
+ base::mHasFailed = true;
+ base::mPastEof = false;
+ base::mCursor = cursor;
+ mRollback = rollback;
+ return false;
+ }(base::mCursor, mRollback);
+}
+
+template <typename TChar>
+bool TTokenizer<TChar>::ReadChar(TChar* aValue) {
+ MOZ_RELEASE_ASSERT(aValue);
+
+ typename base::Token t;
+ if (!Check(base::TOKEN_CHAR, t)) {
+ return false;
+ }
+
+ *aValue = t.AsChar();
+ return true;
+}
+
+template <typename TChar>
+bool TTokenizer<TChar>::ReadChar(bool (*aClassifier)(const TChar aChar),
+ TChar* aValue) {
+ MOZ_RELEASE_ASSERT(aValue);
+
+ if (!CheckChar(aClassifier)) {
+ return false;
+ }
+
+ *aValue = *mRollback;
+ return true;
+}
+
+template <typename TChar>
+bool TTokenizer<TChar>::ReadWord(typename base::TAString& aValue) {
+ typename base::Token t;
+ if (!Check(base::TOKEN_WORD, t)) {
+ return false;
+ }
+
+ aValue.Assign(t.AsString());
+ return true;
+}
+
+template <typename TChar>
+bool TTokenizer<TChar>::ReadWord(typename base::TDependentSubstring& aValue) {
+ typename base::Token t;
+ if (!Check(base::TOKEN_WORD, t)) {
+ return false;
+ }
+
+ aValue.Rebind(t.AsString().BeginReading(), t.AsString().Length());
+ return true;
+}
+
+template <typename TChar>
+bool TTokenizer<TChar>::ReadUntil(typename base::Token const& aToken,
+ typename base::TAString& aResult,
+ ClaimInclusion aInclude) {
+ typename base::TDependentSubstring substring;
+ bool rv = ReadUntil(aToken, substring, aInclude);
+ aResult.Assign(substring);
+ return rv;
+}
+
+template <typename TChar>
+bool TTokenizer<TChar>::ReadUntil(typename base::Token const& aToken,
+ typename base::TDependentSubstring& aResult,
+ ClaimInclusion aInclude) {
+ typename base::TAString::const_char_iterator record = mRecord;
+ Record();
+ typename base::TAString::const_char_iterator rollback = mRollback =
+ base::mCursor;
+
+ bool found = false;
+ typename base::Token t;
+ while (Next(t)) {
+ if (aToken.Equals(t)) {
+ found = true;
+ break;
+ }
+ if (t.Equals(base::Token::EndOfFile())) {
+ // We don't want to eat it.
+ Rollback();
+ break;
+ }
+ }
+
+ Claim(aResult, aInclude);
+ mRollback = rollback;
+ mRecord = record;
+ return found;
+}
+
+template <typename TChar>
+void TTokenizer<TChar>::Rollback() {
+ MOZ_ASSERT(base::mCursor > mRollback || base::mPastEof, "TODO!!!");
+
+ base::mPastEof = false;
+ base::mHasFailed = false;
+ base::mCursor = mRollback;
+}
+
+template <typename TChar>
+void TTokenizer<TChar>::Record(ClaimInclusion aInclude) {
+ mRecord = aInclude == INCLUDE_LAST ? mRollback : base::mCursor;
+}
+
+template <typename TChar>
+void TTokenizer<TChar>::Claim(typename base::TAString& aResult,
+ ClaimInclusion aInclusion) {
+ typename base::TAString::const_char_iterator close =
+ aInclusion == EXCLUDE_LAST ? mRollback : base::mCursor;
+ aResult.Assign(Substring(mRecord, close));
+}
+
+template <typename TChar>
+void TTokenizer<TChar>::Claim(typename base::TDependentSubstring& aResult,
+ ClaimInclusion aInclusion) {
+ typename base::TAString::const_char_iterator close =
+ aInclusion == EXCLUDE_LAST ? mRollback : base::mCursor;
+
+ MOZ_RELEASE_ASSERT(close >= mRecord, "Overflow!");
+ aResult.Rebind(mRecord, close - mRecord);
+}
+
+// TokenizerBase
+
+template <typename TChar>
+TokenizerBase<TChar>::TokenizerBase(const TChar* aWhitespaces,
+ const TChar* aAdditionalWordChars)
+ : mPastEof(false),
+ mHasFailed(false),
+ mInputFinished(true),
+ mMode(Mode::FULL),
+ mMinRawDelivery(1024),
+ mWhitespaces(aWhitespaces ? aWhitespaces : sWhitespaces),
+ mAdditionalWordChars(aAdditionalWordChars),
+ mCursor(nullptr),
+ mEnd(nullptr),
+ mNextCustomTokenID(TOKEN_CUSTOM0) {}
+
+template <typename TChar>
+auto TokenizerBase<TChar>::AddCustomToken(const TAString& aValue,
+ ECaseSensitivity aCaseInsensitivity,
+ bool aEnabled) -> Token {
+ MOZ_ASSERT(!aValue.IsEmpty());
+
+ UniquePtr<Token>& t = *mCustomTokens.AppendElement();
+ t = MakeUnique<Token>();
+
+ t->mType = static_cast<TokenType>(++mNextCustomTokenID);
+ t->mCustomCaseInsensitivity = aCaseInsensitivity;
+ t->mCustomEnabled = aEnabled;
+ t->mCustom.Assign(aValue);
+ return *t;
+}
+
+template <typename TChar>
+void TokenizerBase<TChar>::RemoveCustomToken(Token& aToken) {
+ if (aToken.mType == TOKEN_UNKNOWN) {
+ // Already removed
+ return;
+ }
+
+ for (UniquePtr<Token> const& custom : mCustomTokens) {
+ if (custom->mType == aToken.mType) {
+ mCustomTokens.RemoveElement(custom);
+ aToken.mType = TOKEN_UNKNOWN;
+ return;
+ }
+ }
+
+ MOZ_ASSERT(false, "Token to remove not found");
+}
+
+template <typename TChar>
+void TokenizerBase<TChar>::EnableCustomToken(Token const& aToken,
+ bool aEnabled) {
+ if (aToken.mType == TOKEN_UNKNOWN) {
+ // Already removed
+ return;
+ }
+
+ for (UniquePtr<Token> const& custom : mCustomTokens) {
+ if (custom->Type() == aToken.Type()) {
+ // This effectively destroys the token instance.
+ custom->mCustomEnabled = aEnabled;
+ return;
+ }
+ }
+
+ MOZ_ASSERT(false, "Token to change not found");
+}
+
+template <typename TChar>
+void TokenizerBase<TChar>::SetTokenizingMode(Mode aMode) {
+ mMode = aMode;
+}
+
+template <typename TChar>
+bool TokenizerBase<TChar>::HasFailed() const {
+ return mHasFailed;
+}
+
+template <typename TChar>
+bool TokenizerBase<TChar>::HasInput() const {
+ return !mPastEof;
+}
+
+template <typename TChar>
+auto TokenizerBase<TChar>::Parse(Token& aToken) const ->
+ typename TAString::const_char_iterator {
+ if (mCursor == mEnd) {
+ if (!mInputFinished) {
+ return mCursor;
+ }
+
+ aToken = Token::EndOfFile();
+ return mEnd;
+ }
+
+ MOZ_RELEASE_ASSERT(mEnd >= mCursor, "Overflow!");
+ typename TAString::size_type available = mEnd - mCursor;
+
+ uint32_t longestCustom = 0;
+ for (UniquePtr<Token> const& custom : mCustomTokens) {
+ if (IsCustom(mCursor, *custom, &longestCustom)) {
+ aToken = *custom;
+ return mCursor + custom->mCustom.Length();
+ }
+ }
+
+ if (!mInputFinished && available < longestCustom) {
+ // Not enough data to deterministically decide.
+ return mCursor;
+ }
+
+ typename TAString::const_char_iterator next = mCursor;
+
+ if (mMode == Mode::CUSTOM_ONLY) {
+ // We have to do a brute-force search for all of the enabled custom
+ // tokens.
+ while (next < mEnd) {
+ ++next;
+ for (UniquePtr<Token> const& custom : mCustomTokens) {
+ if (IsCustom(next, *custom)) {
+ aToken = Token::Raw();
+ return next;
+ }
+ }
+ }
+
+ if (mInputFinished) {
+ // End of the data reached.
+ aToken = Token::Raw();
+ return next;
+ }
+
+ if (longestCustom < available && available > mMinRawDelivery) {
+ // We can return some data w/o waiting for either a custom token
+ // or call to FinishData() when we leave the tail where all the
+ // custom tokens potentially fit, so we can't lose only partially
+ // delivered tokens. This preserves reasonable granularity.
+ aToken = Token::Raw();
+ return mEnd - longestCustom + 1;
+ }
+
+ // Not enough data to deterministically decide.
+ return mCursor;
+ }
+
+ enum State {
+ PARSE_INTEGER,
+ PARSE_WORD,
+ PARSE_CRLF,
+ PARSE_LF,
+ PARSE_WS,
+ PARSE_CHAR,
+ } state;
+
+ if (IsWordFirst(*next)) {
+ state = PARSE_WORD;
+ } else if (IsNumber(*next)) {
+ state = PARSE_INTEGER;
+ } else if (contains(mWhitespaces, *next)) { // not UTF-8 friendly?
+ state = PARSE_WS;
+ } else if (*next == '\r') {
+ state = PARSE_CRLF;
+ } else if (*next == '\n') {
+ state = PARSE_LF;
+ } else {
+ state = PARSE_CHAR;
+ }
+
+ mozilla::CheckedUint64 resultingNumber = 0;
+
+ while (next < mEnd) {
+ switch (state) {
+ case PARSE_INTEGER:
+ // Keep it simple for now
+ resultingNumber *= 10;
+ resultingNumber += static_cast<uint64_t>(*next - '0');
+
+ ++next;
+ if (IsPending(next)) {
+ break;
+ }
+ if (IsEnd(next) || !IsNumber(*next)) {
+ if (!resultingNumber.isValid()) {
+ aToken = Token::Error();
+ } else {
+ aToken = Token::Number(resultingNumber.value());
+ }
+ return next;
+ }
+ break;
+
+ case PARSE_WORD:
+ ++next;
+ if (IsPending(next)) {
+ break;
+ }
+ if (IsEnd(next) || !IsWord(*next)) {
+ aToken = Token::Word(Substring(mCursor, next));
+ return next;
+ }
+ break;
+
+ case PARSE_CRLF:
+ ++next;
+ if (IsPending(next)) {
+ break;
+ }
+ if (!IsEnd(next) && *next == '\n') { // LF is optional
+ ++next;
+ }
+ aToken = Token::NewLine();
+ return next;
+
+ case PARSE_LF:
+ ++next;
+ aToken = Token::NewLine();
+ return next;
+
+ case PARSE_WS:
+ ++next;
+ aToken = Token::Whitespace();
+ return next;
+
+ case PARSE_CHAR:
+ ++next;
+ aToken = Token::Char(*mCursor);
+ return next;
+ } // switch (state)
+ } // while (next < end)
+
+ MOZ_ASSERT(!mInputFinished);
+ return mCursor;
+}
+
+template <typename TChar>
+bool TokenizerBase<TChar>::IsEnd(
+ const typename TAString::const_char_iterator& caret) const {
+ return caret == mEnd;
+}
+
+template <typename TChar>
+bool TokenizerBase<TChar>::IsPending(
+ const typename TAString::const_char_iterator& caret) const {
+ return IsEnd(caret) && !mInputFinished;
+}
+
+template <typename TChar>
+bool TokenizerBase<TChar>::IsWordFirst(const TChar aInput) const {
+ // TODO: make this fully work with unicode
+ return (ToLowerCase(static_cast<uint32_t>(aInput)) !=
+ ToUpperCase(static_cast<uint32_t>(aInput))) ||
+ '_' == aInput ||
+ (mAdditionalWordChars ? contains(mAdditionalWordChars, aInput)
+ : false);
+}
+
+template <typename TChar>
+bool TokenizerBase<TChar>::IsWord(const TChar aInput) const {
+ return IsWordFirst(aInput) || IsNumber(aInput);
+}
+
+template <typename TChar>
+bool TokenizerBase<TChar>::IsNumber(const TChar aInput) const {
+ // TODO: are there unicode numbers?
+ return aInput >= '0' && aInput <= '9';
+}
+
+template <typename TChar>
+bool TokenizerBase<TChar>::IsCustom(
+ const typename TAString::const_char_iterator& caret,
+ const Token& aCustomToken, uint32_t* aLongest) const {
+ MOZ_ASSERT(aCustomToken.mType > TOKEN_CUSTOM0);
+ if (!aCustomToken.mCustomEnabled) {
+ return false;
+ }
+
+ if (aLongest) {
+ *aLongest = std::max<uint32_t>(*aLongest, aCustomToken.mCustom.Length());
+ }
+
+ // This is not very likely to happen according to how we call this method
+ // and since it's on a hot path, it's just a diagnostic assert,
+ // not a release assert.
+ MOZ_DIAGNOSTIC_ASSERT(mEnd >= caret, "Overflow?");
+ uint32_t inputLength = mEnd - caret;
+ if (aCustomToken.mCustom.Length() > inputLength) {
+ return false;
+ }
+
+ TDependentSubstring inputFragment(caret, aCustomToken.mCustom.Length());
+ if (aCustomToken.mCustomCaseInsensitivity == CASE_INSENSITIVE) {
+ if constexpr (std::is_same_v<TChar, char>) {
+ return inputFragment.Equals(aCustomToken.mCustom,
+ nsCaseInsensitiveUTF8StringComparator);
+ } else {
+ return inputFragment.Equals(aCustomToken.mCustom,
+ nsCaseInsensitiveStringComparator);
+ }
+ }
+ return inputFragment.Equals(aCustomToken.mCustom);
+}
+
+template <typename TChar>
+void TokenizerBase<TChar>::AssignFragment(
+ Token& aToken, typename TAString::const_char_iterator begin,
+ typename TAString::const_char_iterator end) {
+ aToken.AssignFragment(begin, end);
+}
+
+#ifdef DEBUG
+
+template <typename TChar>
+void TokenizerBase<TChar>::Validate(Token const& aToken) {
+ if (aToken.Type() == TOKEN_WORD) {
+ typename TAString::const_char_iterator c = aToken.AsString().BeginReading();
+ typename TAString::const_char_iterator e = aToken.AsString().EndReading();
+
+ if (c < e) {
+ MOZ_ASSERT(IsWordFirst(*c));
+ while (++c < e) {
+ MOZ_ASSERT(IsWord(*c));
+ }
+ }
+ }
+}
+
+#endif
+
+// TokenizerBase::Token
+
+template <typename TChar>
+TokenizerBase<TChar>::Token::Token()
+ : mType(TOKEN_UNKNOWN),
+ mChar(0),
+ mInteger(0),
+ mCustomCaseInsensitivity(CASE_SENSITIVE),
+ mCustomEnabled(false) {}
+
+template <typename TChar>
+TokenizerBase<TChar>::Token::Token(const Token& aOther)
+ : mType(aOther.mType),
+ mCustom(aOther.mCustom),
+ mChar(aOther.mChar),
+ mInteger(aOther.mInteger),
+ mCustomCaseInsensitivity(aOther.mCustomCaseInsensitivity),
+ mCustomEnabled(aOther.mCustomEnabled) {
+ if (mType == TOKEN_WORD || mType > TOKEN_CUSTOM0) {
+ mWord.Rebind(aOther.mWord.BeginReading(), aOther.mWord.Length());
+ }
+}
+
+template <typename TChar>
+auto TokenizerBase<TChar>::Token::operator=(const Token& aOther) -> Token& {
+ mType = aOther.mType;
+ mCustom = aOther.mCustom;
+ mChar = aOther.mChar;
+ mWord.Rebind(aOther.mWord.BeginReading(), aOther.mWord.Length());
+ mInteger = aOther.mInteger;
+ mCustomCaseInsensitivity = aOther.mCustomCaseInsensitivity;
+ mCustomEnabled = aOther.mCustomEnabled;
+ return *this;
+}
+
+template <typename TChar>
+void TokenizerBase<TChar>::Token::AssignFragment(
+ typename TAString::const_char_iterator begin,
+ typename TAString::const_char_iterator end) {
+ MOZ_RELEASE_ASSERT(end >= begin, "Overflow!");
+ mFragment.Rebind(begin, end - begin);
+}
+
+// static
+template <typename TChar>
+auto TokenizerBase<TChar>::Token::Raw() -> Token {
+ Token t;
+ t.mType = TOKEN_RAW;
+ return t;
+}
+
+// static
+template <typename TChar>
+auto TokenizerBase<TChar>::Token::Word(TAString const& aValue) -> Token {
+ Token t;
+ t.mType = TOKEN_WORD;
+ t.mWord.Rebind(aValue.BeginReading(), aValue.Length());
+ return t;
+}
+
+// static
+template <typename TChar>
+auto TokenizerBase<TChar>::Token::Char(TChar const aValue) -> Token {
+ Token t;
+ t.mType = TOKEN_CHAR;
+ t.mChar = aValue;
+ return t;
+}
+
+// static
+template <typename TChar>
+auto TokenizerBase<TChar>::Token::Number(uint64_t const aValue) -> Token {
+ Token t;
+ t.mType = TOKEN_INTEGER;
+ t.mInteger = aValue;
+ return t;
+}
+
+// static
+template <typename TChar>
+auto TokenizerBase<TChar>::Token::Whitespace() -> Token {
+ Token t;
+ t.mType = TOKEN_WS;
+ t.mChar = '\0';
+ return t;
+}
+
+// static
+template <typename TChar>
+auto TokenizerBase<TChar>::Token::NewLine() -> Token {
+ Token t;
+ t.mType = TOKEN_EOL;
+ return t;
+}
+
+// static
+template <typename TChar>
+auto TokenizerBase<TChar>::Token::EndOfFile() -> Token {
+ Token t;
+ t.mType = TOKEN_EOF;
+ return t;
+}
+
+// static
+template <typename TChar>
+auto TokenizerBase<TChar>::Token::Error() -> Token {
+ Token t;
+ t.mType = TOKEN_ERROR;
+ return t;
+}
+
+template <typename TChar>
+bool TokenizerBase<TChar>::Token::Equals(const Token& aOther) const {
+ if (mType != aOther.mType) {
+ return false;
+ }
+
+ switch (mType) {
+ case TOKEN_INTEGER:
+ return AsInteger() == aOther.AsInteger();
+ case TOKEN_WORD:
+ return AsString() == aOther.AsString();
+ case TOKEN_CHAR:
+ return AsChar() == aOther.AsChar();
+ default:
+ return true;
+ }
+}
+
+template <typename TChar>
+TChar TokenizerBase<TChar>::Token::AsChar() const {
+ MOZ_ASSERT(mType == TOKEN_CHAR || mType == TOKEN_WS);
+ return mChar;
+}
+
+template <typename TChar>
+auto TokenizerBase<TChar>::Token::AsString() const -> TDependentSubstring {
+ MOZ_ASSERT(mType == TOKEN_WORD);
+ return mWord;
+}
+
+template <typename TChar>
+uint64_t TokenizerBase<TChar>::Token::AsInteger() const {
+ MOZ_ASSERT(mType == TOKEN_INTEGER);
+ return mInteger;
+}
+
+template class TokenizerBase<char>;
+template class TokenizerBase<char16_t>;
+
+template class TTokenizer<char>;
+template class TTokenizer<char16_t>;
+
+} // namespace mozilla
diff --git a/xpcom/ds/Tokenizer.h b/xpcom/ds/Tokenizer.h
new file mode 100644
index 0000000000..713b63f269
--- /dev/null
+++ b/xpcom/ds/Tokenizer.h
@@ -0,0 +1,524 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef Tokenizer_h__
+#define Tokenizer_h__
+
+#include <type_traits>
+
+#include "nsString.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+template <typename TChar>
+class TokenizerBase {
+ public:
+ typedef nsTSubstring<TChar> TAString;
+ typedef nsTString<TChar> TString;
+ typedef nsTDependentString<TChar> TDependentString;
+ typedef nsTDependentSubstring<TChar> TDependentSubstring;
+
+ static TChar const sWhitespaces[];
+
+ /**
+ * The analyzer works with elements in the input cut to a sequence of token
+ * where each token has an elementary type
+ */
+ enum TokenType : uint32_t {
+ TOKEN_UNKNOWN,
+ TOKEN_RAW,
+ TOKEN_ERROR,
+ TOKEN_INTEGER,
+ TOKEN_WORD,
+ TOKEN_CHAR,
+ TOKEN_WS,
+ TOKEN_EOL,
+ TOKEN_EOF,
+ TOKEN_CUSTOM0 = 1000
+ };
+
+ enum ECaseSensitivity { CASE_SENSITIVE, CASE_INSENSITIVE };
+
+ /**
+ * Class holding the type and the value of a token. It can be manually
+ * created to allow checks against it via methods of TTokenizer or are results
+ * of some of the TTokenizer's methods.
+ */
+ class Token {
+ TokenType mType;
+ TDependentSubstring mWord;
+ TString mCustom;
+ TChar mChar;
+ uint64_t mInteger;
+ ECaseSensitivity mCustomCaseInsensitivity;
+ bool mCustomEnabled;
+
+ // If this token is a result of the parsing process, this member is
+ // referencing a sub-string in the input buffer. If this is externally
+ // created Token this member is left an empty string.
+ TDependentSubstring mFragment;
+
+ friend class TokenizerBase<TChar>;
+ void AssignFragment(typename TAString::const_char_iterator begin,
+ typename TAString::const_char_iterator end);
+
+ static Token Raw();
+
+ public:
+ Token();
+ Token(const Token& aOther);
+ Token& operator=(const Token& aOther);
+
+ // Static constructors of tokens by type and value
+ static Token Word(TAString const& aWord);
+ static Token Char(TChar const aChar);
+ static Token Number(uint64_t const aNumber);
+ static Token Whitespace();
+ static Token NewLine();
+ static Token EndOfFile();
+ static Token Error();
+
+ // Compares the two tokens, type must be identical and value
+ // of one of the tokens must be 'any' or equal.
+ bool Equals(const Token& aOther) const;
+
+ TokenType Type() const { return mType; }
+ TChar AsChar() const;
+ TDependentSubstring AsString() const;
+ uint64_t AsInteger() const;
+
+ TDependentSubstring Fragment() const { return mFragment; }
+ };
+
+ /**
+ * Consumers may register a custom string that, when found in the input, is
+ * considered a token and returned by Next*() and accepted by Check*()
+ * methods. AddCustomToken() returns a reference to a token that can then be
+ * comapred using Token::Equals() againts the output from Next*() or be passed
+ * to Check*().
+ */
+ Token AddCustomToken(const TAString& aValue,
+ ECaseSensitivity aCaseInsensitivity,
+ bool aEnabled = true);
+ template <uint32_t N>
+ Token AddCustomToken(const TChar (&aValue)[N],
+ ECaseSensitivity aCaseInsensitivity,
+ bool aEnabled = true) {
+ return AddCustomToken(TDependentSubstring(aValue, N - 1),
+ aCaseInsensitivity, aEnabled);
+ }
+ void RemoveCustomToken(Token& aToken);
+ /**
+ * Only applies to a custom type of a Token (see AddCustomToken above.)
+ * This turns on and off token recognition. When a custom token is disabled,
+ * it's ignored as never added as a custom token.
+ */
+ void EnableCustomToken(Token const& aToken, bool aEnable);
+
+ /**
+ * Mode of tokenization.
+ * FULL tokenization, the default, recognizes built-in tokens and any custom
+ * tokens, if added. CUSTOM_ONLY will only recognize custom tokens, the rest
+ * is seen as 'raw'. This mode can be understood as a 'binary' mode.
+ */
+ enum class Mode { FULL, CUSTOM_ONLY };
+ void SetTokenizingMode(Mode aMode);
+
+ /**
+ * Return false iff the last Check*() call has returned false or when we've
+ * read past the end of the input string.
+ */
+ [[nodiscard]] bool HasFailed() const;
+
+ protected:
+ explicit TokenizerBase(const TChar* aWhitespaces = nullptr,
+ const TChar* aAdditionalWordChars = nullptr);
+
+ // false if we have already read the EOF token.
+ bool HasInput() const;
+ // Main parsing function, it doesn't shift the read cursor, just returns the
+ // next token position.
+ typename TAString::const_char_iterator Parse(Token& aToken) const;
+ // Is read cursor at the end?
+ bool IsEnd(const typename TAString::const_char_iterator& caret) const;
+ // True, when we are at the end of the input data, but it has not been marked
+ // as complete yet. In that case we cannot proceed with providing a
+ // multi-TChar token.
+ bool IsPending(const typename TAString::const_char_iterator& caret) const;
+ // Is read cursor on a character that is a word start?
+ bool IsWordFirst(const TChar aInput) const;
+ // Is read cursor on a character that is an in-word letter?
+ bool IsWord(const TChar aInput) const;
+ // Is read cursor on a character that is a valid number?
+ // TODO - support multiple radix
+ bool IsNumber(const TChar aInput) const;
+ // Is equal to the given custom token?
+ bool IsCustom(const typename TAString::const_char_iterator& caret,
+ const Token& aCustomToken, uint32_t* aLongest = nullptr) const;
+
+ // Friendly helper to assign a fragment on a Token
+ static void AssignFragment(Token& aToken,
+ typename TAString::const_char_iterator begin,
+ typename TAString::const_char_iterator end);
+
+#ifdef DEBUG
+ // This is called from inside Tokenizer methods to make sure the token is
+ // valid.
+ void Validate(Token const& aToken);
+#endif
+
+ // true iff we have already read the EOF token
+ bool mPastEof;
+ // true iff the last Check*() call has returned false, reverts to true on
+ // Rollback() call
+ bool mHasFailed;
+ // true if the input string is final (finished), false when we expect more
+ // data yet to be fed to the tokenizer (see IncrementalTokenizer derived
+ // class).
+ bool mInputFinished;
+ // custom only vs full tokenizing mode, see the Parse() method
+ Mode mMode;
+ // minimal raw data chunked delivery during incremental feed
+ uint32_t mMinRawDelivery;
+
+ // Customizable list of whitespaces
+ const TChar* mWhitespaces;
+ // Additinal custom word characters
+ const TChar* mAdditionalWordChars;
+
+ // All these point to the original buffer passed to the constructor or to the
+ // incremental buffer after FeedInput.
+ typename TAString::const_char_iterator
+ mCursor; // Position of the current (actually next to read) token start
+ typename TAString::const_char_iterator mEnd; // End of the input position
+
+ // This is the list of tokens user has registered with AddCustomToken()
+ nsTArray<UniquePtr<Token>> mCustomTokens;
+ uint32_t mNextCustomTokenID;
+
+ private:
+ TokenizerBase() = delete;
+ TokenizerBase(const TokenizerBase&) = delete;
+ TokenizerBase(TokenizerBase&&) = delete;
+ TokenizerBase(const TokenizerBase&&) = delete;
+ TokenizerBase& operator=(const TokenizerBase&) = delete;
+};
+
+/**
+ * This is a simple implementation of a lexical analyzer or maybe better
+ * called a tokenizer.
+ *
+ * Please use Tokenizer or Tokenizer16 classes, that are specializations
+ * of this template class. Tokenizer is for ASCII input, Tokenizer16 may
+ * handle char16_t input, but doesn't recognize whitespaces or numbers
+ * other than standard `char` specialized Tokenizer class.
+ */
+template <typename TChar>
+class TTokenizer : public TokenizerBase<TChar> {
+ public:
+ typedef TokenizerBase<TChar> base;
+
+ /**
+ * @param aSource
+ * The string to parse.
+ * IMPORTANT NOTE: TTokenizer doesn't ensure the input string buffer
+ * lifetime. It's up to the consumer to make sure the string's buffer outlives
+ * the TTokenizer!
+ * @param aWhitespaces
+ * If non-null TTokenizer will use this custom set of whitespaces for
+ * CheckWhite() and SkipWhites() calls. By default the list consists of space
+ * and tab.
+ * @param aAdditionalWordChars
+ * If non-null it will be added to the list of characters that consist a
+ * word. This is useful when you want to accept e.g. '-' in HTTP headers. By
+ * default a word character is consider any character for which upper case
+ * is different from lower case.
+ *
+ * If there is an overlap between aWhitespaces and aAdditionalWordChars, the
+ * check for word characters is made first.
+ */
+ explicit TTokenizer(const typename base::TAString& aSource,
+ const TChar* aWhitespaces = nullptr,
+ const TChar* aAdditionalWordChars = nullptr);
+ explicit TTokenizer(const TChar* aSource, const TChar* aWhitespaces = nullptr,
+ const TChar* aAdditionalWordChars = nullptr);
+
+ /**
+ * When there is still anything to read from the input, tokenize it, store the
+ * token type and value to aToken result and shift the cursor past this just
+ * parsed token. Each call to Next() reads another token from the input and
+ * shifts the cursor. Returns false if we have passed the end of the input.
+ */
+ [[nodiscard]] bool Next(typename base::Token& aToken);
+
+ /**
+ * Parse the token on the input read cursor position, check its type is equal
+ * to aTokenType and if so, put it into aResult, shift the cursor and return
+ * true. Otherwise, leave the input read cursor position intact and return
+ * false.
+ */
+ [[nodiscard]] bool Check(const typename base::TokenType aTokenType,
+ typename base::Token& aResult);
+ /**
+ * Same as above method, just compares both token type and token value passed
+ * in aToken. When both the type and the value equals, shift the cursor and
+ * return true. Otherwise return false.
+ */
+ [[nodiscard]] bool Check(const typename base::Token& aToken);
+
+ /**
+ * SkipWhites method (below) may also skip new line characters automatically.
+ */
+ enum WhiteSkipping {
+ /**
+ * SkipWhites will only skip what is defined as a white space (default).
+ */
+ DONT_INCLUDE_NEW_LINE = 0,
+ /**
+ * SkipWhites will skip definited white spaces as well as new lines
+ * automatically.
+ */
+ INCLUDE_NEW_LINE = 1
+ };
+
+ /**
+ * Skips any occurence of whitespaces specified in mWhitespaces member,
+ * optionally skip also new lines.
+ */
+ void SkipWhites(WhiteSkipping aIncludeNewLines = DONT_INCLUDE_NEW_LINE);
+
+ /**
+ * Skips all tokens until the given one is found or EOF is hit. The token
+ * or EOF are next to read.
+ */
+ void SkipUntil(typename base::Token const& aToken);
+
+ // These are mostly shortcuts for the Check() methods above.
+
+ /**
+ * Check whitespace character is present.
+ */
+ [[nodiscard]] bool CheckWhite() { return Check(base::Token::Whitespace()); }
+ /**
+ * Check there is a single character on the read cursor position. If so,
+ * shift the read cursor position and return true. Otherwise false.
+ */
+ [[nodiscard]] bool CheckChar(const TChar aChar) {
+ return Check(base::Token::Char(aChar));
+ }
+ /**
+ * This is a customizable version of CheckChar. aClassifier is a function
+ * called with value of the character on the current input read position. If
+ * this user function returns true, read cursor is shifted and true returned.
+ * Otherwise false. The user classifiction function is not called when we are
+ * at or past the end and false is immediately returned.
+ */
+ [[nodiscard]] bool CheckChar(bool (*aClassifier)(const TChar aChar));
+ /**
+ * Check for a whole expected word.
+ */
+ [[nodiscard]] bool CheckWord(const typename base::TAString& aWord) {
+ return Check(base::Token::Word(aWord));
+ }
+ /**
+ * Shortcut for literal const word check with compile time length calculation.
+ */
+ template <uint32_t N>
+ [[nodiscard]] bool CheckWord(const TChar (&aWord)[N]) {
+ return Check(
+ base::Token::Word(typename base::TDependentString(aWord, N - 1)));
+ }
+ /**
+ * Helper to check for a string compound of multiple tokens like "foo bar".
+ * The match is binary-exact, a white space or a delimiter character in the
+ * phrase must match exactly the characters in the input.
+ */
+ [[nodiscard]] bool CheckPhrase(const typename base::TAString& aPhrase);
+ template <uint32_t N>
+ [[nodiscard]] bool CheckPhrase(const TChar (&aPhrase)[N]) {
+ return CheckPhrase(typename base::TDependentString(aPhrase, N - 1));
+ }
+ /**
+ * Checks \r, \n or \r\n.
+ */
+ [[nodiscard]] bool CheckEOL() { return Check(base::Token::NewLine()); }
+ /**
+ * Checks we are at the end of the input string reading. If so, shift past
+ * the end and returns true. Otherwise does nothing and returns false.
+ */
+ [[nodiscard]] bool CheckEOF() { return Check(base::Token::EndOfFile()); }
+
+ /**
+ * These are shortcuts to obtain the value immediately when the token type
+ * matches.
+ */
+ [[nodiscard]] bool ReadChar(TChar* aValue);
+ [[nodiscard]] bool ReadChar(bool (*aClassifier)(const TChar aChar),
+ TChar* aValue);
+ [[nodiscard]] bool ReadWord(typename base::TAString& aValue);
+ [[nodiscard]] bool ReadWord(typename base::TDependentSubstring& aValue);
+
+ /**
+ * This is an integer read helper. It returns false and doesn't move the read
+ * cursor when any of the following happens:
+ * - the token at the read cursor is not an integer
+ * - the final number doesn't fit the T type
+ * Otherwise true is returned, aValue is filled with the integral number
+ * and the cursor is moved forward.
+ */
+ template <typename T>
+ [[nodiscard]] bool ReadInteger(T* aValue) {
+ MOZ_RELEASE_ASSERT(aValue);
+
+ typename base::TAString::const_char_iterator rollback = mRollback;
+ typename base::TAString::const_char_iterator cursor = base::mCursor;
+ typename base::Token t;
+ if (!Check(base::TOKEN_INTEGER, t)) {
+ return false;
+ }
+
+ mozilla::CheckedInt<T> checked(t.AsInteger());
+ if (!checked.isValid()) {
+ // Move to a state as if Check() call has failed
+ mRollback = rollback;
+ base::mCursor = cursor;
+ base::mHasFailed = true;
+ return false;
+ }
+
+ *aValue = checked.value();
+ return true;
+ }
+
+ /**
+ * Same as above, but accepts an integer with an optional minus sign.
+ */
+ template <typename T, typename V = std::enable_if_t<
+ std::is_signed_v<std::remove_pointer_t<T>>,
+ std::remove_pointer_t<T>>>
+ [[nodiscard]] bool ReadSignedInteger(T* aValue) {
+ MOZ_RELEASE_ASSERT(aValue);
+
+ typename base::TAString::const_char_iterator rollback = mRollback;
+ typename base::TAString::const_char_iterator cursor = base::mCursor;
+ auto revert = MakeScopeExit([&] {
+ // Move to a state as if Check() call has failed
+ mRollback = rollback;
+ base::mCursor = cursor;
+ base::mHasFailed = true;
+ });
+
+ // Using functional raw access because '-' could be part of the word set
+ // making CheckChar('-') not work.
+ bool minus = CheckChar([](const TChar aChar) { return aChar == '-'; });
+
+ typename base::Token t;
+ if (!Check(base::TOKEN_INTEGER, t)) {
+ return false;
+ }
+
+ mozilla::CheckedInt<T> checked(t.AsInteger());
+ if (minus) {
+ checked *= -1;
+ }
+
+ if (!checked.isValid()) {
+ return false;
+ }
+
+ *aValue = checked.value();
+ revert.release();
+ return true;
+ }
+
+ /**
+ * Returns the read cursor position back as it was before the last call of any
+ * parsing method of TTokenizer (Next, Check*, Skip*, Read*) so that the last
+ * operation can be repeated. Rollback cannot be used multiple times, it only
+ * reverts the last successfull parse operation. It also cannot be used
+ * before any parsing operation has been called on the TTokenizer.
+ */
+ void Rollback();
+
+ /**
+ * Record() and Claim() are collecting the input as it is being parsed to
+ * obtain a substring between particular syntax bounderies defined by any
+ * recursive descent parser or simple parser the TTokenizer is used to read
+ * the input for. Inlucsion of a token that has just been parsed can be
+ * controlled using an arguemnt.
+ */
+ enum ClaimInclusion {
+ /**
+ * Include resulting (or passed) token of the last lexical analyzer
+ * operation in the result.
+ */
+ INCLUDE_LAST,
+ /**
+ * Do not include it.
+ */
+ EXCLUDE_LAST
+ };
+
+ /**
+ * Start the process of recording. Based on aInclude value the begining of
+ * the recorded sub-string is at the current position (EXCLUDE_LAST) or at the
+ * position before the last parsed token (INCLUDE_LAST).
+ */
+ void Record(ClaimInclusion aInclude = EXCLUDE_LAST);
+ /**
+ * Claim result of the record started with Record() call before. Depending on
+ * aInclude the ending of the sub-string result includes or excludes the last
+ * parsed or checked token.
+ */
+ void Claim(typename base::TAString& aResult,
+ ClaimInclusion aInclude = EXCLUDE_LAST);
+ void Claim(typename base::TDependentSubstring& aResult,
+ ClaimInclusion aInclude = EXCLUDE_LAST);
+
+ /**
+ * If aToken is found, aResult is set to the substring between the current
+ * position and the position of aToken, potentially including aToken depending
+ * on aInclude.
+ * If aToken isn't found aResult is set to the substring between the current
+ * position and the end of the string.
+ * If aToken is found, the method returns true. Otherwise it returns false.
+ *
+ * Calling Rollback() after ReadUntil() will return the read cursor to the
+ * position it had before ReadUntil was called.
+ */
+ [[nodiscard]] bool ReadUntil(typename base::Token const& aToken,
+ typename base::TDependentSubstring& aResult,
+ ClaimInclusion aInclude = EXCLUDE_LAST);
+ [[nodiscard]] bool ReadUntil(typename base::Token const& aToken,
+ typename base::TAString& aResult,
+ ClaimInclusion aInclude = EXCLUDE_LAST);
+
+ protected:
+ // All these point to the original buffer passed to the TTokenizer's
+ // constructor
+ typename base::TAString::const_char_iterator
+ mRecord; // Position where the recorded sub-string for Claim() is
+ typename base::TAString::const_char_iterator
+ mRollback; // Position of the previous token start
+
+ private:
+ TTokenizer() = delete;
+ TTokenizer(const TTokenizer&) = delete;
+ TTokenizer(TTokenizer&&) = delete;
+ TTokenizer(const TTokenizer&&) = delete;
+ TTokenizer& operator=(const TTokenizer&) = delete;
+};
+
+typedef TTokenizer<char> Tokenizer;
+typedef TTokenizer<char16_t> Tokenizer16;
+
+} // namespace mozilla
+
+#endif // Tokenizer_h__
diff --git a/xpcom/ds/components.conf b/xpcom/ds/components.conf
new file mode 100644
index 0000000000..1b600352b3
--- /dev/null
+++ b/xpcom/ds/components.conf
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{35c66fd1-95e9-4e0a-80c5-c3bd2b375481}',
+ 'contract_ids': ['@mozilla.org/array;1'],
+ 'legacy_constructor': 'nsArrayBase::XPCOMConstructor',
+ 'headers': ['nsArray.h'],
+ },
+ {
+ 'name': 'Observer',
+ 'js_name': 'obs',
+ 'cid': '{d07f5195-e3d1-11d2-8acd-00105a1b8860}',
+ 'contract_ids': ['@mozilla.org/observer-service;1'],
+ 'interfaces': ['nsIObserverService'],
+ 'legacy_constructor': 'nsObserverService::Create',
+ 'headers': ['/xpcom/ds/nsObserverService.h'],
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS,
+ },
+]
diff --git a/xpcom/ds/moz.build b/xpcom/ds/moz.build
new file mode 100644
index 0000000000..1d0488b9b2
--- /dev/null
+++ b/xpcom/ds/moz.build
@@ -0,0 +1,157 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "nsIArray.idl",
+ "nsIArrayExtensions.idl",
+ "nsIINIParser.idl",
+ "nsIMutableArray.idl",
+ "nsIObserver.idl",
+ "nsIObserverService.idl",
+ "nsIPersistentProperties2.idl",
+ "nsIProperties.idl",
+ "nsIProperty.idl",
+ "nsIPropertyBag.idl",
+ "nsIPropertyBag2.idl",
+ "nsISerializable.idl",
+ "nsISimpleEnumerator.idl",
+ "nsIStringEnumerator.idl",
+ "nsISupportsIterators.idl",
+ "nsISupportsPrimitives.idl",
+ "nsIVariant.idl",
+ "nsIWritablePropertyBag.idl",
+ "nsIWritablePropertyBag2.idl",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ XPIDL_SOURCES += [
+ "nsIWindowsRegKey.idl",
+ ]
+ EXPORTS += ["nsWindowsRegKey.h"]
+ SOURCES += ["nsWindowsRegKey.cpp"]
+
+XPIDL_MODULE = "xpcom_ds"
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+EXPORTS += [
+ "!nsGkAtomConsts.h",
+ "!nsGkAtomList.h",
+ "nsArray.h",
+ "nsArrayEnumerator.h",
+ "nsArrayUtils.h",
+ "nsAtom.h",
+ "nsBaseHashtable.h",
+ "nsCharSeparatedTokenizer.h",
+ "nsCheapSets.h",
+ "nsClassHashtable.h",
+ "nsCOMArray.h",
+ "nsCRT.h",
+ "nsDeque.h",
+ "nsEnumeratorUtils.h",
+ "nsExpirationTracker.h",
+ "nsGkAtoms.h",
+ "nsHashKeys.h",
+ "nsHashPropertyBag.h",
+ "nsHashtablesFwd.h",
+ "nsInterfaceHashtable.h",
+ "nsMathUtils.h",
+ "nsPersistentProperties.h",
+ "nsPointerHashKeys.h",
+ "nsProperties.h",
+ "nsQuickSort.h",
+ "nsRefCountedHashtable.h",
+ "nsRefPtrHashtable.h",
+ "nsSimpleEnumerator.h",
+ "nsStaticAtomUtils.h",
+ "nsStaticNameTable.h",
+ "nsStringEnumerator.h",
+ "nsSupportsPrimitives.h",
+ "nsTArray-inl.h",
+ "nsTArray.h",
+ "nsTArrayForwardDeclare.h",
+ "nsTHashMap.h",
+ "nsTHashSet.h",
+ "nsTHashtable.h",
+ "nsTObserverArray.h",
+ "nsTPriorityQueue.h",
+ "nsVariant.h",
+ "nsWhitespaceTokenizer.h",
+ "PLDHashTable.h",
+]
+
+EXPORTS.mozilla += [
+ "ArenaAllocator.h",
+ "ArenaAllocatorExtensions.h",
+ "ArrayAlgorithm.h",
+ "ArrayIterator.h",
+ "AtomArray.h",
+ "Dafsa.h",
+ "IncrementalTokenizer.h",
+ "MruCache.h",
+ "Observer.h",
+ "PerfectHash.h",
+ "SimpleEnumerator.h",
+ "StickyTimeDuration.h",
+ "Tokenizer.h",
+]
+
+UNIFIED_SOURCES += [
+ "Dafsa.cpp",
+ "IncrementalTokenizer.cpp",
+ "nsArray.cpp",
+ "nsArrayEnumerator.cpp",
+ "nsArrayUtils.cpp",
+ "nsAtomTable.cpp",
+ "nsCharSeparatedTokenizer.cpp",
+ "nsCOMArray.cpp",
+ "nsCRT.cpp",
+ "nsDeque.cpp",
+ "nsEnumeratorUtils.cpp",
+ "nsGkAtoms.cpp",
+ "nsHashPropertyBag.cpp",
+ "nsINIParserImpl.cpp",
+ "nsObserverList.cpp",
+ "nsObserverService.cpp",
+ "nsPersistentProperties.cpp",
+ "nsProperties.cpp",
+ "nsQuickSort.cpp",
+ "nsSimpleEnumerator.cpp",
+ "nsStaticNameTable.cpp",
+ "nsStringEnumerator.cpp",
+ "nsSupportsPrimitives.cpp",
+ "nsTArray.cpp",
+ "nsTObserverArray.cpp",
+ "nsVariant.cpp",
+ "PLDHashTable.cpp",
+ "Tokenizer.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "../io",
+]
+
+GeneratedFile(
+ "nsGkAtomList.h",
+ script="StaticAtoms.py",
+ entry_point="generate_nsgkatomlist_h",
+ inputs=["Atom.py", "HTMLAtoms.py"],
+)
+
+GeneratedFile(
+ "nsGkAtomConsts.h",
+ script="StaticAtoms.py",
+ entry_point="generate_nsgkatomconsts_h",
+ inputs=["Atom.py", "HTMLAtoms.py"],
+)
+
+FINAL_LIBRARY = "xul"
+
+PYTHON_UNITTEST_MANIFESTS += [
+ "test/python.ini",
+]
diff --git a/xpcom/ds/nsArray.cpp b/xpcom/ds/nsArray.cpp
new file mode 100644
index 0000000000..60758133d9
--- /dev/null
+++ b/xpcom/ds/nsArray.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsThreadUtils.h"
+
+NS_INTERFACE_MAP_BEGIN(nsArray)
+ NS_INTERFACE_MAP_ENTRY(nsIArray)
+ NS_INTERFACE_MAP_ENTRY(nsIArrayExtensions)
+ NS_INTERFACE_MAP_ENTRY(nsIMutableArray)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMutableArray)
+NS_INTERFACE_MAP_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsArrayCC)
+ NS_INTERFACE_MAP_ENTRY(nsIArray)
+ NS_INTERFACE_MAP_ENTRY(nsIArrayExtensions)
+ NS_INTERFACE_MAP_ENTRY(nsIMutableArray)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMutableArray)
+NS_INTERFACE_MAP_END
+
+nsArrayBase::~nsArrayBase() { Clear(); }
+
+NS_IMPL_ADDREF(nsArray)
+NS_IMPL_RELEASE(nsArray)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsArrayCC)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsArrayCC)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsArrayCC)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsArrayCC)
+ tmp->Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsArrayCC)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArray)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMETHODIMP
+nsArrayBase::GetLength(uint32_t* aLength) {
+ *aLength = mArray.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsArrayBase::QueryElementAt(uint32_t aIndex, const nsIID& aIID,
+ void** aResult) {
+ nsISupports* obj = mArray.SafeObjectAt(aIndex);
+ if (!obj) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // no need to worry about a leak here, because SafeObjectAt()
+ // doesn't addref its result
+ return obj->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsArrayBase::IndexOf(uint32_t aStartIndex, nsISupports* aElement,
+ uint32_t* aResult) {
+ int32_t idx = mArray.IndexOf(aElement, aStartIndex);
+ if (idx == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = static_cast<uint32_t>(idx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsArrayBase::ScriptedEnumerate(const nsIID& aElemIID, uint8_t aArgc,
+ nsISimpleEnumerator** aResult) {
+ if (aArgc > 0) {
+ return NS_NewArrayEnumerator(aResult, static_cast<nsIArray*>(this),
+ aElemIID);
+ }
+ return NS_NewArrayEnumerator(aResult, static_cast<nsIArray*>(this));
+}
+
+NS_IMETHODIMP
+nsArrayBase::EnumerateImpl(const nsID& aElemIID,
+ nsISimpleEnumerator** aResult) {
+ return NS_NewArrayEnumerator(aResult, static_cast<nsIArray*>(this), aElemIID);
+}
+
+// nsIMutableArray implementation
+
+NS_IMETHODIMP
+nsArrayBase::AppendElement(nsISupports* aElement) {
+ bool result = mArray.AppendObject(aElement);
+ return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsArrayBase::RemoveElementAt(uint32_t aIndex) {
+ bool result = mArray.RemoveObjectAt(aIndex);
+ return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsArrayBase::InsertElementAt(nsISupports* aElement, uint32_t aIndex) {
+ bool result = mArray.InsertObjectAt(aElement, aIndex);
+ return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsArrayBase::ReplaceElementAt(nsISupports* aElement, uint32_t aIndex) {
+ mArray.ReplaceObjectAt(aElement, aIndex);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsArrayBase::Clear() {
+ mArray.Clear();
+ return NS_OK;
+}
+
+// nsIArrayExtensions implementation.
+
+NS_IMETHODIMP
+nsArrayBase::Count(uint32_t* aResult) { return GetLength(aResult); }
+
+NS_IMETHODIMP
+nsArrayBase::GetElementAt(uint32_t aIndex, nsISupports** aResult) {
+ nsCOMPtr<nsISupports> obj = mArray.SafeObjectAt(aIndex);
+ obj.forget(aResult);
+ return NS_OK;
+}
+
+nsresult nsArrayBase::XPCOMConstructor(const nsIID& aIID, void** aResult) {
+ nsCOMPtr<nsIMutableArray> inst = Create();
+ return inst->QueryInterface(aIID, aResult);
+}
+
+already_AddRefed<nsIMutableArray> nsArrayBase::Create() {
+ nsCOMPtr<nsIMutableArray> inst;
+ if (NS_IsMainThread()) {
+ inst = new nsArrayCC;
+ } else {
+ inst = new nsArray;
+ }
+ return inst.forget();
+}
diff --git a/xpcom/ds/nsArray.h b/xpcom/ds/nsArray.h
new file mode 100644
index 0000000000..49d701d466
--- /dev/null
+++ b/xpcom/ds/nsArray.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsArray_h__
+#define nsArray_h__
+
+#include "nsIMutableArray.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+// {35C66FD1-95E9-4e0a-80C5-C3BD2B375481}
+#define NS_ARRAY_CID \
+ { \
+ 0x35c66fd1, 0x95e9, 0x4e0a, { \
+ 0x80, 0xc5, 0xc3, 0xbd, 0x2b, 0x37, 0x54, 0x81 \
+ } \
+ }
+
+// nsArray without any refcounting declarations
+class nsArrayBase : public nsIMutableArray {
+ public:
+ NS_DECL_NSIARRAY
+ NS_DECL_NSIARRAYEXTENSIONS
+ NS_DECL_NSIMUTABLEARRAY
+
+ /* Both of these factory functions create a cycle-collectable array
+ on the main thread and a non-cycle-collectable array on other
+ threads. */
+ static already_AddRefed<nsIMutableArray> Create();
+ /* Only for the benefit of the XPCOM module system, use Create()
+ instead. */
+ static nsresult XPCOMConstructor(const nsIID& aIID, void** aResult);
+
+ protected:
+ nsArrayBase() = default;
+ nsArrayBase(const nsArrayBase& aOther);
+ explicit nsArrayBase(const nsCOMArray_base& aBaseArray)
+ : mArray(aBaseArray) {}
+ virtual ~nsArrayBase();
+
+ nsCOMArray_base mArray;
+};
+
+class nsArray final : public nsArrayBase {
+ friend class nsArrayBase;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ private:
+ nsArray() : nsArrayBase() {}
+ nsArray(const nsArray& aOther);
+ explicit nsArray(const nsCOMArray_base& aBaseArray)
+ : nsArrayBase(aBaseArray) {}
+ ~nsArray() = default;
+};
+
+class nsArrayCC final : public nsArrayBase {
+ friend class nsArrayBase;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsArrayCC, nsIMutableArray)
+
+ private:
+ nsArrayCC() : nsArrayBase() {}
+ nsArrayCC(const nsArrayCC& aOther);
+ explicit nsArrayCC(const nsCOMArray_base& aBaseArray)
+ : nsArrayBase(aBaseArray) {}
+ ~nsArrayCC() = default;
+};
+
+#endif
diff --git a/xpcom/ds/nsArrayEnumerator.cpp b/xpcom/ds/nsArrayEnumerator.cpp
new file mode 100644
index 0000000000..ec475f9e5b
--- /dev/null
+++ b/xpcom/ds/nsArrayEnumerator.cpp
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+
+#include "nsArrayEnumerator.h"
+
+#include "nsIArray.h"
+#include "nsSimpleEnumerator.h"
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/RefPtr.h"
+
+class nsSimpleArrayEnumerator final : public nsSimpleEnumerator {
+ public:
+ // nsISimpleEnumerator interface
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsSimpleArrayEnumerator methods
+ explicit nsSimpleArrayEnumerator(nsIArray* aValueArray, const nsID& aEntryIID)
+ : mValueArray(aValueArray), mEntryIID(aEntryIID), mIndex(0) {}
+
+ const nsID& DefaultInterface() override { return mEntryIID; }
+
+ private:
+ ~nsSimpleArrayEnumerator() override = default;
+
+ protected:
+ nsCOMPtr<nsIArray> mValueArray;
+ const nsID mEntryIID;
+ uint32_t mIndex;
+};
+
+NS_IMETHODIMP
+nsSimpleArrayEnumerator::HasMoreElements(bool* aResult) {
+ MOZ_ASSERT(aResult != 0, "null ptr");
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!mValueArray) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ uint32_t cnt;
+ nsresult rv = mValueArray->GetLength(&cnt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aResult = (mIndex < cnt);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleArrayEnumerator::GetNext(nsISupports** aResult) {
+ MOZ_ASSERT(aResult != 0, "null ptr");
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!mValueArray) {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+
+ uint32_t cnt;
+ nsresult rv = mValueArray->GetLength(&cnt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (mIndex >= cnt) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return mValueArray->QueryElementAt(mIndex++, NS_GET_IID(nsISupports),
+ (void**)aResult);
+}
+
+nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, nsIArray* aArray,
+ const nsID& aEntryIID) {
+ RefPtr<nsSimpleArrayEnumerator> enumer =
+ new nsSimpleArrayEnumerator(aArray, aEntryIID);
+ enumer.forget(aResult);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// enumerator implementation for nsCOMArray
+// creates a snapshot of the array in question
+// you MUST use NS_NewArrayEnumerator to create this, so that
+// allocation is done correctly
+class nsCOMArrayEnumerator final : public nsSimpleEnumerator {
+ public:
+ // nsISimpleEnumerator interface
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // Use this instead of `new`.
+ static nsCOMArrayEnumerator* Allocate(const nsCOMArray_base& aArray,
+ const nsID& aEntryIID);
+
+ // specialized operator to make sure we make room for mValues
+ void operator delete(void* aPtr) { free(aPtr); }
+
+ const nsID& DefaultInterface() override { return mEntryIID; }
+
+ private:
+ // nsSimpleArrayEnumerator methods
+ explicit nsCOMArrayEnumerator(const nsID& aEntryIID)
+ : mIndex(0), mArraySize(0), mEntryIID(aEntryIID) {
+ mValueArray[0] = nullptr;
+ }
+
+ ~nsCOMArrayEnumerator(void) override;
+
+ protected:
+ uint32_t mIndex; // current position
+ uint32_t mArraySize; // size of the array
+
+ const nsID& mEntryIID;
+
+ // this is actually bigger
+ nsISupports* mValueArray[1];
+};
+
+nsCOMArrayEnumerator::~nsCOMArrayEnumerator() {
+ // only release the entries that we haven't visited yet
+ for (; mIndex < mArraySize; ++mIndex) {
+ NS_IF_RELEASE(mValueArray[mIndex]);
+ }
+}
+
+NS_IMETHODIMP
+nsCOMArrayEnumerator::HasMoreElements(bool* aResult) {
+ MOZ_ASSERT(aResult != 0, "null ptr");
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ *aResult = (mIndex < mArraySize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCOMArrayEnumerator::GetNext(nsISupports** aResult) {
+ MOZ_ASSERT(aResult != 0, "null ptr");
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (mIndex >= mArraySize) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // pass the ownership of the reference to the caller. Since
+ // we AddRef'ed during creation of |this|, there is no need
+ // to AddRef here
+ *aResult = mValueArray[mIndex++];
+
+ // this really isn't necessary. just pretend this happens, since
+ // we'll never visit this value again!
+ // mValueArray[(mIndex-1)] = nullptr;
+
+ return NS_OK;
+}
+
+nsCOMArrayEnumerator* nsCOMArrayEnumerator::Allocate(
+ const nsCOMArray_base& aArray, const nsID& aEntryIID) {
+ // create enough space such that mValueArray points to a large
+ // enough value. Note that the initial value of aSize gives us
+ // space for mValueArray[0], so we must subtract
+ size_t size = sizeof(nsCOMArrayEnumerator);
+ uint32_t count;
+ if (aArray.Count() > 0) {
+ count = static_cast<uint32_t>(aArray.Count());
+ size += (count - 1) * sizeof(aArray[0]);
+ } else {
+ count = 0;
+ }
+
+ // Allocate a buffer large enough to contain our object and its array.
+ void* mem = moz_xmalloc(size);
+ auto result =
+ new (mozilla::KnownNotNull, mem) nsCOMArrayEnumerator(aEntryIID);
+
+ result->mArraySize = count;
+
+ // now need to copy over the values, and addref each one
+ // now this might seem like a lot of work, but we're actually just
+ // doing all our AddRef's ahead of time since GetNext() doesn't
+ // need to AddRef() on the way out
+ for (uint32_t i = 0; i < count; ++i) {
+ result->mValueArray[i] = aArray[i];
+ NS_IF_ADDREF(result->mValueArray[i]);
+ }
+
+ return result;
+}
+
+nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult,
+ const nsCOMArray_base& aArray,
+ const nsID& aEntryIID) {
+ RefPtr<nsCOMArrayEnumerator> enumerator =
+ nsCOMArrayEnumerator::Allocate(aArray, aEntryIID);
+ enumerator.forget(aResult);
+ return NS_OK;
+}
diff --git a/xpcom/ds/nsArrayEnumerator.h b/xpcom/ds/nsArrayEnumerator.h
new file mode 100644
index 0000000000..a406a9ea66
--- /dev/null
+++ b/xpcom/ds/nsArrayEnumerator.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsArrayEnumerator_h__
+#define nsArrayEnumerator_h__
+
+// enumerator implementation for nsIArray
+
+#include "nsISupports.h"
+
+class nsISimpleEnumerator;
+class nsIArray;
+class nsCOMArray_base;
+
+// Create an enumerator for an existing nsIArray implementation
+// The enumerator holds an owning reference to the array.
+nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, nsIArray* aArray,
+ const nsID& aEntryIID = NS_GET_IID(nsISupports));
+
+// create an enumerator for an existing nsCOMArray<T> implementation
+// The enumerator will hold an owning reference to each ELEMENT in
+// the array. This means that the nsCOMArray<T> can safely go away
+// without its objects going away.
+nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult,
+ const nsCOMArray_base& aArray,
+ const nsID& aEntryIID = NS_GET_IID(nsISupports));
+
+#endif
diff --git a/xpcom/ds/nsArrayUtils.cpp b/xpcom/ds/nsArrayUtils.cpp
new file mode 100644
index 0000000000..e8ea8bd0f2
--- /dev/null
+++ b/xpcom/ds/nsArrayUtils.cpp
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsArrayUtils.h"
+
+//
+// do_QueryElementAt helper stuff
+//
+nsresult nsQueryArrayElementAt::operator()(const nsIID& aIID,
+ void** aResult) const {
+ nsresult status = mArray ? mArray->QueryElementAt(mIndex, aIID, aResult)
+ : NS_ERROR_NULL_POINTER;
+
+ if (mErrorPtr) {
+ *mErrorPtr = status;
+ }
+
+ return status;
+}
diff --git a/xpcom/ds/nsArrayUtils.h b/xpcom/ds/nsArrayUtils.h
new file mode 100644
index 0000000000..6adad119c7
--- /dev/null
+++ b/xpcom/ds/nsArrayUtils.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsArrayUtils_h__
+#define nsArrayUtils_h__
+
+#include "nsCOMPtr.h"
+#include "nsIArray.h"
+
+// helper class for do_QueryElementAt
+class MOZ_STACK_CLASS nsQueryArrayElementAt final : public nsCOMPtr_helper {
+ public:
+ nsQueryArrayElementAt(nsIArray* aArray, uint32_t aIndex, nsresult* aErrorPtr)
+ : mArray(aArray), mIndex(aIndex), mErrorPtr(aErrorPtr) {}
+
+ virtual nsresult NS_FASTCALL operator()(const nsIID& aIID,
+ void**) const override;
+
+ private:
+ nsIArray* MOZ_NON_OWNING_REF mArray;
+ uint32_t mIndex;
+ nsresult* mErrorPtr;
+};
+
+inline const nsQueryArrayElementAt do_QueryElementAt(nsIArray* aArray,
+ uint32_t aIndex,
+ nsresult* aErrorPtr = 0) {
+ return nsQueryArrayElementAt(aArray, aIndex, aErrorPtr);
+}
+
+#endif // nsArrayUtils_h__
diff --git a/xpcom/ds/nsAtom.h b/xpcom/ds/nsAtom.h
new file mode 100644
index 0000000000..0e45db208f
--- /dev/null
+++ b/xpcom/ds/nsAtom.h
@@ -0,0 +1,297 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAtom_h
+#define nsAtom_h
+
+#include <type_traits>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Char16.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsISupports.h"
+#include "nsString.h"
+
+namespace mozilla {
+struct AtomsSizes;
+} // namespace mozilla
+
+class nsStaticAtom;
+class nsDynamicAtom;
+
+// This class encompasses both static and dynamic atoms.
+//
+// - In places where static and dynamic atoms can be used, use RefPtr<nsAtom>.
+// This is by far the most common case.
+//
+// - In places where only static atoms can appear, use nsStaticAtom* to avoid
+// unnecessary refcounting. This is a moderately common case.
+//
+// - In places where only dynamic atoms can appear, it doesn't matter much
+// whether you use RefPtr<nsAtom> or RefPtr<nsDynamicAtom>. This is an
+// extremely rare case.
+//
+class nsAtom {
+ public:
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ mozilla::AtomsSizes& aSizes) const;
+
+ bool Equals(char16ptr_t aString, uint32_t aLength) const {
+ return mLength == aLength &&
+ memcmp(GetUTF16String(), aString, mLength * sizeof(char16_t)) == 0;
+ }
+
+ bool Equals(const nsAString& aString) const {
+ return Equals(aString.BeginReading(), aString.Length());
+ }
+
+ bool IsStatic() const { return mIsStatic; }
+ bool IsDynamic() const { return !IsStatic(); }
+
+ inline const nsStaticAtom* AsStatic() const;
+ inline const nsDynamicAtom* AsDynamic() const;
+ inline nsDynamicAtom* AsDynamic();
+
+ char16ptr_t GetUTF16String() const;
+
+ uint32_t GetLength() const { return mLength; }
+
+ operator mozilla::Span<const char16_t>() const {
+ // Explicitly specify template argument here to avoid instantiating
+ // Span<char16_t> first and then implicitly converting to Span<const
+ // char16_t>
+ return mozilla::Span<const char16_t>{GetUTF16String(), GetLength()};
+ }
+
+ void ToString(nsAString& aString) const;
+ void ToUTF8String(nsACString& aString) const;
+
+ // A hashcode that is better distributed than the actual atom pointer, for
+ // use in situations that need a well-distributed hashcode. It's called hash()
+ // rather than Hash() so we can use mozilla::BloomFilter<N, nsAtom>, because
+ // BloomFilter requires elements to implement a function called hash().
+ //
+ uint32_t hash() const { return mHash; }
+
+ // This function returns true if ToLowercaseASCII would return the string
+ // unchanged.
+ bool IsAsciiLowercase() const { return mIsAsciiLowercase; }
+
+ // This function returns true if this is the empty atom. This is exactly
+ // equivalent to `this == nsGkAtoms::_empty`, but it's a bit less foot-gunny,
+ // since we also have `nsGkAtoms::empty`.
+ //
+ // Defined in nsGkAtoms.h
+ inline bool IsEmpty() const;
+
+ // We can't use NS_INLINE_DECL_THREADSAFE_REFCOUNTING because the refcounting
+ // of this type is special.
+ inline MozExternalRefCountType AddRef();
+ inline MozExternalRefCountType Release();
+
+ using HasThreadSafeRefCnt = std::true_type;
+
+ protected:
+ // Used by nsStaticAtom.
+ constexpr nsAtom(uint32_t aLength, uint32_t aHash, bool aIsAsciiLowercase)
+ : mLength(aLength),
+ mIsStatic(true),
+ mIsAsciiLowercase(aIsAsciiLowercase),
+ mHash(aHash) {}
+
+ // Used by nsDynamicAtom.
+ nsAtom(const nsAString& aString, uint32_t aHash, bool aIsAsciiLowercase)
+ : mLength(aString.Length()),
+ mIsStatic(false),
+ mIsAsciiLowercase(aIsAsciiLowercase),
+ mHash(aHash) {}
+
+ ~nsAtom() = default;
+
+ const uint32_t mLength : 30;
+ const uint32_t mIsStatic : 1;
+ const uint32_t mIsAsciiLowercase : 1;
+ const uint32_t mHash;
+};
+
+// This class would be |final| if it wasn't for nsCSSAnonBoxPseudoStaticAtom
+// and nsCSSPseudoElementStaticAtom, which are trivial subclasses used to
+// ensure only certain static atoms are passed to certain functions.
+class nsStaticAtom : public nsAtom {
+ public:
+ // These are deleted so it's impossible to RefPtr<nsStaticAtom>. Raw
+ // nsStaticAtom pointers should be used instead.
+ MozExternalRefCountType AddRef() = delete;
+ MozExternalRefCountType Release() = delete;
+
+ // The static atom's precomputed hash value is an argument here, but it
+ // must be the same as would be computed by mozilla::HashString(aStr),
+ // which is what we use when atomizing strings. We compute this hash in
+ // Atom.py and assert in nsAtomTable::RegisterStaticAtoms that the two
+ // hashes match.
+ constexpr nsStaticAtom(uint32_t aLength, uint32_t aHash,
+ uint32_t aStringOffset, bool aIsAsciiLowercase)
+ : nsAtom(aLength, aHash, aIsAsciiLowercase),
+ mStringOffset(aStringOffset) {}
+
+ const char16_t* String() const {
+ return reinterpret_cast<const char16_t*>(uintptr_t(this) - mStringOffset);
+ }
+
+ already_AddRefed<nsAtom> ToAddRefed() {
+ return already_AddRefed<nsAtom>(static_cast<nsAtom*>(this));
+ }
+
+ private:
+ // This is an offset to the string chars, which must be at a lower address in
+ // memory.
+ uint32_t mStringOffset;
+};
+
+class nsDynamicAtom : public nsAtom {
+ public:
+ // We can't use NS_INLINE_DECL_THREADSAFE_REFCOUNTING because the refcounting
+ // of this type is special.
+ MozExternalRefCountType AddRef() {
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
+ nsrefcnt count = ++mRefCnt;
+ if (count == 1) {
+ gUnusedAtomCount--;
+ }
+ return count;
+ }
+
+ MozExternalRefCountType Release() {
+#ifdef DEBUG
+ // We set a lower GC threshold for atoms in debug builds so that we exercise
+ // the GC machinery more often.
+ static const int32_t kAtomGCThreshold = 20;
+#else
+ static const int32_t kAtomGCThreshold = 10000;
+#endif
+
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+ nsrefcnt count = --mRefCnt;
+ if (count == 0) {
+ if (++gUnusedAtomCount >= kAtomGCThreshold) {
+ GCAtomTable();
+ }
+ }
+
+ return count;
+ }
+
+ const char16_t* String() const {
+ return reinterpret_cast<const char16_t*>(this + 1);
+ }
+
+ static nsDynamicAtom* FromChars(char16_t* chars) {
+ return reinterpret_cast<nsDynamicAtom*>(chars) - 1;
+ }
+
+ private:
+ friend class nsAtomTable;
+ friend class nsAtomSubTable;
+ friend int32_t NS_GetUnusedAtomCount();
+
+ static mozilla::Atomic<int32_t, mozilla::ReleaseAcquire> gUnusedAtomCount;
+ static void GCAtomTable();
+
+ // These shouldn't be used directly, even by friend classes. The
+ // Create()/Destroy() methods use them.
+ nsDynamicAtom(const nsAString& aString, uint32_t aHash,
+ bool aIsAsciiLowercase);
+ ~nsDynamicAtom() = default;
+
+ static nsDynamicAtom* Create(const nsAString& aString, uint32_t aHash);
+ static void Destroy(nsDynamicAtom* aAtom);
+
+ mozilla::ThreadSafeAutoRefCnt mRefCnt;
+
+ // The atom's chars are stored at the end of the struct.
+};
+
+const nsStaticAtom* nsAtom::AsStatic() const {
+ MOZ_ASSERT(IsStatic());
+ return static_cast<const nsStaticAtom*>(this);
+}
+
+const nsDynamicAtom* nsAtom::AsDynamic() const {
+ MOZ_ASSERT(IsDynamic());
+ return static_cast<const nsDynamicAtom*>(this);
+}
+
+nsDynamicAtom* nsAtom::AsDynamic() {
+ MOZ_ASSERT(IsDynamic());
+ return static_cast<nsDynamicAtom*>(this);
+}
+
+MozExternalRefCountType nsAtom::AddRef() {
+ return IsStatic() ? 2 : AsDynamic()->AddRef();
+}
+
+MozExternalRefCountType nsAtom::Release() {
+ return IsStatic() ? 1 : AsDynamic()->Release();
+}
+
+// The four forms of NS_Atomize (for use with |RefPtr<nsAtom>|) return the
+// atom for the string given. At any given time there will always be one atom
+// representing a given string. Atoms are intended to make string comparison
+// cheaper by simplifying it to pointer equality. A pointer to the atom that
+// does not own a reference is not guaranteed to be valid.
+
+// Find an atom that matches the given UTF-8 string. The string is assumed to
+// be zero terminated. Never returns null.
+already_AddRefed<nsAtom> NS_Atomize(const char* aUTF8String);
+
+// Find an atom that matches the given UTF-8 string. Never returns null.
+already_AddRefed<nsAtom> NS_Atomize(const nsACString& aUTF8String);
+
+// Find an atom that matches the given UTF-16 string. The string is assumed to
+// be zero terminated. Never returns null.
+already_AddRefed<nsAtom> NS_Atomize(const char16_t* aUTF16String);
+
+// Find an atom that matches the given UTF-16 string. Never returns null.
+already_AddRefed<nsAtom> NS_Atomize(const nsAString& aUTF16String);
+
+// An optimized version of the method above for the main thread.
+already_AddRefed<nsAtom> NS_AtomizeMainThread(const nsAString& aUTF16String);
+
+// Return a count of the total number of atoms currently alive in the system.
+//
+// Note that the result is imprecise and racy if other threads are currently
+// operating on atoms. It's also slow, since it triggers a GC before counting.
+// Currently this function is only used in tests, which should probably remain
+// the case.
+nsrefcnt NS_GetNumberOfAtoms();
+
+// Return a pointer for a static atom for the string or null if there's no
+// static atom for this string.
+nsStaticAtom* NS_GetStaticAtom(const nsAString& aUTF16String);
+
+class nsAtomString : public nsString {
+ public:
+ explicit nsAtomString(const nsAtom* aAtom) { aAtom->ToString(*this); }
+};
+
+class nsAtomCString : public nsCString {
+ public:
+ explicit nsAtomCString(const nsAtom* aAtom) { aAtom->ToUTF8String(*this); }
+};
+
+class nsDependentAtomString : public nsDependentString {
+ public:
+ explicit nsDependentAtomString(const nsAtom* aAtom)
+ : nsDependentString(aAtom->GetUTF16String(), aAtom->GetLength()) {}
+};
+
+// Checks if the ascii chars in a given atom are already lowercase.
+// If they are, no-op. Otherwise, converts all the ascii uppercase
+// chars to lowercase and atomizes, storing the result in the inout
+// param.
+void ToLowerCaseASCII(RefPtr<nsAtom>& aAtom);
+
+#endif // nsAtom_h
diff --git a/xpcom/ds/nsAtomTable.cpp b/xpcom/ds/nsAtomTable.cpp
new file mode 100644
index 0000000000..56618cd46c
--- /dev/null
+++ b/xpcom/ds/nsAtomTable.cpp
@@ -0,0 +1,670 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/MruCache.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Unused.h"
+
+#include "nsAtom.h"
+#include "nsAtomTable.h"
+#include "nsCRT.h"
+#include "nsGkAtoms.h"
+#include "nsHashKeys.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsUnicharUtils.h"
+#include "PLDHashTable.h"
+#include "prenv.h"
+
+// There are two kinds of atoms handled by this module.
+//
+// - Dynamic: the atom itself is heap allocated, as is the char buffer it
+// points to. |gAtomTable| holds weak references to dynamic atoms. When the
+// refcount of a dynamic atom drops to zero, we increment a static counter.
+// When that counter reaches a certain threshold, we iterate over the atom
+// table, removing and deleting dynamic atoms with refcount zero. This allows
+// us to avoid acquiring the atom table lock during normal refcounting.
+//
+// - Static: both the atom and its chars are statically allocated and
+// immutable, so it ignores all AddRef/Release calls.
+//
+// Note that gAtomTable is used on multiple threads, and has internal
+// synchronization.
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------
+
+enum class GCKind {
+ RegularOperation,
+ Shutdown,
+};
+
+//----------------------------------------------------------------------
+
+// gUnusedAtomCount is incremented when an atom loses its last reference
+// (and thus turned into unused state), and decremented when an unused
+// atom gets a reference again. The atom table relies on this value to
+// schedule GC. This value can temporarily go below zero when multiple
+// threads are operating the same atom, so it has to be signed so that
+// we wouldn't use overflow value for comparison.
+// See nsAtom::AddRef() and nsAtom::Release().
+// This atomic can be accessed during the GC and other places where recorded
+// events are not allowed, so its value is not preserved when recording or
+// replaying.
+Atomic<int32_t, ReleaseAcquire> nsDynamicAtom::gUnusedAtomCount;
+
+nsDynamicAtom::nsDynamicAtom(const nsAString& aString, uint32_t aHash,
+ bool aIsAsciiLowercase)
+ : nsAtom(aString, aHash, aIsAsciiLowercase), mRefCnt(1) {}
+
+// Returns true if ToLowercaseASCII would return the string unchanged.
+static bool IsAsciiLowercase(const char16_t* aString, const uint32_t aLength) {
+ for (uint32_t i = 0; i < aLength; ++i) {
+ if (IS_ASCII_UPPER(aString[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsDynamicAtom* nsDynamicAtom::Create(const nsAString& aString, uint32_t aHash) {
+ // We tack the chars onto the end of the nsDynamicAtom object.
+ size_t numCharBytes = (aString.Length() + 1) * sizeof(char16_t);
+ size_t numTotalBytes = sizeof(nsDynamicAtom) + numCharBytes;
+
+ bool isAsciiLower = ::IsAsciiLowercase(aString.Data(), aString.Length());
+
+ nsDynamicAtom* atom = (nsDynamicAtom*)moz_xmalloc(numTotalBytes);
+ new (atom) nsDynamicAtom(aString, aHash, isAsciiLower);
+ memcpy(const_cast<char16_t*>(atom->String()),
+ PromiseFlatString(aString).get(), numCharBytes);
+
+ MOZ_ASSERT(atom->String()[atom->GetLength()] == char16_t(0));
+ MOZ_ASSERT(atom->Equals(aString));
+ MOZ_ASSERT(atom->mHash == HashString(atom->String(), atom->GetLength()));
+ MOZ_ASSERT(atom->mIsAsciiLowercase == isAsciiLower);
+
+ return atom;
+}
+
+void nsDynamicAtom::Destroy(nsDynamicAtom* aAtom) {
+ aAtom->~nsDynamicAtom();
+ free(aAtom);
+}
+
+void nsAtom::ToString(nsAString& aString) const {
+ // See the comment on |mString|'s declaration.
+ if (IsStatic()) {
+ // AssignLiteral() lets us assign without copying. This isn't a string
+ // literal, but it's a static atom and thus has an unbounded lifetime,
+ // which is what's important.
+ aString.AssignLiteral(AsStatic()->String(), mLength);
+ } else {
+ aString.Assign(AsDynamic()->String(), mLength);
+ }
+}
+
+void nsAtom::ToUTF8String(nsACString& aBuf) const {
+ CopyUTF16toUTF8(nsDependentString(GetUTF16String(), mLength), aBuf);
+}
+
+void nsAtom::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ AtomsSizes& aSizes) const {
+ // Static atoms are in static memory, and so are not measured here.
+ if (IsDynamic()) {
+ aSizes.mDynamicAtoms += aMallocSizeOf(this);
+ }
+}
+
+char16ptr_t nsAtom::GetUTF16String() const {
+ return IsStatic() ? AsStatic()->String() : AsDynamic()->String();
+}
+
+//----------------------------------------------------------------------
+
+struct AtomTableKey {
+ explicit AtomTableKey(const nsStaticAtom* aAtom)
+ : mUTF16String(aAtom->String()),
+ mUTF8String(nullptr),
+ mLength(aAtom->GetLength()),
+ mHash(aAtom->hash()) {
+ MOZ_ASSERT(HashString(mUTF16String, mLength) == mHash);
+ }
+
+ AtomTableKey(const char16_t* aUTF16String, uint32_t aLength)
+ : mUTF16String(aUTF16String), mUTF8String(nullptr), mLength(aLength) {
+ mHash = HashString(mUTF16String, mLength);
+ }
+
+ AtomTableKey(const char* aUTF8String, uint32_t aLength, bool* aErr)
+ : mUTF16String(nullptr), mUTF8String(aUTF8String), mLength(aLength) {
+ mHash = HashUTF8AsUTF16(mUTF8String, mLength, aErr);
+ }
+
+ const char16_t* mUTF16String;
+ const char* mUTF8String;
+ uint32_t mLength;
+ uint32_t mHash;
+};
+
+struct AtomTableEntry : public PLDHashEntryHdr {
+ // These references are either to dynamic atoms, in which case they are
+ // non-owning, or they are to static atoms, which aren't really refcounted.
+ // See the comment at the top of this file for more details.
+ nsAtom* MOZ_NON_OWNING_REF mAtom;
+};
+
+struct AtomCache : public MruCache<AtomTableKey, nsAtom*, AtomCache> {
+ static HashNumber Hash(const AtomTableKey& aKey) { return aKey.mHash; }
+ static bool Match(const AtomTableKey& aKey, const nsAtom* aVal) {
+ MOZ_ASSERT(aKey.mUTF16String);
+ return aVal->Equals(aKey.mUTF16String, aKey.mLength);
+ }
+};
+
+static AtomCache sRecentlyUsedMainThreadAtoms;
+
+// In order to reduce locking contention for concurrent atomization, we segment
+// the atom table into N subtables, each with a separate lock. If the hash
+// values we use to select the subtable are evenly distributed, this reduces the
+// probability of contention by a factor of N. See bug 1440824.
+//
+// NB: This is somewhat similar to the technique used by Java's
+// ConcurrentHashTable.
+class nsAtomSubTable {
+ friend class nsAtomTable;
+ Mutex mLock;
+ PLDHashTable mTable;
+ nsAtomSubTable();
+ void GCLocked(GCKind aKind) MOZ_REQUIRES(mLock);
+ void AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf,
+ AtomsSizes& aSizes) MOZ_REQUIRES(mLock);
+
+ AtomTableEntry* Search(AtomTableKey& aKey) const MOZ_REQUIRES(mLock) {
+ mLock.AssertCurrentThreadOwns();
+ return static_cast<AtomTableEntry*>(mTable.Search(&aKey));
+ }
+
+ AtomTableEntry* Add(AtomTableKey& aKey) MOZ_REQUIRES(mLock) {
+ mLock.AssertCurrentThreadOwns();
+ return static_cast<AtomTableEntry*>(mTable.Add(&aKey)); // Infallible
+ }
+};
+
+// The outer atom table, which coordinates access to the inner array of
+// subtables.
+class nsAtomTable {
+ public:
+ nsAtomSubTable& SelectSubTable(AtomTableKey& aKey);
+ void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes);
+ void GC(GCKind aKind);
+ already_AddRefed<nsAtom> Atomize(const nsAString& aUTF16String);
+ already_AddRefed<nsAtom> Atomize(const nsACString& aUTF8String);
+ already_AddRefed<nsAtom> AtomizeMainThread(const nsAString& aUTF16String);
+ nsStaticAtom* GetStaticAtom(const nsAString& aUTF16String);
+ void RegisterStaticAtoms(const nsStaticAtom* aAtoms, size_t aAtomsLen);
+
+ // The result of this function may be imprecise if other threads are operating
+ // on atoms concurrently. It's also slow, since it triggers a GC before
+ // counting.
+ size_t RacySlowCount();
+
+ // This hash table op is a static member of this class so that it can take
+ // advantage of |friend| declarations.
+ static void AtomTableClearEntry(PLDHashTable* aTable,
+ PLDHashEntryHdr* aEntry);
+
+ // We achieve measurable reduction in locking contention in parallel CSS
+ // parsing by increasing the number of subtables up to 128. This has been
+ // measured to have neglible impact on the performance of initialization, GC,
+ // and shutdown.
+ //
+ // Another important consideration is memory, since we're adding fixed
+ // overhead per content process, which we try to avoid. Measuring a
+ // mostly-empty page [1] with various numbers of subtables, we get the
+ // following deep sizes for the atom table:
+ // 1 subtable: 278K
+ // 8 subtables: 279K
+ // 16 subtables: 282K
+ // 64 subtables: 286K
+ // 128 subtables: 290K
+ //
+ // So 128 subtables costs us 12K relative to a single table, and 4K relative
+ // to 64 subtables. Conversely, measuring parallel (6 thread) CSS parsing on
+ // tp6-facebook, a single table provides ~150ms of locking overhead per
+ // thread, 64 subtables provides ~2-3ms of overhead, and 128 subtables
+ // provides <1ms. And so while either 64 or 128 subtables would probably be
+ // acceptable, achieving a measurable reduction in contention for 4k of fixed
+ // memory overhead is probably worth it.
+ //
+ // [1] The numbers will look different for content processes with complex
+ // pages loaded, but in those cases the actual atoms will dominate memory
+ // usage and the overhead of extra tables will be negligible. We're mostly
+ // interested in the fixed cost for nearly-empty content processes.
+ const static size_t kNumSubTables = 128; // Must be power of two.
+
+ private:
+ nsAtomSubTable mSubTables[kNumSubTables];
+};
+
+// Static singleton instance for the atom table.
+static nsAtomTable* gAtomTable;
+
+static PLDHashNumber AtomTableGetHash(const void* aKey) {
+ const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey);
+ return k->mHash;
+}
+
+static bool AtomTableMatchKey(const PLDHashEntryHdr* aEntry, const void* aKey) {
+ const AtomTableEntry* he = static_cast<const AtomTableEntry*>(aEntry);
+ const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey);
+
+ if (k->mUTF8String) {
+ bool err = false;
+ return (CompareUTF8toUTF16(nsDependentCSubstring(
+ k->mUTF8String, k->mUTF8String + k->mLength),
+ nsDependentAtomString(he->mAtom), &err) == 0) &&
+ !err;
+ }
+
+ return he->mAtom->Equals(k->mUTF16String, k->mLength);
+}
+
+void nsAtomTable::AtomTableClearEntry(PLDHashTable* aTable,
+ PLDHashEntryHdr* aEntry) {
+ auto entry = static_cast<AtomTableEntry*>(aEntry);
+ entry->mAtom = nullptr;
+}
+
+static void AtomTableInitEntry(PLDHashEntryHdr* aEntry, const void* aKey) {
+ static_cast<AtomTableEntry*>(aEntry)->mAtom = nullptr;
+}
+
+static const PLDHashTableOps AtomTableOps = {
+ AtomTableGetHash, AtomTableMatchKey, PLDHashTable::MoveEntryStub,
+ nsAtomTable::AtomTableClearEntry, AtomTableInitEntry};
+
+// The atom table very quickly gets 10,000+ entries in it (or even 100,000+).
+// But choosing the best initial subtable length has some subtleties: we add
+// ~2700 static atoms at start-up, and then we start adding and removing
+// dynamic atoms. If we make the tables too big to start with, when the first
+// dynamic atom gets removed from a given table the load factor will be < 25%
+// and we will shrink it.
+//
+// So we first make the simplifying assumption that the atoms are more or less
+// evenly-distributed across the subtables (which is the case empirically).
+// Then, we take the total atom count when the first dynamic atom is removed
+// (~2700), divide that across the N subtables, and the largest capacity that
+// will allow each subtable to be > 25% full with that count.
+//
+// So want an initial subtable capacity less than (2700 / N) * 4 = 10800 / N.
+// Rounding down to the nearest power of two gives us 8192 / N. Since the
+// capacity is double the initial length, we end up with (4096 / N) per
+// subtable.
+#define INITIAL_SUBTABLE_LENGTH (4096 / nsAtomTable::kNumSubTables)
+
+nsAtomSubTable& nsAtomTable::SelectSubTable(AtomTableKey& aKey) {
+ // There are a few considerations around how we select subtables.
+ //
+ // First, we want entries to be evenly distributed across the subtables. This
+ // can be achieved by using any bits in the hash key, assuming the key itself
+ // is evenly-distributed. Empirical measurements indicate that this method
+ // produces a roughly-even distribution across subtables.
+ //
+ // Second, we want to use the hash bits that are least likely to influence an
+ // entry's position within the subtable. If we used the exact same bits used
+ // by the subtables, then each subtable would compute the same position for
+ // every entry it observes, leading to pessimal performance. In this case,
+ // we're using PLDHashTable, whose primary hash function uses the N leftmost
+ // bits of the hash value (where N is the log2 capacity of the table). This
+ // means we should prefer the rightmost bits here.
+ //
+ // Note that the below is equivalent to mHash % kNumSubTables, a replacement
+ // which an optimizing compiler should make, but let's avoid any doubt.
+ static_assert((kNumSubTables & (kNumSubTables - 1)) == 0,
+ "must be power of two");
+ return mSubTables[aKey.mHash & (kNumSubTables - 1)];
+}
+
+void nsAtomTable::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ AtomsSizes& aSizes) {
+ MOZ_ASSERT(NS_IsMainThread());
+ aSizes.mTable += aMallocSizeOf(this);
+ for (auto& table : mSubTables) {
+ MutexAutoLock lock(table.mLock);
+ table.AddSizeOfExcludingThisLocked(aMallocSizeOf, aSizes);
+ }
+}
+
+void nsAtomTable::GC(GCKind aKind) {
+ MOZ_ASSERT(NS_IsMainThread());
+ sRecentlyUsedMainThreadAtoms.Clear();
+
+ // Note that this is effectively an incremental GC, since only one subtable
+ // is locked at a time.
+ for (auto& table : mSubTables) {
+ MutexAutoLock lock(table.mLock);
+ table.GCLocked(aKind);
+ }
+
+ // We would like to assert that gUnusedAtomCount matches the number of atoms
+ // we found in the table which we removed. However, there are two problems
+ // with this:
+ // * We have multiple subtables, each with their own lock. For optimal
+ // performance we only want to hold one lock at a time, but this means
+ // that atoms can be added and removed between GC slices.
+ // * Even if we held all the locks and performed all GC slices atomically,
+ // the locks are not acquired for AddRef() and Release() calls. This means
+ // we might see a gUnusedAtomCount value in between, say, AddRef()
+ // incrementing mRefCnt and it decrementing gUnusedAtomCount.
+ //
+ // So, we don't bother asserting that there are no unused atoms at the end of
+ // a regular GC. But we can (and do) assert this just after the last GC at
+ // shutdown.
+ //
+ // Note that, barring refcounting bugs, an atom can only go from a zero
+ // refcount to a non-zero refcount while the atom table lock is held, so
+ // so we won't try to resurrect a zero refcount atom while trying to delete
+ // it.
+
+ MOZ_ASSERT_IF(aKind == GCKind::Shutdown,
+ nsDynamicAtom::gUnusedAtomCount == 0);
+}
+
+size_t nsAtomTable::RacySlowCount() {
+ // Trigger a GC so that the result is deterministic modulo other threads.
+ GC(GCKind::RegularOperation);
+ size_t count = 0;
+ for (auto& table : mSubTables) {
+ MutexAutoLock lock(table.mLock);
+ count += table.mTable.EntryCount();
+ }
+
+ return count;
+}
+
+nsAtomSubTable::nsAtomSubTable()
+ : mLock("Atom Sub-Table Lock"),
+ mTable(&AtomTableOps, sizeof(AtomTableEntry), INITIAL_SUBTABLE_LENGTH) {}
+
+void nsAtomSubTable::GCLocked(GCKind aKind) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mLock.AssertCurrentThreadOwns();
+
+ int32_t removedCount = 0; // A non-atomic temporary for cheaper increments.
+ nsAutoCString nonZeroRefcountAtoms;
+ uint32_t nonZeroRefcountAtomsCount = 0;
+ for (auto i = mTable.Iter(); !i.Done(); i.Next()) {
+ auto entry = static_cast<AtomTableEntry*>(i.Get());
+ if (entry->mAtom->IsStatic()) {
+ continue;
+ }
+
+ nsAtom* atom = entry->mAtom;
+ if (atom->IsDynamic() && atom->AsDynamic()->mRefCnt == 0) {
+ i.Remove();
+ nsDynamicAtom::Destroy(atom->AsDynamic());
+ ++removedCount;
+ }
+#ifdef NS_FREE_PERMANENT_DATA
+ else if (aKind == GCKind::Shutdown && PR_GetEnv("XPCOM_MEM_BLOAT_LOG")) {
+ // Only report leaking atoms in leak-checking builds in a run where we
+ // are checking for leaks, during shutdown. If something is anomalous,
+ // then we'll assert later in this function.
+ nsAutoCString name;
+ atom->ToUTF8String(name);
+ if (nonZeroRefcountAtomsCount == 0) {
+ nonZeroRefcountAtoms = name;
+ } else if (nonZeroRefcountAtomsCount < 20) {
+ nonZeroRefcountAtoms += ","_ns + name;
+ } else if (nonZeroRefcountAtomsCount == 20) {
+ nonZeroRefcountAtoms += ",..."_ns;
+ }
+ nonZeroRefcountAtomsCount++;
+ }
+#endif
+ }
+ if (nonZeroRefcountAtomsCount) {
+ nsPrintfCString msg("%d dynamic atom(s) with non-zero refcount: %s",
+ nonZeroRefcountAtomsCount, nonZeroRefcountAtoms.get());
+ NS_ASSERTION(nonZeroRefcountAtomsCount == 0, msg.get());
+ }
+
+ nsDynamicAtom::gUnusedAtomCount -= removedCount;
+}
+
+void nsDynamicAtom::GCAtomTable() {
+ MOZ_ASSERT(gAtomTable);
+ if (NS_IsMainThread()) {
+ gAtomTable->GC(GCKind::RegularOperation);
+ }
+}
+
+//----------------------------------------------------------------------
+
+// Have the static atoms been inserted into the table?
+static bool gStaticAtomsDone = false;
+
+void NS_InitAtomTable() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!gAtomTable);
+
+ // We register static atoms immediately so they're available for use as early
+ // as possible.
+ gAtomTable = new nsAtomTable();
+ gAtomTable->RegisterStaticAtoms(nsGkAtoms::sAtoms, nsGkAtoms::sAtomsLen);
+ gStaticAtomsDone = true;
+}
+
+void NS_ShutdownAtomTable() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(gAtomTable);
+
+#ifdef NS_FREE_PERMANENT_DATA
+ // Do a final GC to satisfy leak checking. We skip this step in release
+ // builds.
+ gAtomTable->GC(GCKind::Shutdown);
+#endif
+
+ delete gAtomTable;
+ gAtomTable = nullptr;
+}
+
+void NS_AddSizeOfAtoms(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(gAtomTable);
+ return gAtomTable->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
+}
+
+void nsAtomSubTable::AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf,
+ AtomsSizes& aSizes) {
+ mLock.AssertCurrentThreadOwns();
+ aSizes.mTable += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<AtomTableEntry*>(iter.Get());
+ entry->mAtom->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
+ }
+}
+
+void nsAtomTable::RegisterStaticAtoms(const nsStaticAtom* aAtoms,
+ size_t aAtomsLen) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(!gStaticAtomsDone, "Static atom insertion is finished!");
+
+ for (uint32_t i = 0; i < aAtomsLen; ++i) {
+ const nsStaticAtom* atom = &aAtoms[i];
+ MOZ_ASSERT(IsAsciiNullTerminated(atom->String()));
+ MOZ_ASSERT(NS_strlen(atom->String()) == atom->GetLength());
+ MOZ_ASSERT(atom->IsAsciiLowercase() ==
+ ::IsAsciiLowercase(atom->String(), atom->GetLength()));
+
+ // This assertion ensures the static atom's precomputed hash value matches
+ // what would be computed by mozilla::HashString(aStr), which is what we use
+ // when atomizing strings. We compute this hash in Atom.py.
+ MOZ_ASSERT(HashString(atom->String()) == atom->hash());
+
+ AtomTableKey key(atom);
+ nsAtomSubTable& table = SelectSubTable(key);
+ MutexAutoLock lock(table.mLock);
+ AtomTableEntry* he = table.Add(key);
+
+ if (he->mAtom) {
+ // There are two ways we could get here.
+ // - Register two static atoms with the same string.
+ // - Create a dynamic atom and then register a static atom with the same
+ // string while the dynamic atom is alive.
+ // Both cases can cause subtle bugs, and are disallowed. We're
+ // programming in C++ here, not Smalltalk.
+ nsAutoCString name;
+ he->mAtom->ToUTF8String(name);
+ MOZ_CRASH_UNSAFE_PRINTF("Atom for '%s' already exists", name.get());
+ }
+ he->mAtom = const_cast<nsStaticAtom*>(atom);
+ }
+}
+
+already_AddRefed<nsAtom> NS_Atomize(const char* aUTF8String) {
+ MOZ_ASSERT(gAtomTable);
+ return gAtomTable->Atomize(nsDependentCString(aUTF8String));
+}
+
+already_AddRefed<nsAtom> nsAtomTable::Atomize(const nsACString& aUTF8String) {
+ bool err;
+ AtomTableKey key(aUTF8String.Data(), aUTF8String.Length(), &err);
+ if (MOZ_UNLIKELY(err)) {
+ MOZ_ASSERT_UNREACHABLE("Tried to atomize invalid UTF-8.");
+ // The input was invalid UTF-8. Let's replace the errors with U+FFFD
+ // and atomize the result.
+ nsString str;
+ CopyUTF8toUTF16(aUTF8String, str);
+ return Atomize(str);
+ }
+ nsAtomSubTable& table = SelectSubTable(key);
+ MutexAutoLock lock(table.mLock);
+ AtomTableEntry* he = table.Add(key);
+
+ if (he->mAtom) {
+ RefPtr<nsAtom> atom = he->mAtom;
+ return atom.forget();
+ }
+
+ nsString str;
+ CopyUTF8toUTF16(aUTF8String, str);
+ RefPtr<nsAtom> atom = dont_AddRef(nsDynamicAtom::Create(str, key.mHash));
+
+ he->mAtom = atom;
+
+ return atom.forget();
+}
+
+already_AddRefed<nsAtom> NS_Atomize(const nsACString& aUTF8String) {
+ MOZ_ASSERT(gAtomTable);
+ return gAtomTable->Atomize(aUTF8String);
+}
+
+already_AddRefed<nsAtom> NS_Atomize(const char16_t* aUTF16String) {
+ MOZ_ASSERT(gAtomTable);
+ return gAtomTable->Atomize(nsDependentString(aUTF16String));
+}
+
+already_AddRefed<nsAtom> nsAtomTable::Atomize(const nsAString& aUTF16String) {
+ AtomTableKey key(aUTF16String.Data(), aUTF16String.Length());
+ nsAtomSubTable& table = SelectSubTable(key);
+ MutexAutoLock lock(table.mLock);
+ AtomTableEntry* he = table.Add(key);
+
+ if (he->mAtom) {
+ RefPtr<nsAtom> atom = he->mAtom;
+ return atom.forget();
+ }
+
+ RefPtr<nsAtom> atom =
+ dont_AddRef(nsDynamicAtom::Create(aUTF16String, key.mHash));
+ he->mAtom = atom;
+
+ return atom.forget();
+}
+
+already_AddRefed<nsAtom> NS_Atomize(const nsAString& aUTF16String) {
+ MOZ_ASSERT(gAtomTable);
+ return gAtomTable->Atomize(aUTF16String);
+}
+
+already_AddRefed<nsAtom> nsAtomTable::AtomizeMainThread(
+ const nsAString& aUTF16String) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<nsAtom> retVal;
+ AtomTableKey key(aUTF16String.Data(), aUTF16String.Length());
+ auto p = sRecentlyUsedMainThreadAtoms.Lookup(key);
+ if (p) {
+ retVal = p.Data();
+ return retVal.forget();
+ }
+
+ nsAtomSubTable& table = SelectSubTable(key);
+ MutexAutoLock lock(table.mLock);
+ AtomTableEntry* he = table.Add(key);
+
+ if (he->mAtom) {
+ retVal = he->mAtom;
+ } else {
+ RefPtr<nsAtom> newAtom =
+ dont_AddRef(nsDynamicAtom::Create(aUTF16String, key.mHash));
+ he->mAtom = newAtom;
+ retVal = std::move(newAtom);
+ }
+
+ p.Set(retVal);
+ return retVal.forget();
+}
+
+already_AddRefed<nsAtom> NS_AtomizeMainThread(const nsAString& aUTF16String) {
+ MOZ_ASSERT(gAtomTable);
+ return gAtomTable->AtomizeMainThread(aUTF16String);
+}
+
+nsrefcnt NS_GetNumberOfAtoms(void) {
+ MOZ_ASSERT(gAtomTable);
+ return gAtomTable->RacySlowCount();
+}
+
+int32_t NS_GetUnusedAtomCount(void) { return nsDynamicAtom::gUnusedAtomCount; }
+
+nsStaticAtom* NS_GetStaticAtom(const nsAString& aUTF16String) {
+ MOZ_ASSERT(gStaticAtomsDone, "Static atom setup not yet done.");
+ MOZ_ASSERT(gAtomTable);
+ return gAtomTable->GetStaticAtom(aUTF16String);
+}
+
+nsStaticAtom* nsAtomTable::GetStaticAtom(const nsAString& aUTF16String) {
+ AtomTableKey key(aUTF16String.Data(), aUTF16String.Length());
+ nsAtomSubTable& table = SelectSubTable(key);
+ MutexAutoLock lock(table.mLock);
+ AtomTableEntry* he = table.Search(key);
+ return he && he->mAtom->IsStatic() ? static_cast<nsStaticAtom*>(he->mAtom)
+ : nullptr;
+}
+
+void ToLowerCaseASCII(RefPtr<nsAtom>& aAtom) {
+ // Assume the common case is that the atom is already ASCII lowercase.
+ if (aAtom->IsAsciiLowercase()) {
+ return;
+ }
+
+ nsAutoString lowercased;
+ ToLowerCaseASCII(nsDependentAtomString(aAtom), lowercased);
+ aAtom = NS_Atomize(lowercased);
+}
diff --git a/xpcom/ds/nsAtomTable.h b/xpcom/ds/nsAtomTable.h
new file mode 100644
index 0000000000..ac69daea9f
--- /dev/null
+++ b/xpcom/ds/nsAtomTable.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAtomTable_h__
+#define nsAtomTable_h__
+
+#include "mozilla/MemoryReporting.h"
+#include "nsAtom.h"
+
+#include <stddef.h>
+
+void NS_InitAtomTable();
+void NS_ShutdownAtomTable();
+
+namespace mozilla {
+struct AtomsSizes {
+ size_t mTable;
+ size_t mDynamicAtoms;
+
+ AtomsSizes() : mTable(0), mDynamicAtoms(0) {}
+};
+} // namespace mozilla
+
+void NS_AddSizeOfAtoms(mozilla::MallocSizeOf aMallocSizeOf,
+ mozilla::AtomsSizes& aSizes);
+
+#endif // nsAtomTable_h__
diff --git a/xpcom/ds/nsBaseHashtable.h b/xpcom/ds/nsBaseHashtable.h
new file mode 100644
index 0000000000..7b57ef4666
--- /dev/null
+++ b/xpcom/ds/nsBaseHashtable.h
@@ -0,0 +1,1029 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseHashtable_h__
+#define nsBaseHashtable_h__
+
+#include <functional>
+#include <utility>
+
+#include "mozilla/dom/SafeRefPtr.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsHashtablesFwd.h"
+#include "nsTHashtable.h"
+
+namespace mozilla::detail {
+
+template <typename SmartPtr>
+struct SmartPtrTraits {
+ static constexpr bool IsSmartPointer = false;
+ static constexpr bool IsRefCounted = false;
+};
+
+template <typename Pointee>
+struct SmartPtrTraits<UniquePtr<Pointee>> {
+ static constexpr bool IsSmartPointer = true;
+ static constexpr bool IsRefCounted = false;
+ using SmartPointerType = UniquePtr<Pointee>;
+ using PointeeType = Pointee;
+ using RawPointerType = Pointee*;
+ template <typename U>
+ using OtherSmartPtrType = UniquePtr<U>;
+
+ template <typename U, typename... Args>
+ static SmartPointerType NewObject(Args&&... aConstructionArgs) {
+ return mozilla::MakeUnique<U>(std::forward<Args>(aConstructionArgs)...);
+ }
+};
+
+template <typename Pointee>
+struct SmartPtrTraits<RefPtr<Pointee>> {
+ static constexpr bool IsSmartPointer = true;
+ static constexpr bool IsRefCounted = true;
+ using SmartPointerType = RefPtr<Pointee>;
+ using PointeeType = Pointee;
+ using RawPointerType = Pointee*;
+ template <typename U>
+ using OtherSmartPtrType = RefPtr<U>;
+
+ template <typename U, typename... Args>
+ static SmartPointerType NewObject(Args&&... aConstructionArgs) {
+ return MakeRefPtr<U>(std::forward<Args>(aConstructionArgs)...);
+ }
+};
+
+template <typename Pointee>
+struct SmartPtrTraits<SafeRefPtr<Pointee>> {
+ static constexpr bool IsSmartPointer = true;
+ static constexpr bool IsRefCounted = true;
+ using SmartPointerType = SafeRefPtr<Pointee>;
+ using PointeeType = Pointee;
+ using RawPointerType = Pointee*;
+ template <typename U>
+ using OtherSmartPtrType = SafeRefPtr<U>;
+
+ template <typename U, typename... Args>
+ static SmartPointerType NewObject(Args&&... aConstructionArgs) {
+ return MakeSafeRefPtr<U>(std::forward<Args>(aConstructionArgs)...);
+ }
+};
+
+template <typename Pointee>
+struct SmartPtrTraits<nsCOMPtr<Pointee>> {
+ static constexpr bool IsSmartPointer = true;
+ static constexpr bool IsRefCounted = true;
+ using SmartPointerType = nsCOMPtr<Pointee>;
+ using PointeeType = Pointee;
+ using RawPointerType = Pointee*;
+ template <typename U>
+ using OtherSmartPtrType = nsCOMPtr<U>;
+
+ template <typename U, typename... Args>
+ static SmartPointerType NewObject(Args&&... aConstructionArgs) {
+ return MakeRefPtr<U>(std::forward<Args>(aConstructionArgs)...);
+ }
+};
+
+template <class T>
+T* PtrGetWeak(T* aPtr) {
+ return aPtr;
+}
+
+template <class T>
+T* PtrGetWeak(const RefPtr<T>& aPtr) {
+ return aPtr.get();
+}
+
+template <class T>
+T* PtrGetWeak(const SafeRefPtr<T>& aPtr) {
+ return aPtr.unsafeGetRawPtr();
+}
+
+template <class T>
+T* PtrGetWeak(const nsCOMPtr<T>& aPtr) {
+ return aPtr.get();
+}
+
+template <class T>
+T* PtrGetWeak(const UniquePtr<T>& aPtr) {
+ return aPtr.get();
+}
+
+template <typename EntryType>
+class nsBaseHashtableValueIterator : public ::detail::nsTHashtableIteratorBase {
+ // friend class nsTHashtable<EntryType>;
+
+ public:
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = const std::decay_t<typename EntryType::DataType>;
+ using difference_type = int32_t;
+ using pointer = value_type*;
+ using reference = value_type&;
+
+ using iterator_type = nsBaseHashtableValueIterator;
+ using const_iterator_type = nsBaseHashtableValueIterator;
+
+ using nsTHashtableIteratorBase::nsTHashtableIteratorBase;
+
+ value_type* operator->() const {
+ return &static_cast<const EntryType*>(mIterator.Get())->GetData();
+ }
+ decltype(auto) operator*() const {
+ return static_cast<const EntryType*>(mIterator.Get())->GetData();
+ }
+
+ iterator_type& operator++() {
+ mIterator.Next();
+ return *this;
+ }
+ iterator_type operator++(int) {
+ iterator_type it = *this;
+ ++*this;
+ return it;
+ }
+};
+
+template <typename EntryType>
+class nsBaseHashtableValueRange {
+ public:
+ using IteratorType = nsBaseHashtableValueIterator<EntryType>;
+ using iterator = IteratorType;
+
+ explicit nsBaseHashtableValueRange(const PLDHashTable& aHashtable)
+ : mHashtable{aHashtable} {}
+
+ auto begin() const { return IteratorType{mHashtable}; }
+ auto end() const {
+ return IteratorType{mHashtable, typename IteratorType::EndIteratorTag{}};
+ }
+ auto cbegin() const { return begin(); }
+ auto cend() const { return end(); }
+
+ uint32_t Count() const { return mHashtable.EntryCount(); }
+
+ private:
+ const PLDHashTable& mHashtable;
+};
+
+template <typename EntryType>
+auto RangeSize(const detail::nsBaseHashtableValueRange<EntryType>& aRange) {
+ return aRange.Count();
+}
+
+} // namespace mozilla::detail
+
+/**
+ * Data type conversion helper that is used to wrap and unwrap the specified
+ * DataType.
+ */
+template <class DataType, class UserDataType>
+class nsDefaultConverter {
+ public:
+ /**
+ * Maps the storage DataType to the exposed UserDataType.
+ */
+ static UserDataType Unwrap(DataType& src) { return UserDataType(src); }
+
+ /**
+ * Const ref variant used for example with nsCOMPtr wrappers.
+ */
+ static DataType Wrap(const UserDataType& src) { return DataType(src); }
+
+ /**
+ * Generic conversion, this is useful for things like already_AddRefed.
+ */
+ template <typename U>
+ static DataType Wrap(U&& src) {
+ return std::forward<U>(src);
+ }
+
+ template <typename U>
+ static UserDataType Unwrap(U&& src) {
+ return std::forward<U>(src);
+ }
+};
+
+/**
+ * the private nsTHashtable::EntryType class used by nsBaseHashtable
+ * @see nsTHashtable for the specification of this class
+ * @see nsBaseHashtable for template parameters
+ */
+template <class KeyClass, class TDataType>
+class nsBaseHashtableET : public KeyClass {
+ public:
+ using DataType = TDataType;
+
+ const DataType& GetData() const { return mData; }
+ DataType* GetModifiableData() { return &mData; }
+ template <typename U>
+ void SetData(U&& aData) {
+ mData = std::forward<U>(aData);
+ }
+
+ decltype(auto) GetWeak() const {
+ return mozilla::detail::PtrGetWeak(GetData());
+ }
+
+ private:
+ DataType mData;
+ friend class nsTHashtable<nsBaseHashtableET<KeyClass, DataType>>;
+ template <typename KeyClassX, typename DataTypeX, typename UserDataTypeX,
+ typename ConverterX>
+ friend class nsBaseHashtable;
+ friend class ::detail::nsTHashtableKeyIterator<
+ nsBaseHashtableET<KeyClass, DataType>>;
+
+ typedef typename KeyClass::KeyType KeyType;
+ typedef typename KeyClass::KeyTypePointer KeyTypePointer;
+
+ template <typename... Args>
+ explicit nsBaseHashtableET(KeyTypePointer aKey, Args&&... aArgs);
+ nsBaseHashtableET(nsBaseHashtableET<KeyClass, DataType>&& aToMove) = default;
+ ~nsBaseHashtableET() = default;
+};
+
+/**
+ * Templated hashtable. Usually, this isn't instantiated directly but through
+ * its sub-class templates nsInterfaceHashtable, nsClassHashtable,
+ * nsRefPtrHashtable and nsTHashMap.
+ *
+ * Originally, UserDataType used to be the only type exposed to the user in the
+ * public member function signatures (hence its name), but this has proven to
+ * inadequate over time. Now, UserDataType is only exposed in by-value
+ * getter member functions that are called *Get*. Member functions that provide
+ * access to the DataType are called Lookup rather than Get. Note that this rule
+ * does not apply to nsRefPtrHashtable and nsInterfaceHashtable, as they are
+ * provide a similar interface, but are no genuine sub-classes of
+ * nsBaseHashtable.
+ *
+ * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h
+ * for a complete specification.
+ * @param DataType the datatype stored in the hashtable,
+ * for example, uint32_t or nsCOMPtr.
+ * @param UserDataType the datatype returned from the by-value getter member
+ * functions (named *Get*), for example uint32_t or nsISupports*
+ * @param Converter that is used to map from DataType to UserDataType. A
+ * default converter is provided that assumes implicit conversion is an
+ * option.
+ */
+template <class KeyClass, class DataType, class UserDataType, class Converter>
+class nsBaseHashtable
+ : protected nsTHashtable<nsBaseHashtableET<KeyClass, DataType>> {
+ using Base = nsTHashtable<nsBaseHashtableET<KeyClass, DataType>>;
+ typedef mozilla::fallible_t fallible_t;
+
+ public:
+ typedef typename KeyClass::KeyType KeyType;
+ typedef nsBaseHashtableET<KeyClass, DataType> EntryType;
+
+ using nsTHashtable<EntryType>::Contains;
+ using nsTHashtable<EntryType>::GetGeneration;
+ using nsTHashtable<EntryType>::SizeOfExcludingThis;
+ using nsTHashtable<EntryType>::SizeOfIncludingThis;
+
+ nsBaseHashtable() = default;
+ explicit nsBaseHashtable(uint32_t aInitLength)
+ : nsTHashtable<EntryType>(aInitLength) {}
+
+ /**
+ * Return the number of entries in the table.
+ * @return number of entries
+ */
+ [[nodiscard]] uint32_t Count() const {
+ return nsTHashtable<EntryType>::Count();
+ }
+
+ /**
+ * Return whether the table is empty.
+ * @return whether empty
+ */
+ [[nodiscard]] bool IsEmpty() const {
+ return nsTHashtable<EntryType>::IsEmpty();
+ }
+
+ /**
+ * Get the value, returning a flag indicating the presence of the entry in
+ * the table.
+ *
+ * @param aKey the key to retrieve
+ * @param aData data associated with this key will be placed at this pointer.
+ * If you only need to check if the key exists, aData may be null.
+ * @return true if the key exists. If key does not exist, aData is not
+ * modified.
+ *
+ * @attention As opposed to Remove, this does not assign a value to *aData if
+ * no entry is present! (And also as opposed to the member function Get with
+ * the same signature that nsClassHashtable defines and hides this one.)
+ */
+ [[nodiscard]] bool Get(KeyType aKey, UserDataType* aData) const {
+ EntryType* ent = this->GetEntry(aKey);
+ if (!ent) {
+ return false;
+ }
+
+ if (aData) {
+ *aData = Converter::Unwrap(ent->mData);
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the value, returning a zero-initialized POD or a default-initialized
+ * object if the entry is not present in the table.
+ *
+ * This overload can only be used if UserDataType is default-constructible.
+ * Use the double-argument Get or MaybeGet with non-default-constructible
+ * UserDataType.
+ *
+ * @param aKey the key to retrieve
+ * @return The found value, or UserDataType{} if no entry was found with the
+ * given key.
+ * @note If zero/default-initialized values are stored in the table, it is
+ * not possible to distinguish between such a value and a missing entry.
+ */
+ [[nodiscard]] UserDataType Get(KeyType aKey) const {
+ EntryType* ent = this->GetEntry(aKey);
+ if (!ent) {
+ return UserDataType{};
+ }
+
+ return Converter::Unwrap(ent->mData);
+ }
+
+ /**
+ * Get the value, returning Nothing if the entry is not present in the table.
+ *
+ * @param aKey the key to retrieve
+ * @return The found value wrapped in a Maybe, or Nothing if no entry was
+ * found with the given key.
+ */
+ [[nodiscard]] mozilla::Maybe<UserDataType> MaybeGet(KeyType aKey) const {
+ EntryType* ent = this->GetEntry(aKey);
+ if (!ent) {
+ return mozilla::Nothing();
+ }
+
+ return mozilla::Some(Converter::Unwrap(ent->mData));
+ }
+
+ using SmartPtrTraits = mozilla::detail::SmartPtrTraits<DataType>;
+
+ /**
+ * Looks up aKey in the hash table. If it doesn't exist a new object of
+ * SmartPtrTraits::PointeeType will be created (using the arguments provided)
+ * and then returned.
+ *
+ * \note This can only be instantiated if DataType is a smart pointer.
+ */
+ template <typename... Args>
+ auto GetOrInsertNew(KeyType aKey, Args&&... aConstructionArgs) {
+ static_assert(
+ SmartPtrTraits::IsSmartPointer,
+ "GetOrInsertNew can only be used with smart pointer data types");
+ return mozilla::detail::PtrGetWeak(LookupOrInsertWith(std::move(aKey), [&] {
+ return SmartPtrTraits::template NewObject<
+ typename SmartPtrTraits::PointeeType>(
+ std::forward<Args>(aConstructionArgs)...);
+ }));
+ }
+
+ /**
+ * Add aKey to the table if not already present, and return a reference to its
+ * value. If aKey is not already in the table then the a default-constructed
+ * or the provided value aData is used.
+ *
+ * If the arguments are non-trivial to provide, consider using
+ * LookupOrInsertWith instead.
+ */
+ template <typename... Args>
+ DataType& LookupOrInsert(const KeyType& aKey, Args&&... aArgs) {
+ return WithEntryHandle(aKey, [&](auto entryHandle) -> DataType& {
+ return entryHandle.OrInsert(std::forward<Args>(aArgs)...);
+ });
+ }
+
+ /**
+ * Add aKey to the table if not already present, and return a reference to its
+ * value. If aKey is not already in the table then the value is
+ * constructed using the given factory.
+ */
+ template <typename F>
+ DataType& LookupOrInsertWith(const KeyType& aKey, F&& aFunc) {
+ return WithEntryHandle(aKey, [&aFunc](auto entryHandle) -> DataType& {
+ return entryHandle.OrInsertWith(std::forward<F>(aFunc));
+ });
+ }
+
+ /**
+ * Add aKey to the table if not already present, and return a reference to its
+ * value. If aKey is not already in the table then the value is
+ * constructed using the given factory.
+ */
+ template <typename F>
+ [[nodiscard]] auto TryLookupOrInsertWith(const KeyType& aKey, F&& aFunc) {
+ return WithEntryHandle(
+ aKey,
+ [&aFunc](auto entryHandle)
+ -> mozilla::Result<std::reference_wrapper<DataType>,
+ typename std::invoke_result_t<F>::err_type> {
+ if (entryHandle) {
+ return std::ref(entryHandle.Data());
+ }
+
+ // XXX Use MOZ_TRY after generalizing QM_TRY to mfbt.
+ auto res = std::forward<F>(aFunc)();
+ if (res.isErr()) {
+ return res.propagateErr();
+ }
+ return std::ref(entryHandle.Insert(res.unwrap()));
+ });
+ }
+
+ /**
+ * If it does not yet, inserts a new entry with the handle's key and the
+ * value passed to this function. Otherwise, it updates the entry by the
+ * value passed to this function.
+ *
+ * \tparam U DataType must be implicitly convertible (and assignable) from U
+ * \post HasEntry()
+ * \param aKey the key to put
+ * \param aData the new data
+ */
+ template <typename U>
+ DataType& InsertOrUpdate(KeyType aKey, U&& aData) {
+ return WithEntryHandle(aKey, [&aData](auto entryHandle) -> DataType& {
+ return entryHandle.InsertOrUpdate(std::forward<U>(aData));
+ });
+ }
+
+ template <typename U>
+ [[nodiscard]] bool InsertOrUpdate(KeyType aKey, U&& aData,
+ const fallible_t& aFallible) {
+ return WithEntryHandle(aKey, aFallible, [&aData](auto maybeEntryHandle) {
+ if (!maybeEntryHandle) {
+ return false;
+ }
+ maybeEntryHandle->InsertOrUpdate(std::forward<U>(aData));
+ return true;
+ });
+ }
+
+ /**
+ * Remove the entry associated with aKey (if any), _moving_ its current value
+ * into *aData. Return true if found.
+ *
+ * This overload can only be used if DataType is default-constructible. Use
+ * the single-argument Remove or Extract with non-default-constructible
+ * DataType.
+ *
+ * @param aKey the key to remove from the hashtable
+ * @param aData where to move the value. If an entry is not found, *aData
+ * will be assigned a default-constructed value (i.e. reset to
+ * zero or nullptr for primitive types).
+ * @return true if an entry for aKey was found (and removed)
+ */
+ // XXX This should also better be marked nodiscard, but due to
+ // nsClassHashtable not guaranteeing non-nullness of entries, it is usually
+ // only checked if aData is nullptr in such cases.
+ // [[nodiscard]]
+ bool Remove(KeyType aKey, DataType* aData) {
+ if (auto* ent = this->GetEntry(aKey)) {
+ if (aData) {
+ *aData = std::move(ent->mData);
+ }
+ this->RemoveEntry(ent);
+ return true;
+ }
+ if (aData) {
+ *aData = std::move(DataType());
+ }
+ return false;
+ }
+
+ /**
+ * Remove the entry associated with aKey (if any). Return true if found.
+ *
+ * @param aKey the key to remove from the hashtable
+ * @return true if an entry for aKey was found (and removed)
+ */
+ bool Remove(KeyType aKey) {
+ if (auto* ent = this->GetEntry(aKey)) {
+ this->RemoveEntry(ent);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve the value for a key and remove the corresponding entry at
+ * the same time.
+ *
+ * @param aKey the key to retrieve and remove
+ * @return the found value, or Nothing if no entry was found with the
+ * given key.
+ */
+ [[nodiscard]] mozilla::Maybe<DataType> Extract(KeyType aKey) {
+ mozilla::Maybe<DataType> value;
+ if (EntryType* ent = this->GetEntry(aKey)) {
+ value.emplace(std::move(ent->mData));
+ this->RemoveEntry(ent);
+ }
+ return value;
+ }
+
+ template <typename HashtableRef>
+ struct LookupResult {
+ private:
+ EntryType* mEntry;
+ HashtableRef mTable;
+#ifdef DEBUG
+ uint32_t mTableGeneration;
+#endif
+
+ public:
+ LookupResult(EntryType* aEntry, HashtableRef aTable)
+ : mEntry(aEntry),
+ mTable(aTable)
+#ifdef DEBUG
+ ,
+ mTableGeneration(aTable.GetGeneration())
+#endif
+ {
+ }
+
+ // Is there something stored in the table?
+ explicit operator bool() const {
+ MOZ_ASSERT(mTableGeneration == mTable.GetGeneration());
+ return mEntry;
+ }
+
+ void Remove() {
+ if (!*this) {
+ return;
+ }
+ mTable.RemoveEntry(mEntry);
+ mEntry = nullptr;
+ }
+
+ [[nodiscard]] DataType& Data() {
+ MOZ_ASSERT(!!*this, "must have an entry to access its value");
+ return mEntry->mData;
+ }
+
+ [[nodiscard]] const DataType& Data() const {
+ MOZ_ASSERT(!!*this, "must have an entry to access its value");
+ return mEntry->mData;
+ }
+
+ [[nodiscard]] DataType* DataPtrOrNull() {
+ return static_cast<bool>(*this) ? &mEntry->mData : nullptr;
+ }
+
+ [[nodiscard]] const DataType* DataPtrOrNull() const {
+ return static_cast<bool>(*this) ? &mEntry->mData : nullptr;
+ }
+
+ [[nodiscard]] DataType* operator->() { return &Data(); }
+ [[nodiscard]] const DataType* operator->() const { return &Data(); }
+
+ [[nodiscard]] DataType& operator*() { return Data(); }
+ [[nodiscard]] const DataType& operator*() const { return Data(); }
+ };
+
+ /**
+ * Removes all entries matching a predicate.
+ *
+ * The predicate must be compatible with signature bool (const Iterator &).
+ */
+ template <typename Pred>
+ void RemoveIf(Pred&& aPred) {
+ for (auto iter = Iter(); !iter.Done(); iter.Next()) {
+ if (aPred(const_cast<std::add_const_t<decltype(iter)>&>(iter))) {
+ iter.Remove();
+ }
+ }
+ }
+
+ /**
+ * Looks up aKey in the hashtable and returns an object that allows you to
+ * read/modify the value of the entry, or remove the entry (if found).
+ *
+ * A typical usage of this API looks like this:
+ *
+ * if (auto entry = hashtable.Lookup(key)) {
+ * DoSomething(entry.Data());
+ * if (entry.Data() > 42) {
+ * entry.Remove();
+ * }
+ * } // else - an entry with the given key doesn't exist
+ *
+ * This is useful for cases where you want to read/write the value of an entry
+ * and (optionally) remove the entry without having to do multiple hashtable
+ * lookups. If you want to insert a new entry if one does not exist, then use
+ * WithEntryHandle instead, see below.
+ */
+ [[nodiscard]] auto Lookup(KeyType aKey) {
+ return LookupResult<nsBaseHashtable&>(this->GetEntry(aKey), *this);
+ }
+
+ [[nodiscard]] auto Lookup(KeyType aKey) const {
+ return LookupResult<const nsBaseHashtable&>(this->GetEntry(aKey), *this);
+ }
+
+ /**
+ * Used by WithEntryHandle as the argument type to its functor. It is
+ * associated with the Key passed to WithEntryHandle and manages only the
+ * potential entry with that key. Note that in case no modifying operations
+ * are called on the handle, the state of the hashtable remains unchanged,
+ * i.e. WithEntryHandle does not modify the hashtable itself.
+ *
+ * Provides query functions (Key, HasEntry/operator bool, Data) and
+ * modifying operations for inserting new entries (Insert), updating existing
+ * entries (Update) and removing existing entries (Remove). They have
+ * debug-only assertion that fail when the state of the entry doesn't match
+ * the expectation. There are variants prefixed with "Or" (OrInsert, OrUpdate,
+ * OrRemove) that are a no-op in case the entry does already exist resp. does
+ * not exist. There are also variants OrInsertWith and OrUpdateWith that don't
+ * accept a value, but a functor, which is only called if the operation takes
+ * place, which should be used if the provision of the value is not trivial
+ * (e.g. allocates a heap object). Finally, there's InsertOrUpdate that
+ * handles both existing and non-existing entries.
+ *
+ * Note that all functions of EntryHandle only deal with DataType, not with
+ * UserDataType.
+ */
+ class EntryHandle : protected nsTHashtable<EntryType>::EntryHandle {
+ public:
+ using Base = typename nsTHashtable<EntryType>::EntryHandle;
+
+ EntryHandle(EntryHandle&& aOther) = default;
+ ~EntryHandle() = default;
+
+ EntryHandle(const EntryHandle&) = delete;
+ EntryHandle& operator=(const EntryHandle&) = delete;
+ EntryHandle& operator=(const EntryHandle&&) = delete;
+
+ using Base::Key;
+
+ using Base::HasEntry;
+
+ using Base::operator bool;
+
+ using Base::Entry;
+
+ /**
+ * Inserts a new entry with the handle's key and the value passed to this
+ * function.
+ *
+ * \tparam Args DataType must be constructible from Args
+ * \pre !HasEntry()
+ * \post HasEntry()
+ */
+ template <typename... Args>
+ DataType& Insert(Args&&... aArgs) {
+ Base::InsertInternal(std::forward<Args>(aArgs)...);
+ return Data();
+ }
+
+ /**
+ * If it doesn't yet exist, inserts a new entry with the handle's key and
+ * the value passed to this function. The value is not consumed if no insert
+ * takes place.
+ *
+ * \tparam Args DataType must be constructible from Args
+ * \post HasEntry()
+ */
+ template <typename... Args>
+ DataType& OrInsert(Args&&... aArgs) {
+ if (!HasEntry()) {
+ return Insert(std::forward<Args>(aArgs)...);
+ }
+ return Data();
+ }
+
+ /**
+ * If it doesn't yet exist, inserts a new entry with the handle's key and
+ * the result of the functor passed to this function. The functor is not
+ * called if no insert takes place.
+ *
+ * \tparam F must return a value that is implicitly convertible to DataType
+ * \post HasEntry()
+ */
+ template <typename F>
+ DataType& OrInsertWith(F&& aFunc) {
+ if (!HasEntry()) {
+ return Insert(std::forward<F>(aFunc)());
+ }
+ return Data();
+ }
+
+ /**
+ * Updates the entry with the handle's key by the value passed to this
+ * function.
+ *
+ * \tparam U DataType must be assignable from U
+ * \pre HasEntry()
+ */
+ template <typename U>
+ DataType& Update(U&& aData) {
+ MOZ_RELEASE_ASSERT(HasEntry());
+ Data() = std::forward<U>(aData);
+ return Data();
+ }
+
+ /**
+ * If an entry with the handle's key already exists, updates its value by
+ * the value passed to this function. The value is not consumed if no update
+ * takes place.
+ *
+ * \tparam U DataType must be assignable from U
+ */
+ template <typename U>
+ void OrUpdate(U&& aData) {
+ if (HasEntry()) {
+ Update(std::forward<U>(aData));
+ }
+ }
+
+ /**
+ * If an entry with the handle's key already exists, updates its value by
+ * the the result of the functor passed to this function. The functor is not
+ * called if no update takes place.
+ *
+ * \tparam F must return a value that DataType is assignable from
+ */
+ template <typename F>
+ void OrUpdateWith(F&& aFunc) {
+ if (HasEntry()) {
+ Update(std::forward<F>(aFunc)());
+ }
+ }
+
+ /**
+ * If it does not yet, inserts a new entry with the handle's key and the
+ * value passed to this function. Otherwise, it updates the entry by the
+ * value passed to this function.
+ *
+ * \tparam U DataType must be implicitly convertible (and assignable) from U
+ * \post HasEntry()
+ */
+ template <typename U>
+ DataType& InsertOrUpdate(U&& aData) {
+ if (!HasEntry()) {
+ Insert(std::forward<U>(aData));
+ } else {
+ Update(std::forward<U>(aData));
+ }
+ return Data();
+ }
+
+ using Base::Remove;
+
+ using Base::OrRemove;
+
+ /**
+ * Returns a reference to the value of the entry.
+ *
+ * \pre HasEntry()
+ */
+ [[nodiscard]] DataType& Data() { return Entry()->mData; }
+
+ [[nodiscard]] DataType* DataPtrOrNull() {
+ return static_cast<bool>(*this) ? &Data() : nullptr;
+ }
+
+ [[nodiscard]] DataType* operator->() { return &Data(); }
+
+ [[nodiscard]] DataType& operator*() { return Data(); }
+
+ private:
+ friend class nsBaseHashtable;
+
+ explicit EntryHandle(Base&& aBase) : Base(std::move(aBase)) {}
+ };
+
+ /**
+ * Performs a scoped operation on the entry for aKey, which may or may not
+ * exist when the function is called. It calls aFunc with an EntryHandle. The
+ * result of aFunc is returned as the result of this function. Its return type
+ * may be void. See the documentation of EntryHandle for the query and
+ * modifying operations it offers.
+ *
+ * A simple use of this function is, e.g.,
+ *
+ * hashtable.WithEntryHandle(key, [](auto&& entry) { entry.OrInsert(42); });
+ *
+ * \attention It is not safe to perform modifying operations on the hashtable
+ * other than through the EntryHandle within aFunc, and trying to do so will
+ * trigger debug assertions, and result in undefined behaviour otherwise.
+ */
+ template <class F>
+ [[nodiscard]] auto WithEntryHandle(KeyType aKey, F&& aFunc)
+ -> std::invoke_result_t<F, EntryHandle&&> {
+ return Base::WithEntryHandle(
+ aKey, [&aFunc](auto entryHandle) -> decltype(auto) {
+ return std::forward<F>(aFunc)(EntryHandle{std::move(entryHandle)});
+ });
+ }
+
+ /**
+ * Fallible variant of WithEntryHandle, with the following differences:
+ * - The functor aFunc must accept a Maybe<EntryHandle> (instead of an
+ * EntryHandle).
+ * - In case allocation of the slot for the entry fails, Nothing is passed to
+ * the functor.
+ *
+ * For more details, see the explanation on the non-fallible overload above.
+ */
+ template <class F>
+ [[nodiscard]] auto WithEntryHandle(KeyType aKey, const fallible_t& aFallible,
+ F&& aFunc)
+ -> std::invoke_result_t<F, mozilla::Maybe<EntryHandle>&&> {
+ return Base::WithEntryHandle(
+ aKey, aFallible, [&aFunc](auto maybeEntryHandle) {
+ return std::forward<F>(aFunc)(
+ maybeEntryHandle
+ ? mozilla::Some(EntryHandle{maybeEntryHandle.extract()})
+ : mozilla::Nothing());
+ });
+ }
+
+ public:
+ class ConstIterator {
+ public:
+ explicit ConstIterator(nsBaseHashtable* aTable)
+ : mBaseIterator(&aTable->mTable) {}
+ ~ConstIterator() = default;
+
+ KeyType Key() const {
+ return static_cast<EntryType*>(mBaseIterator.Get())->GetKey();
+ }
+ UserDataType UserData() const {
+ return Converter::Unwrap(
+ static_cast<EntryType*>(mBaseIterator.Get())->mData);
+ }
+ const DataType& Data() const {
+ return static_cast<EntryType*>(mBaseIterator.Get())->mData;
+ }
+
+ bool Done() const { return mBaseIterator.Done(); }
+ void Next() { mBaseIterator.Next(); }
+
+ ConstIterator() = delete;
+ ConstIterator(const ConstIterator&) = delete;
+ ConstIterator(ConstIterator&& aOther) = delete;
+ ConstIterator& operator=(const ConstIterator&) = delete;
+ ConstIterator& operator=(ConstIterator&&) = delete;
+
+ protected:
+ PLDHashTable::Iterator mBaseIterator;
+ };
+
+ // This is an iterator that also allows entry removal. Example usage:
+ //
+ // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
+ // const KeyType key = iter.Key();
+ // const UserDataType data = iter.UserData();
+ // // or
+ // const DataType& data = iter.Data();
+ // // ... do stuff with |key| and/or |data| ...
+ // // ... possibly call iter.Remove() once ...
+ // }
+ //
+ class Iterator final : public ConstIterator {
+ public:
+ using ConstIterator::ConstIterator;
+
+ using ConstIterator::Data;
+ DataType& Data() {
+ return static_cast<EntryType*>(this->mBaseIterator.Get())->mData;
+ }
+
+ void Remove() { this->mBaseIterator.Remove(); }
+ };
+
+ Iterator Iter() { return Iterator(this); }
+
+ ConstIterator ConstIter() const {
+ return ConstIterator(const_cast<nsBaseHashtable*>(this));
+ }
+
+ using nsTHashtable<EntryType>::Remove;
+
+ /**
+ * Remove the entry associated with aIter.
+ *
+ * @param aIter the iterator pointing to the entry
+ * @pre !aIter.Done()
+ */
+ void Remove(ConstIterator& aIter) { aIter.mBaseIterator.Remove(); }
+
+ using typename nsTHashtable<EntryType>::iterator;
+ using typename nsTHashtable<EntryType>::const_iterator;
+
+ using nsTHashtable<EntryType>::begin;
+ using nsTHashtable<EntryType>::end;
+ using nsTHashtable<EntryType>::cbegin;
+ using nsTHashtable<EntryType>::cend;
+
+ using nsTHashtable<EntryType>::Keys;
+
+ /**
+ * Return a range of the values (of DataType). Note this range iterates over
+ * the values in place, so modifications to the nsTHashtable invalidate the
+ * range while it's iterated, except when calling Remove() with a value
+ * iterator derived from that range.
+ */
+ auto Values() const {
+ return mozilla::detail::nsBaseHashtableValueRange<EntryType>{this->mTable};
+ }
+
+ /**
+ * Remove an entry from a value range, specified via a value iterator, e.g.
+ *
+ * for (auto it = hash.Values().begin(), end = hash.Values().end();
+ * it != end; * ++it) {
+ * if (*it > 42) { hash.Remove(it); }
+ * }
+ *
+ * You might also consider using RemoveIf though.
+ */
+ void Remove(mozilla::detail::nsBaseHashtableValueIterator<EntryType>& aIter) {
+ aIter.mIterator.Remove();
+ }
+
+ /**
+ * reset the hashtable, removing all entries
+ */
+ void Clear() { nsTHashtable<EntryType>::Clear(); }
+
+ /**
+ * Measure the size of the table's entry storage. The size of things pointed
+ * to by entries must be measured separately; hence the "Shallow" prefix.
+ *
+ * @param aMallocSizeOf the function used to measure heap-allocated blocks
+ * @return the summed size of the table's storage
+ */
+ size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return this->mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ /**
+ * Like ShallowSizeOfExcludingThis, but includes sizeof(*this).
+ */
+ size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ /**
+ * Swap the elements in this hashtable with the elements in aOther.
+ */
+ void SwapElements(nsBaseHashtable& aOther) {
+ nsTHashtable<EntryType>::SwapElements(aOther);
+ }
+
+ using nsTHashtable<EntryType>::MarkImmutable;
+
+ /**
+ * Makes a clone of this hashtable by copying all entries. This requires
+ * KeyType and DataType to be copy-constructible.
+ */
+ nsBaseHashtable Clone() const { return CloneAs<nsBaseHashtable>(); }
+
+ protected:
+ template <typename T>
+ T CloneAs() const {
+ static_assert(std::is_base_of_v<nsBaseHashtable, T>);
+ // XXX This can probably be optimized, see Bug 1694368.
+ T result(Count());
+ for (const auto& srcEntry : *this) {
+ result.WithEntryHandle(srcEntry.GetKey(), [&](auto&& dstEntry) {
+ dstEntry.Insert(srcEntry.GetData());
+ });
+ }
+ return result;
+ }
+};
+
+//
+// nsBaseHashtableET definitions
+//
+
+template <class KeyClass, class DataType>
+template <typename... Args>
+nsBaseHashtableET<KeyClass, DataType>::nsBaseHashtableET(KeyTypePointer aKey,
+ Args&&... aArgs)
+ : KeyClass(aKey), mData(std::forward<Args>(aArgs)...) {}
+
+#endif // nsBaseHashtable_h__
diff --git a/xpcom/ds/nsCOMArray.cpp b/xpcom/ds/nsCOMArray.cpp
new file mode 100644
index 0000000000..d388857573
--- /dev/null
+++ b/xpcom/ds/nsCOMArray.cpp
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMArray.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "nsQuickSort.h"
+
+#include "nsCOMPtr.h"
+
+// This specialization is private to nsCOMArray.
+// It exists solely to automatically zero-out newly created array elements.
+template <>
+class nsTArrayElementTraits<nsISupports*> {
+ typedef nsISupports* E;
+
+ public:
+ // Zero out the value
+ static inline void Construct(E* aE) {
+ new (mozilla::KnownNotNull, static_cast<void*>(aE)) E();
+ }
+ // Invoke the copy-constructor in place.
+ template <class A>
+ static inline void Construct(E* aE, const A& aArg) {
+ new (mozilla::KnownNotNull, static_cast<void*>(aE)) E(aArg);
+ }
+ // Construct in place.
+ template <class... Args>
+ static inline void Emplace(E* aE, Args&&... aArgs) {
+ new (mozilla::KnownNotNull, static_cast<void*>(aE))
+ E(std::forward<Args>(aArgs)...);
+ }
+ // Invoke the destructor in place.
+ static inline void Destruct(E* aE) { aE->~E(); }
+};
+
+static void ReleaseObjects(nsTArray<nsISupports*>& aArray);
+
+// implementations of non-trivial methods in nsCOMArray_base
+
+nsCOMArray_base::nsCOMArray_base(const nsCOMArray_base& aOther) {
+ // make sure we do only one allocation
+ mArray.SetCapacity(aOther.Count());
+ AppendObjects(aOther);
+}
+
+nsCOMArray_base::~nsCOMArray_base() { Clear(); }
+
+int32_t nsCOMArray_base::IndexOf(nsISupports* aObject,
+ uint32_t aStartIndex) const {
+ return mArray.IndexOf(aObject, aStartIndex);
+}
+
+int32_t nsCOMArray_base::IndexOfObject(nsISupports* aObject) const {
+ nsCOMPtr<nsISupports> supports = do_QueryInterface(aObject);
+ if (NS_WARN_IF(!supports)) {
+ return -1;
+ }
+
+ uint32_t i, count;
+ int32_t retval = -1;
+ count = mArray.Length();
+ for (i = 0; i < count; ++i) {
+ nsCOMPtr<nsISupports> arrayItem = do_QueryInterface(mArray[i]);
+ if (arrayItem == supports) {
+ retval = i;
+ break;
+ }
+ }
+ return retval;
+}
+
+bool nsCOMArray_base::EnumerateForwards(nsBaseArrayEnumFunc aFunc,
+ void* aData) const {
+ for (uint32_t index = 0; index < mArray.Length(); ++index) {
+ if (!(*aFunc)(mArray[index], aData)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool nsCOMArray_base::EnumerateBackwards(nsBaseArrayEnumFunc aFunc,
+ void* aData) const {
+ for (uint32_t index = mArray.Length(); index--;) {
+ if (!(*aFunc)(mArray[index], aData)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int nsCOMArray_base::VoidStarComparator(const void* aElement1,
+ const void* aElement2, void* aData) {
+ auto ctx = static_cast<nsISupportsComparatorContext*>(aData);
+ return (*ctx->mComparatorFunc)(*static_cast<nsISupports* const*>(aElement1),
+ *static_cast<nsISupports* const*>(aElement2),
+ ctx->mData);
+}
+
+void nsCOMArray_base::Sort(nsISupportsComparatorFunc aFunc, void* aData) {
+ if (mArray.Length() > 1) {
+ nsISupportsComparatorContext ctx = {aFunc, aData};
+ NS_QuickSort(mArray.Elements(), mArray.Length(), sizeof(nsISupports*),
+ VoidStarComparator, &ctx);
+ }
+}
+
+bool nsCOMArray_base::InsertObjectAt(nsISupports* aObject, int32_t aIndex) {
+ if ((uint32_t)aIndex > mArray.Length()) {
+ return false;
+ }
+
+ mArray.InsertElementAt(aIndex, aObject);
+
+ NS_IF_ADDREF(aObject);
+ return true;
+}
+
+void nsCOMArray_base::InsertElementAt(uint32_t aIndex, nsISupports* aElement) {
+ mArray.InsertElementAt(aIndex, aElement);
+ NS_IF_ADDREF(aElement);
+}
+
+void nsCOMArray_base::InsertElementAt(uint32_t aIndex,
+ already_AddRefed<nsISupports> aElement) {
+ mArray.InsertElementAt(aIndex, aElement.take());
+}
+
+bool nsCOMArray_base::InsertObjectsAt(const nsCOMArray_base& aObjects,
+ int32_t aIndex) {
+ if ((uint32_t)aIndex > mArray.Length()) {
+ return false;
+ }
+
+ mArray.InsertElementsAt(aIndex, aObjects.mArray);
+
+ // need to addref all these
+ uint32_t count = aObjects.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ NS_IF_ADDREF(aObjects[i]);
+ }
+
+ return true;
+}
+
+void nsCOMArray_base::InsertElementsAt(uint32_t aIndex,
+ const nsCOMArray_base& aElements) {
+ mArray.InsertElementsAt(aIndex, aElements.mArray);
+
+ // need to addref all these
+ uint32_t count = aElements.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ NS_IF_ADDREF(aElements[i]);
+ }
+}
+
+void nsCOMArray_base::InsertElementsAt(uint32_t aIndex,
+ nsISupports* const* aElements,
+ uint32_t aCount) {
+ mArray.InsertElementsAt(aIndex, aElements, aCount);
+
+ // need to addref all these
+ for (uint32_t i = 0; i < aCount; ++i) {
+ NS_IF_ADDREF(aElements[i]);
+ }
+}
+
+void nsCOMArray_base::ReplaceObjectAt(nsISupports* aObject, int32_t aIndex) {
+ mArray.EnsureLengthAtLeast(aIndex + 1);
+ nsISupports* oldObject = mArray[aIndex];
+ // Make sure to addref first, in case aObject == oldObject
+ NS_IF_ADDREF(mArray[aIndex] = aObject);
+ NS_IF_RELEASE(oldObject);
+}
+
+bool nsCOMArray_base::RemoveObject(nsISupports* aObject) {
+ bool result = mArray.RemoveElement(aObject);
+ if (result) {
+ NS_IF_RELEASE(aObject);
+ }
+ return result;
+}
+
+bool nsCOMArray_base::RemoveObjectAt(int32_t aIndex) {
+ if (uint32_t(aIndex) < mArray.Length()) {
+ nsISupports* element = mArray[aIndex];
+
+ mArray.RemoveElementAt(aIndex);
+ NS_IF_RELEASE(element);
+ return true;
+ }
+
+ return false;
+}
+
+void nsCOMArray_base::RemoveElementAt(uint32_t aIndex) {
+ nsISupports* element = mArray[aIndex];
+ mArray.RemoveElementAt(aIndex);
+ NS_IF_RELEASE(element);
+}
+
+bool nsCOMArray_base::RemoveObjectsAt(int32_t aIndex, int32_t aCount) {
+ if (uint32_t(aIndex) + uint32_t(aCount) <= mArray.Length()) {
+ nsTArray<nsISupports*> elementsToDestroy(aCount);
+ elementsToDestroy.AppendElements(mArray.Elements() + aIndex, aCount);
+ mArray.RemoveElementsAt(aIndex, aCount);
+ ReleaseObjects(elementsToDestroy);
+ return true;
+ }
+
+ return false;
+}
+
+void nsCOMArray_base::RemoveElementsAt(uint32_t aIndex, uint32_t aCount) {
+ nsTArray<nsISupports*> elementsToDestroy(aCount);
+ elementsToDestroy.AppendElements(mArray.Elements() + aIndex, aCount);
+ mArray.RemoveElementsAt(aIndex, aCount);
+ ReleaseObjects(elementsToDestroy);
+}
+
+// useful for destructors
+void ReleaseObjects(nsTArray<nsISupports*>& aArray) {
+ for (uint32_t i = 0; i < aArray.Length(); ++i) {
+ NS_IF_RELEASE(aArray[i]);
+ }
+}
+
+void nsCOMArray_base::Clear() {
+ nsTArray<nsISupports*> objects = std::move(mArray);
+ ReleaseObjects(objects);
+}
+
+bool nsCOMArray_base::SetCount(int32_t aNewCount) {
+ NS_ASSERTION(aNewCount >= 0, "SetCount(negative index)");
+ if (aNewCount < 0) {
+ return false;
+ }
+
+ int32_t count = mArray.Length();
+ if (count > aNewCount) {
+ RemoveObjectsAt(aNewCount, mArray.Length() - aNewCount);
+ }
+ mArray.SetLength(aNewCount);
+ return true;
+}
diff --git a/xpcom/ds/nsCOMArray.h b/xpcom/ds/nsCOMArray.h
new file mode 100644
index 0000000000..0ea1437f25
--- /dev/null
+++ b/xpcom/ds/nsCOMArray.h
@@ -0,0 +1,398 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCOMArray_h__
+#define nsCOMArray_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "nsCycleCollectionNoteChild.h"
+#include "nsTArray.h"
+#include "nsISupports.h"
+
+#include <iterator>
+
+// See below for the definition of nsCOMArray<T>
+
+// a class that's nsISupports-specific, so that we can contain the
+// work of this class in the XPCOM dll
+class nsCOMArray_base {
+ friend class nsArrayBase;
+
+ protected:
+ nsCOMArray_base() = default;
+ explicit nsCOMArray_base(int32_t aCount) : mArray(aCount) {}
+ nsCOMArray_base(const nsCOMArray_base& aOther);
+ nsCOMArray_base(nsCOMArray_base&& aOther) = default;
+ nsCOMArray_base& operator=(nsCOMArray_base&& aOther) = default;
+ ~nsCOMArray_base();
+
+ int32_t IndexOf(nsISupports* aObject, uint32_t aStartIndex = 0) const;
+ bool Contains(nsISupports* aObject) const { return IndexOf(aObject) != -1; }
+
+ int32_t IndexOfObject(nsISupports* aObject) const;
+ bool ContainsObject(nsISupports* aObject) const {
+ return IndexOfObject(aObject) != -1;
+ }
+
+ typedef bool (*nsBaseArrayEnumFunc)(void* aElement, void* aData);
+
+ // enumerate through the array with a callback.
+ bool EnumerateForwards(nsBaseArrayEnumFunc aFunc, void* aData) const;
+
+ bool EnumerateBackwards(nsBaseArrayEnumFunc aFunc, void* aData) const;
+
+ typedef int (*nsISupportsComparatorFunc)(nsISupports* aElement1,
+ nsISupports* aElement2, void* aData);
+
+ struct nsISupportsComparatorContext {
+ nsISupportsComparatorFunc mComparatorFunc;
+ void* mData;
+ };
+
+ static int VoidStarComparator(const void* aElement1, const void* aElement2,
+ void* aData);
+ void Sort(nsISupportsComparatorFunc aFunc, void* aData);
+
+ bool InsertObjectAt(nsISupports* aObject, int32_t aIndex);
+ void InsertElementAt(uint32_t aIndex, nsISupports* aElement);
+ void InsertElementAt(uint32_t aIndex, already_AddRefed<nsISupports> aElement);
+ bool InsertObjectsAt(const nsCOMArray_base& aObjects, int32_t aIndex);
+ void InsertElementsAt(uint32_t aIndex, const nsCOMArray_base& aElements);
+ void InsertElementsAt(uint32_t aIndex, nsISupports* const* aElements,
+ uint32_t aCount);
+ void ReplaceObjectAt(nsISupports* aObject, int32_t aIndex);
+ void ReplaceElementAt(uint32_t aIndex, nsISupports* aElement) {
+ nsISupports* oldElement = mArray[aIndex];
+ NS_IF_ADDREF(mArray[aIndex] = aElement);
+ NS_IF_RELEASE(oldElement);
+ }
+ bool AppendObject(nsISupports* aObject) {
+ return InsertObjectAt(aObject, Count());
+ }
+ void AppendElement(nsISupports* aElement) {
+ InsertElementAt(Length(), aElement);
+ }
+ void AppendElement(already_AddRefed<nsISupports> aElement) {
+ InsertElementAt(Length(), std::move(aElement));
+ }
+
+ bool AppendObjects(const nsCOMArray_base& aObjects) {
+ return InsertObjectsAt(aObjects, Count());
+ }
+ void AppendElements(const nsCOMArray_base& aElements) {
+ return InsertElementsAt(Length(), aElements);
+ }
+ void AppendElements(nsISupports* const* aElements, uint32_t aCount) {
+ return InsertElementsAt(Length(), aElements, aCount);
+ }
+ bool RemoveObject(nsISupports* aObject);
+ nsISupports** Elements() { return mArray.Elements(); }
+ void SwapElements(nsCOMArray_base& aOther) {
+ mArray.SwapElements(aOther.mArray);
+ }
+
+ public:
+ // elements in the array (including null elements!)
+ int32_t Count() const { return mArray.Length(); }
+ // nsTArray-compatible version
+ uint32_t Length() const { return mArray.Length(); }
+ bool IsEmpty() const { return mArray.IsEmpty(); }
+
+ // If the array grows, the newly created entries will all be null;
+ // if the array shrinks, the excess entries will all be released.
+ bool SetCount(int32_t aNewCount);
+ // nsTArray-compatible version
+ void TruncateLength(uint32_t aNewLength) {
+ if (mArray.Length() > aNewLength) {
+ RemoveElementsAt(aNewLength, mArray.Length() - aNewLength);
+ }
+ }
+
+ // remove all elements in the array, and call NS_RELEASE on each one
+ void Clear();
+
+ nsISupports* ObjectAt(int32_t aIndex) const { return mArray[aIndex]; }
+ // nsTArray-compatible version
+ nsISupports* ElementAt(uint32_t aIndex) const { return mArray[aIndex]; }
+
+ nsISupports* SafeObjectAt(int32_t aIndex) const {
+ return mArray.SafeElementAt(aIndex, nullptr);
+ }
+ // nsTArray-compatible version
+ nsISupports* SafeElementAt(uint32_t aIndex) const {
+ return mArray.SafeElementAt(aIndex, nullptr);
+ }
+
+ nsISupports* operator[](int32_t aIndex) const { return mArray[aIndex]; }
+
+ // remove an element at a specific position, shrinking the array
+ // as necessary
+ bool RemoveObjectAt(int32_t aIndex);
+ // nsTArray-compatible version
+ void RemoveElementAt(uint32_t aIndex);
+
+ // remove a range of elements at a specific position, shrinking the array
+ // as necessary
+ bool RemoveObjectsAt(int32_t aIndex, int32_t aCount);
+ // nsTArray-compatible version
+ void RemoveElementsAt(uint32_t aIndex, uint32_t aCount);
+
+ void SwapElementsAt(uint32_t aIndex1, uint32_t aIndex2) {
+ nsISupports* tmp = mArray[aIndex1];
+ mArray[aIndex1] = mArray[aIndex2];
+ mArray[aIndex2] = tmp;
+ }
+
+ // Ensures there is enough space to store a total of aCapacity objects.
+ // This method never deletes any objects.
+ void SetCapacity(uint32_t aCapacity) { mArray.SetCapacity(aCapacity); }
+ uint32_t Capacity() { return mArray.Capacity(); }
+
+ // Measures the size of the array's element storage. If you want to measure
+ // anything hanging off the array, you must iterate over the elements and
+ // measure them individually; hence the "Shallow" prefix. Note that because
+ // each element in an nsCOMArray<T> is actually a T* any such iteration
+ // should use a SizeOfIncludingThis() function on each element rather than a
+ // SizeOfExcludingThis() function, so that the memory taken by the T itself
+ // is included as well as anything it points to.
+ size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ // the actual storage
+ nsTArray<nsISupports*> mArray;
+
+ // don't implement these, defaults will muck with refcounts!
+ nsCOMArray_base& operator=(const nsCOMArray_base& aOther) = delete;
+};
+
+inline void ImplCycleCollectionUnlink(nsCOMArray_base& aField) {
+ aField.Clear();
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, nsCOMArray_base& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ aFlags |= CycleCollectionEdgeNameArrayFlag;
+ int32_t length = aField.Count();
+ for (int32_t i = 0; i < length; ++i) {
+ CycleCollectionNoteChild(aCallback, aField[i], aName, aFlags);
+ }
+}
+
+// a non-XPCOM, refcounting array of XPCOM objects
+// used as a member variable or stack variable - this object is NOT
+// refcounted, but the objects that it holds are
+//
+// most of the read-only accessors like ObjectAt()/etc do NOT refcount
+// on the way out. This means that you can do one of two things:
+//
+// * does an addref, but holds onto a reference
+// nsCOMPtr<T> foo = array[i];
+//
+// * avoids the refcount, but foo might go stale if array[i] is ever
+// * modified/removed. Be careful not to NS_RELEASE(foo)!
+// T* foo = array[i];
+//
+// This array will accept null as an argument for any object, and will store
+// null in the array. But that also means that methods like ObjectAt() may
+// return null when referring to an existing, but null entry in the array.
+template <class T>
+class nsCOMArray : public nsCOMArray_base {
+ public:
+ typedef int32_t index_type;
+ typedef mozilla::ArrayIterator<T*, nsCOMArray> iterator;
+ typedef mozilla::ArrayIterator<const T*, nsCOMArray> const_iterator;
+ typedef std::reverse_iterator<iterator> reverse_iterator;
+ typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+
+ nsCOMArray() = default;
+ explicit nsCOMArray(int32_t aCount) : nsCOMArray_base(aCount) {}
+ explicit nsCOMArray(const nsCOMArray<T>& aOther) : nsCOMArray_base(aOther) {}
+ nsCOMArray(nsCOMArray<T>&& aOther) = default;
+ ~nsCOMArray() = default;
+
+ // We have a move assignment operator, but no copy assignment operator.
+ nsCOMArray<T>& operator=(nsCOMArray<T>&& aOther) = default;
+
+ // these do NOT refcount on the way out, for speed
+ T* ObjectAt(int32_t aIndex) const {
+ return static_cast<T*>(nsCOMArray_base::ObjectAt(aIndex));
+ }
+ // nsTArray-compatible version
+ T* ElementAt(uint32_t aIndex) const {
+ return static_cast<T*>(nsCOMArray_base::ElementAt(aIndex));
+ }
+
+ // these do NOT refcount on the way out, for speed
+ T* SafeObjectAt(int32_t aIndex) const {
+ return static_cast<T*>(nsCOMArray_base::SafeObjectAt(aIndex));
+ }
+ // nsTArray-compatible version
+ T* SafeElementAt(uint32_t aIndex) const {
+ return static_cast<T*>(nsCOMArray_base::SafeElementAt(aIndex));
+ }
+
+ // indexing operator for syntactic sugar
+ T* operator[](int32_t aIndex) const { return ObjectAt(aIndex); }
+
+ // index of the element in question.. does NOT refcount
+ // note: this does not check COM object identity. Use
+ // IndexOfObject() for that purpose
+ int32_t IndexOf(T* aObject, uint32_t aStartIndex = 0) const {
+ return nsCOMArray_base::IndexOf(aObject, aStartIndex);
+ }
+ bool Contains(T* aObject) const { return nsCOMArray_base::Contains(aObject); }
+
+ // index of the element in question.. be careful!
+ // this is much slower than IndexOf() because it uses
+ // QueryInterface to determine actual COM identity of the object
+ // if you need to do this frequently then consider enforcing
+ // COM object identity before adding/comparing elements
+ int32_t IndexOfObject(T* aObject) const {
+ return nsCOMArray_base::IndexOfObject(aObject);
+ }
+ bool ContainsObject(nsISupports* aObject) const {
+ return nsCOMArray_base::ContainsObject(aObject);
+ }
+
+ // inserts aObject at aIndex, shifting the objects at aIndex and
+ // later to make space
+ bool InsertObjectAt(T* aObject, int32_t aIndex) {
+ return nsCOMArray_base::InsertObjectAt(aObject, aIndex);
+ }
+ // nsTArray-compatible version
+ void InsertElementAt(uint32_t aIndex, T* aElement) {
+ nsCOMArray_base::InsertElementAt(aIndex, aElement);
+ }
+
+ // inserts the objects from aObject at aIndex, shifting the
+ // objects at aIndex and later to make space
+ bool InsertObjectsAt(const nsCOMArray<T>& aObjects, int32_t aIndex) {
+ return nsCOMArray_base::InsertObjectsAt(aObjects, aIndex);
+ }
+ // nsTArray-compatible version
+ void InsertElementsAt(uint32_t aIndex, const nsCOMArray<T>& aElements) {
+ nsCOMArray_base::InsertElementsAt(aIndex, aElements);
+ }
+ void InsertElementsAt(uint32_t aIndex, T* const* aElements, uint32_t aCount) {
+ nsCOMArray_base::InsertElementsAt(
+ aIndex, reinterpret_cast<nsISupports* const*>(aElements), aCount);
+ }
+
+ // replaces an existing element. Warning: if the array grows,
+ // the newly created entries will all be null
+ void ReplaceObjectAt(T* aObject, int32_t aIndex) {
+ nsCOMArray_base::ReplaceObjectAt(aObject, aIndex);
+ }
+ // nsTArray-compatible version
+ void ReplaceElementAt(uint32_t aIndex, T* aElement) {
+ nsCOMArray_base::ReplaceElementAt(aIndex, aElement);
+ }
+
+ typedef int (*TComparatorFunc)(T* aElement1, T* aElement2, void* aData);
+
+ struct TComparatorContext {
+ TComparatorFunc mComparatorFunc;
+ void* mData;
+ };
+
+ static int nsISupportsComparator(nsISupports* aElement1,
+ nsISupports* aElement2, void* aData) {
+ auto ctx = static_cast<TComparatorContext*>(aData);
+ return (*ctx->mComparatorFunc)(static_cast<T*>(aElement1),
+ static_cast<T*>(aElement2), ctx->mData);
+ }
+
+ void Sort(TComparatorFunc aFunc, void* aData) {
+ TComparatorContext ctx = {aFunc, aData};
+ nsCOMArray_base::Sort(nsISupportsComparator, &ctx);
+ }
+
+ // append an object, growing the array as necessary
+ bool AppendObject(T* aObject) {
+ return nsCOMArray_base::AppendObject(aObject);
+ }
+ // nsTArray-compatible version
+ void AppendElement(T* aElement) { nsCOMArray_base::AppendElement(aElement); }
+ void AppendElement(already_AddRefed<T> aElement) {
+ nsCOMArray_base::AppendElement(std::move(aElement));
+ }
+
+ // append objects, growing the array as necessary
+ bool AppendObjects(const nsCOMArray<T>& aObjects) {
+ return nsCOMArray_base::AppendObjects(aObjects);
+ }
+ // nsTArray-compatible version
+ void AppendElements(const nsCOMArray<T>& aElements) {
+ return nsCOMArray_base::AppendElements(aElements);
+ }
+ void AppendElements(T* const* aElements, uint32_t aCount) {
+ InsertElementsAt(Length(), aElements, aCount);
+ }
+
+ // remove the first instance of the given object and shrink the
+ // array as necessary
+ // Warning: if you pass null here, it will remove the first null element
+ bool RemoveObject(T* aObject) {
+ return nsCOMArray_base::RemoveObject(aObject);
+ }
+ // nsTArray-compatible version
+ bool RemoveElement(T* aElement) {
+ return nsCOMArray_base::RemoveObject(aElement);
+ }
+
+ T** Elements() { return reinterpret_cast<T**>(nsCOMArray_base::Elements()); }
+ void SwapElements(nsCOMArray<T>& aOther) {
+ nsCOMArray_base::SwapElements(aOther);
+ }
+
+ // Methods for range-based for loops.
+ iterator begin() { return iterator(*this, 0); }
+ const_iterator begin() const { return const_iterator(*this, 0); }
+ const_iterator cbegin() const { return begin(); }
+ iterator end() { return iterator(*this, Length()); }
+ const_iterator end() const { return const_iterator(*this, Length()); }
+ const_iterator cend() const { return end(); }
+
+ // Methods for reverse iterating.
+ reverse_iterator rbegin() { return reverse_iterator(end()); }
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator(end());
+ }
+ const_reverse_iterator crbegin() const { return rbegin(); }
+ reverse_iterator rend() { return reverse_iterator(begin()); }
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator(begin());
+ }
+ const_reverse_iterator crend() const { return rend(); }
+
+ private:
+ // don't implement these!
+ nsCOMArray<T>& operator=(const nsCOMArray<T>& aOther) = delete;
+};
+
+template <typename T>
+inline void ImplCycleCollectionUnlink(nsCOMArray<T>& aField) {
+ aField.Clear();
+}
+
+template <typename E>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, nsCOMArray<E>& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ aFlags |= CycleCollectionEdgeNameArrayFlag;
+ int32_t length = aField.Count();
+ for (int32_t i = 0; i < length; ++i) {
+ CycleCollectionNoteChild(aCallback, aField[i], aName, aFlags);
+ }
+}
+
+#endif
diff --git a/xpcom/ds/nsCRT.cpp b/xpcom/ds/nsCRT.cpp
new file mode 100644
index 0000000000..9d2fcb7120
--- /dev/null
+++ b/xpcom/ds/nsCRT.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * MODULE NOTES:
+ * @update gess7/30/98
+ *
+ * Much as I hate to do it, we were using string compares wrong.
+ * Often, programmers call functions like strcmp(s1,s2), and pass
+ * one or more null strings. Rather than blow up on these, I've
+ * added quick checks to ensure that cases like this don't cause
+ * us to fail.
+ *
+ * In general, if you pass a null into any of these string compare
+ * routines, we simply return 0.
+ */
+
+#include "nsCRT.h"
+#include "nsDebug.h"
+
+//----------------------------------------------------------------------
+
+////////////////////////////////////////////////////////////////////////////////
+// My lovely strtok routine
+
+#define IS_DELIM(m, c) ((m)[(c) >> 3] & (1 << ((c)&7)))
+#define SET_DELIM(m, c) ((m)[(c) >> 3] |= (1 << ((c)&7)))
+#define DELIM_TABLE_SIZE 32
+
+char* nsCRT::strtok(char* aString, const char* aDelims, char** aNewStr) {
+ NS_ASSERTION(aString,
+ "Unlike regular strtok, the first argument cannot be null.");
+
+ char delimTable[DELIM_TABLE_SIZE];
+ uint32_t i;
+ char* result;
+ char* str = aString;
+
+ for (i = 0; i < DELIM_TABLE_SIZE; ++i) {
+ delimTable[i] = '\0';
+ }
+
+ for (i = 0; aDelims[i]; i++) {
+ SET_DELIM(delimTable, static_cast<uint8_t>(aDelims[i]));
+ }
+ NS_ASSERTION(aDelims[i] == '\0', "too many delimiters");
+
+ // skip to beginning
+ while (*str && IS_DELIM(delimTable, static_cast<uint8_t>(*str))) {
+ str++;
+ }
+ result = str;
+
+ // fix up the end of the token
+ while (*str) {
+ if (IS_DELIM(delimTable, static_cast<uint8_t>(*str))) {
+ *str++ = '\0';
+ break;
+ }
+ str++;
+ }
+ *aNewStr = str;
+
+ return str == result ? nullptr : result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Compare unichar string ptrs, stopping at the 1st null
+ * NOTE: If both are null, we return 0.
+ * NOTE: We terminate the search upon encountering a nullptr
+ *
+ * @update gess 11/10/99
+ * @param s1 and s2 both point to unichar strings
+ * @return 0 if they match, -1 if s1<s2; 1 if s1>s2
+ */
+int32_t nsCRT::strcmp(const char16_t* aStr1, const char16_t* aStr2) {
+ if (aStr1 && aStr2) {
+ for (;;) {
+ char16_t c1 = *aStr1++;
+ char16_t c2 = *aStr2++;
+ if (c1 != c2) {
+ if (c1 < c2) {
+ return -1;
+ }
+ return 1;
+ }
+ if (c1 == 0 || c2 == 0) {
+ break;
+ }
+ }
+ } else {
+ if (aStr1) { // aStr2 must have been null
+ return -1;
+ }
+ if (aStr2) { // aStr1 must have been null
+ return 1;
+ }
+ }
+ return 0;
+}
+
+// This should use NSPR but NSPR isn't exporting its PR_strtoll function
+// Until then...
+int64_t nsCRT::atoll(const char* aStr) {
+ if (!aStr) {
+ return 0;
+ }
+
+ int64_t ll = 0;
+
+ while (*aStr && *aStr >= '0' && *aStr <= '9') {
+ ll *= 10;
+ ll += *aStr - '0';
+ aStr++;
+ }
+
+ return ll;
+}
diff --git a/xpcom/ds/nsCRT.h b/xpcom/ds/nsCRT.h
new file mode 100644
index 0000000000..14b46c5059
--- /dev/null
+++ b/xpcom/ds/nsCRT.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsCRT_h___
+#define nsCRT_h___
+
+#include <stdlib.h>
+#include <ctype.h>
+#include "plstr.h"
+#include "nscore.h"
+#include "nsCRTGlue.h"
+
+#if defined(XP_WIN)
+# define NS_LINEBREAK "\015\012"
+# define NS_ULINEBREAK u"\015\012"
+# define NS_LINEBREAK_LEN 2
+#else
+# ifdef XP_UNIX
+# define NS_LINEBREAK "\012"
+# define NS_ULINEBREAK u"\012"
+# define NS_LINEBREAK_LEN 1
+# endif /* XP_UNIX */
+#endif /* XP_WIN */
+
+/// This is a wrapper class around all the C runtime functions.
+
+class nsCRT {
+ public:
+ enum {
+ LF = '\n' /* Line Feed */,
+ VTAB = '\v' /* Vertical Tab */,
+ CR = '\r' /* Carriage Return */
+ };
+
+ /// String comparison.
+ static int32_t strcmp(const char* aStr1, const char* aStr2) {
+ return int32_t(PL_strcmp(aStr1, aStr2));
+ }
+
+ /// Case-insensitive string comparison.
+ static int32_t strcasecmp(const char* aStr1, const char* aStr2) {
+ /* Some functions like `PL_strcasecmp` are reimplementations
+ * of the their native POSIX counterparts, which breaks libFuzzer.
+ * For this purpose, we use the natives instead when fuzzing.
+ */
+#if defined(LIBFUZZER) && defined(LINUX)
+ return int32_t(::strcasecmp(aStr1, aStr2));
+#else
+ return int32_t(PL_strcasecmp(aStr1, aStr2));
+#endif
+ }
+
+ /// Case-insensitive string comparison with length
+ static int32_t strncasecmp(const char* aStr1, const char* aStr2,
+ uint32_t aMaxLen) {
+#if defined(LIBFUZZER) && defined(LINUX)
+ int32_t result = int32_t(::strncasecmp(aStr1, aStr2, aMaxLen));
+#else
+ int32_t result = int32_t(PL_strncasecmp(aStr1, aStr2, aMaxLen));
+#endif
+ // Egads. PL_strncasecmp is returning *very* negative numbers.
+ // Some folks expect -1,0,1, so let's temper its enthusiasm.
+ if (result < 0) {
+ result = -1;
+ }
+ return result;
+ }
+
+ /// Case-insensitive substring search.
+ static char* strcasestr(const char* aStr1, const char* aStr2) {
+#if defined(LIBFUZZER) && defined(LINUX)
+ return const_cast<char*>(::strcasestr(aStr1, aStr2));
+#else
+ return PL_strcasestr(aStr1, aStr2);
+#endif
+ }
+
+ /**
+
+ How to use this fancy (thread-safe) version of strtok:
+
+ void main(void) {
+ printf("%s\n\nTokens:\n", string);
+ // Establish string and get the first token:
+ char* newStr;
+ token = nsCRT::strtok(string, seps, &newStr);
+ while (token != nullptr) {
+ // While there are tokens in "string"
+ printf(" %s\n", token);
+ // Get next token:
+ token = nsCRT::strtok(newStr, seps, &newStr);
+ }
+ }
+ * WARNING - STRTOK WHACKS str THE FIRST TIME IT IS CALLED *
+ * MAKE A COPY OF str IF YOU NEED TO USE IT AFTER strtok() *
+ */
+ static char* strtok(char* aStr, const char* aDelims, char** aNewStr);
+
+ /// Like strcmp except for ucs2 strings
+ static int32_t strcmp(const char16_t* aStr1, const char16_t* aStr2);
+
+ // String to longlong
+ static int64_t atoll(const char* aStr);
+
+ static char ToUpper(char aChar) { return NS_ToUpper(aChar); }
+ static char ToLower(char aChar) { return NS_ToLower(aChar); }
+
+ static bool IsAsciiSpace(char16_t aChar) {
+ return NS_IsAsciiWhitespace(aChar);
+ }
+};
+
+inline bool NS_IS_SPACE(char16_t aChar) {
+ return ((int(aChar) & 0x7f) == int(aChar)) && isspace(int(aChar));
+}
+
+#endif /* nsCRT_h___ */
diff --git a/xpcom/ds/nsCharSeparatedTokenizer.cpp b/xpcom/ds/nsCharSeparatedTokenizer.cpp
new file mode 100644
index 0000000000..7288ed070f
--- /dev/null
+++ b/xpcom/ds/nsCharSeparatedTokenizer.cpp
@@ -0,0 +1,10 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCharSeparatedTokenizer.h"
+
+template class nsTSubstringSplitter<char>;
+template class nsTSubstringSplitter<char16_t>;
diff --git a/xpcom/ds/nsCharSeparatedTokenizer.h b/xpcom/ds/nsCharSeparatedTokenizer.h
new file mode 100644
index 0000000000..5cf6992e3e
--- /dev/null
+++ b/xpcom/ds/nsCharSeparatedTokenizer.h
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsCharSeparatedTokenizer_h
+#define __nsCharSeparatedTokenizer_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/RangedPtr.h"
+#include "mozilla/TypedEnumBits.h"
+
+#include "nsCRTGlue.h"
+#include "nsTDependentSubstring.h"
+
+// Flags -- only one for now. If we need more, they should be defined to
+// be 1 << 1, 1 << 2, etc. (They're masks, and aFlags is a bitfield.)
+enum class nsTokenizerFlags {
+ Default = 0,
+ SeparatorOptional = 1 << 0,
+ IncludeEmptyTokenAtEnd = 1 << 1
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTokenizerFlags)
+
+/**
+ * This parses a SeparatorChar-separated string into tokens.
+ * Whitespace surrounding tokens is not treated as part of tokens, however
+ * whitespace inside a token is. If the final token is the empty string, it is
+ * not returned by default.
+ *
+ * Some examples, with SeparatorChar = ',':
+ *
+ * "foo, bar, baz" -> "foo" "bar" "baz"
+ * "foo,bar,baz" -> "foo" "bar" "baz"
+ * "foo , bar hi , baz" -> "foo" "bar hi" "baz"
+ * "foo, ,bar,baz" -> "foo" "" "bar" "baz"
+ * "foo,,bar,baz" -> "foo" "" "bar" "baz"
+ * "foo,bar,baz," -> "foo" "bar" "baz"
+ *
+ * The function used for whitespace detection is a template argument.
+ * By default, it is NS_IsAsciiWhitespace.
+ */
+template <typename TDependentSubstringType, bool IsWhitespace(char16_t),
+ nsTokenizerFlags Flags = nsTokenizerFlags::Default>
+class nsTCharSeparatedTokenizer {
+ using CharType = typename TDependentSubstringType::char_type;
+ using SubstringType = typename TDependentSubstringType::substring_type;
+
+ public:
+ using DependentSubstringType = TDependentSubstringType;
+
+ nsTCharSeparatedTokenizer(const SubstringType& aSource,
+ CharType aSeparatorChar)
+ : mIter(aSource.Data(), aSource.Length()),
+ mEnd(aSource.Data() + aSource.Length(), aSource.Data(),
+ aSource.Length()),
+ mSeparatorChar(aSeparatorChar),
+ mWhitespaceBeforeFirstToken(false),
+ mWhitespaceAfterCurrentToken(false),
+ mSeparatorAfterCurrentToken(false) {
+ // Skip initial whitespace
+ while (mIter < mEnd && IsWhitespace(*mIter)) {
+ mWhitespaceBeforeFirstToken = true;
+ ++mIter;
+ }
+ }
+
+ /**
+ * Checks if any more tokens are available.
+ */
+ bool hasMoreTokens() const {
+ MOZ_ASSERT(mIter == mEnd || !IsWhitespace(*mIter),
+ "Should be at beginning of token if there is one");
+
+ if constexpr (Flags & nsTokenizerFlags::IncludeEmptyTokenAtEnd) {
+ return mIter < mEnd || (mIter == mEnd && mSeparatorAfterCurrentToken);
+ } else {
+ return mIter < mEnd;
+ }
+ }
+
+ /*
+ * Returns true if there is whitespace prior to the first token.
+ */
+ bool whitespaceBeforeFirstToken() const {
+ return mWhitespaceBeforeFirstToken;
+ }
+
+ /*
+ * Returns true if there is a separator after the current token.
+ * Useful if you want to check whether the last token has a separator
+ * after it which may not be valid.
+ */
+ bool separatorAfterCurrentToken() const {
+ return mSeparatorAfterCurrentToken;
+ }
+
+ /*
+ * Returns true if there is any whitespace after the current token.
+ */
+ bool whitespaceAfterCurrentToken() const {
+ return mWhitespaceAfterCurrentToken;
+ }
+
+ /**
+ * Returns the next token.
+ */
+ const DependentSubstringType nextToken() {
+ mozilla::RangedPtr<const CharType> tokenStart = mIter;
+ mozilla::RangedPtr<const CharType> tokenEnd = mIter;
+
+ MOZ_ASSERT(mIter == mEnd || !IsWhitespace(*mIter),
+ "Should be at beginning of token if there is one");
+
+ // Search until we hit separator or end (or whitespace, if a separator
+ // isn't required -- see clause with 'break' below).
+ while (mIter < mEnd && *mIter != mSeparatorChar) {
+ // Skip to end of the current word.
+ while (mIter < mEnd && !IsWhitespace(*mIter) &&
+ *mIter != mSeparatorChar) {
+ ++mIter;
+ }
+ tokenEnd = mIter;
+
+ // Skip whitespace after the current word.
+ mWhitespaceAfterCurrentToken = false;
+ while (mIter < mEnd && IsWhitespace(*mIter)) {
+ mWhitespaceAfterCurrentToken = true;
+ ++mIter;
+ }
+ if constexpr (Flags & nsTokenizerFlags::SeparatorOptional) {
+ // We've hit (and skipped) whitespace, and that's sufficient to end
+ // our token, regardless of whether we've reached a SeparatorChar.
+ break;
+ } // (else, we'll keep looping until we hit mEnd or SeparatorChar)
+ }
+
+ mSeparatorAfterCurrentToken = (mIter != mEnd && *mIter == mSeparatorChar);
+ MOZ_ASSERT((Flags & nsTokenizerFlags::SeparatorOptional) ||
+ (mSeparatorAfterCurrentToken == (mIter < mEnd)),
+ "If we require a separator and haven't hit the end of "
+ "our string, then we shouldn't have left the loop "
+ "unless we hit a separator");
+
+ // Skip separator (and any whitespace after it), if we're at one.
+ if (mSeparatorAfterCurrentToken) {
+ ++mIter;
+
+ while (mIter < mEnd && IsWhitespace(*mIter)) {
+ mWhitespaceAfterCurrentToken = true;
+ ++mIter;
+ }
+ }
+
+ return Substring(tokenStart.get(), tokenEnd.get());
+ }
+
+ auto ToRange() const;
+
+ private:
+ mozilla::RangedPtr<const CharType> mIter;
+ const mozilla::RangedPtr<const CharType> mEnd;
+ const CharType mSeparatorChar;
+ bool mWhitespaceBeforeFirstToken;
+ bool mWhitespaceAfterCurrentToken;
+ bool mSeparatorAfterCurrentToken;
+};
+
+constexpr bool NS_TokenizerIgnoreNothing(char16_t) { return false; }
+
+template <bool IsWhitespace(char16_t), typename CharType,
+ nsTokenizerFlags Flags = nsTokenizerFlags::Default>
+using nsTCharSeparatedTokenizerTemplate =
+ nsTCharSeparatedTokenizer<nsTDependentSubstring<CharType>, IsWhitespace,
+ Flags>;
+
+template <bool IsWhitespace(char16_t),
+ nsTokenizerFlags Flags = nsTokenizerFlags::Default>
+using nsCharSeparatedTokenizerTemplate =
+ nsTCharSeparatedTokenizerTemplate<IsWhitespace, char16_t, Flags>;
+
+using nsCharSeparatedTokenizer =
+ nsCharSeparatedTokenizerTemplate<NS_IsAsciiWhitespace>;
+
+template <bool IsWhitespace(char16_t),
+ nsTokenizerFlags Flags = nsTokenizerFlags::Default>
+using nsCCharSeparatedTokenizerTemplate =
+ nsTCharSeparatedTokenizerTemplate<IsWhitespace, char, Flags>;
+
+using nsCCharSeparatedTokenizer =
+ nsCCharSeparatedTokenizerTemplate<NS_IsAsciiWhitespace>;
+
+/**
+ * Adapts a char separated tokenizer for use in a range-based for loop.
+ *
+ * Use this typically only indirectly, e.g. like
+ *
+ * for (const auto& token : nsCharSeparatedTokenizer(aText, ' ').ToRange()) {
+ * // ...
+ * }
+ */
+template <typename Tokenizer>
+class nsTokenizedRange {
+ public:
+ using DependentSubstringType = typename Tokenizer::DependentSubstringType;
+
+ explicit nsTokenizedRange(Tokenizer&& aTokenizer)
+ : mTokenizer(std::move(aTokenizer)) {}
+
+ struct EndSentinel {};
+ struct Iterator {
+ explicit Iterator(const Tokenizer& aTokenizer) : mTokenizer(aTokenizer) {
+ Next();
+ }
+
+ const DependentSubstringType& operator*() const { return *mCurrentToken; }
+
+ Iterator& operator++() {
+ Next();
+ return *this;
+ }
+
+ bool operator==(const EndSentinel&) const {
+ return mCurrentToken.isNothing();
+ }
+
+ bool operator!=(const EndSentinel&) const { return mCurrentToken.isSome(); }
+
+ private:
+ void Next() {
+ mCurrentToken.reset();
+
+ if (mTokenizer.hasMoreTokens()) {
+ mCurrentToken.emplace(mTokenizer.nextToken());
+ }
+ }
+
+ Tokenizer mTokenizer;
+ mozilla::Maybe<DependentSubstringType> mCurrentToken;
+ };
+
+ auto begin() const { return Iterator{mTokenizer}; }
+ auto end() const { return EndSentinel{}; }
+
+ private:
+ const Tokenizer mTokenizer;
+};
+
+template <typename TDependentSubstringType, bool IsWhitespace(char16_t),
+ nsTokenizerFlags Flags>
+auto nsTCharSeparatedTokenizer<TDependentSubstringType, IsWhitespace,
+ Flags>::ToRange() const {
+ return nsTokenizedRange{nsTCharSeparatedTokenizer{*this}};
+}
+
+// You should not need to instantiate this class directly.
+// Use nsTSubstring::Split instead.
+template <typename T>
+class nsTSubstringSplitter
+ : public nsTokenizedRange<nsTCharSeparatedTokenizerTemplate<
+ NS_TokenizerIgnoreNothing, T,
+ nsTokenizerFlags::IncludeEmptyTokenAtEnd>> {
+ public:
+ using nsTokenizedRange<nsTCharSeparatedTokenizerTemplate<
+ NS_TokenizerIgnoreNothing, T,
+ nsTokenizerFlags::IncludeEmptyTokenAtEnd>>::nsTokenizedRange;
+};
+
+extern template class nsTSubstringSplitter<char>;
+extern template class nsTSubstringSplitter<char16_t>;
+
+#endif /* __nsCharSeparatedTokenizer_h */
diff --git a/xpcom/ds/nsCheapSets.h b/xpcom/ds/nsCheapSets.h
new file mode 100644
index 0000000000..3d09ccfdb7
--- /dev/null
+++ b/xpcom/ds/nsCheapSets.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsCheapSets_h__
+#define __nsCheapSets_h__
+
+#include "nsTHashtable.h"
+#include <stdint.h>
+
+enum nsCheapSetOperator {
+ OpNext = 0, // enumerator says continue
+ OpRemove = 1, // enumerator says remove and continue
+};
+
+/**
+ * A set that takes up minimal size when there are 0 or 1 entries in the set.
+ * Use for cases where sizes of 0 and 1 are even slightly common.
+ */
+template <typename EntryType>
+class nsCheapSet {
+ public:
+ typedef typename EntryType::KeyType KeyType;
+ typedef nsCheapSetOperator (*Enumerator)(EntryType* aEntry, void* userArg);
+
+ nsCheapSet() : mState(ZERO) { mUnion.table = nullptr; }
+ ~nsCheapSet() { Clear(); }
+
+ /**
+ * Remove all entries.
+ */
+ void Clear() {
+ switch (mState) {
+ case ZERO:
+ break;
+ case ONE:
+ GetSingleEntry()->~EntryType();
+ break;
+ case MANY:
+ delete mUnion.table;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bogus state");
+ break;
+ }
+ mState = ZERO;
+ }
+
+ void Put(const KeyType aVal);
+
+ void Remove(const KeyType aVal);
+
+ bool Contains(const KeyType aVal) {
+ switch (mState) {
+ case ZERO:
+ return false;
+ case ONE:
+ return GetSingleEntry()->KeyEquals(EntryType::KeyToPointer(aVal));
+ case MANY:
+ return !!mUnion.table->GetEntry(aVal);
+ default:
+ MOZ_ASSERT_UNREACHABLE("bogus state");
+ return false;
+ }
+ }
+
+ uint32_t EnumerateEntries(Enumerator aEnumFunc, void* aUserArg) {
+ switch (mState) {
+ case ZERO:
+ return 0;
+ case ONE:
+ if (aEnumFunc(GetSingleEntry(), aUserArg) == OpRemove) {
+ GetSingleEntry()->~EntryType();
+ mState = ZERO;
+ }
+ return 1;
+ case MANY: {
+ uint32_t n = mUnion.table->Count();
+ for (auto iter = mUnion.table->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<EntryType*>(iter.Get());
+ if (aEnumFunc(entry, aUserArg) == OpRemove) {
+ iter.Remove();
+ }
+ }
+ return n;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("bogus state");
+ return 0;
+ }
+ }
+
+ private:
+ EntryType* GetSingleEntry() {
+ return reinterpret_cast<EntryType*>(&mUnion.singleEntry[0]);
+ }
+
+ enum SetState { ZERO, ONE, MANY };
+
+ union {
+ nsTHashtable<EntryType>* table;
+ char singleEntry[sizeof(EntryType)];
+ } mUnion;
+ enum SetState mState;
+};
+
+template <typename EntryType>
+void nsCheapSet<EntryType>::Put(const KeyType aVal) {
+ switch (mState) {
+ case ZERO:
+ new (GetSingleEntry()) EntryType(EntryType::KeyToPointer(aVal));
+ mState = ONE;
+ return;
+ case ONE: {
+ nsTHashtable<EntryType>* table = new nsTHashtable<EntryType>();
+ EntryType* entry = GetSingleEntry();
+ table->PutEntry(entry->GetKey());
+ entry->~EntryType();
+ mUnion.table = table;
+ mState = MANY;
+ }
+ [[fallthrough]];
+
+ case MANY:
+ mUnion.table->PutEntry(aVal);
+ return;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bogus state");
+ return;
+ }
+}
+
+template <typename EntryType>
+void nsCheapSet<EntryType>::Remove(const KeyType aVal) {
+ switch (mState) {
+ case ZERO:
+ break;
+ case ONE:
+ if (Contains(aVal)) {
+ GetSingleEntry()->~EntryType();
+ mState = ZERO;
+ }
+ break;
+ case MANY:
+ mUnion.table->RemoveEntry(aVal);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bogus state");
+ break;
+ }
+}
+
+#endif
diff --git a/xpcom/ds/nsClassHashtable.h b/xpcom/ds/nsClassHashtable.h
new file mode 100644
index 0000000000..a1a2384f7e
--- /dev/null
+++ b/xpcom/ds/nsClassHashtable.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsClassHashtable_h__
+#define nsClassHashtable_h__
+
+#include <utility>
+
+#include "mozilla/UniquePtr.h"
+#include "nsBaseHashtable.h"
+#include "nsHashKeys.h"
+
+/**
+ * Helper class that provides methods to wrap and unwrap the UserDataType.
+ */
+template <class T>
+class nsUniquePtrConverter {
+ public:
+ using UserDataType = T*;
+ using DataType = mozilla::UniquePtr<T>;
+
+ static UserDataType Unwrap(DataType& src) { return src.get(); }
+ static DataType Wrap(UserDataType&& src) { return DataType(std::move(src)); }
+ static DataType Wrap(const UserDataType& src) { return DataType(src); }
+};
+
+/**
+ * templated hashtable class maps keys to C++ object pointers.
+ * See nsBaseHashtable for complete declaration.
+ * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h
+ * for a complete specification.
+ * @param Class the class-type being wrapped
+ * @see nsInterfaceHashtable, nsClassHashtable
+ */
+template <class KeyClass, class T>
+class nsClassHashtable : public nsBaseHashtable<KeyClass, mozilla::UniquePtr<T>,
+ T*, nsUniquePtrConverter<T>> {
+ public:
+ typedef typename KeyClass::KeyType KeyType;
+ typedef T* UserDataType;
+ typedef nsBaseHashtable<KeyClass, mozilla::UniquePtr<T>, T*,
+ nsUniquePtrConverter<T>>
+ base_type;
+
+ using base_type::IsEmpty;
+ using base_type::Remove;
+
+ nsClassHashtable() = default;
+ explicit nsClassHashtable(uint32_t aInitLength) : base_type(aInitLength) {}
+
+ /**
+ * @copydoc nsBaseHashtable::Get
+ * @param aData if the key doesn't exist, pData will be set to nullptr.
+ */
+ bool Get(KeyType aKey, UserDataType* aData) const;
+
+ /**
+ * @copydoc nsBaseHashtable::Get
+ * @returns nullptr if the key is not present.
+ */
+ [[nodiscard]] UserDataType Get(KeyType aKey) const;
+};
+
+template <typename K, typename T>
+inline void ImplCycleCollectionUnlink(nsClassHashtable<K, T>& aField) {
+ aField.Clear();
+}
+
+template <typename K, typename T>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ const nsClassHashtable<K, T>& aField, const char* aName,
+ uint32_t aFlags = 0) {
+ for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) {
+ ImplCycleCollectionTraverse(aCallback, *iter.UserData(), aName, aFlags);
+ }
+}
+
+//
+// nsClassHashtable definitions
+//
+
+template <class KeyClass, class T>
+bool nsClassHashtable<KeyClass, T>::Get(KeyType aKey, T** aRetVal) const {
+ typename base_type::EntryType* ent = this->GetEntry(aKey);
+
+ if (ent) {
+ if (aRetVal) {
+ *aRetVal = ent->GetData().get();
+ }
+
+ return true;
+ }
+
+ if (aRetVal) {
+ *aRetVal = nullptr;
+ }
+
+ return false;
+}
+
+template <class KeyClass, class T>
+T* nsClassHashtable<KeyClass, T>::Get(KeyType aKey) const {
+ typename base_type::EntryType* ent = this->GetEntry(aKey);
+ if (!ent) {
+ return nullptr;
+ }
+
+ return ent->GetData().get();
+}
+
+#endif // nsClassHashtable_h__
diff --git a/xpcom/ds/nsDeque.cpp b/xpcom/ds/nsDeque.cpp
new file mode 100644
index 0000000000..07f61fa471
--- /dev/null
+++ b/xpcom/ds/nsDeque.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDeque.h"
+#include "nsISupportsImpl.h"
+#include <string.h>
+
+#include "mozilla/CheckedInt.h"
+
+#define modulus(x, y) ((x) % (y))
+
+namespace mozilla {
+namespace detail {
+
+/**
+ * Standard constructor
+ * @param deallocator, called by Erase and ~nsDequeBase
+ */
+nsDequeBase::nsDequeBase() {
+ MOZ_COUNT_CTOR(nsDequeBase);
+ mOrigin = mSize = 0;
+ mData = mBuffer; // don't allocate space until you must
+ mCapacity = sizeof(mBuffer) / sizeof(mBuffer[0]);
+ memset(mData, 0, mCapacity * sizeof(mBuffer[0]));
+}
+
+/**
+ * Destructor
+ */
+nsDequeBase::~nsDequeBase() {
+ MOZ_COUNT_DTOR(nsDequeBase);
+
+ if (mData && mData != mBuffer) {
+ free(mData);
+ }
+ mData = nullptr;
+}
+
+size_t nsDequeBase::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t size = 0;
+ if (mData != mBuffer) {
+ size += aMallocSizeOf(mData);
+ }
+
+ return size;
+}
+
+/**
+ * Remove all items from container without destroying them.
+ */
+void nsDequeBase::Empty() {
+ if (mSize && mData) {
+ memset(mData, 0, mCapacity * sizeof(*mData));
+ }
+ mSize = 0;
+ mOrigin = 0;
+}
+
+/**
+ * This method quadruples the size of the deque
+ * Elements in the deque are resequenced so that elements
+ * in the deque are stored sequentially
+ *
+ * @return whether growing succeeded
+ */
+bool nsDequeBase::GrowCapacity() {
+ mozilla::CheckedInt<size_t> newCapacity = mCapacity;
+ newCapacity *= 4;
+
+ NS_ASSERTION(newCapacity.isValid(), "Overflow");
+ if (!newCapacity.isValid()) {
+ return false;
+ }
+
+ // Sanity check the new byte size.
+ mozilla::CheckedInt<size_t> newByteSize = newCapacity;
+ newByteSize *= sizeof(void*);
+
+ NS_ASSERTION(newByteSize.isValid(), "Overflow");
+ if (!newByteSize.isValid()) {
+ return false;
+ }
+
+ void** temp = (void**)malloc(newByteSize.value());
+ if (!temp) {
+ return false;
+ }
+
+ // Here's the interesting part: You can't just move the elements
+ // directly (in situ) from the old buffer to the new one.
+ // Since capacity has changed, the old origin doesn't make
+ // sense anymore. It's better to resequence the elements now.
+
+ memcpy(temp, mData + mOrigin, sizeof(void*) * (mCapacity - mOrigin));
+ memcpy(temp + (mCapacity - mOrigin), mData, sizeof(void*) * mOrigin);
+
+ if (mData != mBuffer) {
+ free(mData);
+ }
+
+ mCapacity = newCapacity.value();
+ mOrigin = 0; // now realign the origin...
+ mData = temp;
+
+ return true;
+}
+
+/**
+ * This method adds an item to the end of the deque.
+ * This operation has the potential to cause the
+ * underlying buffer to resize.
+ *
+ * @param aItem: new item to be added to deque
+ */
+bool nsDequeBase::Push(void* aItem, const fallible_t&) {
+ if (mSize == mCapacity && !GrowCapacity()) {
+ return false;
+ }
+ mData[modulus(mOrigin + mSize, mCapacity)] = aItem;
+ mSize++;
+ return true;
+}
+
+/**
+ * This method adds an item to the front of the deque.
+ * This operation has the potential to cause the
+ * underlying buffer to resize.
+ *
+ * --Commments for GrowCapacity() case
+ * We've grown and shifted which means that the old
+ * final element in the deque is now the first element
+ * in the deque. This is temporary.
+ * We haven't inserted the new element at the front.
+ *
+ * To continue with the idea of having the front at zero
+ * after a grow, we move the old final item (which through
+ * the voodoo of mOrigin-- is now the first) to its final
+ * position which is conveniently the old length.
+ *
+ * Note that this case only happens when the deque is full.
+ * [And that pieces of this magic only work if the deque is full.]
+ * picture:
+ * [ABCDEFGH] @[mOrigin:3]:D.
+ * Task: PushFront("Z")
+ * shift mOrigin so, @[mOrigin:2]:C
+ * stretch and rearrange: (mOrigin:0)
+ * [CDEFGHAB ________ ________ ________]
+ * copy: (The second C is currently out of bounds)
+ * [CDEFGHAB C_______ ________ ________]
+ * later we will insert Z:
+ * [ZDEFGHAB C_______ ________ ________]
+ * and increment size: 9. (C is no longer out of bounds)
+ * --
+ * @param aItem: new item to be added to deque
+ */
+bool nsDequeBase::PushFront(void* aItem, const fallible_t&) {
+ if (mOrigin == 0) {
+ mOrigin = mCapacity - 1;
+ } else {
+ mOrigin--;
+ }
+
+ if (mSize == mCapacity) {
+ if (!GrowCapacity()) {
+ return false;
+ }
+ /* Comments explaining this are above*/
+ mData[mSize] = mData[mOrigin];
+ }
+ mData[mOrigin] = aItem;
+ mSize++;
+ return true;
+}
+
+/**
+ * Remove and return the last item in the container.
+ *
+ * @return ptr to last item in container
+ */
+void* nsDequeBase::Pop() {
+ void* result = nullptr;
+ if (mSize > 0) {
+ --mSize;
+ size_t offset = modulus(mSize + mOrigin, mCapacity);
+ result = mData[offset];
+ mData[offset] = nullptr;
+ if (!mSize) {
+ mOrigin = 0;
+ }
+ }
+ return result;
+}
+
+/**
+ * This method gets called you want to remove and return
+ * the first member in the container.
+ *
+ * @return last item in container
+ */
+void* nsDequeBase::PopFront() {
+ void* result = nullptr;
+ if (mSize > 0) {
+ NS_ASSERTION(mOrigin < mCapacity, "Error: Bad origin");
+ result = mData[mOrigin];
+ mData[mOrigin++] = nullptr; // zero it out for debugging purposes.
+ mSize--;
+ // Cycle around if we pop off the end
+ // and reset origin if when we pop the last element
+ if (mCapacity == mOrigin || !mSize) {
+ mOrigin = 0;
+ }
+ }
+ return result;
+}
+
+/**
+ * This method gets called you want to peek at the bottom
+ * member without removing it.
+ *
+ * @return last item in container
+ */
+void* nsDequeBase::Peek() const {
+ void* result = nullptr;
+ if (mSize > 0) {
+ result = mData[modulus(mSize - 1 + mOrigin, mCapacity)];
+ }
+ return result;
+}
+
+/**
+ * This method gets called you want to peek at the topmost
+ * member without removing it.
+ *
+ * @return last item in container
+ */
+void* nsDequeBase::PeekFront() const {
+ void* result = nullptr;
+ if (mSize > 0) {
+ result = mData[mOrigin];
+ }
+ return result;
+}
+
+/**
+ * Call this to retrieve the ith element from this container.
+ * Keep in mind that accessing the underlying elements is
+ * done in a relative fashion. Object 0 is not necessarily
+ * the first element (the first element is at mOrigin).
+ *
+ * @param aIndex : 0 relative offset of item you want
+ * @return void* or null
+ */
+void* nsDequeBase::ObjectAt(size_t aIndex) const {
+ void* result = nullptr;
+ if (aIndex < mSize) {
+ result = mData[modulus(mOrigin + aIndex, mCapacity)];
+ }
+ return result;
+}
+} // namespace detail
+} // namespace mozilla
diff --git a/xpcom/ds/nsDeque.h b/xpcom/ds/nsDeque.h
new file mode 100644
index 0000000000..4f0bf27037
--- /dev/null
+++ b/xpcom/ds/nsDeque.h
@@ -0,0 +1,538 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * MODULE NOTES:
+ *
+ * The Deque is a very small, very efficient container object
+ * than can hold items of type T*, offering the following features:
+ * - Its interface supports pushing, popping, and peeking of items at the back
+ * or front, and retrieval from any position.
+ * - It can iterate over items via a ForEach method, range-for, or an iterator
+ * class.
+ * - When full, it can efficiently resize dynamically.
+ *
+ * NOTE: The only bit of trickery here is that this deque is
+ * built upon a ring-buffer. Like all ring buffers, the first
+ * item may not be at index[0]. The mOrigin member determines
+ * where the first child is. This point is quietly hidden from
+ * customers of this class.
+ */
+
+#ifndef _NSDEQUE
+#define _NSDEQUE
+#include <cstddef>
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/fallible.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsISupports.h"
+
+namespace mozilla {
+
+namespace detail {
+/**
+ * The deque (double-ended queue) class is a common container type,
+ * whose behavior mimics a line in your favorite checkout stand.
+ * Classic CS describes the common behavior of a queue as FIFO.
+ * A deque allows insertion and removal at both ends of
+ * the container.
+ *
+ * nsDequeBase implements the untyped deque data structure while
+ * nsDeque provides the typed implementation and iterators.
+ */
+class nsDequeBase {
+ public:
+ /**
+ * Returns the number of items currently stored in
+ * this deque.
+ *
+ * @return number of items currently in the deque
+ */
+ inline size_t GetSize() const { return mSize; }
+
+ protected:
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ /**
+ * Constructs an empty deque.
+ *
+ * @param aDeallocator Optional deallocator functor that will be called from
+ * Erase() and the destructor on any remaining item.
+ * The deallocator is owned by the deque and will be
+ * deleted at destruction time.
+ */
+ explicit nsDequeBase();
+
+ /**
+ * Deque destructor. Erases all items, deletes the deallocator.
+ */
+ ~nsDequeBase();
+
+ /**
+ * Appends new member at the end of the deque.
+ *
+ * @param aItem item to store in deque
+ * @return true if succeeded, false if failed to resize deque as needed
+ */
+ [[nodiscard]] bool Push(void* aItem, const fallible_t&);
+
+ /**
+ * Inserts new member at the front of the deque.
+ *
+ * @param aItem item to store in deque
+ * @return true if succeeded, false if failed to resize deque as needed
+ */
+ [[nodiscard]] bool PushFront(void* aItem, const fallible_t&);
+
+ /**
+ * Remove and return the last item in the container.
+ *
+ * @return the item that was the last item in container
+ */
+ void* Pop();
+
+ /**
+ * Remove and return the first item in the container.
+ *
+ * @return the item that was first item in container
+ */
+ void* PopFront();
+
+ /**
+ * Retrieve the last item without removing it.
+ *
+ * @return the last item in container
+ */
+ void* Peek() const;
+
+ /**
+ * Retrieve the first item without removing it.
+ *
+ * @return the first item in container
+ */
+ void* PeekFront() const;
+
+ /**
+ * Retrieve a member from the deque without removing it.
+ *
+ * @param index of desired item
+ * @return item in list, or nullptr if index is outside the deque
+ */
+ void* ObjectAt(size_t aIndex) const;
+
+ bool GrowCapacity();
+
+ /**
+ * Remove all items from container without destroying them.
+ */
+ void Empty();
+
+ size_t mSize;
+ size_t mCapacity;
+ size_t mOrigin;
+ void* mBuffer[8];
+ void** mData;
+
+ nsDequeBase& operator=(const nsDequeBase& aOther) = delete;
+ nsDequeBase(const nsDequeBase& aOther) = delete;
+};
+
+// This iterator assumes that the deque itself is const, i.e., it cannot be
+// modified while the iterator is used.
+// Also it is a 'const' iterator in that it provides copies of the deque's
+// elements, and therefore it is not possible to modify the deque's contents
+// by assigning to a dereference of this iterator.
+template <typename Deque>
+class ConstDequeIterator {
+ public:
+ ConstDequeIterator(const Deque& aDeque, size_t aIndex)
+ : mDeque(aDeque), mIndex(aIndex) {}
+ ConstDequeIterator& operator++() {
+ ++mIndex;
+ return *this;
+ }
+ bool operator==(const ConstDequeIterator& aOther) const {
+ return mIndex == aOther.mIndex;
+ }
+ bool operator!=(const ConstDequeIterator& aOther) const {
+ return mIndex != aOther.mIndex;
+ }
+ typename Deque::PointerType operator*() const {
+ // Don't allow out-of-deque dereferences.
+ MOZ_RELEASE_ASSERT(mIndex < mDeque.GetSize());
+ return mDeque.ObjectAt(mIndex);
+ }
+
+ private:
+ const Deque& mDeque;
+ size_t mIndex;
+};
+
+// It is a 'const' iterator in that it provides copies of the deque's
+// elements, and therefore it is not possible to modify the deque's contents
+// by assigning to a dereference of this iterator.
+// If the deque is modified in other ways, this iterator will stay at the same
+// index, and will handle past-the-end comparisons, but not dereferencing.
+template <typename Deque>
+class ConstIterator {
+ public:
+ // Special index for the end iterator, to track the possibly-shifting
+ // deque size.
+ static const size_t EndIteratorIndex = size_t(-1);
+
+ ConstIterator(const Deque& aDeque, size_t aIndex)
+ : mDeque(aDeque), mIndex(aIndex) {}
+ ConstIterator& operator++() {
+ // End-iterator shouldn't be modified.
+ MOZ_ASSERT(mIndex != EndIteratorIndex);
+ ++mIndex;
+ return *this;
+ }
+ bool operator==(const ConstIterator& aOther) const {
+ return EffectiveIndex() == aOther.EffectiveIndex();
+ }
+ bool operator!=(const ConstIterator& aOther) const {
+ return EffectiveIndex() != aOther.EffectiveIndex();
+ }
+ typename Deque::PointerType operator*() const {
+ // Don't allow out-of-deque dereferences.
+ MOZ_RELEASE_ASSERT(mIndex < mDeque.GetSize());
+ return mDeque.ObjectAt(mIndex);
+ }
+
+ private:
+ // 0 <= index < deque.GetSize() inside the deque, deque.GetSize() otherwise.
+ // Only used when comparing indices, not to actually access items.
+ size_t EffectiveIndex() const {
+ return (mIndex < mDeque.GetSize()) ? mIndex : mDeque.GetSize();
+ }
+
+ const Deque& mDeque;
+ size_t mIndex; // May point outside the deque!
+};
+
+} // namespace detail
+} // namespace mozilla
+
+/**
+ * The nsDequeFunctor class is used when you want to create
+ * callbacks between the deque and your generic code.
+ * Use these objects in a call to ForEach(), and as custom deallocators.
+ */
+template <typename T>
+class nsDequeFunctor {
+ public:
+ virtual void operator()(T* aObject) = 0;
+ virtual ~nsDequeFunctor() = default;
+};
+
+/******************************************************
+ * Here comes the nsDeque class itself...
+ ******************************************************/
+
+/**
+ * The deque (double-ended queue) class is a common container type,
+ * whose behavior mimics a line in your favorite checkout stand.
+ * Classic CS describes the common behavior of a queue as FIFO.
+ * A deque allows insertion and removal at both ends of
+ * the container.
+ *
+ * The deque stores pointers to items.
+ */
+template <typename T>
+class nsDeque : public mozilla::detail::nsDequeBase {
+ typedef mozilla::fallible_t fallible_t;
+
+ public:
+ using PointerType = T*;
+ using ConstDequeIterator = mozilla::detail::ConstDequeIterator<nsDeque<T>>;
+ using ConstIterator = mozilla::detail::ConstIterator<nsDeque<T>>;
+
+ /**
+ * Constructs an empty deque.
+ *
+ * @param aDeallocator Optional deallocator functor that will be called from
+ * Erase() and the destructor on any remaining item.
+ * The deallocator is owned by the deque and will be
+ * deleted at destruction time.
+ */
+ explicit nsDeque(nsDequeFunctor<T>* aDeallocator = nullptr) {
+ MOZ_COUNT_CTOR(nsDeque);
+ mDeallocator = aDeallocator;
+ }
+
+ /**
+ * Deque destructor. Erases all items, deletes the deallocator.
+ */
+ ~nsDeque() {
+ MOZ_COUNT_DTOR(nsDeque);
+
+ Erase();
+ SetDeallocator(nullptr);
+ }
+
+ /**
+ * Appends new member at the end of the deque.
+ *
+ * @param aItem item to store in deque
+ */
+ inline void Push(T* aItem) {
+ if (!nsDequeBase::Push(aItem, mozilla::fallible)) {
+ NS_ABORT_OOM(mSize * sizeof(T*));
+ }
+ }
+
+ /**
+ * Appends new member at the end of the deque.
+ *
+ * @param aItem item to store in deque
+ * @return true if succeeded, false if failed to resize deque as needed
+ */
+ [[nodiscard]] inline bool Push(T* aItem, const fallible_t& aFaillible) {
+ return nsDequeBase::Push(aItem, aFaillible);
+ }
+
+ /**
+ * Inserts new member at the front of the deque.
+ *
+ * @param aItem item to store in deque
+ */
+ inline void PushFront(T* aItem) {
+ if (!nsDequeBase::PushFront(aItem, mozilla::fallible)) {
+ NS_ABORT_OOM(mSize * sizeof(T*));
+ }
+ }
+
+ /**
+ * Inserts new member at the front of the deque.
+ *
+ * @param aItem item to store in deque
+ * @return true if succeeded, false if failed to resize deque as needed
+ */
+ [[nodiscard]] bool PushFront(T* aItem, const fallible_t& aFallible) {
+ return nsDequeBase::PushFront(aItem, aFallible);
+ }
+
+ /**
+ * Remove and return the last item in the container.
+ *
+ * @return the item that was the last item in container
+ */
+ inline T* Pop() { return static_cast<T*>(nsDequeBase::Pop()); }
+
+ /**
+ * Remove and return the first item in the container.
+ *
+ * @return the item that was first item in container
+ */
+ inline T* PopFront() { return static_cast<T*>(nsDequeBase::PopFront()); }
+
+ /**
+ * Retrieve the last item without removing it.
+ *
+ * @return the last item in container
+ */
+ inline T* Peek() const { return static_cast<T*>(nsDequeBase::Peek()); }
+
+ /**
+ * Retrieve the first item without removing it.
+ *
+ * @return the first item in container
+ */
+ inline T* PeekFront() const {
+ return static_cast<T*>(nsDequeBase::PeekFront());
+ }
+
+ /**
+ * Retrieve a member from the deque without removing it.
+ *
+ * @param index of desired item
+ * @return item in list, or nullptr if index is outside the deque
+ */
+ inline T* ObjectAt(size_t aIndex) const {
+ if (NS_WARN_IF(aIndex >= GetSize())) {
+ return nullptr;
+ }
+ return static_cast<T*>(nsDequeBase::ObjectAt(aIndex));
+ }
+
+ /**
+ * Remove and delete all items from container.
+ * Deletes are handled by the deallocator nsDequeFunctor
+ * which is specified at deque construction.
+ */
+ void Erase() {
+ if (mDeallocator && mSize) {
+ ForEach(*mDeallocator);
+ }
+ Empty();
+ }
+
+ /**
+ * Call this method when you want to iterate through all
+ * items in the container, passing a functor along
+ * to call your code.
+ * If the deque is modified during ForEach, iteration will continue based on
+ * item indices; meaning that front operations may effectively skip over
+ * items or visit some items multiple times.
+ *
+ * @param aFunctor object to call for each member
+ */
+ void ForEach(nsDequeFunctor<T>& aFunctor) const {
+ for (size_t i = 0; i < mSize; ++i) {
+ aFunctor(ObjectAt(i));
+ }
+ }
+
+ // If this deque is const, we can provide ConstDequeIterator's.
+ ConstDequeIterator begin() const { return ConstDequeIterator(*this, 0); }
+ ConstDequeIterator end() const { return ConstDequeIterator(*this, mSize); }
+
+ // If this deque is *not* const, we provide ConstIterator's that can handle
+ // deque size changes.
+ ConstIterator begin() { return ConstIterator(*this, 0); }
+ ConstIterator end() {
+ return ConstIterator(*this, ConstIterator::EndIteratorIndex);
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t size = nsDequeBase::SizeOfExcludingThis(aMallocSizeOf);
+ if (mDeallocator) {
+ size += aMallocSizeOf(mDeallocator);
+ }
+ return size;
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ protected:
+ nsDequeFunctor<T>* mDeallocator;
+
+ private:
+ /**
+ * Copy constructor (deleted)
+ *
+ * @param aOther another deque
+ */
+ nsDeque(const nsDeque& aOther) = delete;
+
+ /**
+ * Deque assignment operator (deleted)
+ *
+ * @param aOther another deque
+ * @return *this
+ */
+ nsDeque& operator=(const nsDeque& aOther) = delete;
+
+ void SetDeallocator(nsDequeFunctor<T>* aDeallocator) {
+ delete mDeallocator;
+ mDeallocator = aDeallocator;
+ }
+};
+
+/**
+ * Specialization of nsDeque for reference counted pointers.
+ *
+ * Provides builtin reference handling as part of Push/Peek/Pop
+ */
+template <typename T>
+class nsRefPtrDeque : private nsDeque<T> {
+ typedef mozilla::fallible_t fallible_t;
+
+ class RefPtrDeallocator : public nsDequeFunctor<T> {
+ public:
+ virtual void operator()(T* aObject) override {
+ RefPtr<T> releaseMe = dont_AddRef(aObject);
+ }
+ };
+
+ public:
+ using PointerType = RefPtr<T>;
+ using ConstDequeIterator =
+ mozilla::detail::ConstDequeIterator<nsRefPtrDeque<T>>;
+ using ConstIterator = mozilla::detail::ConstIterator<nsRefPtrDeque<T>>;
+
+ explicit nsRefPtrDeque() : nsDeque<T>(new RefPtrDeallocator()) {}
+
+ inline void PushFront(already_AddRefed<T> aItem) {
+ T* item = aItem.take();
+ nsDeque<T>::PushFront(item);
+ }
+
+ inline void PushFront(T* aItem) { PushFront(do_AddRef(aItem)); }
+
+ inline void Push(T* aItem) { Push(do_AddRef(aItem)); }
+
+ inline void Push(already_AddRefed<T> aItem) {
+ T* item = aItem.take();
+ nsDeque<T>::Push(item);
+ }
+
+ inline already_AddRefed<T> PopFront() {
+ return dont_AddRef(nsDeque<T>::PopFront());
+ }
+
+ inline already_AddRefed<T> Pop() { return dont_AddRef(nsDeque<T>::Pop()); }
+
+ inline T* PeekFront() const { return nsDeque<T>::PeekFront(); }
+
+ inline T* Peek() const { return nsDeque<T>::Peek(); }
+
+ inline T* ObjectAt(size_t aIndex) const {
+ return nsDeque<T>::ObjectAt(aIndex);
+ }
+
+ inline void Erase() { nsDeque<T>::Erase(); }
+
+ // If this deque is const, we can provide ConstDequeIterator's.
+ ConstDequeIterator begin() const { return ConstDequeIterator(*this, 0); }
+ ConstDequeIterator end() const {
+ return ConstDequeIterator(*this, GetSize());
+ }
+
+ // If this deque is *not* const, we provide ConstIterator's that can handle
+ // deque size changes.
+ ConstIterator begin() { return ConstIterator(*this, 0); }
+ ConstIterator end() {
+ return ConstIterator(*this, ConstIterator::EndIteratorIndex);
+ }
+
+ inline size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return nsDeque<T>::SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ inline size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return nsDeque<T>::SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ inline size_t GetSize() const { return nsDeque<T>::GetSize(); }
+
+ /**
+ * Call this method when you want to iterate through all
+ * items in the container, passing a functor along
+ * to call your code.
+ * If the deque is modified during ForEach, iteration will continue based on
+ * item indices; meaning that front operations may effectively skip over
+ * items or visit some items multiple times.
+ *
+ * @param aFunctor object to call for each member
+ */
+ void ForEach(nsDequeFunctor<T>& aFunctor) const {
+ size_t size = GetSize();
+ for (size_t i = 0; i < size; ++i) {
+ aFunctor(ObjectAt(i));
+ }
+ }
+};
+
+#endif
diff --git a/xpcom/ds/nsEnumeratorUtils.cpp b/xpcom/ds/nsEnumeratorUtils.cpp
new file mode 100644
index 0000000000..69631818a3
--- /dev/null
+++ b/xpcom/ds/nsEnumeratorUtils.cpp
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+
+#include "nsEnumeratorUtils.h"
+
+#include "nsIStringEnumerator.h"
+#include "nsSimpleEnumerator.h"
+
+#include "nsCOMPtr.h"
+#include "mozilla/RefPtr.h"
+
+class EmptyEnumeratorImpl : public nsSimpleEnumerator,
+ public nsIUTF8StringEnumerator,
+ public nsIStringEnumerator {
+ public:
+ EmptyEnumeratorImpl() = default;
+
+ // nsISupports interface. Not really inherited, but no mRefCnt.
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsISimpleEnumerator
+ NS_DECL_NSISIMPLEENUMERATOR
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+ NS_DECL_NSISTRINGENUMERATORBASE
+ // can't use NS_DECL_NSISTRINGENUMERATOR because they share the
+ // HasMore() signature
+
+ NS_IMETHOD GetNext(nsAString& aResult) override;
+
+ static EmptyEnumeratorImpl* GetInstance() {
+ static const EmptyEnumeratorImpl kInstance;
+ return const_cast<EmptyEnumeratorImpl*>(&kInstance);
+ }
+};
+
+// nsISupports interface
+NS_IMETHODIMP_(MozExternalRefCountType)
+EmptyEnumeratorImpl::AddRef(void) { return 2; }
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+EmptyEnumeratorImpl::Release(void) { return 1; }
+
+NS_IMPL_QUERY_INTERFACE_INHERITED(EmptyEnumeratorImpl, nsSimpleEnumerator,
+ nsIUTF8StringEnumerator, nsIStringEnumerator)
+
+// nsISimpleEnumerator interface
+NS_IMETHODIMP
+EmptyEnumeratorImpl::HasMoreElements(bool* aResult) {
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EmptyEnumeratorImpl::HasMore(bool* aResult) {
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EmptyEnumeratorImpl::GetNext(nsISupports** aResult) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+EmptyEnumeratorImpl::GetNext(nsACString& aResult) {
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+EmptyEnumeratorImpl::GetNext(nsAString& aResult) { return NS_ERROR_UNEXPECTED; }
+
+NS_IMETHODIMP
+EmptyEnumeratorImpl::StringIterator(nsIJSEnumerator** aRetVal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult NS_NewEmptyEnumerator(nsISimpleEnumerator** aResult) {
+ *aResult = EmptyEnumeratorImpl::GetInstance();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsSingletonEnumerator final : public nsSimpleEnumerator {
+ public:
+ // nsISimpleEnumerator methods
+ NS_IMETHOD HasMoreElements(bool* aResult) override;
+ NS_IMETHOD GetNext(nsISupports** aResult) override;
+
+ explicit nsSingletonEnumerator(nsISupports* aValue);
+
+ private:
+ ~nsSingletonEnumerator() override;
+
+ protected:
+ nsCOMPtr<nsISupports> mValue;
+ bool mConsumed;
+};
+
+nsSingletonEnumerator::nsSingletonEnumerator(nsISupports* aValue)
+ : mValue(aValue) {
+ mConsumed = (mValue ? false : true);
+}
+
+nsSingletonEnumerator::~nsSingletonEnumerator() = default;
+
+NS_IMETHODIMP
+nsSingletonEnumerator::HasMoreElements(bool* aResult) {
+ MOZ_ASSERT(aResult != 0, "null ptr");
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ *aResult = !mConsumed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSingletonEnumerator::GetNext(nsISupports** aResult) {
+ MOZ_ASSERT(aResult != 0, "null ptr");
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (mConsumed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mConsumed = true;
+
+ *aResult = mValue;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+nsresult NS_NewSingletonEnumerator(nsISimpleEnumerator** aResult,
+ nsISupports* aSingleton) {
+ RefPtr<nsSingletonEnumerator> enumer = new nsSingletonEnumerator(aSingleton);
+ enumer.forget(aResult);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsUnionEnumerator final : public nsSimpleEnumerator {
+ public:
+ // nsISimpleEnumerator methods
+ NS_IMETHOD HasMoreElements(bool* aResult) override;
+ NS_IMETHOD GetNext(nsISupports** aResult) override;
+
+ nsUnionEnumerator(nsISimpleEnumerator* aFirstEnumerator,
+ nsISimpleEnumerator* aSecondEnumerator);
+
+ private:
+ ~nsUnionEnumerator() override;
+
+ protected:
+ nsCOMPtr<nsISimpleEnumerator> mFirstEnumerator, mSecondEnumerator;
+ bool mConsumed;
+ bool mAtSecond;
+};
+
+nsUnionEnumerator::nsUnionEnumerator(nsISimpleEnumerator* aFirstEnumerator,
+ nsISimpleEnumerator* aSecondEnumerator)
+ : mFirstEnumerator(aFirstEnumerator),
+ mSecondEnumerator(aSecondEnumerator),
+ mConsumed(false),
+ mAtSecond(false) {}
+
+nsUnionEnumerator::~nsUnionEnumerator() = default;
+
+NS_IMETHODIMP
+nsUnionEnumerator::HasMoreElements(bool* aResult) {
+ MOZ_ASSERT(aResult != 0, "null ptr");
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsresult rv;
+
+ if (mConsumed) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ if (!mAtSecond) {
+ rv = mFirstEnumerator->HasMoreElements(aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (*aResult) {
+ return NS_OK;
+ }
+
+ mAtSecond = true;
+ }
+
+ rv = mSecondEnumerator->HasMoreElements(aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (*aResult) {
+ return NS_OK;
+ }
+
+ *aResult = false;
+ mConsumed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUnionEnumerator::GetNext(nsISupports** aResult) {
+ MOZ_ASSERT(aResult != 0, "null ptr");
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (mConsumed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mAtSecond) {
+ return mFirstEnumerator->GetNext(aResult);
+ }
+
+ return mSecondEnumerator->GetNext(aResult);
+}
+
+nsresult NS_NewUnionEnumerator(nsISimpleEnumerator** aResult,
+ nsISimpleEnumerator* aFirstEnumerator,
+ nsISimpleEnumerator* aSecondEnumerator) {
+ *aResult = nullptr;
+ if (!aFirstEnumerator) {
+ *aResult = aSecondEnumerator;
+ } else if (!aSecondEnumerator) {
+ *aResult = aFirstEnumerator;
+ } else {
+ auto* enumer = new nsUnionEnumerator(aFirstEnumerator, aSecondEnumerator);
+ *aResult = enumer;
+ }
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
diff --git a/xpcom/ds/nsEnumeratorUtils.h b/xpcom/ds/nsEnumeratorUtils.h
new file mode 100644
index 0000000000..f7a0db099e
--- /dev/null
+++ b/xpcom/ds/nsEnumeratorUtils.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsEnumeratorUtils_h__
+#define nsEnumeratorUtils_h__
+
+#include "nscore.h"
+
+class nsISupports;
+class nsISimpleEnumerator;
+
+nsresult NS_NewEmptyEnumerator(nsISimpleEnumerator** aResult);
+
+nsresult NS_NewSingletonEnumerator(nsISimpleEnumerator** aResult,
+ nsISupports* aSingleton);
+
+nsresult NS_NewUnionEnumerator(nsISimpleEnumerator** aResult,
+ nsISimpleEnumerator* aFirstEnumerator,
+ nsISimpleEnumerator* aSecondEnumerator);
+
+#endif /* nsEnumeratorUtils_h__ */
diff --git a/xpcom/ds/nsExpirationTracker.h b/xpcom/ds/nsExpirationTracker.h
new file mode 100644
index 0000000000..ec2a25f67d
--- /dev/null
+++ b/xpcom/ds/nsExpirationTracker.h
@@ -0,0 +1,618 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSEXPIRATIONTRACKER_H_
+#define NSEXPIRATIONTRACKER_H_
+
+#include <cstring>
+#include "MainThreadUtils.h"
+#include "nsAlgorithm.h"
+#include "nsDebug.h"
+#include "nsTArray.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsIEventTarget.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsISupports.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefCountType.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Services.h"
+
+/**
+ * Data used to track the expiration state of an object. We promise that this
+ * is 32 bits so that objects that includes this as a field can pad and align
+ * efficiently.
+ */
+struct nsExpirationState {
+ enum {
+ NOT_TRACKED = (1U << 4) - 1,
+ MAX_INDEX_IN_GENERATION = (1U << 28) - 1
+ };
+
+ nsExpirationState() : mGeneration(NOT_TRACKED), mIndexInGeneration(0) {}
+ bool IsTracked() { return mGeneration != NOT_TRACKED; }
+
+ /**
+ * The generation that this object belongs to, or NOT_TRACKED.
+ */
+ uint32_t mGeneration : 4;
+ uint32_t mIndexInGeneration : 28;
+};
+
+/**
+ * ExpirationTracker classes:
+ * - ExpirationTrackerImpl (Thread-safe class)
+ * - nsExpirationTracker (Main-thread only class)
+ *
+ * These classes can track the lifetimes and usage of a large number of
+ * objects, and send a notification some window of time after a live object was
+ * last used. This is very useful when you manage a large number of objects
+ * and want to flush some after they haven't been used for a while.
+ * nsExpirationTracker is designed to be very space and time efficient.
+ *
+ * The type parameter T is the object type that we will track pointers to. T
+ * must include an accessible method GetExpirationState() that returns a
+ * pointer to an nsExpirationState associated with the object (preferably,
+ * stored in a field of the object).
+ *
+ * The parameter K is the number of generations that will be used. Increasing
+ * the number of generations narrows the window within which we promise
+ * to fire notifications, at a slight increase in space cost for the tracker.
+ * We require 2 <= K <= nsExpirationState::NOT_TRACKED (currently 15).
+ *
+ * To use this class, you need to inherit from it and override the
+ * NotifyExpired() method.
+ *
+ * The approach is to track objects in K generations. When an object is accessed
+ * it moves from its current generation to the newest generation. Generations
+ * are stored in a cyclic array; when a timer interrupt fires, we advance
+ * the current generation pointer to effectively age all objects very
+ * efficiently. By storing information in each object about its generation and
+ * index within its generation array, we make removal of objects from a
+ * generation very cheap.
+ *
+ * Future work:
+ * -- Add a method to change the timer period?
+ */
+
+/**
+ * Base class for ExiprationTracker implementations.
+ *
+ * nsExpirationTracker class below is a specialized class to be inherited by the
+ * instances to be accessed only on main-thread.
+ *
+ * For creating a thread-safe tracker, you can define a subclass inheriting this
+ * base class and specialize the Mutex and AutoLock to be used.
+ *
+ * For an example of using ExpirationTrackerImpl with a DataMutex
+ * @see mozilla::gfx::GradientCache.
+ *
+ */
+template <typename T, uint32_t K, typename Mutex, typename AutoLock>
+class ExpirationTrackerImpl {
+ public:
+ /**
+ * Initialize the tracker.
+ * @param aTimerPeriod the timer period in milliseconds. The guarantees
+ * provided by the tracker are defined in terms of this period. If the
+ * period is zero, then we don't use a timer and rely on someone calling
+ * AgeOneGenerationLocked explicitly.
+ * @param aName the name of the subclass for telemetry.
+ * @param aEventTarget the optional event target on main thread to label the
+ * runnable of the asynchronous invocation to NotifyExpired().
+
+ */
+ ExpirationTrackerImpl(uint32_t aTimerPeriod, const char* aName,
+ nsIEventTarget* aEventTarget = nullptr)
+ : mTimerPeriod(aTimerPeriod),
+ mNewestGeneration(0),
+ mInAgeOneGeneration(false),
+ mName(aName),
+ mEventTarget(aEventTarget) {
+ static_assert(K >= 2 && K <= nsExpirationState::NOT_TRACKED,
+ "Unsupported number of generations (must be 2 <= K <= 15)");
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mEventTarget) {
+ bool current = false;
+ // NOTE: The following check+crash could be condensed into a
+ // MOZ_RELEASE_ASSERT, but that triggers a segfault during compilation in
+ // clang 3.8. Once we don't have to care about clang 3.8 anymore, though,
+ // we can convert to MOZ_RELEASE_ASSERT here.
+ if (MOZ_UNLIKELY(NS_FAILED(mEventTarget->IsOnCurrentThread(&current)) ||
+ !current)) {
+ MOZ_CRASH("Provided event target must be on the main thread");
+ }
+ }
+ mObserver = new ExpirationTrackerObserver();
+ mObserver->Init(this);
+ }
+
+ virtual ~ExpirationTrackerImpl() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ mObserver->Destroy();
+ }
+
+ /**
+ * Add an object to be tracked. It must not already be tracked. It will
+ * be added to the newest generation, i.e., as if it was just used.
+ * @return an error on out-of-memory
+ */
+ nsresult AddObjectLocked(T* aObj, const AutoLock& aAutoLock) {
+ if (NS_WARN_IF(!aObj)) {
+ MOZ_DIAGNOSTIC_ASSERT(false, "Invalid object to add");
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsExpirationState* state = aObj->GetExpirationState();
+ if (NS_WARN_IF(state->IsTracked())) {
+ MOZ_DIAGNOSTIC_ASSERT(false,
+ "Tried to add an object that's already tracked");
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsTArray<T*>& generation = mGenerations[mNewestGeneration];
+ uint32_t index = generation.Length();
+ if (index > nsExpirationState::MAX_INDEX_IN_GENERATION) {
+ NS_WARNING("More than 256M elements tracked, this is probably a problem");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (index == 0) {
+ // We might need to start the timer
+ nsresult rv = CheckStartTimerLocked(aAutoLock);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ generation.AppendElement(aObj);
+ state->mGeneration = mNewestGeneration;
+ state->mIndexInGeneration = index;
+ return NS_OK;
+ }
+
+ /**
+ * Remove an object from the tracker. It must currently be tracked.
+ */
+ void RemoveObjectLocked(T* aObj, const AutoLock& aAutoLock) {
+ if (NS_WARN_IF(!aObj)) {
+ MOZ_DIAGNOSTIC_ASSERT(false, "Invalid object to remove");
+ return;
+ }
+ nsExpirationState* state = aObj->GetExpirationState();
+ if (NS_WARN_IF(!state->IsTracked())) {
+ MOZ_DIAGNOSTIC_ASSERT(false,
+ "Tried to remove an object that's not tracked");
+ return;
+ }
+ nsTArray<T*>& generation = mGenerations[state->mGeneration];
+ uint32_t index = state->mIndexInGeneration;
+ MOZ_ASSERT(generation.Length() > index && generation[index] == aObj,
+ "Object is lying about its index");
+ // Move the last object to fill the hole created by removing aObj
+ T* lastObj = generation.PopLastElement();
+ // XXX It looks weird that index might point to the element that was just
+ // removed. Is that really correct?
+ if (index < generation.Length()) {
+ generation[index] = lastObj;
+ }
+ lastObj->GetExpirationState()->mIndexInGeneration = index;
+ state->mGeneration = nsExpirationState::NOT_TRACKED;
+ // We do not check whether we need to stop the timer here. The timer
+ // will check that itself next time it fires. Checking here would not
+ // be efficient since we'd need to track all generations. Also we could
+ // thrash by incessantly creating and destroying timers if someone
+ // kept adding and removing an object from the tracker.
+ }
+
+ /**
+ * Notify that an object has been used.
+ * @return an error if we lost the object from the tracker...
+ */
+ nsresult MarkUsedLocked(T* aObj, const AutoLock& aAutoLock) {
+ nsExpirationState* state = aObj->GetExpirationState();
+ if (mNewestGeneration == state->mGeneration) {
+ return NS_OK;
+ }
+ RemoveObjectLocked(aObj, aAutoLock);
+ return AddObjectLocked(aObj, aAutoLock);
+ }
+
+ /**
+ * The timer calls this, but it can also be manually called if you want
+ * to age objects "artifically". This can result in calls to
+ * NotifyExpiredLocked.
+ */
+ void AgeOneGenerationLocked(const AutoLock& aAutoLock) {
+ if (mInAgeOneGeneration) {
+ NS_WARNING("Can't reenter AgeOneGeneration from NotifyExpired");
+ return;
+ }
+
+ mInAgeOneGeneration = true;
+ uint32_t reapGeneration =
+ mNewestGeneration > 0 ? mNewestGeneration - 1 : K - 1;
+ nsTArray<T*>& generation = mGenerations[reapGeneration];
+ // The following is rather tricky. We have to cope with objects being
+ // removed from this generation either because of a call to RemoveObject
+ // (or indirectly via MarkUsedLocked) inside NotifyExpiredLocked.
+ // Fortunately no objects can be added to this generation because it's not
+ // the newest generation. We depend on the fact that RemoveObject can only
+ // cause the indexes of objects in this generation to *decrease*, not
+ // increase. So if we start from the end and work our way backwards we are
+ // guaranteed to see each object at least once.
+ size_t index = generation.Length();
+ for (;;) {
+ // Objects could have been removed so index could be outside
+ // the array
+ index = XPCOM_MIN(index, generation.Length());
+ if (index == 0) {
+ break;
+ }
+ --index;
+ NotifyExpiredLocked(generation[index], aAutoLock);
+ }
+ // Any leftover objects from reapGeneration just end up in the new
+ // newest-generation. This is bad form, though, so warn if there are any.
+ if (!generation.IsEmpty()) {
+ NS_WARNING("Expired objects were not removed or marked used");
+ }
+ // Free excess memory used by the generation array, since we probably
+ // just removed most or all of its elements.
+ generation.Compact();
+ mNewestGeneration = reapGeneration;
+ mInAgeOneGeneration = false;
+ }
+
+ /**
+ * This just calls AgeOneGenerationLocked K times. Under normal circumstances
+ * this will result in all objects getting NotifyExpiredLocked called on them,
+ * but if NotifyExpiredLocked itself marks some objects as used, then those
+ * objects might not expire. This would be a good thing to call if we get into
+ * a critically-low memory situation.
+ */
+ void AgeAllGenerationsLocked(const AutoLock& aAutoLock) {
+ uint32_t i;
+ for (i = 0; i < K; ++i) {
+ AgeOneGenerationLocked(aAutoLock);
+ }
+ }
+
+ class Iterator {
+ private:
+ ExpirationTrackerImpl<T, K, Mutex, AutoLock>* mTracker;
+ uint32_t mGeneration;
+ uint32_t mIndex;
+
+ public:
+ Iterator(ExpirationTrackerImpl<T, K, Mutex, AutoLock>* aTracker,
+ AutoLock& aAutoLock)
+ : mTracker(aTracker), mGeneration(0), mIndex(0) {}
+
+ T* Next() {
+ while (mGeneration < K) {
+ nsTArray<T*>* generation = &mTracker->mGenerations[mGeneration];
+ if (mIndex < generation->Length()) {
+ ++mIndex;
+ return (*generation)[mIndex - 1];
+ }
+ ++mGeneration;
+ mIndex = 0;
+ }
+ return nullptr;
+ }
+ };
+
+ friend class Iterator;
+
+ bool IsEmptyLocked(const AutoLock& aAutoLock) const {
+ for (uint32_t i = 0; i < K; ++i) {
+ if (!mGenerations[i].IsEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ size_t Length(const AutoLock& aAutoLock) const {
+ size_t len = 0;
+ for (uint32_t i = 0; i < K; ++i) {
+ len += mGenerations[i].Length();
+ }
+ return len;
+ }
+
+ // @return The amount of memory used by this ExpirationTrackerImpl, excluding
+ // sizeof(*this). If you want to measure anything hanging off the mGenerations
+ // array, you must iterate over the elements and measure them individually;
+ // hence the "Shallow" prefix.
+ size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t bytes = 0;
+ for (uint32_t i = 0; i < K; ++i) {
+ bytes += mGenerations[i].ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ return bytes;
+ }
+
+ protected:
+ /**
+ * This must be overridden to catch notifications. It is called whenever
+ * we detect that an object has not been used for at least (K-1)*mTimerPeriod
+ * milliseconds. If timer events are not delayed, it will be called within
+ * roughly K*mTimerPeriod milliseconds after the last use.
+ * (Unless AgeOneGenerationLocked or AgeAllGenerationsLocked have been called
+ * to accelerate the aging process.)
+ *
+ * NOTE: These bounds ignore delays in timer firings due to actual work being
+ * performed by the browser. We use a slack timer so there is always at least
+ * mTimerPeriod milliseconds between firings, which gives us
+ * (K-1)*mTimerPeriod as a pretty solid lower bound. The upper bound is rather
+ * loose, however. If the maximum amount by which any given timer firing is
+ * delayed is D, then the upper bound before NotifyExpiredLocked is called is
+ * K*(mTimerPeriod + D).
+ *
+ * The NotifyExpiredLocked call is expected to remove the object from the
+ * tracker, but it need not. The object (or other objects) could be
+ * "resurrected" by calling MarkUsedLocked() on them, or they might just not
+ * be removed. Any objects left over that have not been resurrected or removed
+ * are placed in the new newest-generation, but this is considered "bad form"
+ * and should be avoided (we'll issue a warning). (This recycling counts
+ * as "a use" for the purposes of the expiry guarantee above...)
+ *
+ * For robustness and simplicity, we allow objects to be notified more than
+ * once here in the same timer tick.
+ */
+ virtual void NotifyExpiredLocked(T*, const AutoLock&) = 0;
+
+ /**
+ * This may be overridden to perform any post-aging work that needs to be
+ * done while still holding the lock. It will be called once after each timer
+ * event, and each low memory event has been handled.
+ */
+ virtual void NotifyHandlerEndLocked(const AutoLock&){};
+
+ /**
+ * This may be overridden to perform any post-aging work that needs to be
+ * done outside the lock. It will be called once after each
+ * NotifyEndTransactionLocked call.
+ */
+ virtual void NotifyHandlerEnd(){};
+
+ virtual Mutex& GetMutex() = 0;
+
+ private:
+ class ExpirationTrackerObserver;
+ RefPtr<ExpirationTrackerObserver> mObserver;
+ nsTArray<T*> mGenerations[K];
+ nsCOMPtr<nsITimer> mTimer;
+ uint32_t mTimerPeriod;
+ uint32_t mNewestGeneration;
+ bool mInAgeOneGeneration;
+ const char* const mName; // Used for timer firing profiling.
+ const nsCOMPtr<nsIEventTarget> mEventTarget;
+
+ /**
+ * Whenever "memory-pressure" is observed, it calls AgeAllGenerationsLocked()
+ * to minimize memory usage.
+ */
+ class ExpirationTrackerObserver final : public nsIObserver {
+ public:
+ void Init(ExpirationTrackerImpl<T, K, Mutex, AutoLock>* aObj) {
+ mOwner = aObj;
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "memory-pressure", false);
+ }
+ }
+ void Destroy() {
+ mOwner = nullptr;
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "memory-pressure");
+ }
+ }
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ private:
+ ExpirationTrackerImpl<T, K, Mutex, AutoLock>* mOwner;
+ };
+
+ void HandleLowMemory() {
+ {
+ AutoLock lock(GetMutex());
+ AgeAllGenerationsLocked(lock);
+ NotifyHandlerEndLocked(lock);
+ }
+ NotifyHandlerEnd();
+ }
+
+ void HandleTimeout() {
+ {
+ AutoLock lock(GetMutex());
+ AgeOneGenerationLocked(lock);
+ // Cancel the timer if we have no objects to track
+ if (IsEmptyLocked(lock)) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ NotifyHandlerEndLocked(lock);
+ }
+ NotifyHandlerEnd();
+ }
+
+ static void TimerCallback(nsITimer* aTimer, void* aThis) {
+ ExpirationTrackerImpl* tracker = static_cast<ExpirationTrackerImpl*>(aThis);
+ tracker->HandleTimeout();
+ }
+
+ nsresult CheckStartTimerLocked(const AutoLock& aAutoLock) {
+ if (mTimer || !mTimerPeriod) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIEventTarget> target = mEventTarget;
+ if (!target && !NS_IsMainThread()) {
+ // TimerCallback should always be run on the main thread to prevent races
+ // to the destruction of the tracker.
+ target = do_GetMainThread();
+ NS_ENSURE_STATE(target);
+ }
+
+ return NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTimer), TimerCallback, this, mTimerPeriod,
+ nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY, mName, target);
+ }
+};
+
+namespace detail {
+
+class PlaceholderLock {
+ public:
+ void Lock() {}
+ void Unlock() {}
+};
+
+class PlaceholderAutoLock {
+ public:
+ explicit PlaceholderAutoLock(PlaceholderLock&) {}
+ ~PlaceholderAutoLock() = default;
+};
+
+template <typename T, uint32_t K>
+using SingleThreadedExpirationTracker =
+ ExpirationTrackerImpl<T, K, PlaceholderLock, PlaceholderAutoLock>;
+
+} // namespace detail
+
+template <typename T, uint32_t K>
+class nsExpirationTracker
+ : protected ::detail::SingleThreadedExpirationTracker<T, K> {
+ typedef ::detail::PlaceholderLock Lock;
+ typedef ::detail::PlaceholderAutoLock AutoLock;
+
+ Lock mLock;
+
+ AutoLock FakeLock() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ return AutoLock(mLock);
+ }
+
+ Lock& GetMutex() override {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ return mLock;
+ }
+
+ void NotifyExpiredLocked(T* aObject, const AutoLock&) override {
+ NotifyExpired(aObject);
+ }
+
+ /**
+ * Since there are no users of these callbacks in the single threaded case,
+ * we mark them as final with the hope that the compiler can optimize the
+ * method calls out entirely.
+ */
+ void NotifyHandlerEndLocked(const AutoLock&) final {}
+ void NotifyHandlerEnd() final {}
+
+ protected:
+ virtual void NotifyExpired(T* aObj) = 0;
+
+ public:
+ nsExpirationTracker(uint32_t aTimerPeriod, const char* aName,
+ nsIEventTarget* aEventTarget = nullptr)
+ : ::detail::SingleThreadedExpirationTracker<T, K>(aTimerPeriod, aName,
+ aEventTarget) {}
+
+ virtual ~nsExpirationTracker() = default;
+
+ nsresult AddObject(T* aObj) {
+ return this->AddObjectLocked(aObj, FakeLock());
+ }
+
+ void RemoveObject(T* aObj) { this->RemoveObjectLocked(aObj, FakeLock()); }
+
+ nsresult MarkUsed(T* aObj) { return this->MarkUsedLocked(aObj, FakeLock()); }
+
+ void AgeOneGeneration() { this->AgeOneGenerationLocked(FakeLock()); }
+
+ void AgeAllGenerations() { this->AgeAllGenerationsLocked(FakeLock()); }
+
+ class Iterator {
+ private:
+ AutoLock mAutoLock;
+ typename ExpirationTrackerImpl<T, K, Lock, AutoLock>::Iterator mIterator;
+
+ public:
+ explicit Iterator(nsExpirationTracker<T, K>* aTracker)
+ : mAutoLock(aTracker->GetMutex()), mIterator(aTracker, mAutoLock) {}
+
+ T* Next() { return mIterator.Next(); }
+ };
+
+ friend class Iterator;
+
+ bool IsEmpty() { return this->IsEmptyLocked(FakeLock()); }
+};
+
+template <typename T, uint32_t K, typename Mutex, typename AutoLock>
+NS_IMETHODIMP ExpirationTrackerImpl<T, K, Mutex, AutoLock>::
+ ExpirationTrackerObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "memory-pressure") && mOwner) {
+ mOwner->HandleLowMemory();
+ }
+ return NS_OK;
+}
+
+template <class T, uint32_t K, typename Mutex, typename AutoLock>
+NS_IMETHODIMP_(MozExternalRefCountType)
+ExpirationTrackerImpl<T, K, Mutex, AutoLock>::ExpirationTrackerObserver::AddRef(
+ void) {
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
+ NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver);
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "ExpirationTrackerObserver", sizeof(*this));
+ return mRefCnt;
+}
+
+template <class T, uint32_t K, typename Mutex, typename AutoLock>
+NS_IMETHODIMP_(MozExternalRefCountType)
+ExpirationTrackerImpl<T, K, Mutex,
+ AutoLock>::ExpirationTrackerObserver::Release(void) {
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+ NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver);
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "ExpirationTrackerObserver");
+ if (mRefCnt == 0) {
+ NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver);
+ mRefCnt = 1; /* stabilize */
+ delete (this);
+ return 0;
+ }
+ return mRefCnt;
+}
+
+template <class T, uint32_t K, typename Mutex, typename AutoLock>
+NS_IMETHODIMP ExpirationTrackerImpl<T, K, Mutex, AutoLock>::
+ ExpirationTrackerObserver::QueryInterface(REFNSIID aIID,
+ void** aInstancePtr) {
+ NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!");
+ nsresult rv = NS_ERROR_FAILURE;
+ NS_INTERFACE_TABLE(ExpirationTrackerObserver, nsIObserver)
+ return rv;
+}
+
+#endif /*NSEXPIRATIONTRACKER_H_*/
diff --git a/xpcom/ds/nsGkAtoms.cpp b/xpcom/ds/nsGkAtoms.cpp
new file mode 100644
index 0000000000..402e0eb779
--- /dev/null
+++ b/xpcom/ds/nsGkAtoms.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGkAtoms.h"
+
+namespace mozilla {
+namespace detail {
+
+// Because this is `constexpr` it ends up in read-only memory where it can be
+// shared between processes.
+extern constexpr GkAtoms gGkAtoms = {
+// The initialization of each atom's string.
+//
+// Expansion of the example GK_ATOM entries in nsGkAtoms.h:
+//
+// u"a",
+// u"bb",
+// u"Ccc",
+//
+#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \
+ u"" value_,
+#include "nsGkAtomList.h"
+#undef GK_ATOM
+ {
+// The initialization of the atoms themselves.
+//
+// Note that |value_| is an 8-bit string, and so |sizeof(value_)| is equal
+// to the number of chars (including the terminating '\0'). The |u""| prefix
+// converts |value_| to a 16-bit string.
+//
+// Expansion of the example GK_ATOM entries in nsGkAtoms.h:
+//
+// nsStaticAtom(
+// 1,
+// 0x01234567,
+// offsetof(GkAtoms, mAtoms[static_cast<size_t>(GkAtoms::Atoms::a)]) -
+// offsetof(GkAtoms, a_string),
+// true),
+//
+// nsStaticAtom(
+// 2,
+// 0x12345678,
+// offsetof(GkAtoms, mAtoms[static_cast<size_t>(GkAtoms::Atoms::bb)]) -
+// offsetof(GkAtoms, bb_string),
+// false),
+//
+// nsStaticAtom(
+// 3,
+// 0x23456789,
+// offsetof(GkAtoms, mAtoms[static_cast<size_t>(GkAtoms::Atoms::Ccc)]) -
+// offsetof(GkAtoms, Ccc_string),
+// false),
+//
+#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \
+ nsStaticAtom( \
+ sizeof(value_) - 1, hash_, \
+ offsetof(GkAtoms, mAtoms[static_cast<size_t>(GkAtoms::Atoms::name_)]) - \
+ offsetof(GkAtoms, name_##_string), \
+ is_ascii_lower_),
+#include "nsGkAtomList.h"
+#undef GK_ATOM
+ }};
+
+} // namespace detail
+} // namespace mozilla
+
+const nsStaticAtom* const nsGkAtoms::sAtoms = mozilla::detail::gGkAtoms.mAtoms;
diff --git a/xpcom/ds/nsGkAtoms.h b/xpcom/ds/nsGkAtoms.h
new file mode 100644
index 0000000000..eba33bf95c
--- /dev/null
+++ b/xpcom/ds/nsGkAtoms.h
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGkAtoms_h___
+#define nsGkAtoms_h___
+
+#include "nsAtom.h"
+
+// Static atoms are structured carefully to satisfy a lot of constraints.
+//
+// - We have ~2300 static atoms.
+//
+// - We want them to be constexpr so they end up in .rodata, and thus shared
+// between processes, minimizing memory usage.
+//
+// - We need them to be in an array, so we can iterate over them (for
+// registration and lookups).
+//
+// - Each static atom has a string literal associated with it. We can't use a
+// pointer to the string literal because then the atoms won't end up in
+// .rodata. Therefore the string literals and the atoms must be arranged in a
+// way such that a numeric index can be used instead. This numeric index
+// (nsStaticAtom::mStringOffset) must be computable at compile-time to keep
+// the static atom constexpr. It should also not be too large (a uint32_t is
+// reasonable).
+//
+// - Each static atom stores the hash value of its associated string literal;
+// it's used in various ways. The hash value must be specified at
+// compile-time, to keep the static atom constexpr.
+//
+// - As well as accessing each static atom via array indexing, we need an
+// individual pointer, e.g. nsGkAtoms::foo. We want this to be constexpr so
+// it doesn't take up any space in memory.
+//
+// - The array of static atoms can't be in a .h file, because it's a huge
+// constexpr expression, which would blow out compile times. But the
+// individual pointers for the static atoms must be in a .h file so they are
+// public.
+//
+// nsGkAtoms below defines static atoms in a way that satisfies these
+// constraints. It uses nsGkAtomList.h, which defines the names and values of
+// the atoms. nsGkAtomList.h is generated by StaticAtoms.py and has entries
+// that look like this:
+//
+// GK_ATOM(a, "a", 0x01234567, nsStaticAtom, Atom)
+// GK_ATOM(bb, "bb", 0x12345678, nsCSSPseudoElementStaticAtom,
+// PseudoElementAtom)
+// GK_ATOM(Ccc, "Ccc", 0x23456789, nsCSSAnonBoxPseudoStaticAtom,
+// InheritingAnonBoxAtom)
+//
+// Comments throughout this file and nsGkAtoms.cpp show how these entries get
+// expanded by macros.
+
+// Trivial subclasses of nsStaticAtom so that function signatures can require
+// an atom from a specific atom list.
+#define DEFINE_STATIC_ATOM_SUBCLASS(name_) \
+ class name_ : public nsStaticAtom { \
+ public: \
+ constexpr name_(uint32_t aLength, uint32_t aHash, uint32_t aOffset, \
+ bool aIsAsciiLowercase) \
+ : nsStaticAtom(aLength, aHash, aOffset, aIsAsciiLowercase) {} \
+ };
+
+DEFINE_STATIC_ATOM_SUBCLASS(nsCSSAnonBoxPseudoStaticAtom)
+DEFINE_STATIC_ATOM_SUBCLASS(nsCSSPseudoElementStaticAtom)
+
+#undef DEFINE_STATIC_ATOM_SUBCLASS
+
+namespace mozilla {
+namespace detail {
+
+// This `detail` class contains the atom strings and the atom objects. Because
+// they are together in a class, the `mStringOffset` field of the atoms will be
+// small and can be initialized at compile time.
+//
+// A `detail` namespace is used because the things within it aren't directly
+// referenced by external users of these static atoms.
+struct GkAtoms {
+// The declaration of each atom's string.
+//
+// Expansion of the example GK_ATOM entries from above:
+//
+// const char16_t a_string[sizeof("a")];
+// const char16_t bb_string[sizeof("bb")];
+// const char16_t Ccc_string[sizeof("Ccc")];
+//
+#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \
+ const char16_t name_##_string[sizeof(value_)];
+#include "nsGkAtomList.h"
+#undef GK_ATOM
+
+ // The enum value for each atom.
+ enum class Atoms {
+// Expansion of the example GK_ATOM entries above:
+//
+// a,
+// bb,
+// Ccc,
+//
+#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) name_,
+#include "nsGkAtomList.h"
+#undef GK_ATOM
+ AtomsCount
+ };
+
+ const nsStaticAtom mAtoms[static_cast<size_t>(Atoms::AtomsCount)];
+};
+
+// The offset from the start of the GkAtoms object to the start of the
+// nsStaticAtom array inside it. This is used in Rust to avoid problems
+// with lld-link.exe on Windows when rust-bindgen generates a non-opaque
+// version of GkAtoms.
+//
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1517685
+const ptrdiff_t kGkAtomsArrayOffset = offsetof(GkAtoms, mAtoms);
+
+// The GkAtoms instance is `extern const` so it can be defined in a .cpp file.
+//
+// XXX: The NS_EXTERNAL_VIS is necessary to work around an apparent GCC bug:
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87494
+#if defined(__GNUC__) && !defined(__clang__)
+extern NS_EXTERNAL_VIS const GkAtoms gGkAtoms;
+#else
+extern const GkAtoms gGkAtoms;
+#endif
+
+} // namespace detail
+} // namespace mozilla
+
+// This class holds the pointers to the individual atoms.
+class nsGkAtoms {
+ private:
+ friend void NS_InitAtomTable();
+
+ // This is a useful handle to the array of atoms, used below and also
+ // possibly by Rust code.
+ static const nsStaticAtom* const sAtoms;
+
+ // The number of atoms, used below.
+ static constexpr size_t sAtomsLen =
+ static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::AtomsCount);
+
+ public:
+ static nsStaticAtom* GetAtomByIndex(size_t aIndex) {
+ MOZ_ASSERT(aIndex < sAtomsLen);
+ return const_cast<nsStaticAtom*>(&sAtoms[aIndex]);
+ }
+
+ static size_t IndexOf(const nsStaticAtom* atom) {
+ nsStaticAtom* firstAtom = GetAtomByIndex(0);
+ size_t ret = atom - firstAtom;
+ MOZ_ASSERT(ret < sAtomsLen);
+ return ret;
+ }
+
+// The definition of the pointer to each static atom.
+//
+// These types are not `static constexpr <type>* const` -- even though these
+// atoms are immutable -- because they are often passed to functions with
+// `nsAtom*` parameters that can be passed both dynamic and static atoms.
+//
+// Expansion of the example GK_ATOM entries above:
+//
+// static constexpr nsStaticAtom* a =
+// const_cast<nsStaticAtom*>(
+// &mozilla::detail::gGkAtoms.mAtoms[
+// static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::a)]);
+//
+// static constexpr nsStaticAtom* bb =
+// const_cast<nsStaticAtom*>(
+// &mozilla::detail::gGkAtoms.mAtoms[
+// static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::bb)]);
+//
+// static constexpr nsStaticAtom* Ccc =
+// const_cast<nsStaticAtom*>(
+// &mozilla::detail::gGkAtoms.mAtoms[
+// static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::Ccc)]);
+//
+#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \
+ static constexpr nsStaticAtom* name_ = const_cast<nsStaticAtom*>( \
+ &mozilla::detail::gGkAtoms.mAtoms[static_cast<size_t>( \
+ mozilla::detail::GkAtoms::Atoms::name_)]);
+#include "nsGkAtomList.h"
+#undef GK_ATOM
+};
+
+inline bool nsAtom::IsEmpty() const { return this == nsGkAtoms::_empty; }
+
+#endif /* nsGkAtoms_h___ */
diff --git a/xpcom/ds/nsHashKeys.h b/xpcom/ds/nsHashKeys.h
new file mode 100644
index 0000000000..fb0a8244c4
--- /dev/null
+++ b/xpcom/ds/nsHashKeys.h
@@ -0,0 +1,636 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTHashKeys_h__
+#define nsTHashKeys_h__
+
+#include "nsID.h"
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "PLDHashTable.h"
+#include <new>
+
+#include "nsString.h"
+#include "nsCRTGlue.h"
+#include "nsUnicharUtils.h"
+#include "nsPointerHashKeys.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <type_traits>
+#include <utility>
+
+#include "mozilla/HashFunctions.h"
+
+namespace mozilla {
+
+// These are defined analogously to the HashString overloads in mfbt.
+
+inline uint32_t HashString(const nsAString& aStr) {
+ return HashString(aStr.BeginReading(), aStr.Length());
+}
+
+inline uint32_t HashString(const nsACString& aStr) {
+ return HashString(aStr.BeginReading(), aStr.Length());
+}
+
+} // namespace mozilla
+
+/** @file nsHashKeys.h
+ * standard HashKey classes for nsBaseHashtable and relatives. Each of these
+ * classes follows the nsTHashtable::EntryType specification
+ *
+ * Lightweight keytypes provided here:
+ * nsStringHashKey
+ * nsCStringHashKey
+ * nsUint32HashKey
+ * nsUint64HashKey
+ * nsFloatHashKey
+ * IntPtrHashKey
+ * nsPtrHashKey
+ * nsClearingPtrHashKey
+ * nsVoidPtrHashKey
+ * nsClearingVoidPtrHashKey
+ * nsISupportsHashKey
+ * nsIDHashKey
+ * nsDepCharHashKey
+ * nsCharPtrHashKey
+ * nsUnicharPtrHashKey
+ * nsGenericHashKey
+ */
+
+/**
+ * hashkey wrapper using nsAString KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+class nsStringHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const nsAString& KeyType;
+ typedef const nsAString* KeyTypePointer;
+
+ explicit nsStringHashKey(KeyTypePointer aStr) : mStr(*aStr) {}
+ nsStringHashKey(const nsStringHashKey&) = delete;
+ nsStringHashKey(nsStringHashKey&& aToMove)
+ : PLDHashEntryHdr(std::move(aToMove)), mStr(std::move(aToMove.mStr)) {}
+ ~nsStringHashKey() = default;
+
+ KeyType GetKey() const { return mStr; }
+ bool KeyEquals(const KeyTypePointer aKey) const { return mStr.Equals(*aKey); }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+ return mozilla::HashString(*aKey);
+ }
+
+#ifdef MOZILLA_INTERNAL_API
+ // To avoid double-counting, only measure the string if it is unshared.
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+#endif
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ nsString mStr;
+};
+
+#ifdef MOZILLA_INTERNAL_API
+
+namespace mozilla::detail {
+
+template <class CharT, bool Unicode = true>
+struct comparatorTraits {};
+
+template <>
+struct comparatorTraits<char, false> {
+ static int caseInsensitiveCompare(const char* aLhs, const char* aRhs,
+ size_t aLhsLength, size_t aRhsLength) {
+ return nsCaseInsensitiveCStringComparator(aLhs, aRhs, aLhsLength,
+ aRhsLength);
+ };
+};
+
+template <>
+struct comparatorTraits<char, true> {
+ static int caseInsensitiveCompare(const char* aLhs, const char* aRhs,
+ size_t aLhsLength, size_t aRhsLength) {
+ return nsCaseInsensitiveUTF8StringComparator(aLhs, aRhs, aLhsLength,
+ aRhsLength);
+ };
+};
+
+template <>
+struct comparatorTraits<char16_t, true> {
+ static int caseInsensitiveCompare(const char16_t* aLhs, const char16_t* aRhs,
+ size_t aLhsLength, size_t aRhsLength) {
+ return nsCaseInsensitiveStringComparator(aLhs, aRhs, aLhsLength,
+ aRhsLength);
+ };
+};
+
+} // namespace mozilla::detail
+
+/**
+ * This is internal-API only because nsCaseInsensitive{C}StringComparator is
+ * internal-only.
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+
+template <typename T, bool Unicode>
+class nsTStringCaseInsensitiveHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const nsTSubstring<T>& KeyType;
+ typedef const nsTSubstring<T>* KeyTypePointer;
+
+ explicit nsTStringCaseInsensitiveHashKey(KeyTypePointer aStr) : mStr(*aStr) {
+ // take it easy just deal HashKey
+ }
+
+ nsTStringCaseInsensitiveHashKey(const nsTStringCaseInsensitiveHashKey&) =
+ delete;
+ nsTStringCaseInsensitiveHashKey(nsTStringCaseInsensitiveHashKey&& aToMove)
+ : PLDHashEntryHdr(std::move(aToMove)), mStr(std::move(aToMove.mStr)) {}
+ ~nsTStringCaseInsensitiveHashKey() = default;
+
+ KeyType GetKey() const { return mStr; }
+ bool KeyEquals(const KeyTypePointer aKey) const {
+ using comparator = typename mozilla::detail::comparatorTraits<T, Unicode>;
+ return mStr.Equals(*aKey, comparator::caseInsensitiveCompare);
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+ nsTAutoString<T> tmKey(*aKey);
+ ToLowerCase(tmKey);
+ return mozilla::HashString(tmKey);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ // To avoid double-counting, only measure the string if it is unshared.
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+
+ private:
+ const nsTString<T> mStr;
+};
+
+using nsStringCaseInsensitiveHashKey =
+ nsTStringCaseInsensitiveHashKey<char16_t, true>;
+using nsCStringASCIICaseInsensitiveHashKey =
+ nsTStringCaseInsensitiveHashKey<char, false>;
+using nsCStringUTF8CaseInsensitiveHashKey =
+ nsTStringCaseInsensitiveHashKey<char, true>;
+
+#endif // MOZILLA_INTERNAL_API
+
+/**
+ * hashkey wrapper using nsACString KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+class nsCStringHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const nsACString& KeyType;
+ typedef const nsACString* KeyTypePointer;
+
+ explicit nsCStringHashKey(const nsACString* aStr) : mStr(*aStr) {}
+ nsCStringHashKey(nsCStringHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mStr(std::move(aOther.mStr)) {}
+ ~nsCStringHashKey() = default;
+
+ KeyType GetKey() const { return mStr; }
+ bool KeyEquals(KeyTypePointer aKey) const { return mStr.Equals(*aKey); }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return mozilla::HashString(*aKey);
+ }
+
+#ifdef MOZILLA_INTERNAL_API
+ // To avoid double-counting, only measure the string if it is unshared.
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+#endif
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const nsCString mStr;
+};
+
+/**
+ * hashkey wrapper using integral or enum KeyTypes
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+template <typename T,
+ std::enable_if_t<std::is_integral_v<T> || std::is_enum_v<T>, int> = 0>
+class nsIntegralHashKey : public PLDHashEntryHdr {
+ public:
+ using KeyType = const T&;
+ using KeyTypePointer = const T*;
+
+ explicit nsIntegralHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
+ nsIntegralHashKey(nsIntegralHashKey&& aOther) noexcept
+ : PLDHashEntryHdr(std::move(aOther)), mValue(aOther.mValue) {}
+ ~nsIntegralHashKey() = default;
+
+ KeyType GetKey() const { return mValue; }
+ bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return mozilla::HashGeneric(*aKey);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const T mValue;
+};
+
+/**
+ * hashkey wrapper using uint32_t KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+using nsUint32HashKey = nsIntegralHashKey<uint32_t>;
+
+/**
+ * hashkey wrapper using uint64_t KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+using nsUint64HashKey = nsIntegralHashKey<uint64_t>;
+
+/**
+ * hashkey wrapper using float KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+class nsFloatHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const float& KeyType;
+ typedef const float* KeyTypePointer;
+
+ explicit nsFloatHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
+ nsFloatHashKey(nsFloatHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mValue(std::move(aOther.mValue)) {}
+ ~nsFloatHashKey() = default;
+
+ KeyType GetKey() const { return mValue; }
+ bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return *reinterpret_cast<const uint32_t*>(aKey);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const float mValue;
+};
+
+/**
+ * hashkey wrapper using intptr_t KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+using IntPtrHashKey = nsIntegralHashKey<intptr_t>;
+
+/**
+ * hashkey wrapper using nsISupports* KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+class nsISupportsHashKey : public PLDHashEntryHdr {
+ public:
+ typedef nsISupports* KeyType;
+ typedef const nsISupports* KeyTypePointer;
+
+ explicit nsISupportsHashKey(const nsISupports* aKey)
+ : mSupports(const_cast<nsISupports*>(aKey)) {}
+ nsISupportsHashKey(nsISupportsHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)),
+ mSupports(std::move(aOther.mSupports)) {}
+ ~nsISupportsHashKey() = default;
+
+ KeyType GetKey() const { return mSupports; }
+ bool KeyEquals(KeyTypePointer aKey) const { return aKey == mSupports; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return NS_PTR_TO_UINT32(aKey) >> 2;
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ nsCOMPtr<nsISupports> mSupports;
+};
+
+/**
+ * hashkey wrapper using refcounted * KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+template <class T>
+class nsRefPtrHashKey : public PLDHashEntryHdr {
+ public:
+ typedef T* KeyType;
+ typedef const T* KeyTypePointer;
+
+ explicit nsRefPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {}
+ nsRefPtrHashKey(nsRefPtrHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {}
+ ~nsRefPtrHashKey() = default;
+
+ KeyType GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return NS_PTR_TO_UINT32(aKey) >> 2;
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ RefPtr<T> mKey;
+};
+
+template <class T>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, nsRefPtrHashKey<T>& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ CycleCollectionNoteChild(aCallback, aField.GetKey(), aName, aFlags);
+}
+
+/**
+ * hashkey wrapper using T* KeyType that sets key to nullptr upon
+ * destruction. Relevant only in cases where a memory pointer-scanner
+ * like valgrind might get confused about stale references.
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+
+template <class T>
+class nsClearingPtrHashKey : public nsPtrHashKey<T> {
+ public:
+ explicit nsClearingPtrHashKey(const T* aKey) : nsPtrHashKey<T>(aKey) {}
+ nsClearingPtrHashKey(nsClearingPtrHashKey&& aToMove)
+ : nsPtrHashKey<T>(std::move(aToMove)) {}
+ ~nsClearingPtrHashKey() { nsPtrHashKey<T>::mKey = nullptr; }
+};
+
+typedef nsClearingPtrHashKey<const void> nsClearingVoidPtrHashKey;
+
+/**
+ * hashkey wrapper using a function pointer KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+template <class T>
+class nsFuncPtrHashKey : public PLDHashEntryHdr {
+ public:
+ typedef T& KeyType;
+ typedef const T* KeyTypePointer;
+
+ explicit nsFuncPtrHashKey(const T* aKey) : mKey(*const_cast<T*>(aKey)) {}
+ nsFuncPtrHashKey(const nsFuncPtrHashKey<T>& aToCopy) : mKey(aToCopy.mKey) {}
+ ~nsFuncPtrHashKey() = default;
+
+ KeyType GetKey() const { return const_cast<T&>(mKey); }
+ bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mKey; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return NS_PTR_TO_UINT32(*aKey) >> 2;
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ T mKey;
+};
+
+/**
+ * hashkey wrapper using nsID KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+class nsIDHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const nsID& KeyType;
+ typedef const nsID* KeyTypePointer;
+
+ explicit nsIDHashKey(const nsID* aInID) : mID(*aInID) {}
+ nsIDHashKey(nsIDHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mID(std::move(aOther.mID)) {}
+ ~nsIDHashKey() = default;
+
+ KeyType GetKey() const { return mID; }
+ bool KeyEquals(KeyTypePointer aKey) const { return aKey->Equals(mID); }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ // Hash the nsID object's raw bytes.
+ return mozilla::HashBytes(aKey, sizeof(KeyType));
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ nsID mID;
+};
+
+/**
+ * hashkey wrapper using nsID* KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+class nsIDPointerHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const nsID* KeyType;
+ typedef const nsID* KeyTypePointer;
+
+ explicit nsIDPointerHashKey(const nsID* aInID) : mID(aInID) {}
+ nsIDPointerHashKey(nsIDPointerHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mID(aOther.mID) {}
+ ~nsIDPointerHashKey() = default;
+
+ KeyType GetKey() const { return mID; }
+ bool KeyEquals(KeyTypePointer aKey) const { return aKey->Equals(*mID); }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ // Hash the nsID object's raw bytes.
+ return mozilla::HashBytes(aKey, sizeof(*aKey));
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const nsID* mID;
+};
+
+/**
+ * hashkey wrapper for "dependent" const char*; this class does not "own"
+ * its string pointer.
+ *
+ * This class must only be used if the strings have a lifetime longer than
+ * the hashtable they occupy. This normally occurs only for static
+ * strings or strings that have been arena-allocated.
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+class nsDepCharHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const char* KeyType;
+ typedef const char* KeyTypePointer;
+
+ explicit nsDepCharHashKey(const char* aKey) : mKey(aKey) {}
+ nsDepCharHashKey(nsDepCharHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {}
+ ~nsDepCharHashKey() = default;
+
+ const char* GetKey() const { return mKey; }
+ bool KeyEquals(const char* aKey) const { return !strcmp(mKey, aKey); }
+
+ static const char* KeyToPointer(const char* aKey) { return aKey; }
+ static PLDHashNumber HashKey(const char* aKey) {
+ return mozilla::HashString(aKey);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const char* mKey;
+};
+
+/**
+ * hashkey wrapper for const char*; at construction, this class duplicates
+ * a string pointed to by the pointer so that it doesn't matter whether or not
+ * the string lives longer than the hash table.
+ */
+class nsCharPtrHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const char* KeyType;
+ typedef const char* KeyTypePointer;
+
+ explicit nsCharPtrHashKey(const char* aKey) : mKey(strdup(aKey)) {}
+
+ nsCharPtrHashKey(const nsCharPtrHashKey&) = delete;
+ nsCharPtrHashKey(nsCharPtrHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mKey(aOther.mKey) {
+ aOther.mKey = nullptr;
+ }
+
+ ~nsCharPtrHashKey() {
+ if (mKey) {
+ free(const_cast<char*>(mKey));
+ }
+ }
+
+ const char* GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const { return !strcmp(mKey, aKey); }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return mozilla::HashString(aKey);
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(mKey);
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const char* mKey;
+};
+
+/**
+ * hashkey wrapper for const char16_t*; at construction, this class duplicates
+ * a string pointed to by the pointer so that it doesn't matter whether or not
+ * the string lives longer than the hash table.
+ */
+class nsUnicharPtrHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const char16_t* KeyType;
+ typedef const char16_t* KeyTypePointer;
+
+ explicit nsUnicharPtrHashKey(const char16_t* aKey) : mKey(NS_xstrdup(aKey)) {}
+ nsUnicharPtrHashKey(const nsUnicharPtrHashKey& aToCopy) = delete;
+ nsUnicharPtrHashKey(nsUnicharPtrHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mKey(aOther.mKey) {
+ aOther.mKey = nullptr;
+ }
+
+ ~nsUnicharPtrHashKey() {
+ if (mKey) {
+ free(const_cast<char16_t*>(mKey));
+ }
+ }
+
+ const char16_t* GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const { return !NS_strcmp(mKey, aKey); }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return mozilla::HashString(aKey);
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(mKey);
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const char16_t* mKey;
+};
+
+namespace mozilla {
+
+template <typename T>
+PLDHashNumber Hash(const T& aValue) {
+ return aValue.Hash();
+}
+
+} // namespace mozilla
+
+/**
+ * Hashtable key class to use with objects for which Hash() and operator==()
+ * are defined.
+ */
+template <typename T>
+class nsGenericHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const T& KeyType;
+ typedef const T* KeyTypePointer;
+
+ explicit nsGenericHashKey(KeyTypePointer aKey) : mKey(*aKey) {}
+ nsGenericHashKey(const nsGenericHashKey&) = delete;
+ nsGenericHashKey(nsGenericHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {}
+
+ KeyType GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mKey; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return ::mozilla::Hash(*aKey);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ T mKey;
+};
+
+#endif // nsTHashKeys_h__
diff --git a/xpcom/ds/nsHashPropertyBag.cpp b/xpcom/ds/nsHashPropertyBag.cpp
new file mode 100644
index 0000000000..68dd612c57
--- /dev/null
+++ b/xpcom/ds/nsHashPropertyBag.cpp
@@ -0,0 +1,366 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsHashPropertyBag.h"
+
+#include <utility>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/SimpleEnumerator.h"
+#include "nsArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsIProperty.h"
+#include "nsIVariant.h"
+#include "nsThreadUtils.h"
+#include "nsVariant.h"
+
+using mozilla::MakeRefPtr;
+using mozilla::SimpleEnumerator;
+using mozilla::Unused;
+
+extern "C" {
+
+// This function uses C linkage because it's exposed to Rust to support the
+// `HashPropertyBag` wrapper in the `storage_variant` crate.
+void NS_NewHashPropertyBag(nsIWritablePropertyBag** aBag) {
+ MakeRefPtr<nsHashPropertyBag>().forget(aBag);
+}
+
+} // extern "C"
+
+/*
+ * nsHashPropertyBagBase implementation.
+ */
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::HasKey(const nsAString& aName, bool* aResult) {
+ *aResult = mPropertyHash.Get(aName, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::Get(const nsAString& aName, nsIVariant** aResult) {
+ if (!mPropertyHash.Get(aName, aResult)) {
+ *aResult = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::GetProperty(const nsAString& aName,
+ nsIVariant** aResult) {
+ bool isFound = mPropertyHash.Get(aName, aResult);
+ if (!isFound) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::SetProperty(const nsAString& aName, nsIVariant* aValue) {
+ if (NS_WARN_IF(!aValue)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mPropertyHash.InsertOrUpdate(aName, aValue);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::DeleteProperty(const nsAString& aName) {
+ return mPropertyHash.Remove(aName) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+//
+// nsSimpleProperty class and impl; used for GetEnumerator
+//
+
+class nsSimpleProperty final : public nsIProperty {
+ ~nsSimpleProperty() = default;
+
+ public:
+ nsSimpleProperty(const nsAString& aName, nsIVariant* aValue)
+ : mName(aName), mValue(aValue) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROPERTY
+ protected:
+ nsString mName;
+ nsCOMPtr<nsIVariant> mValue;
+};
+
+NS_IMPL_ISUPPORTS(nsSimpleProperty, nsIProperty)
+
+NS_IMETHODIMP
+nsSimpleProperty::GetName(nsAString& aName) {
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleProperty::GetValue(nsIVariant** aValue) {
+ NS_IF_ADDREF(*aValue = mValue);
+ return NS_OK;
+}
+
+// end nsSimpleProperty
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::GetEnumerator(nsISimpleEnumerator** aResult) {
+ nsCOMPtr<nsIMutableArray> propertyArray = nsArray::Create();
+ if (!propertyArray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
+ const nsAString& key = iter.Key();
+ nsIVariant* data = iter.UserData();
+ nsSimpleProperty* sprop = new nsSimpleProperty(key, data);
+ propertyArray->AppendElement(sprop);
+ }
+
+ return NS_NewArrayEnumerator(aResult, propertyArray, NS_GET_IID(nsIProperty));
+}
+
+#define IMPL_GETSETPROPERTY_AS(Name, Type) \
+ NS_IMETHODIMP \
+ nsHashPropertyBagBase::GetPropertyAs##Name(const nsAString& prop, \
+ Type* _retval) { \
+ nsIVariant* v = mPropertyHash.GetWeak(prop); \
+ if (!v) return NS_ERROR_NOT_AVAILABLE; \
+ return v->GetAs##Name(_retval); \
+ } \
+ \
+ NS_IMETHODIMP \
+ nsHashPropertyBagBase::SetPropertyAs##Name(const nsAString& prop, \
+ Type value) { \
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant(); \
+ var->SetAs##Name(value); \
+ return SetProperty(prop, var); \
+ }
+
+IMPL_GETSETPROPERTY_AS(Int32, int32_t)
+IMPL_GETSETPROPERTY_AS(Uint32, uint32_t)
+IMPL_GETSETPROPERTY_AS(Int64, int64_t)
+IMPL_GETSETPROPERTY_AS(Uint64, uint64_t)
+IMPL_GETSETPROPERTY_AS(Double, double)
+IMPL_GETSETPROPERTY_AS(Bool, bool)
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::GetPropertyAsAString(const nsAString& aProp,
+ nsAString& aResult) {
+ nsIVariant* v = mPropertyHash.GetWeak(aProp);
+ if (!v) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return v->GetAsAString(aResult);
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::GetPropertyAsACString(const nsAString& aProp,
+ nsACString& aResult) {
+ nsIVariant* v = mPropertyHash.GetWeak(aProp);
+ if (!v) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return v->GetAsACString(aResult);
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::GetPropertyAsAUTF8String(const nsAString& aProp,
+ nsACString& aResult) {
+ nsIVariant* v = mPropertyHash.GetWeak(aProp);
+ if (!v) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return v->GetAsAUTF8String(aResult);
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::GetPropertyAsInterface(const nsAString& aProp,
+ const nsIID& aIID,
+ void** aResult) {
+ nsIVariant* v = mPropertyHash.GetWeak(aProp);
+ if (!v) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<nsISupports> val;
+ nsresult rv = v->GetAsISupports(getter_AddRefs(val));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!val) {
+ // We have a value, but it's null
+ *aResult = nullptr;
+ return NS_OK;
+ }
+ return val->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::SetPropertyAsAString(const nsAString& aProp,
+ const nsAString& aValue) {
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsAString(aValue);
+ return SetProperty(aProp, var);
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::SetPropertyAsACString(const nsAString& aProp,
+ const nsACString& aValue) {
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsACString(aValue);
+ return SetProperty(aProp, var);
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::SetPropertyAsAUTF8String(const nsAString& aProp,
+ const nsACString& aValue) {
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsAUTF8String(aValue);
+ return SetProperty(aProp, var);
+}
+
+NS_IMETHODIMP
+nsHashPropertyBagBase::SetPropertyAsInterface(const nsAString& aProp,
+ nsISupports* aValue) {
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsISupports(aValue);
+ return SetProperty(aProp, var);
+}
+
+void nsHashPropertyBagBase::CopyFrom(const nsHashPropertyBagBase* aOther) {
+ for (const auto& entry : aOther->mPropertyHash) {
+ SetProperty(entry.GetKey(), entry.GetWeak());
+ }
+}
+
+void nsHashPropertyBagBase::CopyFrom(nsIPropertyBag* aOther) {
+ CopyFrom(this, aOther);
+}
+
+/* static */ void nsHashPropertyBagBase::CopyFrom(nsIWritablePropertyBag* aTo,
+ nsIPropertyBag* aFrom) {
+ if (aTo && aFrom) {
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ if (NS_SUCCEEDED(aFrom->GetEnumerator(getter_AddRefs(enumerator)))) {
+ for (auto& property : SimpleEnumerator<nsIProperty>(enumerator)) {
+ nsString name;
+ nsCOMPtr<nsIVariant> value;
+ Unused << NS_WARN_IF(NS_FAILED(property->GetName(name)));
+ Unused << NS_WARN_IF(
+ NS_FAILED(property->GetValue(getter_AddRefs(value))));
+ Unused << NS_WARN_IF(
+ NS_FAILED(aTo->SetProperty(std::move(name), value)));
+ }
+ } else {
+ NS_WARNING("Unable to copy nsIPropertyBag");
+ }
+ }
+}
+
+nsresult nsGetProperty::operator()(const nsIID& aIID,
+ void** aInstancePtr) const {
+ nsresult rv;
+
+ if (mPropBag) {
+ rv = mPropBag->GetPropertyAsInterface(mPropName, aIID, aInstancePtr);
+ } else {
+ rv = NS_ERROR_NULL_POINTER;
+ *aInstancePtr = 0;
+ }
+
+ if (mErrorPtr) {
+ *mErrorPtr = rv;
+ }
+ return rv;
+}
+
+/*
+ * nsHashPropertyBag implementation.
+ */
+
+NS_IMPL_ADDREF(nsHashPropertyBag)
+NS_IMPL_RELEASE(nsHashPropertyBag)
+
+NS_INTERFACE_MAP_BEGIN(nsHashPropertyBag)
+ NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2)
+ NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2)
+NS_INTERFACE_MAP_END
+
+/*
+ * We need to ensure that the hashtable is destroyed on the main thread, as
+ * the nsIVariant values are main-thread only objects.
+ */
+class ProxyHashtableDestructor final : public mozilla::Runnable {
+ public:
+ using HashtableType = nsInterfaceHashtable<nsStringHashKey, nsIVariant>;
+ explicit ProxyHashtableDestructor(HashtableType&& aTable)
+ : mozilla::Runnable("ProxyHashtableDestructor"),
+ mPropertyHash(std::move(aTable)) {}
+
+ NS_IMETHODIMP
+ Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ HashtableType table(std::move(mPropertyHash));
+ return NS_OK;
+ }
+
+ private:
+ HashtableType mPropertyHash;
+};
+
+nsHashPropertyBag::~nsHashPropertyBag() {
+ if (!NS_IsMainThread()) {
+ RefPtr<ProxyHashtableDestructor> runnable =
+ new ProxyHashtableDestructor(std::move(mPropertyHash));
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+ }
+}
+
+/*
+ * nsHashPropertyBagOMT implementation
+ */
+NS_IMPL_ADDREF(nsHashPropertyBagOMT)
+NS_IMPL_RELEASE(nsHashPropertyBagOMT)
+
+NS_INTERFACE_MAP_BEGIN(nsHashPropertyBagOMT)
+ NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2)
+ NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2)
+NS_INTERFACE_MAP_END
+
+nsHashPropertyBagOMT::nsHashPropertyBagOMT() {
+ // nsHashPropertyBagOMT is supposed to be used off-main thread. If you need a
+ // single threaded property bag on the main thread, you should consider using
+ // nsHashPropertyBagCC instead, to prevent leaks.
+ MOZ_ASSERT(!NS_IsMainThread());
+}
+
+/*
+ * nsHashPropertyBagCC implementation.
+ */
+
+NS_IMPL_CYCLE_COLLECTION(nsHashPropertyBagCC, mPropertyHash)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHashPropertyBagCC)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHashPropertyBagCC)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsHashPropertyBagCC)
+ NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2)
+ NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2)
+NS_INTERFACE_MAP_END
diff --git a/xpcom/ds/nsHashPropertyBag.h b/xpcom/ds/nsHashPropertyBag.h
new file mode 100644
index 0000000000..7a2e6b0ac8
--- /dev/null
+++ b/xpcom/ds/nsHashPropertyBag.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHashPropertyBag_h___
+#define nsHashPropertyBag_h___
+
+#include "nsIVariant.h"
+#include "nsIWritablePropertyBag.h"
+#include "nsIWritablePropertyBag2.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsInterfaceHashtable.h"
+
+class nsHashPropertyBagBase : public nsIWritablePropertyBag,
+ public nsIWritablePropertyBag2 {
+ public:
+ nsHashPropertyBagBase() = default;
+
+ void CopyFrom(const nsHashPropertyBagBase* aOther);
+ void CopyFrom(nsIPropertyBag* aOther);
+ static void CopyFrom(nsIWritablePropertyBag* aTo, nsIPropertyBag* aFrom);
+
+ NS_DECL_NSIPROPERTYBAG
+ NS_DECL_NSIPROPERTYBAG2
+
+ NS_DECL_NSIWRITABLEPROPERTYBAG
+ NS_DECL_NSIWRITABLEPROPERTYBAG2
+
+ protected:
+ // a hash table of string -> nsIVariant
+ nsInterfaceHashtable<nsStringHashKey, nsIVariant> mPropertyHash;
+};
+
+class nsHashPropertyBag : public nsHashPropertyBagBase {
+ public:
+ nsHashPropertyBag() = default;
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ protected:
+ virtual ~nsHashPropertyBag();
+};
+
+/*
+ * Off Main Thread variant of nsHashPropertyBag. Instances of this class
+ * should not be created on a main thread, nor should it contain main thread
+ * only objects, such as XPCVariants. The purpose of this class is to provide a
+ * way to use the property bag off main thread.
+ * Note: this class needs to be created and destroyed on the same thread and
+ * should be used single threaded.
+ */
+class nsHashPropertyBagOMT final : public nsHashPropertyBagBase {
+ public:
+ nsHashPropertyBagOMT();
+ NS_DECL_ISUPPORTS
+
+ protected:
+ // Doesn't need to dispatch to main thread because it cannot contain
+ // XPCVariants
+ virtual ~nsHashPropertyBagOMT() = default;
+};
+
+/* A cycle collected nsHashPropertyBag for main-thread-only use. */
+class nsHashPropertyBagCC final : public nsHashPropertyBagBase {
+ public:
+ nsHashPropertyBagCC() = default;
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHashPropertyBagCC,
+ nsIWritablePropertyBag)
+ protected:
+ virtual ~nsHashPropertyBagCC() = default;
+};
+
+inline nsISupports* ToSupports(nsHashPropertyBagBase* aPropertyBag) {
+ return static_cast<nsIWritablePropertyBag*>(aPropertyBag);
+}
+
+#endif /* nsHashPropertyBag_h___ */
diff --git a/xpcom/ds/nsHashtablesFwd.h b/xpcom/ds/nsHashtablesFwd.h
new file mode 100644
index 0000000000..080f79887d
--- /dev/null
+++ b/xpcom/ds/nsHashtablesFwd.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef XPCOM_DS_NSHASHTABLESFWD_H_
+#define XPCOM_DS_NSHASHTABLESFWD_H_
+
+#include "mozilla/Attributes.h"
+
+struct PLDHashEntryHdr;
+
+template <class T>
+class MOZ_IS_REFPTR nsCOMPtr;
+
+template <class T>
+class MOZ_IS_REFPTR RefPtr;
+
+template <class EntryType>
+class MOZ_NEEDS_NO_VTABLE_TYPE nsTHashtable;
+
+template <class DataType, class UserDataType>
+class nsDefaultConverter;
+
+template <class KeyClass, class DataType, class UserDataType,
+ class Converter = nsDefaultConverter<DataType, UserDataType>>
+class nsBaseHashtable;
+
+template <class KeyClass, class T>
+class nsClassHashtable;
+
+template <class KeyClass, class PtrType>
+class nsRefCountedHashtable;
+
+/**
+ * templated hashtable class maps keys to interface pointers.
+ * See nsBaseHashtable for complete declaration.
+ * @deprecated This is going to be removed. Use nsTHashMap instead.
+ * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h
+ * for a complete specification.
+ * @param Interface the interface-type being wrapped
+ * @see nsClassHashtable, nsTHashMap
+ */
+template <class KeyClass, class Interface>
+using nsInterfaceHashtable =
+ nsRefCountedHashtable<KeyClass, nsCOMPtr<Interface>>;
+
+/**
+ * templated hashtable class maps keys to reference pointers.
+ * See nsBaseHashtable for complete declaration.
+ * @deprecated This is going to be removed. Use nsTHashMap instead.
+ * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h
+ * for a complete specification.
+ * @param PtrType the reference-type being wrapped
+ * @see nsClassHashtable, nsTHashMap
+ */
+template <class KeyClass, class ClassType>
+using nsRefPtrHashtable = nsRefCountedHashtable<KeyClass, RefPtr<ClassType>>;
+
+namespace mozilla::detail {
+template <class KeyType, class = void>
+struct nsKeyClass;
+} // namespace mozilla::detail
+
+/**
+ * A universal hash map that maps some KeyType to some DataType. It can be used
+ * for any DataType, including RefPtr<T>, nsCOMPtr<T> and UniquePtr<T>.
+ *
+ * For the default hash keys types, the appropriate hash key class is determined
+ * automatically, so you can just specify `nsTHashMap<uint32_t,
+ * RefPtr<Foo>>`, for example.
+ *
+ * If you require custom hash behaviour (e.g. case insensitive string handling),
+ * you can still specify a hash key class derived from PLDHashEntryHdr
+ * explicitly.
+ *
+ * If you need to use a custom UserDataType, use nsBaseHashtable (or
+ * nsTHashtable) directly. However, you should double-check if that's really
+ * necessary.
+ */
+template <class KeyType, class DataType>
+using nsTHashMap =
+ nsBaseHashtable<typename mozilla::detail::nsKeyClass<KeyType>::type,
+ DataType, DataType>;
+
+template <class KeyClass>
+class nsTBaseHashSet;
+
+template <class KeyType>
+using nsTHashSet =
+ nsTBaseHashSet<typename mozilla::detail::nsKeyClass<KeyType>::type>;
+
+#endif // XPCOM_DS_NSHASHTABLESFWD_H_
diff --git a/xpcom/ds/nsIArray.idl b/xpcom/ds/nsIArray.idl
new file mode 100644
index 0000000000..7e514649cb
--- /dev/null
+++ b/xpcom/ds/nsIArray.idl
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsISimpleEnumerator;
+
+/**
+ * nsIArray
+ *
+ * An indexed collection of elements. Provides basic functionality for
+ * retrieving elements at a specific position, searching for
+ * elements. Indexes are zero-based, such that the last element in the
+ * array is stored at the index length-1.
+ *
+ * For an array which can be modified, see nsIMutableArray below.
+ *
+ * Neither interface makes any attempt to protect the individual
+ * elements from modification. The convention is that the elements of
+ * the array should not be modified. Documentation within a specific
+ * interface should describe variations from this convention.
+ *
+ * It is also convention that if an interface provides access to an
+ * nsIArray, that the array should not be QueryInterfaced to an
+ * nsIMutableArray for modification. If the interface in question had
+ * intended the array to be modified, it would have returned an
+ * nsIMutableArray!
+ *
+ * null is a valid entry in the array, and as such any nsISupports
+ * parameters may be null, except where noted.
+ */
+[scriptable, builtinclass, uuid(114744d9-c369-456e-b55a-52fe52880d2d)]
+interface nsIArray : nsISupports
+{
+ /**
+ * length
+ *
+ * number of elements in the array.
+ */
+ readonly attribute unsigned long length;
+
+ /**
+ * queryElementAt()
+ *
+ * Retrieve a specific element of the array, and QueryInterface it
+ * to the specified interface. null is a valid result for
+ * this method, but exceptions are thrown in other circumstances
+ *
+ * @param index position of element
+ * @param uuid the IID of the requested interface
+ * @param result the object, QI'd to the requested interface
+ *
+ * @throws NS_ERROR_NO_INTERFACE when an entry exists at the
+ * specified index, but the requested interface is not
+ * available.
+ * @throws NS_ERROR_ILLEGAL_VALUE when index > length-1
+ *
+ */
+ void queryElementAt(in unsigned long index,
+ in nsIIDRef uuid,
+ [iid_is(uuid), retval] out nsQIResult result);
+
+ /**
+ * indexOf()
+ *
+ * Get the position of a specific element. Note that since null is
+ * a valid input, exceptions are used to indicate that an element
+ * is not found.
+ *
+ * @param startIndex The initial element to search in the array
+ * To start at the beginning, use 0 as the
+ * startIndex
+ * @param element The element you are looking for
+ * @returns a number >= startIndex which is the position of the
+ * element in the array.
+ * @throws NS_ERROR_FAILURE if the element was not in the array.
+ */
+ unsigned long indexOf(in unsigned long startIndex,
+ in nsISupports element);
+
+ /**
+ * enumerate the array
+ *
+ * @returns a new enumerator positioned at the start of the array
+ * @throws NS_ERROR_FAILURE if the array is empty (to make it easy
+ * to detect errors), or NS_ERROR_OUT_OF_MEMORY if out of memory.
+ */
+ [binaryname(ScriptedEnumerate), optional_argc]
+ nsISimpleEnumerator enumerate([optional] in nsIIDRef aElemIID);
+
+ [noscript]
+ nsISimpleEnumerator enumerateImpl(in nsIDRef aElemIID);
+
+ %{C++
+ nsresult
+ Enumerate(nsISimpleEnumerator** aRetVal, const nsID& aElemIID = NS_GET_IID(nsISupports))
+ {
+ return EnumerateImpl(aElemIID, aRetVal);
+ }
+ %}
+};
diff --git a/xpcom/ds/nsIArrayExtensions.idl b/xpcom/ds/nsIArrayExtensions.idl
new file mode 100644
index 0000000000..872a5c018d
--- /dev/null
+++ b/xpcom/ds/nsIArrayExtensions.idl
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIArray.idl"
+
+/**
+ * Helper interface for allowing scripts to treat nsIArray instances as if
+ * they were nsISupportsArray instances while iterating.
+ *
+ * nsISupportsArray is convenient to iterate over in JavaScript:
+ *
+ * for (let i = 0; i < array.Count(); ++i) {
+ * let elem = array.GetElementAt(i);
+ * ...
+ * }
+ *
+ * but doing the same with nsIArray is somewhat less convenient, since
+ * queryElementAt is not nearly so nice to use from JavaScript. So we provide
+ * this extension interface so interfaces that currently return
+ * nsISupportsArray can start returning nsIArrayExtensions and all JavaScript
+ * should Just Work. Eventually we'll roll this interface into nsIArray
+ * itself, possibly getting rid of the Count() method, as it duplicates
+ * nsIArray functionality.
+ */
+[scriptable, builtinclass, uuid(261d442e-050c-453d-8aaa-b3f23bcc528b)]
+interface nsIArrayExtensions : nsIArray
+{
+ /**
+ * Count()
+ *
+ * Retrieves the length of the array. This is an alias for the
+ * |nsIArray.length| attribute.
+ */
+ uint32_t Count();
+
+ /**
+ * GetElementAt()
+ *
+ * Retrieve a specific element of the array. null is a valid result for
+ * this method.
+ *
+ * Note: If the index is out of bounds null will be returned.
+ * This differs from the behavior of nsIArray.queryElementAt() which
+ * will throw if an invalid index is specified.
+ *
+ * @param index position of element
+ */
+ nsISupports GetElementAt(in uint32_t index);
+};
diff --git a/xpcom/ds/nsIINIParser.idl b/xpcom/ds/nsIINIParser.idl
new file mode 100644
index 0000000000..434490bf42
--- /dev/null
+++ b/xpcom/ds/nsIINIParser.idl
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIUTF8StringEnumerator;
+interface nsIFile;
+
+[scriptable, uuid(7eb955f6-3e78-4d39-b72f-c1bf12a94bce)]
+interface nsIINIParser : nsISupports
+{
+ /**
+ * Initializes an INI file from string data
+ */
+ void initFromString(in AUTF8String aData);
+
+ /**
+ * Enumerates the [section]s available in the INI file.
+ */
+ nsIUTF8StringEnumerator getSections();
+
+ /**
+ * Enumerates the keys available within a section.
+ */
+ nsIUTF8StringEnumerator getKeys(in AUTF8String aSection);
+
+ /**
+ * Get the value of a string for a particular section and key.
+ */
+ AUTF8String getString(in AUTF8String aSection, in AUTF8String aKey);
+};
+
+[scriptable, uuid(b67bb24b-31a3-4a6a-a5d9-0485c9af5a04)]
+interface nsIINIParserWriter : nsISupports
+{
+
+ /**
+ * Set the value of a string for a particular section and key.
+ */
+ void setString(in AUTF8String aSection, in AUTF8String aKey, in AUTF8String aValue);
+
+ /**
+ * Write to the INI file.
+ */
+ void writeFile(in nsIFile aINIFile);
+
+ /**
+ * Return the formatted INI file contents
+ */
+ AUTF8String writeToString();
+};
+
+[scriptable, uuid(ccae7ea5-1218-4b51-aecb-c2d8ecd46af9)]
+interface nsIINIParserFactory : nsISupports
+{
+ /**
+ * Create an iniparser instance from a local file.
+ */
+ nsIINIParser createINIParser([optional] in nsIFile aINIFile);
+};
diff --git a/xpcom/ds/nsIMutableArray.idl b/xpcom/ds/nsIMutableArray.idl
new file mode 100644
index 0000000000..3f06ecbeb1
--- /dev/null
+++ b/xpcom/ds/nsIMutableArray.idl
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIArrayExtensions.idl"
+
+/**
+ * nsIMutableArray
+ * A separate set of methods that will act on the array. Consumers of
+ * nsIArray should not QueryInterface to nsIMutableArray unless they
+ * own the array.
+ *
+ * As above, it is legal to add null elements to the array. Note also
+ * that null elements can be created as a side effect of
+ * insertElementAt(). Conversely, if insertElementAt() is never used,
+ * and null elements are never explicitly added to the array, then it
+ * is guaranteed that queryElementAt() will never return a null value.
+ *
+ * Any of these methods may throw NS_ERROR_OUT_OF_MEMORY when the
+ * array must grow to complete the call, but the allocation fails.
+ */
+[scriptable, builtinclass, uuid(af059da0-c85b-40ec-af07-ae4bfdc192cc)]
+interface nsIMutableArray : nsIArrayExtensions
+{
+ /**
+ * appendElement()
+ *
+ * Append an element at the end of the array.
+ *
+ * @param element The element to append.
+ */
+ void appendElement(in nsISupports element);
+
+ /**
+ * removeElementAt()
+ *
+ * Remove an element at a specific position, moving all elements
+ * stored at a higher position down one.
+ * To remove a specific element, use indexOf() to find the index
+ * first, then call removeElementAt().
+ *
+ * @param index the position of the item
+ *
+ */
+ void removeElementAt(in unsigned long index);
+
+ /**
+ * insertElementAt()
+ *
+ * Insert an element at the given position, moving the element
+ * currently located in that position, and all elements in higher
+ * position, up by one.
+ *
+ * @param element The element to insert
+ * @param index The position in the array:
+ * If the position is lower than the current length
+ * of the array, the elements at that position and
+ * onwards are bumped one position up.
+ * If the position is equal to the current length
+ * of the array, the new element is appended.
+ * An index lower than 0 or higher than the current
+ * length of the array is invalid and will be ignored.
+ */
+ void insertElementAt(in nsISupports element, in unsigned long index);
+
+ /**
+ * replaceElementAt()
+ *
+ * Replace the element at the given position.
+ *
+ * @param element The new element to insert
+ * @param index The position in the array
+ * If the position is lower than the current length
+ * of the array, an existing element will be replaced.
+ * If the position is equal to the current length
+ * of the array, the new element is appended.
+ * If the position is higher than the current length
+ * of the array, empty elements are appended followed
+ * by the new element at the specified position.
+ * An index lower than 0 is invalid and will be ignored.
+ */
+ void replaceElementAt(in nsISupports element, in unsigned long index);
+
+
+ /**
+ * clear()
+ *
+ * clear the entire array, releasing all stored objects
+ */
+ void clear();
+};
diff --git a/xpcom/ds/nsINIParserImpl.cpp b/xpcom/ds/nsINIParserImpl.cpp
new file mode 100644
index 0000000000..6ed52947d8
--- /dev/null
+++ b/xpcom/ds/nsINIParserImpl.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsINIParserImpl.h"
+
+#include "nsINIParser.h"
+#include "nsStringEnumerator.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+class nsINIParserImpl final : public nsIINIParser, public nsIINIParserWriter {
+ ~nsINIParserImpl() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINIPARSER
+ NS_DECL_NSIINIPARSERWRITER
+
+ nsresult Init(nsIFile* aINIFile) { return mParser.Init(aINIFile); }
+
+ private:
+ nsINIParser mParser;
+ bool ContainsNull(const nsACString& aStr);
+};
+
+NS_IMPL_ISUPPORTS(nsINIParserFactory, nsIINIParserFactory)
+
+NS_IMETHODIMP
+nsINIParserFactory::CreateINIParser(nsIFile* aINIFile, nsIINIParser** aResult) {
+ *aResult = nullptr;
+
+ RefPtr<nsINIParserImpl> p(new nsINIParserImpl());
+
+ if (aINIFile) {
+ nsresult rv = p->Init(aINIFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ p.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsINIParserImpl, nsIINIParser, nsIINIParserWriter)
+
+bool nsINIParserImpl::ContainsNull(const nsACString& aStr) {
+ return aStr.CountChar('\0') > 0;
+}
+
+static bool SectionCB(const char* aSection, void* aClosure) {
+ nsTArray<nsCString>* strings = static_cast<nsTArray<nsCString>*>(aClosure);
+ strings->AppendElement()->Assign(aSection);
+ return true;
+}
+
+NS_IMETHODIMP
+nsINIParserImpl::GetSections(nsIUTF8StringEnumerator** aResult) {
+ nsTArray<nsCString>* strings = new nsTArray<nsCString>;
+
+ nsresult rv = mParser.GetSections(SectionCB, strings);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_NewAdoptingUTF8StringEnumerator(aResult, strings);
+ }
+
+ if (NS_FAILED(rv)) {
+ delete strings;
+ }
+
+ return rv;
+}
+
+static bool KeyCB(const char* aKey, const char* aValue, void* aClosure) {
+ nsTArray<nsCString>* strings = static_cast<nsTArray<nsCString>*>(aClosure);
+ strings->AppendElement()->Assign(aKey);
+ return true;
+}
+
+NS_IMETHODIMP
+nsINIParserImpl::GetKeys(const nsACString& aSection,
+ nsIUTF8StringEnumerator** aResult) {
+ if (ContainsNull(aSection)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsTArray<nsCString>* strings = new nsTArray<nsCString>;
+
+ nsresult rv =
+ mParser.GetStrings(PromiseFlatCString(aSection).get(), KeyCB, strings);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_NewAdoptingUTF8StringEnumerator(aResult, strings);
+ }
+
+ if (NS_FAILED(rv)) {
+ delete strings;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsINIParserImpl::GetString(const nsACString& aSection, const nsACString& aKey,
+ nsACString& aResult) {
+ if (ContainsNull(aSection) || ContainsNull(aKey)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return mParser.GetString(PromiseFlatCString(aSection).get(),
+ PromiseFlatCString(aKey).get(), aResult);
+}
+
+NS_IMETHODIMP
+nsINIParserImpl::InitFromString(const nsACString& aData) {
+ return mParser.InitFromString(nsCString(aData));
+}
+
+NS_IMETHODIMP
+nsINIParserImpl::SetString(const nsACString& aSection, const nsACString& aKey,
+ const nsACString& aValue) {
+ if (ContainsNull(aSection) || ContainsNull(aKey) || ContainsNull(aValue)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return mParser.SetString(PromiseFlatCString(aSection).get(),
+ PromiseFlatCString(aKey).get(),
+ PromiseFlatCString(aValue).get());
+}
+
+NS_IMETHODIMP
+nsINIParserImpl::WriteFile(nsIFile* aINIFile) {
+ return mParser.WriteToFile(aINIFile);
+}
+
+NS_IMETHODIMP
+nsINIParserImpl::WriteToString(nsACString& aOutput) {
+ aOutput.Truncate();
+ mParser.WriteToString(aOutput);
+
+ return NS_OK;
+}
diff --git a/xpcom/ds/nsINIParserImpl.h b/xpcom/ds/nsINIParserImpl.h
new file mode 100644
index 0000000000..1a60fd1071
--- /dev/null
+++ b/xpcom/ds/nsINIParserImpl.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsINIParserImpl_h__
+#define nsINIParserImpl_h__
+
+#include "nsIINIParser.h"
+#include "mozilla/Attributes.h"
+
+#define NS_INIPARSERFACTORY_CONTRACTID "@mozilla.org/xpcom/ini-parser-factory;1"
+
+class nsINIParserFactory final : public nsIINIParserFactory {
+ ~nsINIParserFactory() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINIPARSERFACTORY
+};
+
+#endif // nsINIParserImpl_h__
diff --git a/xpcom/ds/nsIObserver.idl b/xpcom/ds/nsIObserver.idl
new file mode 100644
index 0000000000..773424d0a7
--- /dev/null
+++ b/xpcom/ds/nsIObserver.idl
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface is implemented by an object that wants
+ * to observe an event corresponding to a topic.
+ */
+
+[scriptable, function, uuid(DB242E01-E4D9-11d2-9DDE-000064657374)]
+interface nsIObserver : nsISupports {
+
+ /**
+ * Observe will be called when there is a notification for the
+ * topic |aTopic|. This assumes that the object implementing
+ * this interface has been registered with an observer service
+ * such as the nsIObserverService.
+ *
+ * If you expect multiple topics/subjects, the impl is
+ * responsible for filtering.
+ *
+ * You should not modify, add, remove, or enumerate
+ * notifications in the implemention of observe.
+ *
+ * @param aSubject : Notification specific interface pointer.
+ * @param aTopic : The notification topic or subject.
+ * @param aData : Notification specific wide string.
+ * subject event.
+ */
+ void observe( in nsISupports aSubject,
+ in string aTopic,
+ in wstring aData );
+
+};
diff --git a/xpcom/ds/nsIObserverService.idl b/xpcom/ds/nsIObserverService.idl
new file mode 100644
index 0000000000..5ca51d5d44
--- /dev/null
+++ b/xpcom/ds/nsIObserverService.idl
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIObserver;
+interface nsISimpleEnumerator;
+
+/**
+ * nsIObserverService
+ *
+ * Service allows a client listener (nsIObserver) to register and unregister for
+ * notifications of specific string referenced topic. Service also provides a
+ * way to notify registered listeners and a way to enumerate registered client
+ * listeners.
+ */
+
+[scriptable, builtinclass, uuid(D07F5192-E3D1-11d2-8ACD-00105A1B8860)]
+interface nsIObserverService : nsISupports
+{
+
+ /**
+ * AddObserver
+ *
+ * Registers a given listener for a notifications regarding the specified
+ * topic.
+ *
+ * @param anObserve : The interface pointer which will receive notifications.
+ * @param aTopic : The notification topic or subject.
+ * @param ownsWeak : If set to false, the nsIObserverService will hold a
+ * strong reference to |anObserver|. If set to true and
+ * |anObserver| supports the nsIWeakReference interface,
+ * a weak reference will be held. Otherwise an error will be
+ * returned.
+ */
+ void addObserver( in nsIObserver anObserver, in string aTopic,
+ [optional] in boolean ownsWeak);
+
+ /**
+ * removeObserver
+ *
+ * Unregisters a given listener from notifications regarding the specified
+ * topic.
+ *
+ * @param anObserver : The interface pointer which will stop recieving
+ * notifications.
+ * @param aTopic : The notification topic or subject.
+ */
+ void removeObserver( in nsIObserver anObserver, in string aTopic );
+
+ /**
+ * notifyObservers
+ *
+ * Notifies all registered listeners of the given topic.
+ * Must not be used with shutdown topics (will assert
+ * on the parent process).
+ *
+ * @param aSubject : Notification specific interface pointer.
+ * @param aTopic : The notification topic or subject.
+ * @param someData : Notification specific wide string.
+ */
+ void notifyObservers( in nsISupports aSubject,
+ in string aTopic,
+ [optional] in wstring someData );
+
+ /**
+ * hasObservers
+ *
+ * Checks to see if there are registered listeners for the given topic.
+ *
+ * Implemented in "nsObserverService.cpp".
+ *
+ * @param aTopic : The notification topic or subject.
+ * @param aFound : An out parameter; True if there are registered observers,
+ * False otherwise.
+ */
+ [noscript, notxpcom, nostdcall] boolean hasObservers(in string aTopic);
+
+ %{C++
+ /**
+ * notifyWhenScriptSafe
+ *
+ * Notifies all registered listeners of the given topic once it is safe to
+ * run script.
+ *
+ * Implemented in "nsObserverService.cpp".
+ *
+ * @param aSubject : Notification specific interface pointer.
+ * @param aTopic : The notification topic or subject.
+ * @param someData : Notification specific wide string.
+ */
+ nsresult NotifyWhenScriptSafe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData = nullptr);
+ %}
+
+ /**
+ * enumerateObservers
+ *
+ * Returns an enumeration of all registered listeners.
+ *
+ * @param aTopic : The notification topic or subject.
+ */
+ nsISimpleEnumerator enumerateObservers( in string aTopic );
+
+
+};
diff --git a/xpcom/ds/nsIPersistentProperties.h b/xpcom/ds/nsIPersistentProperties.h
new file mode 100644
index 0000000000..eef30c2ff2
--- /dev/null
+++ b/xpcom/ds/nsIPersistentProperties.h
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __gen_nsIPersistentProperties_h__
+#define __gen_nsIPersistentProperties_h__
+
+// "soft" switch over to an IDL generated header file
+#include "nsIPersistentProperties2.h"
+
+#endif /* __gen_nsIPersistentProperties_h__ */
diff --git a/xpcom/ds/nsIPersistentProperties2.idl b/xpcom/ds/nsIPersistentProperties2.idl
new file mode 100644
index 0000000000..7b07ba33ab
--- /dev/null
+++ b/xpcom/ds/nsIPersistentProperties2.idl
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProperties.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsISimpleEnumerator;
+
+%{C++
+#include "mozilla/MemoryReporting.h"
+%}
+
+native MallocSizeOf(mozilla::MallocSizeOf);
+
+[scriptable, uuid(283EE646-1AEF-11D4-98B3-00C04fA0CE9A)]
+interface nsIPropertyElement : nsISupports {
+ attribute AUTF8String key;
+ attribute AString value;
+};
+
+[scriptable, builtinclass, uuid(706867af-0400-4faa-beb1-0dae87308784)]
+interface nsIPersistentProperties : nsIProperties
+{
+ /**
+ * load a set of name/value pairs from the input stream
+ * names and values should be in UTF8
+ */
+ void load(in nsIInputStream input);
+
+ /**
+ * output the values to the stream - results will be in UTF8
+ */
+ void save(in nsIOutputStream output, in AUTF8String header);
+
+ /**
+ * get an enumeration of nsIPropertyElement objects,
+ * which are read-only (i.e. setting properties on the element will
+ * not make changes back into the source nsIPersistentProperties
+ */
+ nsISimpleEnumerator enumerate();
+
+ /**
+ * shortcut to nsIProperty's get() which retrieves a string value
+ * directly (and thus faster)
+ */
+ AString getStringProperty(in AUTF8String key);
+
+ /**
+ * shortcut to nsIProperty's set() which sets a string value
+ * directly (and thus faster). If the given property already exists,
+ * then the old value will be returned
+ */
+ AString setStringProperty(in AUTF8String key, in AString value);
+
+ [notxpcom, nostdcall] size_t sizeOfIncludingThis(in MallocSizeOf aMallocSizeOf);
+};
diff --git a/xpcom/ds/nsIProperties.idl b/xpcom/ds/nsIProperties.idl
new file mode 100644
index 0000000000..50d833e231
--- /dev/null
+++ b/xpcom/ds/nsIProperties.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/*
+ * Simple mapping service interface.
+ */
+
+[scriptable, uuid(78650582-4e93-4b60-8e85-26ebd3eb14ca)]
+interface nsIProperties : nsISupports
+{
+ /**
+ * Gets a property with a given name.
+ *
+ * @throws NS_ERROR_FAILURE if a property with that name doesn't exist.
+ * @throws NS_ERROR_NO_INTERFACE if the found property fails to QI to the
+ * given iid.
+ */
+ void get(in string prop, in nsIIDRef iid,
+ [iid_is(iid),retval] out nsQIResult result);
+
+ /**
+ * Sets a property with a given name to a given value.
+ */
+ void set(in string prop, in nsISupports value);
+
+ /**
+ * Returns true if the property with the given name exists.
+ */
+ boolean has(in string prop);
+
+ /**
+ * Undefines a property.
+ * @throws NS_ERROR_FAILURE if a property with that name doesn't
+ * already exist.
+ */
+ void undefine(in string prop);
+
+ /**
+ * Returns an array of the keys.
+ */
+ Array<ACString> getKeys();
+};
diff --git a/xpcom/ds/nsIProperty.idl b/xpcom/ds/nsIProperty.idl
new file mode 100644
index 0000000000..9e6e1e487e
--- /dev/null
+++ b/xpcom/ds/nsIProperty.idl
@@ -0,0 +1,25 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* nsIVariant based Property support. */
+
+#include "nsISupports.idl"
+
+interface nsIVariant;
+
+[scriptable, uuid(6dcf9030-a49f-11d5-910d-0010a4e73d9a)]
+interface nsIProperty : nsISupports
+{
+ /**
+ * Get the name of the property.
+ */
+ readonly attribute AString name;
+
+ /**
+ * Get the value of the property.
+ */
+ readonly attribute nsIVariant value;
+};
diff --git a/xpcom/ds/nsIPropertyBag.idl b/xpcom/ds/nsIPropertyBag.idl
new file mode 100644
index 0000000000..57bd263451
--- /dev/null
+++ b/xpcom/ds/nsIPropertyBag.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* nsIVariant based Property Bag support. */
+
+#include "nsISupports.idl"
+
+interface nsIVariant;
+interface nsISimpleEnumerator;
+
+[scriptable, uuid(bfcd37b0-a49f-11d5-910d-0010a4e73d9a)]
+interface nsIPropertyBag : nsISupports
+{
+ /**
+ * Get a nsISimpleEnumerator whose elements are nsIProperty objects.
+ */
+ readonly attribute nsISimpleEnumerator enumerator;
+
+ /**
+ * Get a property value for the given name.
+ * @throws NS_ERROR_FAILURE if a property with that name doesn't
+ * exist.
+ */
+ nsIVariant getProperty(in AString name);
+};
diff --git a/xpcom/ds/nsIPropertyBag2.idl b/xpcom/ds/nsIPropertyBag2.idl
new file mode 100644
index 0000000000..3232dce0ce
--- /dev/null
+++ b/xpcom/ds/nsIPropertyBag2.idl
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* nsIVariant based Property Bag support. */
+
+#include "nsIPropertyBag.idl"
+
+[scriptable, uuid(625cfd1e-da1e-4417-9ee9-dbc8e0b3fd79)]
+interface nsIPropertyBag2 : nsIPropertyBag
+{
+ // Accessing a property as a different type may attempt conversion to the
+ // requested value
+
+ int32_t getPropertyAsInt32 (in AString prop);
+ uint32_t getPropertyAsUint32 (in AString prop);
+ int64_t getPropertyAsInt64 (in AString prop);
+ uint64_t getPropertyAsUint64 (in AString prop);
+ double getPropertyAsDouble (in AString prop);
+ AString getPropertyAsAString (in AString prop);
+ ACString getPropertyAsACString (in AString prop);
+ AUTF8String getPropertyAsAUTF8String (in AString prop);
+ boolean getPropertyAsBool (in AString prop);
+
+ /**
+ * This method returns null if the value exists, but is null.
+ *
+ * Note: C++ callers should not use this method. They should use the
+ * typesafe `do_GetProperty` wrapper instead.
+ */
+ void getPropertyAsInterface (in AString prop,
+ in nsIIDRef iid,
+ [iid_is(iid), retval] out nsQIResult result);
+
+ /**
+ * This method returns null if the value does not exist,
+ * or exists but is null.
+ */
+ nsIVariant get (in AString prop);
+
+ /**
+ * Check for the existence of a key.
+ */
+ boolean hasKey (in AString prop);
+};
+
+
+%{C++
+#include "nsCOMPtr.h"
+#include "nsAString.h"
+
+class MOZ_STACK_CLASS nsGetProperty final : public nsCOMPtr_helper {
+ public:
+ nsGetProperty(nsIPropertyBag2* aPropBag, const nsAString& aPropName, nsresult* aError)
+ : mPropBag(aPropBag), mPropName(aPropName), mErrorPtr(aError) {}
+
+ virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override;
+
+ private:
+ nsIPropertyBag2* MOZ_NON_OWNING_REF mPropBag;
+ const nsAString& mPropName;
+ nsresult* mErrorPtr;
+};
+
+/**
+ * A typesafe wrapper around nsIPropertyBag2::GetPropertyAsInterface. Similar
+ * to the `do_QueryInterface` family of functions, when assigned to a
+ * `nsCOMPtr` of a given type, attempts to query the given property to that
+ * type.
+ *
+ * If `aError` is passed, the return value of `GetPropertyAsInterface` is
+ * stored in it.
+ */
+inline const nsGetProperty do_GetProperty(nsIPropertyBag2* aPropBag,
+ const nsAString& aPropName,
+ nsresult* aError = 0) {
+ return nsGetProperty(aPropBag, aPropName, aError);
+}
+
+%}
diff --git a/xpcom/ds/nsISerializable.idl b/xpcom/ds/nsISerializable.idl
new file mode 100644
index 0000000000..1920fa1b16
--- /dev/null
+++ b/xpcom/ds/nsISerializable.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIObjectInputStream;
+interface nsIObjectOutputStream;
+
+[scriptable, uuid(91cca981-c26d-44a8-bebe-d9ed4891503a)]
+interface nsISerializable : nsISupports
+{
+ /**
+ * Initialize the object implementing nsISerializable, which must have
+ * been freshly constructed via CreateInstance. All data members that
+ * can't be set to default values must have been serialized by write,
+ * and should be read from aInputStream in the same order by this method.
+ */
+ [must_use] void read(in nsIObjectInputStream aInputStream);
+
+ /**
+ * Serialize the object implementing nsISerializable to aOutputStream, by
+ * writing each data member that must be recovered later to reconstitute
+ * a working replica of this object, in a canonical member and byte order,
+ * to aOutputStream.
+ *
+ * NB: a class that implements nsISerializable *must* also implement
+ * nsIClassInfo, in particular nsIClassInfo::GetClassID.
+ */
+ void write(in nsIObjectOutputStream aOutputStream);
+};
diff --git a/xpcom/ds/nsISimpleEnumerator.idl b/xpcom/ds/nsISimpleEnumerator.idl
new file mode 100644
index 0000000000..1a2b47705d
--- /dev/null
+++ b/xpcom/ds/nsISimpleEnumerator.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Used to enumerate over elements defined by its implementor.
+ * Although hasMoreElements() can be called independently of getNext(),
+ * getNext() must be pre-ceeded by a call to hasMoreElements(). There is
+ * no way to "reset" an enumerator, once you obtain one.
+ *
+ * @version 1.0
+ */
+
+/**
+ * A wrapper for an nsISimpleEnumerator instance which implements the
+ * JavaScript iteration protocol.
+ */
+[scriptable, uuid(4432e8ae-d4d3-42a6-a4d1-829f1c29512b)]
+interface nsIJSEnumerator : nsISupports {
+ [symbol]
+ nsIJSEnumerator iterator();
+
+ [implicit_jscontext]
+ jsval next();
+};
+
+[scriptable, uuid(796f340d-0a2a-490b-9c60-640765e99782)]
+interface nsISimpleEnumeratorBase : nsISupports {
+ /**
+ * Returns a JavaScript iterator for all remaining entries in the enumerator.
+ * Each entry is typically queried to the appropriate interface for the
+ * enumerator.
+ */
+ [symbol]
+ nsIJSEnumerator iterator();
+
+ /**
+ * Returns JavaScript iterator for all remaining entries in the enumerator.
+ * Each entry is queried only to the supplied interface. If any element
+ * fails to query to that interface, the error is propagated to the caller.
+ */
+ nsIJSEnumerator entries(in nsIIDRef aIface);
+};
+
+[scriptable, uuid(D1899240-F9D2-11D2-BDD6-000064657374)]
+interface nsISimpleEnumerator : nsISimpleEnumeratorBase {
+ /**
+ * Called to determine whether or not the enumerator has
+ * any elements that can be returned via getNext(). This method
+ * is generally used to determine whether or not to initiate or
+ * continue iteration over the enumerator, though it can be
+ * called without subsequent getNext() calls. Does not affect
+ * internal state of enumerator.
+ *
+ * @see getNext()
+ * @return true if there are remaining elements in the enumerator.
+ * false if there are no more elements in the enumerator.
+ */
+ boolean hasMoreElements();
+
+ /**
+ * Called to retrieve the next element in the enumerator. The "next"
+ * element is the first element upon the first call. Must be
+ * pre-ceeded by a call to hasMoreElements() which returns PR_TRUE.
+ * This method is generally called within a loop to iterate over
+ * the elements in the enumerator.
+ *
+ * @see hasMoreElements()
+ * @throws NS_ERROR_FAILURE if there are no more elements
+ * to enumerate.
+ * @return the next element in the enumeration.
+ */
+ nsISupports getNext();
+};
diff --git a/xpcom/ds/nsIStringEnumerator.idl b/xpcom/ds/nsIStringEnumerator.idl
new file mode 100644
index 0000000000..96ab966bff
--- /dev/null
+++ b/xpcom/ds/nsIStringEnumerator.idl
@@ -0,0 +1,37 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIJSEnumerator;
+
+/**
+ * Used to enumerate over an ordered list of strings.
+ */
+
+/**
+ * Base class for C++-implemented string iterators. JS implementors need not
+ * be queryable to it.
+ */
+[scriptable, uuid(f5213d15-a4d1-4fb7-8a48-d69ccb7fb0eb)]
+interface nsIStringEnumeratorBase : nsISupports
+{
+ [symbol, binaryname(StringIterator)]
+ nsIJSEnumerator iterator();
+};
+
+[scriptable, uuid(50d3ef6c-9380-4f06-9fb2-95488f7d141c)]
+interface nsIStringEnumerator : nsIStringEnumeratorBase
+{
+ boolean hasMore();
+ AString getNext();
+};
+
+[scriptable, uuid(9bdf1010-3695-4907-95ed-83d0410ec307)]
+interface nsIUTF8StringEnumerator : nsIStringEnumeratorBase
+{
+ boolean hasMore();
+ AUTF8String getNext();
+};
diff --git a/xpcom/ds/nsISupportsIterators.idl b/xpcom/ds/nsISupportsIterators.idl
new file mode 100644
index 0000000000..8d47375d57
--- /dev/null
+++ b/xpcom/ds/nsISupportsIterators.idl
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* nsISupportsIterators.idl --- IDL defining general purpose iterators */
+
+
+#include "nsISupports.idl"
+
+
+ /*
+ ...
+ */
+
+
+ /**
+ * ...
+ */
+[scriptable, uuid(7330650e-1dd2-11b2-a0c2-9ff86ee97bed)]
+interface nsIOutputIterator : nsISupports
+ {
+ /**
+ * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator.
+ * The iterator and the underlying container or sequence cooperate to |Release()|
+ * the replaced element, if any and if necessary, and to |AddRef()| the new element.
+ *
+ * The result is undefined if this iterator currently points outside the
+ * useful range of the underlying container or sequence.
+ *
+ * @param anElementToPut the element to place into the underlying container or sequence
+ */
+ void putElement( in nsISupports anElementToPut );
+
+ /**
+ * Advance this iterator to the next position in the underlying container or sequence.
+ */
+ void stepForward();
+ };
+
+ /**
+ * ...
+ */
+[scriptable, uuid(85585e12-1dd2-11b2-a930-f6929058269a)]
+interface nsIInputIterator : nsISupports
+ {
+ /**
+ * Retrieve (and |AddRef()|) the element this iterator currently points to.
+ *
+ * The result is undefined if this iterator currently points outside the
+ * useful range of the underlying container or sequence.
+ *
+ * @result a new reference to the element this iterator currently points to (if any)
+ */
+ nsISupports getElement();
+
+ /**
+ * Advance this iterator to the next position in the underlying container or sequence.
+ */
+ void stepForward();
+
+ /**
+ * Test if |anotherIterator| points to the same position in the underlying container or sequence.
+ *
+ * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence.
+ *
+ * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence
+ * @result true if |anotherIterator| points to the same position in the underlying container or sequence
+ */
+ boolean isEqualTo( in nsISupports anotherIterator );
+
+ /**
+ * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points.
+ * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator.
+ *
+ * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator
+ */
+ nsISupports clone();
+ };
+
+ /**
+ * ...
+ */
+[scriptable, uuid(8da01646-1dd2-11b2-98a7-c7009045be7e)]
+interface nsIForwardIterator : nsISupports
+ {
+ /**
+ * Retrieve (and |AddRef()|) the element this iterator currently points to.
+ *
+ * The result is undefined if this iterator currently points outside the
+ * useful range of the underlying container or sequence.
+ *
+ * @result a new reference to the element this iterator currently points to (if any)
+ */
+ nsISupports getElement();
+
+ /**
+ * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator.
+ * The iterator and the underlying container or sequence cooperate to |Release()|
+ * the replaced element, if any and if necessary, and to |AddRef()| the new element.
+ *
+ * The result is undefined if this iterator currently points outside the
+ * useful range of the underlying container or sequence.
+ *
+ * @param anElementToPut the element to place into the underlying container or sequence
+ */
+ void putElement( in nsISupports anElementToPut );
+
+ /**
+ * Advance this iterator to the next position in the underlying container or sequence.
+ */
+ void stepForward();
+
+ /**
+ * Test if |anotherIterator| points to the same position in the underlying container or sequence.
+ *
+ * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence.
+ *
+ * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence
+ * @result true if |anotherIterator| points to the same position in the underlying container or sequence
+ */
+ boolean isEqualTo( in nsISupports anotherIterator );
+
+ /**
+ * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points.
+ * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator.
+ *
+ * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator
+ */
+ nsISupports clone();
+ };
+
+ /**
+ * ...
+ */
+[scriptable, uuid(948defaa-1dd1-11b2-89f6-8ce81f5ebda9)]
+interface nsIBidirectionalIterator : nsISupports
+ {
+ /**
+ * Retrieve (and |AddRef()|) the element this iterator currently points to.
+ *
+ * The result is undefined if this iterator currently points outside the
+ * useful range of the underlying container or sequence.
+ *
+ * @result a new reference to the element this iterator currently points to (if any)
+ */
+ nsISupports getElement();
+
+ /**
+ * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator.
+ * The iterator and the underlying container or sequence cooperate to |Release()|
+ * the replaced element, if any and if necessary, and to |AddRef()| the new element.
+ *
+ * The result is undefined if this iterator currently points outside the
+ * useful range of the underlying container or sequence.
+ *
+ * @param anElementToPut the element to place into the underlying container or sequence
+ */
+ void putElement( in nsISupports anElementToPut );
+
+ /**
+ * Advance this iterator to the next position in the underlying container or sequence.
+ */
+ void stepForward();
+
+ /**
+ * Move this iterator to the previous position in the underlying container or sequence.
+ */
+ void stepBackward();
+
+ /**
+ * Test if |anotherIterator| points to the same position in the underlying container or sequence.
+ *
+ * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence.
+ *
+ * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence
+ * @result true if |anotherIterator| points to the same position in the underlying container or sequence
+ */
+ boolean isEqualTo( in nsISupports anotherIterator );
+
+ /**
+ * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points.
+ * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator.
+ *
+ * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator
+ */
+ nsISupports clone();
+ };
+
+ /**
+ * ...
+ */
+[scriptable, uuid(9bd6fdb0-1dd1-11b2-9101-d15375968230)]
+interface nsIRandomAccessIterator : nsISupports
+ {
+ /**
+ * Retrieve (and |AddRef()|) the element this iterator currently points to.
+ *
+ * The result is undefined if this iterator currently points outside the
+ * useful range of the underlying container or sequence.
+ *
+ * @result a new reference to the element this iterator currently points to (if any)
+ */
+ nsISupports getElement();
+
+ /**
+ * Retrieve (and |AddRef()|) an element at some offset from where this iterator currently points.
+ * The offset may be negative. |getElementAt(0)| is equivalent to |getElement()|.
+ *
+ * The result is undefined if this iterator currently points outside the
+ * useful range of the underlying container or sequence.
+ *
+ * @param anOffset a |0|-based offset from the position to which this iterator currently points
+ * @result a new reference to the indicated element (if any)
+ */
+ nsISupports getElementAt( in int32_t anOffset );
+
+ /**
+ * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator.
+ * The iterator and the underlying container or sequence cooperate to |Release()|
+ * the replaced element, if any and if necessary, and to |AddRef()| the new element.
+ *
+ * The result is undefined if this iterator currently points outside the
+ * useful range of the underlying container or sequence.
+ *
+ * @param anElementToPut the element to place into the underlying container or sequence
+ */
+ void putElement( in nsISupports anElementToPut );
+
+ /**
+ * Put |anElementToPut| into the underlying container or sequence at the position |anOffset| away from that currently pointed to by this iterator.
+ * The iterator and the underlying container or sequence cooperate to |Release()|
+ * the replaced element, if any and if necessary, and to |AddRef()| the new element.
+ * |putElementAt(0, obj)| is equivalent to |putElement(obj)|.
+ *
+ * The result is undefined if this iterator currently points outside the
+ * useful range of the underlying container or sequence.
+ *
+ * @param anOffset a |0|-based offset from the position to which this iterator currently points
+ * @param anElementToPut the element to place into the underlying container or sequence
+ */
+ void putElementAt( in int32_t anOffset, in nsISupports anElementToPut );
+
+ /**
+ * Advance this iterator to the next position in the underlying container or sequence.
+ */
+ void stepForward();
+
+ /**
+ * Move this iterator by |anOffset| positions in the underlying container or sequence.
+ * |anOffset| may be negative. |stepForwardBy(1)| is equivalent to |stepForward()|.
+ * |stepForwardBy(0)| is a no-op.
+ *
+ * @param anOffset a |0|-based offset from the position to which this iterator currently points
+ */
+ void stepForwardBy( in int32_t anOffset );
+
+ /**
+ * Move this iterator to the previous position in the underlying container or sequence.
+ */
+ void stepBackward();
+
+ /**
+ * Move this iterator backwards by |anOffset| positions in the underlying container or sequence.
+ * |anOffset| may be negative. |stepBackwardBy(1)| is equivalent to |stepBackward()|.
+ * |stepBackwardBy(n)| is equivalent to |stepForwardBy(-n)|. |stepBackwardBy(0)| is a no-op.
+ *
+ * @param anOffset a |0|-based offset from the position to which this iterator currently points
+ */
+ void stepBackwardBy( in int32_t anOffset );
+
+ /**
+ * Test if |anotherIterator| points to the same position in the underlying container or sequence.
+ *
+ * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence.
+ *
+ * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence
+ * @result true if |anotherIterator| points to the same position in the underlying container or sequence
+ */
+ boolean isEqualTo( in nsISupports anotherIterator );
+
+ /**
+ * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points.
+ * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator.
+ *
+ * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator
+ */
+ nsISupports clone();
+ };
+
+%{C++
+%}
diff --git a/xpcom/ds/nsISupportsPrimitives.idl b/xpcom/ds/nsISupportsPrimitives.idl
new file mode 100644
index 0000000000..d661ce3b21
--- /dev/null
+++ b/xpcom/ds/nsISupportsPrimitives.idl
@@ -0,0 +1,222 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* nsISupports wrappers for single primitive pieces of data. */
+
+#include "nsISupports.idl"
+
+/**
+ * Primitive base interface.
+ *
+ * These first three are pointer types and do data copying
+ * using the nsIMemory. Be careful!
+ */
+
+[scriptable, builtinclass, uuid(d0d4b136-1dd1-11b2-9371-f0727ef827c0)]
+interface nsISupportsPrimitive : nsISupports
+{
+ const unsigned short TYPE_ID = 1;
+ const unsigned short TYPE_CSTRING = 2;
+ const unsigned short TYPE_STRING = 3;
+ const unsigned short TYPE_PRBOOL = 4;
+ const unsigned short TYPE_PRUINT8 = 5;
+ const unsigned short TYPE_PRUINT16 = 6;
+ const unsigned short TYPE_PRUINT32 = 7;
+ const unsigned short TYPE_PRUINT64 = 8;
+ const unsigned short TYPE_PRTIME = 9;
+ const unsigned short TYPE_CHAR = 10;
+ const unsigned short TYPE_PRINT16 = 11;
+ const unsigned short TYPE_PRINT32 = 12;
+ const unsigned short TYPE_PRINT64 = 13;
+ const unsigned short TYPE_FLOAT = 14;
+ const unsigned short TYPE_DOUBLE = 15;
+ // 16 was for TYPE_VOID
+ const unsigned short TYPE_INTERFACE_POINTER = 17;
+
+ readonly attribute unsigned short type;
+};
+
+/**
+ * Scriptable storage for nsID structures
+ */
+
+[scriptable, builtinclass, uuid(d18290a0-4a1c-11d3-9890-006008962422)]
+interface nsISupportsID : nsISupportsPrimitive
+{
+ attribute nsIDPtr data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for ASCII strings
+ */
+
+[scriptable, builtinclass, uuid(d65ff270-4a1c-11d3-9890-006008962422)]
+interface nsISupportsCString : nsISupportsPrimitive
+{
+ attribute ACString data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for Unicode strings
+ */
+
+[scriptable, builtinclass, uuid(d79dc970-4a1c-11d3-9890-006008962422)]
+interface nsISupportsString : nsISupportsPrimitive
+{
+ attribute AString data;
+ wstring toString();
+};
+
+/**
+ * The rest are truly primitive and are passed by value
+ */
+
+/**
+ * Scriptable storage for booleans
+ */
+
+[scriptable, builtinclass, uuid(ddc3b490-4a1c-11d3-9890-006008962422)]
+interface nsISupportsPRBool : nsISupportsPrimitive
+{
+ attribute boolean data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for 8-bit integers
+ */
+
+[scriptable, builtinclass, uuid(dec2e4e0-4a1c-11d3-9890-006008962422)]
+interface nsISupportsPRUint8 : nsISupportsPrimitive
+{
+ attribute uint8_t data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for unsigned 16-bit integers
+ */
+
+[scriptable, builtinclass, uuid(dfacb090-4a1c-11d3-9890-006008962422)]
+interface nsISupportsPRUint16 : nsISupportsPrimitive
+{
+ attribute uint16_t data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for unsigned 32-bit integers
+ */
+
+[scriptable, builtinclass, uuid(e01dc470-4a1c-11d3-9890-006008962422)]
+interface nsISupportsPRUint32 : nsISupportsPrimitive
+{
+ attribute uint32_t data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for 64-bit integers
+ */
+
+[scriptable, builtinclass, uuid(e13567c0-4a1c-11d3-9890-006008962422)]
+interface nsISupportsPRUint64 : nsISupportsPrimitive
+{
+ attribute uint64_t data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for NSPR date/time values
+ */
+
+[scriptable, builtinclass, uuid(e2563630-4a1c-11d3-9890-006008962422)]
+interface nsISupportsPRTime : nsISupportsPrimitive
+{
+ attribute PRTime data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for single character values
+ * (often used to store an ASCII character)
+ */
+
+[scriptable, builtinclass, uuid(e2b05e40-4a1c-11d3-9890-006008962422)]
+interface nsISupportsChar : nsISupportsPrimitive
+{
+ attribute char data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for 16-bit integers
+ */
+
+[scriptable, builtinclass, uuid(e30d94b0-4a1c-11d3-9890-006008962422)]
+interface nsISupportsPRInt16 : nsISupportsPrimitive
+{
+ attribute int16_t data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for 32-bit integers
+ */
+
+[scriptable, builtinclass, uuid(e36c5250-4a1c-11d3-9890-006008962422)]
+interface nsISupportsPRInt32 : nsISupportsPrimitive
+{
+ attribute int32_t data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for 64-bit integers
+ */
+
+[scriptable, builtinclass, uuid(e3cb0ff0-4a1c-11d3-9890-006008962422)]
+interface nsISupportsPRInt64 : nsISupportsPrimitive
+{
+ attribute int64_t data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for floating point numbers
+ */
+
+[scriptable, builtinclass, uuid(abeaa390-4ac0-11d3-baea-00805f8a5dd7)]
+interface nsISupportsFloat : nsISupportsPrimitive
+{
+ attribute float data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for doubles
+ */
+
+[scriptable, builtinclass, uuid(b32523a0-4ac0-11d3-baea-00805f8a5dd7)]
+interface nsISupportsDouble : nsISupportsPrimitive
+{
+ attribute double data;
+ string toString();
+};
+
+/**
+ * Scriptable storage for other XPCOM objects
+ */
+
+[scriptable, builtinclass, uuid(995ea724-1dd1-11b2-9211-c21bdd3e7ed0)]
+interface nsISupportsInterfacePointer : nsISupportsPrimitive
+{
+ attribute nsISupports data;
+ attribute nsIDPtr dataIID;
+
+ string toString();
+};
diff --git a/xpcom/ds/nsIVariant.idl b/xpcom/ds/nsIVariant.idl
new file mode 100644
index 0000000000..0d7f7e70c3
--- /dev/null
+++ b/xpcom/ds/nsIVariant.idl
@@ -0,0 +1,162 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The long avoided variant support for xpcom. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include "xptinfo.h"
+
+// This enum class used to be a const-only XPIDL interface, containing literal
+// integer descriptions of the different fields. Instead, it now directly
+// references the nsXPTTypeTag variants VTYPE_ are intended to match.
+struct nsIDataType
+{
+ enum {
+ // These MUST match the declarations in xptinfo.h.
+ // Otherwise the world is likely to explode.
+ VTYPE_INT8 = TD_INT8 ,
+ VTYPE_INT16 = TD_INT16 ,
+ VTYPE_INT32 = TD_INT32 ,
+ VTYPE_INT64 = TD_INT64 ,
+ VTYPE_UINT8 = TD_UINT8 ,
+ VTYPE_UINT16 = TD_UINT16 ,
+ VTYPE_UINT32 = TD_UINT32 ,
+ VTYPE_UINT64 = TD_UINT64 ,
+ VTYPE_FLOAT = TD_FLOAT ,
+ VTYPE_DOUBLE = TD_DOUBLE ,
+ VTYPE_BOOL = TD_BOOL ,
+ VTYPE_CHAR = TD_CHAR ,
+ VTYPE_WCHAR = TD_WCHAR ,
+ VTYPE_VOID = TD_VOID ,
+ VTYPE_ID = TD_NSIDPTR ,
+ VTYPE_CHAR_STR = TD_PSTRING ,
+ VTYPE_WCHAR_STR = TD_PWSTRING ,
+ VTYPE_INTERFACE = TD_INTERFACE_TYPE ,
+ VTYPE_INTERFACE_IS = TD_INTERFACE_IS_TYPE,
+ VTYPE_ARRAY = TD_LEGACY_ARRAY ,
+ VTYPE_STRING_SIZE_IS = TD_PSTRING_SIZE_IS ,
+ VTYPE_WSTRING_SIZE_IS = TD_PWSTRING_SIZE_IS ,
+ VTYPE_UTF8STRING = TD_UTF8STRING ,
+ VTYPE_CSTRING = TD_CSTRING ,
+ VTYPE_ASTRING = TD_ASTRING ,
+
+ // Non-xpt variant types
+ VTYPE_EMPTY_ARRAY = 254 ,
+ VTYPE_EMPTY = 255
+ };
+};
+%}
+
+
+/**
+ * XPConnect has magic to transparently convert between nsIVariant and JS types.
+ * We mark the interface [scriptable] so that JS can use methods
+ * that refer to this interface. But we mark all the methods and attributes
+ * [noscript] since any nsIVariant object will be automatically converted to a
+ * JS type anyway.
+ */
+
+[scriptable, builtinclass, uuid(81e4c2de-acac-4ad6-901a-b5fb1b851a0d)]
+interface nsIVariant : nsISupports
+{
+ [notxpcom,nostdcall] readonly attribute uint16_t dataType;
+
+ [noscript] uint8_t getAsInt8();
+ [noscript] int16_t getAsInt16();
+ [noscript] int32_t getAsInt32();
+ [noscript] int64_t getAsInt64();
+ [noscript] uint8_t getAsUint8();
+ [noscript] uint16_t getAsUint16();
+ [noscript] uint32_t getAsUint32();
+ [noscript] uint64_t getAsUint64();
+ [noscript] float getAsFloat();
+ [noscript] double getAsDouble();
+ [noscript] boolean getAsBool();
+ [noscript] char getAsChar();
+ [noscript] wchar getAsWChar();
+ [notxpcom] nsresult getAsID(out nsID retval);
+ [noscript] AString getAsAString();
+ [noscript] ACString getAsACString();
+ [noscript] AUTF8String getAsAUTF8String();
+ [noscript] string getAsString();
+ [noscript] wstring getAsWString();
+ [noscript] nsISupports getAsISupports();
+ [noscript] jsval getAsJSVal();
+
+ [noscript] void getAsInterface(out nsIIDPtr iid,
+ [iid_is(iid), retval] out nsQIResult iface);
+
+ [notxpcom] nsresult getAsArray(out uint16_t type, out nsIID iid,
+ out uint32_t count, out voidPtr ptr);
+
+ [noscript] void getAsStringWithSize(out uint32_t size,
+ [size_is(size), retval] out string str);
+
+ [noscript] void getAsWStringWithSize(out uint32_t size,
+ [size_is(size), retval] out wstring str);
+};
+
+/**
+ * An object that implements nsIVariant may or may NOT also implement this
+ * nsIWritableVariant.
+ *
+ * If the 'writable' attribute is false then attempts to call any of the 'set'
+ * methods can be expected to fail. Setting the 'writable' attribute may or
+ * may not succeed.
+ *
+ */
+
+[scriptable, builtinclass, uuid(5586a590-8c82-11d5-90f3-0010a4e73d9a)]
+interface nsIWritableVariant : nsIVariant
+{
+ attribute boolean writable;
+
+ void setAsInt8(in uint8_t aValue);
+ void setAsInt16(in int16_t aValue);
+ void setAsInt32(in int32_t aValue);
+ void setAsInt64(in int64_t aValue);
+ void setAsUint8(in uint8_t aValue);
+ void setAsUint16(in uint16_t aValue);
+ void setAsUint32(in uint32_t aValue);
+ void setAsUint64(in uint64_t aValue);
+ void setAsFloat(in float aValue);
+ void setAsDouble(in double aValue);
+ void setAsBool(in boolean aValue);
+ void setAsChar(in char aValue);
+ void setAsWChar(in wchar aValue);
+ void setAsID(in nsIDRef aValue);
+ void setAsAString(in AString aValue);
+ void setAsACString(in ACString aValue);
+ void setAsAUTF8String(in AUTF8String aValue);
+ void setAsString(in string aValue);
+ void setAsWString(in wstring aValue);
+ void setAsISupports(in nsISupports aValue);
+
+ void setAsInterface(in nsIIDRef iid,
+ [iid_is(iid)] in nsQIResult iface);
+
+ [noscript] void setAsArray(in uint16_t type, in nsIIDPtr iid,
+ in uint32_t count, in voidPtr ptr);
+
+ void setAsStringWithSize(in uint32_t size,
+ [size_is(size)] in string str);
+
+ void setAsWStringWithSize(in uint32_t size,
+ [size_is(size)] in wstring str);
+
+ void setAsVoid();
+ void setAsEmpty();
+ void setAsEmptyArray();
+
+ void setFromVariant(in nsIVariant aValue);
+};
+
+%{C++
+// The contractID for the generic implementation built in to xpcom.
+#define NS_VARIANT_CONTRACTID "@mozilla.org/variant;1"
+%}
diff --git a/xpcom/ds/nsIWindowsRegKey.idl b/xpcom/ds/nsIWindowsRegKey.idl
new file mode 100644
index 0000000000..197ba69c6d
--- /dev/null
+++ b/xpcom/ds/nsIWindowsRegKey.idl
@@ -0,0 +1,336 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include <windows.h>
+%}
+
+native HKEY(HKEY);
+
+/**
+ * This interface is designed to provide scriptable access to the Windows
+ * registry system ("With Great Power Comes Great Responsibility"). The
+ * interface represents a single key in the registry.
+ *
+ * This interface is highly Win32 specific.
+ */
+[scriptable, uuid(2555b930-d64f-437e-9be7-0a2cb252c1f4)]
+interface nsIWindowsRegKey : nsISupports
+{
+ /**
+ * Root keys. The values for these keys correspond to the values from
+ * WinReg.h in the MS Platform SDK. The ROOT_KEY_ prefix corresponds to the
+ * HKEY_ prefix in the MS Platform SDK.
+ *
+ * This interface is not restricted to using only these root keys.
+ */
+ const unsigned long ROOT_KEY_CLASSES_ROOT = 0x80000000;
+ const unsigned long ROOT_KEY_CURRENT_USER = 0x80000001;
+ const unsigned long ROOT_KEY_LOCAL_MACHINE = 0x80000002;
+
+ /**
+ * Values for the mode parameter passed to the open and create methods.
+ * The values defined here correspond to the REGSAM values defined in
+ * WinNT.h in the MS Platform SDK, where ACCESS_ is replaced with KEY_.
+ *
+ * This interface is not restricted to using only these access types.
+ */
+ const unsigned long ACCESS_BASIC = 0x00020000;
+ const unsigned long ACCESS_QUERY_VALUE = 0x00000001;
+ const unsigned long ACCESS_SET_VALUE = 0x00000002;
+ const unsigned long ACCESS_CREATE_SUB_KEY = 0x00000004;
+ const unsigned long ACCESS_ENUMERATE_SUB_KEYS = 0x00000008;
+ const unsigned long ACCESS_NOTIFY = 0x00000010;
+ const unsigned long ACCESS_READ = ACCESS_BASIC |
+ ACCESS_QUERY_VALUE |
+ ACCESS_ENUMERATE_SUB_KEYS |
+ ACCESS_NOTIFY;
+ const unsigned long ACCESS_WRITE = ACCESS_BASIC |
+ ACCESS_SET_VALUE |
+ ACCESS_CREATE_SUB_KEY;
+ const unsigned long ACCESS_ALL = ACCESS_READ |
+ ACCESS_WRITE;
+ const unsigned long WOW64_32 = 0x00000200;
+ const unsigned long WOW64_64 = 0x00000100;
+
+
+ /**
+ * Values for the type of a registry value. The numeric values of these
+ * constants are taken directly from WinNT.h in the MS Platform SDK.
+ * The Microsoft documentation should be consulted for the exact meaning of
+ * these value types.
+ *
+ * This interface is somewhat restricted to using only these value types.
+ * There is no method that is directly equivalent to RegQueryValueEx or
+ * RegSetValueEx. In particular, this interface does not support the
+ * REG_MULTI_SZ and REG_EXPAND_SZ value types. It is still possible to
+ * enumerate values that have other types (i.e., getValueType may return a
+ * type not defined below).
+ */
+ const unsigned long TYPE_NONE = 0; // REG_NONE
+ const unsigned long TYPE_STRING = 1; // REG_SZ
+ const unsigned long TYPE_BINARY = 3; // REG_BINARY
+ const unsigned long TYPE_INT = 4; // REG_DWORD
+ const unsigned long TYPE_INT64 = 11; // REG_QWORD
+
+ /**
+ * This attribute exposes the native HKEY and is available to provide C++
+ * consumers with the flexibility of making other Windows registry API calls
+ * that are not exposed via this interface.
+ *
+ * It is possible to initialize this object by setting an HKEY on it. In
+ * that case, it is the responsibility of the consumer setting the HKEY to
+ * ensure that it is a valid HKEY.
+ *
+ * WARNING: Setting the key does not close the old key.
+ */
+ [noscript] attribute HKEY key;
+
+ /**
+ * This method closes the key. If the key is already closed, then this
+ * method does nothing.
+ */
+ void close();
+
+ /**
+ * This method opens an existing key. This method fails if the key
+ * does not exist.
+ *
+ * NOTE: On 32-bit Windows, it is valid to pass any HKEY as the rootKey
+ * parameter of this function. However, for compatibility with 64-bit
+ * Windows, that usage should probably be avoided in favor of openChild.
+ *
+ * @param rootKey
+ * A root key defined above or any valid HKEY on 32-bit Windows.
+ * @param relPath
+ * A relative path from the given root key.
+ * @param mode
+ * Access mode, which is a bit-wise OR of the ACCESS_ values defined
+ * above.
+ */
+ void open(in unsigned long rootKey, in AString relPath, in unsigned long mode);
+
+ /**
+ * This method opens an existing key or creates a new key.
+ *
+ * NOTE: On 32-bit Windows, it is valid to pass any HKEY as the rootKey
+ * parameter of this function. However, for compatibility with 64-bit
+ * Windows, that usage should probably be avoided in favor of createChild.
+ *
+ * @param rootKey
+ * A root key defined above or any valid HKEY on 32-bit Windows.
+ * @param relPath
+ * A relative path from the given root key.
+ * @param mode
+ * Access mode, which is a bit-wise OR of the ACCESS_ values defined
+ * above.
+ */
+ void create(in unsigned long rootKey, in AString relPath, in unsigned long mode);
+
+ /**
+ * This method opens a subkey relative to this key. This method fails if the
+ * key does not exist.
+ *
+ * @return nsIWindowsRegKey for the newly opened subkey.
+ */
+ nsIWindowsRegKey openChild(in AString relPath, in unsigned long mode);
+
+ /**
+ * This method opens or creates a subkey relative to this key.
+ *
+ * @return nsIWindowsRegKey for the newly opened or created subkey.
+ */
+ nsIWindowsRegKey createChild(in AString relPath, in unsigned long mode);
+
+ /**
+ * This attribute returns the number of child keys.
+ */
+ readonly attribute unsigned long childCount;
+
+ /**
+ * This method returns the name of the n'th child key.
+ *
+ * @param index
+ * The index of the requested child key.
+ */
+ AString getChildName(in unsigned long index);
+
+ /**
+ * This method checks to see if the key has a child by the given name.
+ *
+ * @param name
+ * The name of the requested child key.
+ */
+ boolean hasChild(in AString name);
+
+ /**
+ * This attribute returns the number of values under this key.
+ */
+ readonly attribute unsigned long valueCount;
+
+ /**
+ * This method returns the name of the n'th value under this key.
+ *
+ * @param index
+ * The index of the requested value.
+ */
+ AString getValueName(in unsigned long index);
+
+ /**
+ * This method checks to see if the key has a value by the given name.
+ *
+ * @param name
+ * The name of the requested value.
+ */
+ boolean hasValue(in AString name);
+
+ /**
+ * This method removes a child key and all of its values. This method will
+ * fail if the key has any children of its own.
+ *
+ * @param relPath
+ * The relative path from this key to the key to be removed.
+ */
+ void removeChild(in AString relPath);
+
+ /**
+ * This method removes the value with the given name.
+ *
+ * @param name
+ * The name of the value to be removed.
+ */
+ void removeValue(in AString name);
+
+ /**
+ * This method returns the type of the value with the given name. The return
+ * value is one of the "TYPE_" constants defined above.
+ *
+ * @param name
+ * The name of the value to query.
+ */
+ unsigned long getValueType(in AString name);
+
+ /**
+ * This method reads the string contents of the named value as a Unicode
+ * string.
+ *
+ * @param name
+ * The name of the value to query. This parameter can be the empty
+ * string to request the key's default value.
+ */
+ AString readStringValue(in AString name);
+
+ /**
+ * This method reads the integer contents of the named value.
+ *
+ * @param name
+ * The name of the value to query.
+ */
+ unsigned long readIntValue(in AString name);
+
+ /**
+ * This method reads the 64-bit integer contents of the named value.
+ *
+ * @param name
+ * The name of the value to query.
+ */
+ unsigned long long readInt64Value(in AString name);
+
+ /**
+ * This method reads the binary contents of the named value under this key.
+ *
+ * JavaScript callers should take care with the result of this method since
+ * it will be byte-expanded to form a JS string. (The binary data will be
+ * treated as an ISO-Latin-1 character string, which it is not).
+ *
+ * @param name
+ * The name of the value to query.
+ */
+ ACString readBinaryValue(in AString name);
+
+ /**
+ * This method writes the unicode string contents of the named value. The
+ * value will be created if it does not already exist.
+ *
+ * @param name
+ * The name of the value to modify. This parameter can be the empty
+ * string to modify the key's default value.
+ * @param data
+ * The data for the value to modify.
+ */
+ void writeStringValue(in AString name, in AString data);
+
+ /**
+ * This method writes the integer contents of the named value. The value
+ * will be created if it does not already exist.
+ *
+ * @param name
+ * The name of the value to modify.
+ * @param data
+ * The data for the value to modify.
+ */
+ void writeIntValue(in AString name, in unsigned long data);
+
+ /**
+ * This method writes the 64-bit integer contents of the named value. The
+ * value will be created if it does not already exist.
+ *
+ * @param name
+ * The name of the value to modify.
+ * @param data
+ * The data for the value to modify.
+ */
+ void writeInt64Value(in AString name, in unsigned long long data);
+
+ /**
+ * This method writes the binary contents of the named value. The value will
+ * be created if it does not already exist.
+ *
+ * JavaScript callers should take care with the value passed to this method
+ * since it will be truncated from a JS string (unicode) to a ISO-Latin-1
+ * string. (The binary data will be treated as an ISO-Latin-1 character
+ * string, which it is not). So, JavaScript callers should only pass
+ * character values in the range \u0000 to \u00FF, or else data loss will
+ * occur.
+ *
+ * @param name
+ * The name of the value to modify.
+ * @param data
+ * The data for the value to modify.
+ */
+ void writeBinaryValue(in AString name, in ACString data);
+
+ /**
+ * This method starts watching the key to see if any of its values have
+ * changed. The key must have been opened with mode including ACCESS_NOTIFY.
+ * If recurse is true, then this key and any of its descendant keys are
+ * watched. Otherwise, only this key is watched.
+ *
+ * @param recurse
+ * Indicates whether or not to also watch child keys.
+ */
+ void startWatching(in boolean recurse);
+
+ /**
+ * This method stops any watching of the key initiated by a call to
+ * startWatching. This method does nothing if the key is not being watched.
+ */
+ void stopWatching();
+
+ /**
+ * This method returns true if the key is being watched for changes (i.e.,
+ * if startWatching() was called).
+ */
+ boolean isWatching();
+
+ /**
+ * This method returns true if the key has changed and false otherwise.
+ * This method will always return false if startWatching was not called.
+ */
+ boolean hasChanged();
+};
diff --git a/xpcom/ds/nsIWritablePropertyBag.idl b/xpcom/ds/nsIWritablePropertyBag.idl
new file mode 100644
index 0000000000..e916b7ccd6
--- /dev/null
+++ b/xpcom/ds/nsIWritablePropertyBag.idl
@@ -0,0 +1,27 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* nsIVariant based writable Property Bag support. */
+
+#include "nsIPropertyBag.idl"
+
+[scriptable, uuid(96fc4671-eeb4-4823-9421-e50fb70ad353)]
+interface nsIWritablePropertyBag : nsIPropertyBag
+{
+ /**
+ * Set a property with the given name to the given value. If
+ * a property already exists with the given name, it is
+ * overwritten.
+ */
+ void setProperty(in AString name, in nsIVariant value);
+
+ /**
+ * Delete a property with the given name.
+ * @throws NS_ERROR_FAILURE if a property with that name doesn't
+ * exist.
+ */
+ void deleteProperty(in AString name);
+};
diff --git a/xpcom/ds/nsIWritablePropertyBag2.idl b/xpcom/ds/nsIWritablePropertyBag2.idl
new file mode 100644
index 0000000000..50f093905d
--- /dev/null
+++ b/xpcom/ds/nsIWritablePropertyBag2.idl
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* nsIVariant based Property Bag support. */
+
+#include "nsIPropertyBag2.idl"
+
+[scriptable, uuid(9cfd1587-360e-4957-a58f-4c2b1c5e7ed9)]
+interface nsIWritablePropertyBag2 : nsIPropertyBag2
+{
+ void setPropertyAsInt32 (in AString prop, in int32_t value);
+ void setPropertyAsUint32 (in AString prop, in uint32_t value);
+ void setPropertyAsInt64 (in AString prop, in int64_t value);
+ void setPropertyAsUint64 (in AString prop, in uint64_t value);
+ void setPropertyAsDouble (in AString prop, in double value);
+ void setPropertyAsAString (in AString prop, in AString value);
+ void setPropertyAsACString (in AString prop, in ACString value);
+ void setPropertyAsAUTF8String (in AString prop, in AUTF8String value);
+ void setPropertyAsBool (in AString prop, in boolean value);
+ void setPropertyAsInterface (in AString prop, in nsISupports value);
+};
diff --git a/xpcom/ds/nsInterfaceHashtable.h b/xpcom/ds/nsInterfaceHashtable.h
new file mode 100644
index 0000000000..644864cf8f
--- /dev/null
+++ b/xpcom/ds/nsInterfaceHashtable.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInterfaceHashtable_h__
+#define nsInterfaceHashtable_h__
+
+#include "nsRefCountedHashtable.h"
+#include "nsHashKeys.h"
+#include "nsCOMPtr.h"
+
+#endif // nsInterfaceHashtable_h__
diff --git a/xpcom/ds/nsMathUtils.h b/xpcom/ds/nsMathUtils.h
new file mode 100644
index 0000000000..527e0c3eb2
--- /dev/null
+++ b/xpcom/ds/nsMathUtils.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathUtils_h__
+#define nsMathUtils_h__
+
+#include "nscore.h"
+#include <cmath>
+#include <float.h>
+
+#if defined(XP_SOLARIS)
+# include <ieeefp.h>
+#endif
+
+/*
+ * round
+ */
+inline double NS_round(double aNum) {
+ return aNum >= 0.0 ? floor(aNum + 0.5) : ceil(aNum - 0.5);
+}
+inline float NS_roundf(float aNum) {
+ return aNum >= 0.0f ? floorf(aNum + 0.5f) : ceilf(aNum - 0.5f);
+}
+inline int32_t NS_lround(double aNum) {
+ return aNum >= 0.0 ? int32_t(aNum + 0.5) : int32_t(aNum - 0.5);
+}
+
+/* NS_roundup30 rounds towards infinity for positive and */
+/* negative numbers. */
+
+#if defined(XP_WIN) && defined(_M_IX86) && !defined(__GNUC__) && \
+ !defined(__clang__)
+inline int32_t NS_lroundup30(float x) {
+ /* Code derived from Laurent de Soras' paper at */
+ /* http://ldesoras.free.fr/doc/articles/rounding_en.pdf */
+
+ /* Rounding up on Windows is expensive using the float to */
+ /* int conversion and the floor function. A faster */
+ /* approach is to use f87 rounding while assuming the */
+ /* default rounding mode of rounding to the nearest */
+ /* integer. This rounding mode, however, actually rounds */
+ /* to the nearest integer so we add the floating point */
+ /* number to itself and add our rounding factor before */
+ /* doing the conversion to an integer. We then do a right */
+ /* shift of one bit on the integer to divide by two. */
+
+ /* This routine doesn't handle numbers larger in magnitude */
+ /* than 2^30 but this is fine for NSToCoordRound because */
+ /* Coords are limited to 2^30 in magnitude. */
+
+ static const double round_to_nearest = 0.5f;
+ int i;
+
+ __asm {
+ fld x ; load fp argument
+ fadd st, st(0) ; double it
+ fadd round_to_nearest ; add the rounding factor
+ fistp dword ptr i ; convert the result to int
+ }
+ return i >> 1; /* divide by 2 */
+}
+#endif /* XP_WIN && _M_IX86 && !__GNUC__ */
+
+inline int32_t NS_lroundf(float aNum) {
+ return aNum >= 0.0f ? int32_t(aNum + 0.5f) : int32_t(aNum - 0.5f);
+}
+
+/*
+ * hypot. We don't need a super accurate version of this, if a platform
+ * turns up with none of the possibilities below it would be okay to fall
+ * back to sqrt(x*x + y*y).
+ */
+inline double NS_hypot(double aNum1, double aNum2) {
+#ifdef __GNUC__
+ return __builtin_hypot(aNum1, aNum2);
+#elif defined _WIN32
+ return _hypot(aNum1, aNum2);
+#else
+ return hypot(aNum1, aNum2);
+#endif
+}
+
+/**
+ * Check whether a floating point number is finite (not +/-infinity and not a
+ * NaN value).
+ */
+inline bool NS_finite(double aNum) {
+#ifdef WIN32
+ // NOTE: '!!' casts an int to bool without spamming MSVC warning C4800.
+ return !!_finite(aNum);
+#else
+ return std::isfinite(aNum);
+#endif
+}
+
+/**
+ * Returns the result of the modulo of x by y using a floored division.
+ * fmod(x, y) is using a truncated division.
+ * The main difference is that the result of this method will have the sign of
+ * y while the result of fmod(x, y) will have the sign of x.
+ */
+inline double NS_floorModulo(double aNum1, double aNum2) {
+ return (aNum1 - aNum2 * floor(aNum1 / aNum2));
+}
+
+#endif
diff --git a/xpcom/ds/nsObserverList.cpp b/xpcom/ds/nsObserverList.cpp
new file mode 100644
index 0000000000..ba3eaaa2aa
--- /dev/null
+++ b/xpcom/ds/nsObserverList.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsObserverList.h"
+
+#include "mozilla/ResultExtensions.h"
+#include "nsCOMArray.h"
+#include "xpcpublic.h"
+
+nsresult nsObserverList::AddObserver(nsIObserver* anObserver, bool ownsWeak) {
+ NS_ASSERTION(anObserver, "Null input");
+
+ MOZ_TRY(mObservers.AppendWeakElement(anObserver, ownsWeak));
+ return NS_OK;
+}
+
+nsresult nsObserverList::RemoveObserver(nsIObserver* anObserver) {
+ NS_ASSERTION(anObserver, "Null input");
+
+ MOZ_TRY(mObservers.RemoveWeakElement(anObserver));
+ return NS_OK;
+}
+
+void nsObserverList::GetObserverList(nsISimpleEnumerator** anEnumerator) {
+ RefPtr<nsObserverEnumerator> e(new nsObserverEnumerator(this));
+ e.forget(anEnumerator);
+}
+
+nsCOMArray<nsIObserver> nsObserverList::ReverseCloneObserverArray() {
+ nsCOMArray<nsIObserver> array;
+ array.SetCapacity(mObservers.Length());
+
+ // XXX This could also use RemoveElementsBy if we lifted the promise to fill
+ // aArray in reverse order. Although there shouldn't be anyone explicitly
+ // relying on the order, changing this might cause subtle problems, so we
+ // better leave it as it is.
+
+ for (int32_t i = mObservers.Length() - 1; i >= 0; --i) {
+ nsCOMPtr<nsIObserver> observer = mObservers[i].GetValue();
+ if (observer) {
+ array.AppendElement(observer.forget());
+ } else {
+ // the object has gone away, remove the weakref
+ mObservers.RemoveElementAt(i);
+ }
+ }
+
+ return array;
+}
+
+void nsObserverList::AppendStrongObservers(nsCOMArray<nsIObserver>& aArray) {
+ aArray.SetCapacity(aArray.Length() + mObservers.Length());
+
+ for (int32_t i = mObservers.Length() - 1; i >= 0; --i) {
+ if (!mObservers[i].IsWeak()) {
+ nsCOMPtr<nsIObserver> observer = mObservers[i].GetValue();
+ aArray.AppendObject(observer);
+ }
+ }
+}
+
+void nsObserverList::NotifyObservers(nsISupports* aSubject, const char* aTopic,
+ const char16_t* someData) {
+ const nsCOMArray<nsIObserver> observers = ReverseCloneObserverArray();
+
+ for (int32_t i = 0; i < observers.Count(); ++i) {
+ observers[i]->Observe(aSubject, aTopic, someData);
+ }
+}
+
+nsObserverEnumerator::nsObserverEnumerator(nsObserverList* aObserverList)
+ : mIndex(0), mObservers(aObserverList->ReverseCloneObserverArray()) {}
+
+NS_IMETHODIMP
+nsObserverEnumerator::HasMoreElements(bool* aResult) {
+ *aResult = (mIndex < mObservers.Count());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObserverEnumerator::GetNext(nsISupports** aResult) {
+ if (mIndex == mObservers.Count()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ADDREF(*aResult = mObservers[mIndex]);
+ ++mIndex;
+ return NS_OK;
+}
diff --git a/xpcom/ds/nsObserverList.h b/xpcom/ds/nsObserverList.h
new file mode 100644
index 0000000000..9f209e40d6
--- /dev/null
+++ b/xpcom/ds/nsObserverList.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsObserverList_h___
+#define nsObserverList_h___
+
+#include "nsISupports.h"
+#include "nsCOMArray.h"
+#include "nsIObserver.h"
+#include "nsHashKeys.h"
+#include "nsMaybeWeakPtr.h"
+#include "nsSimpleEnumerator.h"
+#include "mozilla/Attributes.h"
+
+class nsObserverList : public nsCharPtrHashKey {
+ friend class nsObserverService;
+
+ public:
+ explicit nsObserverList(const char* aKey) : nsCharPtrHashKey(aKey) {
+ MOZ_COUNT_CTOR(nsObserverList);
+ }
+
+ nsObserverList(nsObserverList&& aOther)
+ : nsCharPtrHashKey(std::move(aOther)),
+ mObservers(std::move(aOther.mObservers)) {
+ MOZ_COUNT_CTOR(nsObserverList);
+ }
+
+ MOZ_COUNTED_DTOR(nsObserverList)
+
+ [[nodiscard]] nsresult AddObserver(nsIObserver* aObserver, bool aOwnsWeak);
+ [[nodiscard]] nsresult RemoveObserver(nsIObserver* aObserver);
+
+ void NotifyObservers(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aSomeData);
+ void GetObserverList(nsISimpleEnumerator** aEnumerator);
+
+ // Clone an array with the observers of this category.
+ // The array is filled in last-added-first order.
+ nsCOMArray<nsIObserver> ReverseCloneObserverArray();
+
+ // Like FillObserverArray(), but only for strongly held observers.
+ void AppendStrongObservers(nsCOMArray<nsIObserver>& aArray);
+
+ private:
+ nsMaybeWeakPtrArray<nsIObserver> mObservers;
+};
+
+class nsObserverEnumerator final : public nsSimpleEnumerator {
+ public:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ explicit nsObserverEnumerator(nsObserverList* aObserverList);
+
+ const nsID& DefaultInterface() override { return NS_GET_IID(nsIObserver); }
+
+ private:
+ ~nsObserverEnumerator() override = default;
+
+ int32_t mIndex; // Counts up from 0
+ nsCOMArray<nsIObserver> mObservers;
+};
+
+#endif /* nsObserverList_h___ */
diff --git a/xpcom/ds/nsObserverService.cpp b/xpcom/ds/nsObserverService.cpp
new file mode 100644
index 0000000000..8f7a09d1aa
--- /dev/null
+++ b/xpcom/ds/nsObserverService.cpp
@@ -0,0 +1,367 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsIScriptError.h"
+#include "nsObserverService.h"
+#include "nsObserverList.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsEnumeratorUtils.h"
+#include "xpcpublic.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "nsString.h"
+
+static const uint32_t kMinTelemetryNotifyObserversLatencyMs = 1;
+
+// Log module for nsObserverService logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=ObserverService:5
+// set MOZ_LOG_FILE=service.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file service.log.
+static mozilla::LazyLogModule sObserverServiceLog("ObserverService");
+#define LOG(x) MOZ_LOG(sObserverServiceLog, mozilla::LogLevel::Debug, x)
+
+using namespace mozilla;
+
+NS_IMETHODIMP
+nsObserverService::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ struct SuspectObserver {
+ SuspectObserver(const char* aTopic, size_t aReferentCount)
+ : mTopic(aTopic), mReferentCount(aReferentCount) {}
+ const char* mTopic;
+ size_t mReferentCount;
+ };
+
+ size_t totalNumStrong = 0;
+ size_t totalNumWeakAlive = 0;
+ size_t totalNumWeakDead = 0;
+ nsTArray<SuspectObserver> suspectObservers;
+
+ for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) {
+ nsObserverList* observerList = iter.Get();
+ if (!observerList) {
+ continue;
+ }
+
+ size_t topicNumStrong = 0;
+ size_t topicNumWeakAlive = 0;
+ size_t topicNumWeakDead = 0;
+
+ nsMaybeWeakPtrArray<nsIObserver>& observers = observerList->mObservers;
+ for (uint32_t i = 0; i < observers.Length(); i++) {
+ if (observers[i].IsWeak()) {
+ nsCOMPtr<nsIObserver> ref = observers[i].GetValue();
+ if (ref) {
+ topicNumWeakAlive++;
+ } else {
+ topicNumWeakDead++;
+ }
+ } else {
+ topicNumStrong++;
+ }
+ }
+
+ totalNumStrong += topicNumStrong;
+ totalNumWeakAlive += topicNumWeakAlive;
+ totalNumWeakDead += topicNumWeakDead;
+
+ // Keep track of topics that have a suspiciously large number
+ // of referents (symptom of leaks).
+ size_t topicTotal = topicNumStrong + topicNumWeakAlive + topicNumWeakDead;
+ if (topicTotal > kSuspectReferentCount) {
+ SuspectObserver suspect(observerList->GetKey(), topicTotal);
+ suspectObservers.AppendElement(suspect);
+ }
+ }
+
+ // These aren't privacy-sensitive and so don't need anonymizing.
+ for (uint32_t i = 0; i < suspectObservers.Length(); i++) {
+ SuspectObserver& suspect = suspectObservers[i];
+ nsPrintfCString suspectPath("observer-service-suspect/referent(topic=%s)",
+ suspect.mTopic);
+ aHandleReport->Callback(
+ /* process */ ""_ns, suspectPath, KIND_OTHER, UNITS_COUNT,
+ suspect.mReferentCount,
+ nsLiteralCString("A topic with a suspiciously large number of "
+ "referents. This may be symptomatic of a leak "
+ "if the number of referents is high with "
+ "respect to the number of windows."),
+ aData);
+ }
+
+ MOZ_COLLECT_REPORT(
+ "observer-service/referent/strong", KIND_OTHER, UNITS_COUNT,
+ totalNumStrong,
+ "The number of strong references held by the observer service.");
+
+ MOZ_COLLECT_REPORT(
+ "observer-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT,
+ totalNumWeakAlive,
+ "The number of weak references held by the observer service that are "
+ "still alive.");
+
+ MOZ_COLLECT_REPORT(
+ "observer-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT,
+ totalNumWeakDead,
+ "The number of weak references held by the observer service that are "
+ "dead.");
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsObserverService Implementation
+
+NS_IMPL_ISUPPORTS(nsObserverService, nsIObserverService, nsObserverService,
+ nsIMemoryReporter)
+
+nsObserverService::nsObserverService() : mShuttingDown(false) {}
+
+nsObserverService::~nsObserverService(void) { Shutdown(); }
+
+void nsObserverService::RegisterReporter() { RegisterWeakMemoryReporter(this); }
+
+void nsObserverService::Shutdown() {
+ if (mShuttingDown) {
+ return;
+ }
+
+ mShuttingDown = true;
+ UnregisterWeakMemoryReporter(this);
+ mObserverTopicTable.Clear();
+}
+
+nsresult nsObserverService::Create(const nsIID& aIID, void** aInstancePtr) {
+ LOG(("nsObserverService::Create()"));
+
+ RefPtr<nsObserverService> os = new nsObserverService();
+
+ // The memory reporter can not be immediately registered here because
+ // the nsMemoryReporterManager may attempt to get the nsObserverService
+ // during initialization, causing a recursive GetService.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsObserverService::RegisterReporter", os,
+ &nsObserverService::RegisterReporter));
+
+ return os->QueryInterface(aIID, aInstancePtr);
+}
+
+nsresult nsObserverService::EnsureValidCall() const {
+ if (!NS_IsMainThread()) {
+ MOZ_CRASH("Using observer service off the main thread!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mShuttingDown) {
+ NS_ERROR("Using observer service after XPCOM shutdown!");
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsObserverService::FilterHttpOnTopics(const char* aTopic) {
+ // Specifically allow http-on-opening-request and http-on-stop-request in the
+ // child process; see bug 1269765.
+ if (mozilla::net::IsNeckoChild() && !strncmp(aTopic, "http-on-", 8) &&
+ strcmp(aTopic, "http-on-failed-opening-request") &&
+ strcmp(aTopic, "http-on-opening-request") &&
+ strcmp(aTopic, "http-on-stop-request") &&
+ strcmp(aTopic, "http-on-image-cache-response")) {
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ nsCOMPtr<nsIScriptError> error(
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->Init(u"http-on-* observers only work in the parent process"_ns,
+ u""_ns, u""_ns, 0, 0, nsIScriptError::warningFlag,
+ "chrome javascript"_ns, false /* from private window */,
+ true /* from chrome context */);
+ console->LogMessage(error);
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObserverService::AddObserver(nsIObserver* aObserver, const char* aTopic,
+ bool aOwnsWeak) {
+ LOG(("nsObserverService::AddObserver(%p: %s, %s)", (void*)aObserver, aTopic,
+ aOwnsWeak ? "weak" : "strong"));
+
+ MOZ_TRY(EnsureValidCall());
+ if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MOZ_TRY(FilterHttpOnTopics(aTopic));
+
+ nsObserverList* observerList = mObserverTopicTable.PutEntry(aTopic);
+ if (!observerList) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return observerList->AddObserver(aObserver, aOwnsWeak);
+}
+
+NS_IMETHODIMP
+nsObserverService::RemoveObserver(nsIObserver* aObserver, const char* aTopic) {
+ LOG(("nsObserverService::RemoveObserver(%p: %s)", (void*)aObserver, aTopic));
+
+ if (mShuttingDown) {
+ // The service is shutting down. Let's ignore this call.
+ return NS_OK;
+ }
+
+ MOZ_TRY(EnsureValidCall());
+ if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic);
+ if (!observerList) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return observerList->RemoveObserver(aObserver);
+}
+
+NS_IMETHODIMP
+nsObserverService::EnumerateObservers(const char* aTopic,
+ nsISimpleEnumerator** anEnumerator) {
+ LOG(("nsObserverService::EnumerateObservers(%s)", aTopic));
+
+ MOZ_TRY(EnsureValidCall());
+ if (NS_WARN_IF(!anEnumerator) || NS_WARN_IF(!aTopic)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic);
+ if (!observerList) {
+ return NS_NewEmptyEnumerator(anEnumerator);
+ }
+
+ observerList->GetObserverList(anEnumerator);
+ return NS_OK;
+}
+
+// Enumerate observers of aTopic and call Observe on each.
+NS_IMETHODIMP nsObserverService::NotifyObservers(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData) {
+ LOG(("nsObserverService::NotifyObservers(%s)", aTopic));
+
+ MOZ_TRY(EnsureValidCall());
+ if (NS_WARN_IF(!aTopic)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MOZ_ASSERT(AppShutdown::IsNoOrLegalShutdownTopic(aTopic));
+
+ mozilla::TimeStamp start = TimeStamp::Now();
+
+ AUTO_PROFILER_MARKER_TEXT("NotifyObservers", OTHER, MarkerStack::Capture(),
+ nsDependentCString(aTopic));
+ AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
+ "nsObserverService::NotifyObservers", OTHER, aTopic);
+
+ nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic);
+ if (observerList) {
+ observerList->NotifyObservers(aSubject, aTopic, aSomeData);
+ }
+
+ uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds());
+ if (latencyMs >= kMinTelemetryNotifyObserversLatencyMs) {
+ Telemetry::Accumulate(Telemetry::NOTIFY_OBSERVERS_LATENCY_MS,
+ nsDependentCString(aTopic), latencyMs);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObserverService::UnmarkGrayStrongObservers() {
+ MOZ_TRY(EnsureValidCall());
+
+ nsCOMArray<nsIObserver> strongObservers;
+ for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) {
+ nsObserverList* aObserverList = iter.Get();
+ if (aObserverList) {
+ aObserverList->AppendStrongObservers(strongObservers);
+ }
+ }
+
+ for (uint32_t i = 0; i < strongObservers.Length(); ++i) {
+ xpc_TryUnmarkWrappedGrayObject(strongObservers[i]);
+ }
+
+ return NS_OK;
+}
+
+bool nsObserverService::HasObservers(const char* aTopic) {
+ return mObserverTopicTable.Contains(aTopic);
+}
+
+namespace {
+
+class NotifyWhenScriptSafeRunnable : public mozilla::Runnable {
+ public:
+ NotifyWhenScriptSafeRunnable(nsIObserverService* aObs, nsISupports* aSubject,
+ const char* aTopic, const char16_t* aData)
+ : mozilla::Runnable("NotifyWhenScriptSafeRunnable"),
+ mObs(aObs),
+ mSubject(aSubject),
+ mTopic(aTopic) {
+ if (aData) {
+ mData.Assign(aData);
+ } else {
+ mData.SetIsVoid(true);
+ }
+ }
+
+ NS_IMETHOD Run() override {
+ const char16_t* data = mData.IsVoid() ? nullptr : mData.get();
+ return mObs->NotifyObservers(mSubject, mTopic.get(), data);
+ }
+
+ private:
+ nsCOMPtr<nsIObserverService> mObs;
+ nsCOMPtr<nsISupports> mSubject;
+ nsCString mTopic;
+ nsString mData;
+};
+
+} // namespace
+
+nsresult nsIObserverService::NotifyWhenScriptSafe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (nsContentUtils::IsSafeToRunScript()) {
+ return NotifyObservers(aSubject, aTopic, aData);
+ }
+
+ nsContentUtils::AddScriptRunner(MakeAndAddRef<NotifyWhenScriptSafeRunnable>(
+ this, aSubject, aTopic, aData));
+ return NS_OK;
+}
diff --git a/xpcom/ds/nsObserverService.h b/xpcom/ds/nsObserverService.h
new file mode 100644
index 0000000000..f4e445995b
--- /dev/null
+++ b/xpcom/ds/nsObserverService.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsObserverService_h___
+#define nsObserverService_h___
+
+#include "nsIObserverService.h"
+#include "nsObserverList.h"
+#include "nsIMemoryReporter.h"
+#include "nsTHashtable.h"
+#include "mozilla/Attributes.h"
+
+// {D07F5195-E3D1-11d2-8ACD-00105A1B8860}
+#define NS_OBSERVERSERVICE_CID \
+ { \
+ 0xd07f5195, 0xe3d1, 0x11d2, { \
+ 0x8a, 0xcd, 0x0, 0x10, 0x5a, 0x1b, 0x88, 0x60 \
+ } \
+ }
+
+class nsObserverService final : public nsIObserverService,
+ public nsIMemoryReporter {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_OBSERVERSERVICE_CID)
+
+ nsObserverService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVERSERVICE
+ NS_DECL_NSIMEMORYREPORTER
+
+ void Shutdown();
+
+ [[nodiscard]] static nsresult Create(const nsIID& aIID, void** aInstancePtr);
+
+ // Unmark any strongly held observers implemented in JS so the cycle
+ // collector will not traverse them.
+ NS_IMETHOD UnmarkGrayStrongObservers();
+
+ private:
+ ~nsObserverService(void);
+ void RegisterReporter();
+ nsresult EnsureValidCall() const;
+ nsresult FilterHttpOnTopics(const char* aTopic);
+
+ static const size_t kSuspectReferentCount = 100;
+ bool mShuttingDown;
+ nsTHashtable<nsObserverList> mObserverTopicTable;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsObserverService, NS_OBSERVERSERVICE_CID)
+
+#endif /* nsObserverService_h___ */
diff --git a/xpcom/ds/nsPersistentProperties.cpp b/xpcom/ds/nsPersistentProperties.cpp
new file mode 100644
index 0000000000..4e83870742
--- /dev/null
+++ b/xpcom/ds/nsPersistentProperties.cpp
@@ -0,0 +1,584 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsArrayEnumerator.h"
+#include "nsID.h"
+#include "nsCOMArray.h"
+#include "nsUnicharInputStream.h"
+#include "nsPrintfCString.h"
+
+#include "nsPersistentProperties.h"
+#include "nsIProperties.h"
+
+#include "mozilla/ArenaAllocatorExtensions.h"
+
+using mozilla::ArenaStrdup;
+
+struct PropertyTableEntry : public PLDHashEntryHdr {
+ // both of these are arena-allocated
+ const char* mKey;
+ const char16_t* mValue;
+};
+
+static const struct PLDHashTableOps property_HashTableOps = {
+ PLDHashTable::HashStringKey,
+ PLDHashTable::MatchStringKey,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr,
+};
+
+//
+// parser stuff
+//
+enum EParserState {
+ eParserState_AwaitingKey,
+ eParserState_Key,
+ eParserState_AwaitingValue,
+ eParserState_Value,
+ eParserState_Comment
+};
+
+enum EParserSpecial {
+ eParserSpecial_None, // not parsing a special character
+ eParserSpecial_Escaped, // awaiting a special character
+ eParserSpecial_Unicode // parsing a \Uxxx value
+};
+
+class MOZ_STACK_CLASS nsPropertiesParser {
+ public:
+ explicit nsPropertiesParser(nsIPersistentProperties* aProps)
+ : mUnicodeValuesRead(0),
+ mUnicodeValue(u'\0'),
+ mHaveMultiLine(false),
+ mMultiLineCanSkipN(false),
+ mMinLength(0),
+ mState(eParserState_AwaitingKey),
+ mSpecialState(eParserSpecial_None),
+ mProps(aProps) {}
+
+ void FinishValueState(nsAString& aOldValue) {
+ static const char trimThese[] = " \t";
+ mKey.Trim(trimThese, false, true);
+
+ // This is really ugly hack but it should be fast
+ char16_t backup_char;
+ uint32_t minLength = mMinLength;
+ if (minLength) {
+ backup_char = mValue[minLength - 1];
+ mValue.SetCharAt('x', minLength - 1);
+ }
+ mValue.Trim(trimThese, false, true);
+ if (minLength) {
+ mValue.SetCharAt(backup_char, minLength - 1);
+ }
+
+ mProps->SetStringProperty(NS_ConvertUTF16toUTF8(mKey), mValue, aOldValue);
+ mSpecialState = eParserSpecial_None;
+ WaitForKey();
+ }
+
+ EParserState GetState() { return mState; }
+
+ static nsresult SegmentWriter(nsIUnicharInputStream* aStream, void* aClosure,
+ const char16_t* aFromSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount);
+
+ nsresult ParseBuffer(const char16_t* aBuffer, uint32_t aBufferLength);
+
+ private:
+ bool ParseValueCharacter(
+ char16_t aChar, // character that is just being parsed
+ const char16_t* aCur, // pointer to character aChar in the buffer
+ const char16_t*& aTokenStart, // string copying is done in blocks as big
+ // as possible, aTokenStart points to the
+ // beginning of this block
+ nsAString& aOldValue); // when duplicate property is found, new value
+ // is stored into hashtable and the old one is
+ // placed in this variable
+
+ void WaitForKey() { mState = eParserState_AwaitingKey; }
+
+ void EnterKeyState() {
+ mKey.Truncate();
+ mState = eParserState_Key;
+ }
+
+ void WaitForValue() { mState = eParserState_AwaitingValue; }
+
+ void EnterValueState() {
+ mValue.Truncate();
+ mMinLength = 0;
+ mState = eParserState_Value;
+ mSpecialState = eParserSpecial_None;
+ }
+
+ void EnterCommentState() { mState = eParserState_Comment; }
+
+ nsAutoString mKey;
+ nsAutoString mValue;
+
+ uint32_t mUnicodeValuesRead; // should be 4!
+ char16_t mUnicodeValue; // currently parsed unicode value
+ bool mHaveMultiLine; // is TRUE when last processed characters form
+ // any of following sequences:
+ // - "\\\r"
+ // - "\\\n"
+ // - "\\\r\n"
+ // - any sequence above followed by any
+ // combination of ' ' and '\t'
+ bool mMultiLineCanSkipN; // TRUE if "\\\r" was detected
+ uint32_t mMinLength; // limit right trimming at the end to not trim
+ // escaped whitespaces
+ EParserState mState;
+ // if we see a '\' then we enter this special state
+ EParserSpecial mSpecialState;
+ nsCOMPtr<nsIPersistentProperties> mProps;
+};
+
+inline bool IsWhiteSpace(char16_t aChar) {
+ return (aChar == ' ') || (aChar == '\t') || (aChar == '\r') ||
+ (aChar == '\n');
+}
+
+inline bool IsEOL(char16_t aChar) { return (aChar == '\r') || (aChar == '\n'); }
+
+bool nsPropertiesParser::ParseValueCharacter(char16_t aChar,
+ const char16_t* aCur,
+ const char16_t*& aTokenStart,
+ nsAString& aOldValue) {
+ switch (mSpecialState) {
+ // the normal state - look for special characters
+ case eParserSpecial_None:
+ switch (aChar) {
+ case '\\':
+ if (mHaveMultiLine) {
+ // there is nothing to append to mValue yet
+ mHaveMultiLine = false;
+ } else {
+ mValue += Substring(aTokenStart, aCur);
+ }
+
+ mSpecialState = eParserSpecial_Escaped;
+ break;
+
+ case '\n':
+ // if we detected multiline and got only "\\\r" ignore next "\n" if
+ // any
+ if (mHaveMultiLine && mMultiLineCanSkipN) {
+ // but don't allow another '\n' to be skipped
+ mMultiLineCanSkipN = false;
+ // Now there is nothing to append to the mValue since we are
+ // skipping whitespaces at the beginning of the new line of the
+ // multiline property. Set aTokenStart properly to ensure that
+ // nothing is appended if we find regular line-end or the end of the
+ // buffer.
+ aTokenStart = aCur + 1;
+ break;
+ }
+ [[fallthrough]];
+
+ case '\r':
+ // we're done! We have a key and value
+ mValue += Substring(aTokenStart, aCur);
+ FinishValueState(aOldValue);
+ mHaveMultiLine = false;
+ break;
+
+ default:
+ // there is nothing to do with normal characters,
+ // but handle multilines correctly
+ if (mHaveMultiLine) {
+ if (aChar == ' ' || aChar == '\t') {
+ // don't allow another '\n' to be skipped
+ mMultiLineCanSkipN = false;
+ // Now there is nothing to append to the mValue since we are
+ // skipping whitespaces at the beginning of the new line of the
+ // multiline property. Set aTokenStart properly to ensure that
+ // nothing is appended if we find regular line-end or the end of
+ // the buffer.
+ aTokenStart = aCur + 1;
+ break;
+ }
+ mHaveMultiLine = false;
+ aTokenStart = aCur;
+ }
+ break; // from switch on (aChar)
+ }
+ break; // from switch on (mSpecialState)
+
+ // saw a \ character, so parse the character after that
+ case eParserSpecial_Escaped:
+ // probably want to start parsing at the next token
+ // other characters, like 'u' might override this
+ aTokenStart = aCur + 1;
+ mSpecialState = eParserSpecial_None;
+
+ switch (aChar) {
+ // the easy characters - \t, \n, and so forth
+ case 't':
+ mValue += char16_t('\t');
+ mMinLength = mValue.Length();
+ break;
+ case 'n':
+ mValue += char16_t('\n');
+ mMinLength = mValue.Length();
+ break;
+ case 'r':
+ mValue += char16_t('\r');
+ mMinLength = mValue.Length();
+ break;
+ case '\\':
+ mValue += char16_t('\\');
+ break;
+
+ // switch to unicode mode!
+ case 'u':
+ case 'U':
+ mSpecialState = eParserSpecial_Unicode;
+ mUnicodeValuesRead = 0;
+ mUnicodeValue = 0;
+ break;
+
+ // a \ immediately followed by a newline means we're going multiline
+ case '\r':
+ case '\n':
+ mHaveMultiLine = true;
+ mMultiLineCanSkipN = (aChar == '\r');
+ mSpecialState = eParserSpecial_None;
+ break;
+
+ default:
+ // don't recognize the character, so just append it
+ mValue += aChar;
+ break;
+ }
+ break;
+
+ // we're in the middle of parsing a 4-character unicode value
+ // like \u5f39
+ case eParserSpecial_Unicode:
+ if ('0' <= aChar && aChar <= '9') {
+ mUnicodeValue = (mUnicodeValue << 4) | (aChar - '0');
+ } else if ('a' <= aChar && aChar <= 'f') {
+ mUnicodeValue = (mUnicodeValue << 4) | (aChar - 'a' + 0x0a);
+ } else if ('A' <= aChar && aChar <= 'F') {
+ mUnicodeValue = (mUnicodeValue << 4) | (aChar - 'A' + 0x0a);
+ } else {
+ // non-hex character. Append what we have, and move on.
+ mValue += mUnicodeValue;
+ mMinLength = mValue.Length();
+ mSpecialState = eParserSpecial_None;
+
+ // leave aTokenStart at this unknown character, so it gets appended
+ aTokenStart = aCur;
+
+ // ensure parsing this non-hex character again
+ return false;
+ }
+
+ if (++mUnicodeValuesRead >= 4) {
+ aTokenStart = aCur + 1;
+ mSpecialState = eParserSpecial_None;
+ mValue += mUnicodeValue;
+ mMinLength = mValue.Length();
+ }
+
+ break;
+ }
+
+ return true;
+}
+
+nsresult nsPropertiesParser::SegmentWriter(nsIUnicharInputStream* aStream,
+ void* aClosure,
+ const char16_t* aFromSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ nsPropertiesParser* parser = static_cast<nsPropertiesParser*>(aClosure);
+ parser->ParseBuffer(aFromSegment, aCount);
+
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+nsresult nsPropertiesParser::ParseBuffer(const char16_t* aBuffer,
+ uint32_t aBufferLength) {
+ const char16_t* cur = aBuffer;
+ const char16_t* end = aBuffer + aBufferLength;
+
+ // points to the start/end of the current key or value
+ const char16_t* tokenStart = nullptr;
+
+ // if we're in the middle of parsing a key or value, make sure
+ // the current token points to the beginning of the current buffer
+ if (mState == eParserState_Key || mState == eParserState_Value) {
+ tokenStart = aBuffer;
+ }
+
+ nsAutoString oldValue;
+
+ while (cur != end) {
+ char16_t c = *cur;
+
+ switch (mState) {
+ case eParserState_AwaitingKey:
+ if (c == '#' || c == '!') {
+ EnterCommentState();
+ }
+
+ else if (!IsWhiteSpace(c)) {
+ // not a comment, not whitespace, we must have found a key!
+ EnterKeyState();
+ tokenStart = cur;
+ }
+ break;
+
+ case eParserState_Key:
+ if (c == '=' || c == ':') {
+ mKey += Substring(tokenStart, cur);
+ WaitForValue();
+ }
+ break;
+
+ case eParserState_AwaitingValue:
+ if (IsEOL(c)) {
+ // no value at all! mimic the normal value-ending
+ EnterValueState();
+ FinishValueState(oldValue);
+ }
+
+ // ignore white space leading up to the value
+ else if (!IsWhiteSpace(c)) {
+ tokenStart = cur;
+ EnterValueState();
+
+ // make sure to handle this first character
+ if (ParseValueCharacter(c, cur, tokenStart, oldValue)) {
+ cur++;
+ }
+ // If the character isn't consumed, don't do cur++ and parse
+ // the character again. This can happen f.e. for char 'X' in sequence
+ // "\u00X". This character can be control character and must be
+ // processed again.
+ continue;
+ }
+ break;
+
+ case eParserState_Value:
+ if (ParseValueCharacter(c, cur, tokenStart, oldValue)) {
+ cur++;
+ }
+ // See few lines above for reason of doing this
+ continue;
+
+ case eParserState_Comment:
+ // stay in this state till we hit EOL
+ if (c == '\r' || c == '\n') {
+ WaitForKey();
+ }
+ break;
+ }
+
+ // finally, advance to the next character
+ cur++;
+ }
+
+ // if we're still parsing the value and are in eParserSpecial_None, then
+ // append whatever we have..
+ if (mState == eParserState_Value && tokenStart &&
+ mSpecialState == eParserSpecial_None) {
+ mValue += Substring(tokenStart, cur);
+ }
+ // if we're still parsing the key, then append whatever we have..
+ else if (mState == eParserState_Key && tokenStart) {
+ mKey += Substring(tokenStart, cur);
+ }
+
+ return NS_OK;
+}
+
+nsPersistentProperties::nsPersistentProperties()
+ : mIn(nullptr),
+ mTable(&property_HashTableOps, sizeof(PropertyTableEntry), 16),
+ mArena() {}
+
+nsPersistentProperties::~nsPersistentProperties() = default;
+
+size_t nsPersistentProperties::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) {
+ // The memory used by mTable is accounted for in mArena.
+ size_t n = 0;
+ n += mArena.SizeOfExcludingThis(aMallocSizeOf);
+ n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return aMallocSizeOf(this) + n;
+}
+
+NS_IMPL_ISUPPORTS(nsPersistentProperties, nsIPersistentProperties,
+ nsIProperties)
+
+NS_IMETHODIMP
+nsPersistentProperties::Load(nsIInputStream* aIn) {
+ nsresult rv = NS_NewUnicharInputStream(aIn, getter_AddRefs(mIn));
+
+ if (rv != NS_OK) {
+ NS_WARNING("Error creating UnicharInputStream");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsPropertiesParser parser(this);
+
+ uint32_t nProcessed;
+ // If this 4096 is changed to some other value, make sure to adjust
+ // the bug121341.properties test file accordingly.
+ while (NS_SUCCEEDED(rv = mIn->ReadSegments(nsPropertiesParser::SegmentWriter,
+ &parser, 4096, &nProcessed)) &&
+ nProcessed != 0)
+ ;
+ mIn = nullptr;
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We may have an unprocessed value at this point
+ // if the last line did not have a proper line ending.
+ if (parser.GetState() == eParserState_Value) {
+ nsAutoString oldValue;
+ parser.FinishValueState(oldValue);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPersistentProperties::SetStringProperty(const nsACString& aKey,
+ const nsAString& aNewValue,
+ nsAString& aOldValue) {
+ const nsCString& flatKey = PromiseFlatCString(aKey);
+ auto entry = static_cast<PropertyTableEntry*>(mTable.Add(flatKey.get()));
+
+ if (entry->mKey) {
+ aOldValue = entry->mValue;
+ NS_WARNING(
+ nsPrintfCString("the property %s already exists", flatKey.get()).get());
+ } else {
+ aOldValue.Truncate();
+ }
+
+ entry->mKey = ArenaStrdup(flatKey, mArena);
+ entry->mValue = ArenaStrdup(aNewValue, mArena);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPersistentProperties::Save(nsIOutputStream* aOut, const nsACString& aHeader) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPersistentProperties::GetStringProperty(const nsACString& aKey,
+ nsAString& aValue) {
+ const nsCString& flatKey = PromiseFlatCString(aKey);
+
+ auto entry = static_cast<PropertyTableEntry*>(mTable.Search(flatKey.get()));
+ if (!entry) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aValue = entry->mValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPersistentProperties::Enumerate(nsISimpleEnumerator** aResult) {
+ nsCOMArray<nsIPropertyElement> props;
+
+ // We know the necessary size; we can avoid growing it while adding elements
+ props.SetCapacity(mTable.EntryCount());
+
+ // Step through hash entries populating a transient array
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PropertyTableEntry*>(iter.Get());
+
+ RefPtr<nsPropertyElement> element = new nsPropertyElement(
+ nsDependentCString(entry->mKey), nsDependentString(entry->mValue));
+
+ if (!props.AppendObject(element)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return NS_NewArrayEnumerator(aResult, props, NS_GET_IID(nsIPropertyElement));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XXX Some day we'll unify the nsIPersistentProperties interface with
+// nsIProperties, but until now...
+
+NS_IMETHODIMP
+nsPersistentProperties::Get(const char* aProp, const nsIID& aUUID,
+ void** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPersistentProperties::Set(const char* aProp, nsISupports* value) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+nsPersistentProperties::Undefine(const char* aProp) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPersistentProperties::Has(const char* aProp, bool* aResult) {
+ *aResult = !!mTable.Search(aProp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPersistentProperties::GetKeys(nsTArray<nsCString>& aKeys) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PropertyElement
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsPropertyElement::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsPropertyElement> propElem = new nsPropertyElement();
+ return propElem->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS(nsPropertyElement, nsIPropertyElement)
+
+NS_IMETHODIMP
+nsPropertyElement::GetKey(nsACString& aReturnKey) {
+ aReturnKey = mKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPropertyElement::GetValue(nsAString& aReturnValue) {
+ aReturnValue = mValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPropertyElement::SetKey(const nsACString& aKey) {
+ mKey = aKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPropertyElement::SetValue(const nsAString& aValue) {
+ mValue = aValue;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/xpcom/ds/nsPersistentProperties.h b/xpcom/ds/nsPersistentProperties.h
new file mode 100644
index 0000000000..e35f938371
--- /dev/null
+++ b/xpcom/ds/nsPersistentProperties.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPersistentProperties_h___
+#define nsPersistentProperties_h___
+
+#include "nsIPersistentProperties2.h"
+#include "PLDHashTable.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "mozilla/ArenaAllocator.h"
+#include "mozilla/Attributes.h"
+
+class nsIUnicharInputStream;
+
+class nsPersistentProperties final : public nsIPersistentProperties {
+ public:
+ nsPersistentProperties();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROPERTIES
+ NS_DECL_NSIPERSISTENTPROPERTIES
+
+ private:
+ ~nsPersistentProperties();
+
+ protected:
+ nsCOMPtr<nsIUnicharInputStream> mIn;
+
+ PLDHashTable mTable;
+ mozilla::ArenaAllocator<2048, 4> mArena;
+};
+
+class nsPropertyElement final : public nsIPropertyElement {
+ public:
+ nsPropertyElement() = default;
+
+ nsPropertyElement(const nsACString& aKey, const nsAString& aValue)
+ : mKey(aKey), mValue(aValue) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROPERTYELEMENT
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ private:
+ ~nsPropertyElement() = default;
+
+ protected:
+ nsCString mKey;
+ nsString mValue;
+};
+
+#endif /* nsPersistentProperties_h___ */
diff --git a/xpcom/ds/nsPointerHashKeys.h b/xpcom/ds/nsPointerHashKeys.h
new file mode 100644
index 0000000000..5cf2e8c4d3
--- /dev/null
+++ b/xpcom/ds/nsPointerHashKeys.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Definitions for nsPtrHashKey<T> and nsVoidPtrHashKey. */
+
+#ifndef nsPointerHashKeys_h
+#define nsPointerHashKeys_h
+
+#include "nscore.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/HashFunctions.h"
+
+#include "PLDHashTable.h"
+
+/**
+ * hashkey wrapper using T* KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ */
+template <class T>
+class nsPtrHashKey : public PLDHashEntryHdr {
+ public:
+ typedef T* KeyType;
+ typedef const T* KeyTypePointer;
+
+ explicit nsPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {}
+ nsPtrHashKey(nsPtrHashKey<T>&& aToMove)
+ : PLDHashEntryHdr(std::move(aToMove)), mKey(std::move(aToMove.mKey)) {}
+ ~nsPtrHashKey() = default;
+
+ KeyType GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return mozilla::HashGeneric(aKey);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ T* MOZ_NON_OWNING_REF mKey;
+};
+
+typedef nsPtrHashKey<const void> nsVoidPtrHashKey;
+
+#endif // nsPointerHashKeys_h
diff --git a/xpcom/ds/nsProperties.cpp b/xpcom/ds/nsProperties.cpp
new file mode 100644
index 0000000000..2094981634
--- /dev/null
+++ b/xpcom/ds/nsProperties.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsProperties.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsProperties, nsIProperties)
+
+NS_IMETHODIMP
+nsProperties::Get(const char* prop, const nsIID& uuid, void** result) {
+ if (NS_WARN_IF(!prop)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsISupports> value;
+ if (!nsProperties_HashBase::Get(prop, getter_AddRefs(value))) {
+ return NS_ERROR_FAILURE;
+ }
+ return (value) ? value->QueryInterface(uuid, result) : NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsProperties::Set(const char* prop, nsISupports* value) {
+ if (NS_WARN_IF(!prop)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ InsertOrUpdate(prop, value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProperties::Undefine(const char* prop) {
+ if (NS_WARN_IF(!prop)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return nsProperties_HashBase::Remove(prop) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsProperties::Has(const char* prop, bool* result) {
+ if (NS_WARN_IF(!prop)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *result = nsProperties_HashBase::Contains(prop);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProperties::GetKeys(nsTArray<nsCString>& aKeys) {
+ mozilla::AppendToArray(aKeys, this->Keys());
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/xpcom/ds/nsProperties.h b/xpcom/ds/nsProperties.h
new file mode 100644
index 0000000000..c45c7361ea
--- /dev/null
+++ b/xpcom/ds/nsProperties.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsProperties_h___
+#define nsProperties_h___
+
+#include "nsIProperties.h"
+#include "nsInterfaceHashtable.h"
+#include "nsHashKeys.h"
+#include "mozilla/Attributes.h"
+
+typedef nsInterfaceHashtable<nsCharPtrHashKey, nsISupports>
+ nsProperties_HashBase;
+
+class nsProperties final : public nsIProperties, public nsProperties_HashBase {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROPERTIES
+
+ nsProperties() = default;
+
+ private:
+ ~nsProperties() = default;
+};
+
+#endif /* nsProperties_h___ */
diff --git a/xpcom/ds/nsQuickSort.cpp b/xpcom/ds/nsQuickSort.cpp
new file mode 100644
index 0000000000..1fb3dede88
--- /dev/null
+++ b/xpcom/ds/nsQuickSort.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* We need this because Solaris' version of qsort is broken and
+ * causes array bounds reads.
+ */
+
+#include <stdlib.h>
+#include "nsAlgorithm.h"
+#include "nsQuickSort.h"
+
+extern "C" {
+
+#if !defined(DEBUG) && (defined(__cplusplus) || defined(__gcc))
+# ifndef INLINE
+# define INLINE inline
+# endif
+#else
+# define INLINE
+#endif
+
+typedef int cmp_t(const void*, const void*, void*);
+static INLINE char* med3(char*, char*, char*, cmp_t*, void*);
+static INLINE void swapfunc(char*, char*, int, int);
+
+/*
+ * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function".
+ */
+#define swapcode(TYPE, parmi, parmj, n) \
+ { \
+ long i = (n) / sizeof(TYPE); \
+ TYPE* pi = (TYPE*)(parmi); \
+ TYPE* pj = (TYPE*)(parmj); \
+ do { \
+ TYPE t = *pi; \
+ *pi++ = *pj; \
+ *pj++ = t; \
+ } while (--i > 0); \
+ }
+
+#define SWAPINIT(a, es) \
+ swaptype = ((char*)a - (char*)0) % sizeof(long) || es % sizeof(long) ? 2 \
+ : es == sizeof(long) ? 0 \
+ : 1;
+
+static INLINE void swapfunc(char* a, char* b, int n, int swaptype) {
+ if (swaptype <= 1) swapcode(long, a, b, n) else swapcode(char, a, b, n)
+}
+
+#define swap(a, b) \
+ if (swaptype == 0) { \
+ long t = *(long*)(a); \
+ *(long*)(a) = *(long*)(b); \
+ *(long*)(b) = t; \
+ } else \
+ swapfunc((char*)a, (char*)b, (int)es, swaptype)
+
+#define vecswap(a, b, n) \
+ if ((n) > 0) swapfunc((char*)a, (char*)b, (int)n, swaptype)
+
+static INLINE char* med3(char* a, char* b, char* c, cmp_t* cmp, void* data) {
+ return cmp(a, b, data) < 0
+ ? (cmp(b, c, data) < 0 ? b : (cmp(a, c, data) < 0 ? c : a))
+ : (cmp(b, c, data) > 0 ? b : (cmp(a, c, data) < 0 ? a : c));
+}
+
+void NS_QuickSort(void* a, unsigned int n, unsigned int es, cmp_t* cmp,
+ void* data) {
+ char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
+ int d, r, swaptype;
+
+loop:
+ SWAPINIT(a, es);
+ /* Use insertion sort when input is small */
+ if (n < 7) {
+ for (pm = (char*)a + es; pm < (char*)a + n * es; pm += es)
+ for (pl = pm; pl > (char*)a && cmp(pl - es, pl, data) > 0; pl -= es)
+ swap(pl, pl - es);
+ return;
+ }
+ /* Choose pivot */
+ pm = (char*)a + (n / 2) * es;
+ if (n > 7) {
+ pl = (char*)a;
+ pn = (char*)a + (n - 1) * es;
+ if (n > 40) {
+ d = (n / 8) * es;
+ pl = med3(pl, pl + d, pl + 2 * d, cmp, data);
+ pm = med3(pm - d, pm, pm + d, cmp, data);
+ pn = med3(pn - 2 * d, pn - d, pn, cmp, data);
+ }
+ pm = med3(pl, pm, pn, cmp, data);
+ }
+ swap(a, pm);
+ pa = pb = (char*)a + es;
+
+ pc = pd = (char*)a + (n - 1) * es;
+ /* loop invariants:
+ * [a, pa) = pivot
+ * [pa, pb) < pivot
+ * [pb, pc + es) unprocessed
+ * [pc + es, pd + es) > pivot
+ * [pd + es, pn) = pivot
+ */
+ for (;;) {
+ while (pb <= pc && (r = cmp(pb, a, data)) <= 0) {
+ if (r == 0) {
+ swap(pa, pb);
+ pa += es;
+ }
+ pb += es;
+ }
+ while (pb <= pc && (r = cmp(pc, a, data)) >= 0) {
+ if (r == 0) {
+ swap(pc, pd);
+ pd -= es;
+ }
+ pc -= es;
+ }
+ if (pb > pc) break;
+ swap(pb, pc);
+ pb += es;
+ pc -= es;
+ }
+ /* Move pivot values */
+ pn = (char*)a + n * es;
+ r = XPCOM_MIN(pa - (char*)a, pb - pa);
+ vecswap(a, pb - r, r);
+ r = XPCOM_MIN<size_t>(pd - pc, pn - pd - es);
+ vecswap(pb, pn - r, r);
+ /* Recursively process partitioned items */
+ if ((r = pb - pa) > (int)es) NS_QuickSort(a, r / es, es, cmp, data);
+ if ((r = pd - pc) > (int)es) {
+ /* Iterate rather than recurse to save stack space */
+ a = pn - r;
+ n = r / es;
+ goto loop;
+ }
+ /* NS_QuickSort(pn - r, r / es, es, cmp, data);*/
+}
+}
+
+#undef INLINE
+#undef swapcode
+#undef SWAPINIT
+#undef swap
+#undef vecswap
diff --git a/xpcom/ds/nsQuickSort.h b/xpcom/ds/nsQuickSort.h
new file mode 100644
index 0000000000..c2b842bea3
--- /dev/null
+++ b/xpcom/ds/nsQuickSort.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* We need this because Solaris' version of qsort is broken and
+ * causes array bounds reads.
+ */
+
+#ifndef nsQuickSort_h___
+#define nsQuickSort_h___
+
+#include "nscore.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Parameters:
+ * 1. the array to sort
+ * 2. the number of elements in the array
+ * 3. the size of each array element
+ * 4. comparison function taking two elements and parameter #5 and
+ * returning an integer:
+ * + less than zero if the first element should be before the second
+ * + 0 if the order of the elements does not matter
+ * + greater than zero if the second element should be before the first
+ * 5. extra data to pass to comparison function
+ */
+void NS_QuickSort(void*, unsigned int, unsigned int,
+ int (*)(const void*, const void*, void*), void*);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* nsQuickSort_h___ */
diff --git a/xpcom/ds/nsRefCountedHashtable.h b/xpcom/ds/nsRefCountedHashtable.h
new file mode 100644
index 0000000000..e3a0456a09
--- /dev/null
+++ b/xpcom/ds/nsRefCountedHashtable.h
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef XPCOM_DS_NSREFCOUNTEDHASHTABLE_H_
+#define XPCOM_DS_NSREFCOUNTEDHASHTABLE_H_
+
+#include "nsBaseHashtable.h"
+#include "nsHashKeys.h"
+
+/**
+ * templated hashtable class maps keys to reference pointers.
+ * See nsBaseHashtable for complete declaration.
+ * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h
+ * for a complete specification.
+ * @param PtrType the reference-type being wrapped
+ * @see nsClassHashtable, nsTHashMap
+ */
+template <class KeyClass, class PtrType>
+class nsRefCountedHashtable
+ : public nsBaseHashtable<
+ KeyClass, PtrType,
+ typename mozilla::detail::SmartPtrTraits<PtrType>::RawPointerType> {
+ public:
+ using KeyType = typename KeyClass::KeyType;
+ using SmartPtrTraits = mozilla::detail::SmartPtrTraits<PtrType>;
+ using PointeeType = typename SmartPtrTraits::PointeeType;
+ using RawPointerType = typename SmartPtrTraits::RawPointerType;
+ using base_type = nsBaseHashtable<KeyClass, PtrType, RawPointerType>;
+
+ using base_type::base_type;
+
+ static_assert(SmartPtrTraits::IsRefCounted);
+
+ /**
+ * @copydoc nsBaseHashtable::Get
+ * @param aData This is an XPCOM getter, so aData is already_addrefed.
+ * If the key doesn't exist, *aData will be set to nullptr.
+ */
+ bool Get(KeyType aKey, RawPointerType* aData) const;
+
+ /**
+ * @copydoc nsBaseHashtable::Get
+ */
+ [[nodiscard]] already_AddRefed<PointeeType> Get(KeyType aKey) const;
+
+ /**
+ * Gets a weak reference to the hashtable entry.
+ * @param aFound If not nullptr, will be set to true if the entry is found,
+ * to false otherwise.
+ * @return The entry, or nullptr if not found. Do not release this pointer!
+ */
+ [[nodiscard]] RawPointerType GetWeak(KeyType aKey,
+ bool* aFound = nullptr) const;
+
+ using base_type::InsertOrUpdate;
+
+ template <typename U,
+ typename = std::enable_if_t<std::is_base_of_v<PointeeType, U>>>
+ void InsertOrUpdate(
+ KeyType aKey,
+ typename SmartPtrTraits::template OtherSmartPtrType<U>&& aData);
+
+ template <typename U,
+ typename = std::enable_if_t<std::is_base_of_v<PointeeType, U>>>
+ [[nodiscard]] bool InsertOrUpdate(
+ KeyType aKey,
+ typename SmartPtrTraits::template OtherSmartPtrType<U>&& aData,
+ const mozilla::fallible_t&);
+
+ template <typename U,
+ typename = std::enable_if_t<std::is_base_of_v<PointeeType, U>>>
+ void InsertOrUpdate(KeyType aKey, already_AddRefed<U>&& aData);
+
+ template <typename U,
+ typename = std::enable_if_t<std::is_base_of_v<PointeeType, U>>>
+ [[nodiscard]] bool InsertOrUpdate(KeyType aKey, already_AddRefed<U>&& aData,
+ const mozilla::fallible_t&);
+
+ /**
+ * Remove the entry associated with aKey (if any), optionally _moving_ its
+ * current value into *aData, thereby avoiding calls to AddRef and Release.
+ * Return true if found.
+ * @param aKey the key to remove from the hashtable
+ * @param aData where to move the value (if non-null). If an entry is not
+ * found it will be set to nullptr.
+ * @return true if an entry for aKey was found (and removed)
+ */
+ inline bool Remove(KeyType aKey, RawPointerType* aData = nullptr);
+
+ nsRefCountedHashtable Clone() const {
+ return this->template CloneAs<nsRefCountedHashtable>();
+ }
+};
+
+template <typename K, typename T>
+inline void ImplCycleCollectionUnlink(nsRefCountedHashtable<K, T>& aField) {
+ aField.Clear();
+}
+
+template <typename K, typename T>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ nsRefCountedHashtable<K, T>& aField, const char* aName,
+ uint32_t aFlags = 0) {
+ for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) {
+ CycleCollectionNoteChild(aCallback, iter.UserData(), aName, aFlags);
+ }
+}
+
+//
+// nsRefCountedHashtable definitions
+//
+
+template <class KeyClass, class PtrType>
+bool nsRefCountedHashtable<KeyClass, PtrType>::Get(
+ KeyType aKey, RawPointerType* aRefPtr) const {
+ typename base_type::EntryType* ent = this->GetEntry(aKey);
+
+ if (ent) {
+ if (aRefPtr) {
+ *aRefPtr = ent->GetData();
+
+ NS_IF_ADDREF(*aRefPtr);
+ }
+
+ return true;
+ }
+
+ // if the key doesn't exist, set *aRefPtr to null
+ // so that it is a valid XPCOM getter
+ if (aRefPtr) {
+ *aRefPtr = nullptr;
+ }
+
+ return false;
+}
+
+template <class KeyClass, class PtrType>
+already_AddRefed<typename nsRefCountedHashtable<KeyClass, PtrType>::PointeeType>
+nsRefCountedHashtable<KeyClass, PtrType>::Get(KeyType aKey) const {
+ typename base_type::EntryType* ent = this->GetEntry(aKey);
+ if (!ent) {
+ return nullptr;
+ }
+
+ PtrType copy = ent->GetData();
+ return copy.forget();
+}
+
+template <class KeyClass, class PtrType>
+typename nsRefCountedHashtable<KeyClass, PtrType>::RawPointerType
+nsRefCountedHashtable<KeyClass, PtrType>::GetWeak(KeyType aKey,
+ bool* aFound) const {
+ typename base_type::EntryType* ent = this->GetEntry(aKey);
+
+ if (ent) {
+ if (aFound) {
+ *aFound = true;
+ }
+
+ return ent->GetData();
+ }
+
+ // Key does not exist, return nullptr and set aFound to false
+ if (aFound) {
+ *aFound = false;
+ }
+
+ return nullptr;
+}
+
+template <class KeyClass, class PtrType>
+template <typename U, typename>
+void nsRefCountedHashtable<KeyClass, PtrType>::InsertOrUpdate(
+ KeyType aKey,
+ typename SmartPtrTraits::template OtherSmartPtrType<U>&& aData) {
+ if (!InsertOrUpdate(aKey, std::move(aData), mozilla::fallible)) {
+ NS_ABORT_OOM(this->mTable.EntrySize() * this->mTable.EntryCount());
+ }
+}
+
+template <class KeyClass, class PtrType>
+template <typename U, typename>
+bool nsRefCountedHashtable<KeyClass, PtrType>::InsertOrUpdate(
+ KeyType aKey,
+ typename SmartPtrTraits::template OtherSmartPtrType<U>&& aData,
+ const mozilla::fallible_t&) {
+ typename base_type::EntryType* ent = this->PutEntry(aKey, mozilla::fallible);
+
+ if (!ent) {
+ return false;
+ }
+
+ ent->SetData(std::move(aData));
+
+ return true;
+}
+
+template <class KeyClass, class PtrType>
+template <typename U, typename>
+void nsRefCountedHashtable<KeyClass, PtrType>::InsertOrUpdate(
+ KeyType aKey, already_AddRefed<U>&& aData) {
+ if (!InsertOrUpdate(aKey, std::move(aData), mozilla::fallible)) {
+ NS_ABORT_OOM(this->mTable.EntrySize() * this->mTable.EntryCount());
+ }
+}
+
+template <class KeyClass, class PtrType>
+template <typename U, typename>
+bool nsRefCountedHashtable<KeyClass, PtrType>::InsertOrUpdate(
+ KeyType aKey, already_AddRefed<U>&& aData, const mozilla::fallible_t&) {
+ typename base_type::EntryType* ent = this->PutEntry(aKey, mozilla::fallible);
+
+ if (!ent) {
+ return false;
+ }
+
+ ent->SetData(std::move(aData));
+
+ return true;
+}
+
+template <class KeyClass, class PtrType>
+bool nsRefCountedHashtable<KeyClass, PtrType>::Remove(KeyType aKey,
+ RawPointerType* aRefPtr) {
+ typename base_type::EntryType* ent = this->GetEntry(aKey);
+
+ if (ent) {
+ if (aRefPtr) {
+ ent->GetModifiableData()->forget(aRefPtr);
+ }
+ this->RemoveEntry(ent);
+ return true;
+ }
+
+ if (aRefPtr) {
+ *aRefPtr = nullptr;
+ }
+ return false;
+}
+
+#endif // XPCOM_DS_NSREFCOUNTEDHASHTABLE_H_
diff --git a/xpcom/ds/nsRefPtrHashtable.h b/xpcom/ds/nsRefPtrHashtable.h
new file mode 100644
index 0000000000..55b946bf7b
--- /dev/null
+++ b/xpcom/ds/nsRefPtrHashtable.h
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRefPtrHashtable_h__
+#define nsRefPtrHashtable_h__
+
+#include "nsRefCountedHashtable.h"
+
+#endif // nsRefPtrHashtable_h__
diff --git a/xpcom/ds/nsSimpleEnumerator.cpp b/xpcom/ds/nsSimpleEnumerator.cpp
new file mode 100644
index 0000000000..7f7ca9d674
--- /dev/null
+++ b/xpcom/ds/nsSimpleEnumerator.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSimpleEnumerator.h"
+
+#include "mozilla/dom/IteratorResultBinding.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/ResultExtensions.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace {
+
+class JSEnumerator final : public nsIJSEnumerator {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIJSENUMERATOR
+
+ explicit JSEnumerator(nsISimpleEnumerator* aEnumerator, const nsID& aIID)
+ : mEnumerator(aEnumerator), mIID(aIID) {}
+
+ private:
+ ~JSEnumerator() = default;
+
+ nsCOMPtr<nsISimpleEnumerator> mEnumerator;
+ const nsID mIID;
+};
+
+} // anonymous namespace
+
+nsresult JSEnumerator::Iterator(nsIJSEnumerator** aResult) {
+ RefPtr<JSEnumerator> result(this);
+ result.forget(aResult);
+ return NS_OK;
+}
+
+nsresult JSEnumerator::Next(JSContext* aCx, JS::MutableHandleValue aResult) {
+ RootedDictionary<IteratorResult> result(aCx);
+
+ nsCOMPtr<nsISupports> elem;
+ if (NS_FAILED(mEnumerator->GetNext(getter_AddRefs(elem)))) {
+ result.mDone = true;
+ } else {
+ result.mDone = false;
+
+ JS::RootedValue value(aCx);
+ MOZ_TRY(nsContentUtils::WrapNative(aCx, elem, &mIID, &value));
+ result.mValue = value;
+ }
+
+ if (!ToJSValue(aCx, result, aResult)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(JSEnumerator, nsIJSEnumerator)
+
+nsresult nsSimpleEnumerator::Iterator(nsIJSEnumerator** aResult) {
+ auto result = MakeRefPtr<JSEnumerator>(this, DefaultInterface());
+ result.forget(aResult);
+ return NS_OK;
+}
+
+nsresult nsSimpleEnumerator::Entries(const nsIID& aIface,
+ nsIJSEnumerator** aResult) {
+ auto result = MakeRefPtr<JSEnumerator>(this, aIface);
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsSimpleEnumerator, nsISimpleEnumerator,
+ nsISimpleEnumeratorBase)
diff --git a/xpcom/ds/nsSimpleEnumerator.h b/xpcom/ds/nsSimpleEnumerator.h
new file mode 100644
index 0000000000..60ab6419bc
--- /dev/null
+++ b/xpcom/ds/nsSimpleEnumerator.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSimpleEnumerator_h
+#define nsSimpleEnumerator_h
+
+#include "nsISimpleEnumerator.h"
+
+class nsSimpleEnumerator : public nsISimpleEnumerator {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATORBASE
+
+ virtual const nsID& DefaultInterface() { return NS_GET_IID(nsISupports); }
+
+ protected:
+ virtual ~nsSimpleEnumerator() = default;
+};
+
+#endif
diff --git a/xpcom/ds/nsStaticAtomUtils.h b/xpcom/ds/nsStaticAtomUtils.h
new file mode 100644
index 0000000000..8c766f63e1
--- /dev/null
+++ b/xpcom/ds/nsStaticAtomUtils.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStaticAtomUtils_h
+#define nsStaticAtomUtils_h
+
+#include <stdint.h>
+#include "nsAtom.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Maybe.h"
+
+// This class holds basic operations on arrays of static atoms.
+class nsStaticAtomUtils {
+ public:
+ static mozilla::Maybe<uint32_t> Lookup(nsAtom* aAtom,
+ const nsStaticAtom* aAtoms,
+ uint32_t aCount) {
+ if (aAtom->IsStatic()) {
+ ptrdiff_t index = aAtom->AsStatic() - aAtoms;
+ if (index >= 0 && index < static_cast<ptrdiff_t>(aCount)) {
+ return mozilla::Some(static_cast<uint32_t>(index));
+ }
+ }
+ return mozilla::Nothing();
+ }
+
+ static bool IsMember(nsAtom* aAtom, const nsStaticAtom* aAtoms,
+ uint32_t aCount) {
+ return Lookup(aAtom, aAtoms, aCount).isSome();
+ }
+};
+
+#endif // nsStaticAtomUtils_h
diff --git a/xpcom/ds/nsStaticNameTable.cpp b/xpcom/ds/nsStaticNameTable.cpp
new file mode 100644
index 0000000000..2ee731c153
--- /dev/null
+++ b/xpcom/ds/nsStaticNameTable.cpp
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Class to manage lookup of static names in a table. */
+
+#include "mozilla/HashFunctions.h"
+#include "mozilla/TextUtils.h"
+
+#include "nsCRT.h"
+
+#include "nscore.h"
+#include "nsISupportsImpl.h"
+
+#include "nsStaticNameTable.h"
+
+using namespace mozilla;
+
+struct NameTableKey {
+ NameTableKey(const nsDependentCString aNameArray[], const nsCString* aKeyStr)
+ : mNameArray(aNameArray), mIsUnichar(false) {
+ mKeyStr.m1b = aKeyStr;
+ }
+
+ NameTableKey(const nsDependentCString aNameArray[], const nsString* aKeyStr)
+ : mNameArray(aNameArray), mIsUnichar(true) {
+ mKeyStr.m2b = aKeyStr;
+ }
+
+ const nsDependentCString* mNameArray;
+ union {
+ const nsCString* m1b;
+ const nsString* m2b;
+ } mKeyStr;
+ bool mIsUnichar;
+};
+
+struct NameTableEntry : public PLDHashEntryHdr {
+ int32_t mIndex;
+};
+
+static bool matchNameKeysCaseInsensitive(const PLDHashEntryHdr* aHdr,
+ const void* aVoidKey) {
+ auto entry = static_cast<const NameTableEntry*>(aHdr);
+ auto key = static_cast<const NameTableKey*>(aVoidKey);
+ const nsDependentCString* name = &key->mNameArray[entry->mIndex];
+
+ return key->mIsUnichar ? key->mKeyStr.m2b->LowerCaseEqualsASCII(
+ name->get(), name->Length())
+ : key->mKeyStr.m1b->LowerCaseEqualsASCII(
+ name->get(), name->Length());
+}
+
+/*
+ * caseInsensitiveHashKey is just like PLDHashTable::HashStringKey except it
+ * uses (*s & ~0x20) instead of simply *s. This means that "aFOO" and
+ * "afoo" and "aFoo" will all hash to the same thing. It also means
+ * that some strings that aren't case-insensensitively equal will hash
+ * to the same value, but it's just a hash function so it doesn't
+ * matter.
+ */
+static PLDHashNumber caseInsensitiveStringHashKey(const void* aKey) {
+ PLDHashNumber h = 0;
+ const NameTableKey* tableKey = static_cast<const NameTableKey*>(aKey);
+ if (tableKey->mIsUnichar) {
+ for (const char16_t* s = tableKey->mKeyStr.m2b->get(); *s != '\0'; s++) {
+ h = AddToHash(h, *s & ~0x20);
+ }
+ } else {
+ for (const unsigned char* s = reinterpret_cast<const unsigned char*>(
+ tableKey->mKeyStr.m1b->get());
+ *s != '\0'; s++) {
+ h = AddToHash(h, *s & ~0x20);
+ }
+ }
+ return h;
+}
+
+static const struct PLDHashTableOps nametable_CaseInsensitiveHashTableOps = {
+ caseInsensitiveStringHashKey,
+ matchNameKeysCaseInsensitive,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr,
+};
+
+nsStaticCaseInsensitiveNameTable::nsStaticCaseInsensitiveNameTable(
+ const char* const aNames[], int32_t aLength)
+ : mNameArray(nullptr),
+ mNameTable(&nametable_CaseInsensitiveHashTableOps, sizeof(NameTableEntry),
+ aLength),
+ mNullStr("") {
+ MOZ_COUNT_CTOR(nsStaticCaseInsensitiveNameTable);
+
+ MOZ_ASSERT(aNames, "null name table");
+ MOZ_ASSERT(aLength, "0 length");
+
+ mNameArray =
+ (nsDependentCString*)moz_xmalloc(aLength * sizeof(nsDependentCString));
+
+ for (int32_t index = 0; index < aLength; ++index) {
+ const char* raw = aNames[index];
+#ifdef DEBUG
+ {
+ // verify invariants of contents
+ nsAutoCString temp1(raw);
+ nsDependentCString temp2(raw);
+ ToLowerCase(temp1);
+ MOZ_ASSERT(temp1.Equals(temp2), "upper case char in table");
+ MOZ_ASSERT(IsAsciiNullTerminated(raw),
+ "non-ascii string in table -- "
+ "case-insensitive matching won't work right");
+ }
+#endif
+ // use placement-new to initialize the string object
+ nsDependentCString* strPtr = &mNameArray[index];
+ new (strPtr) nsDependentCString(raw);
+
+ NameTableKey key(mNameArray, strPtr);
+
+ auto entry = static_cast<NameTableEntry*>(mNameTable.Add(&key, fallible));
+ if (!entry) {
+ continue;
+ }
+
+ // If the entry already exists it's unlikely but possible that its index is
+ // zero, in which case this assertion won't fail. But if the index is
+ // non-zero (highly likely) then it will fail. In other words, this
+ // assertion is likely but not guaranteed to detect if an entry is already
+ // used.
+ MOZ_ASSERT(entry->mIndex == 0, "Entry already exists!");
+
+ entry->mIndex = index;
+ }
+ mNameTable.MarkImmutable();
+}
+
+nsStaticCaseInsensitiveNameTable::~nsStaticCaseInsensitiveNameTable() {
+ // manually call the destructor on placement-new'ed objects
+ for (uint32_t index = 0; index < mNameTable.EntryCount(); index++) {
+ mNameArray[index].~nsDependentCString();
+ }
+ free((void*)mNameArray);
+ MOZ_COUNT_DTOR(nsStaticCaseInsensitiveNameTable);
+}
+
+int32_t nsStaticCaseInsensitiveNameTable::Lookup(
+ const nsACString& aName) const {
+ NS_ASSERTION(mNameArray, "not inited");
+
+ const nsCString& str = PromiseFlatCString(aName);
+
+ NameTableKey key(mNameArray, &str);
+ auto entry = static_cast<NameTableEntry*>(mNameTable.Search(&key));
+
+ return entry ? entry->mIndex : nsStaticCaseInsensitiveNameTable::NOT_FOUND;
+}
+
+int32_t nsStaticCaseInsensitiveNameTable::Lookup(const nsAString& aName) const {
+ NS_ASSERTION(mNameArray, "not inited");
+
+ const nsString& str = PromiseFlatString(aName);
+
+ NameTableKey key(mNameArray, &str);
+ auto entry = static_cast<NameTableEntry*>(mNameTable.Search(&key));
+
+ return entry ? entry->mIndex : nsStaticCaseInsensitiveNameTable::NOT_FOUND;
+}
+
+const nsCString& nsStaticCaseInsensitiveNameTable::GetStringValue(
+ int32_t aIndex) {
+ NS_ASSERTION(mNameArray, "not inited");
+
+ if ((NOT_FOUND < aIndex) && ((uint32_t)aIndex < mNameTable.EntryCount())) {
+ return mNameArray[aIndex];
+ }
+ return mNullStr;
+}
diff --git a/xpcom/ds/nsStaticNameTable.h b/xpcom/ds/nsStaticNameTable.h
new file mode 100644
index 0000000000..302d82c1e4
--- /dev/null
+++ b/xpcom/ds/nsStaticNameTable.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Classes to manage lookup of static names in a table. */
+
+#ifndef nsStaticNameTable_h___
+#define nsStaticNameTable_h___
+
+#include "PLDHashTable.h"
+#include "nsString.h"
+
+/* This class supports case insensitive lookup.
+ *
+ * It differs from atom tables:
+ * - It supports case insensitive lookup.
+ * - It has minimal footprint by not copying the string table.
+ * - It does no locking.
+ * - It returns zero based indexes and const nsCString& as required by its
+ * callers in the parser.
+ * - It is not an xpcom interface - meant for fast lookup in static tables.
+ *
+ * ***REQUIREMENTS***
+ * - It *requires* that all entries in the table be lowercase only.
+ * - It *requires* that the table of strings be in memory that lives at least
+ * as long as this table object - typically a static string array.
+ */
+
+class nsStaticCaseInsensitiveNameTable {
+ public:
+ enum { NOT_FOUND = -1 };
+
+ int32_t Lookup(const nsACString& aName) const;
+ int32_t Lookup(const nsAString& aName) const;
+ const nsCString& GetStringValue(int32_t aIndex);
+
+ nsStaticCaseInsensitiveNameTable(const char* const aNames[], int32_t aLength);
+ ~nsStaticCaseInsensitiveNameTable();
+
+ private:
+ nsDependentCString* mNameArray;
+ PLDHashTable mNameTable;
+ nsDependentCString mNullStr;
+};
+
+#endif /* nsStaticNameTable_h___ */
diff --git a/xpcom/ds/nsStringEnumerator.cpp b/xpcom/ds/nsStringEnumerator.cpp
new file mode 100644
index 0000000000..7b8cf72d00
--- /dev/null
+++ b/xpcom/ds/nsStringEnumerator.cpp
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStringEnumerator.h"
+#include "nsSimpleEnumerator.h"
+#include "nsSupportsPrimitives.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/dom/IteratorResultBinding.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsTArray.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace {
+
+class JSStringEnumerator final : public nsIJSEnumerator {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIJSENUMERATOR
+
+ explicit JSStringEnumerator(nsIStringEnumerator* aEnumerator)
+ : mEnumerator(aEnumerator) {
+ MOZ_ASSERT(mEnumerator);
+ }
+
+ private:
+ ~JSStringEnumerator() = default;
+
+ nsCOMPtr<nsIStringEnumerator> mEnumerator;
+};
+
+} // anonymous namespace
+
+nsresult JSStringEnumerator::Iterator(nsIJSEnumerator** aResult) {
+ RefPtr<JSStringEnumerator> result(this);
+ result.forget(aResult);
+ return NS_OK;
+}
+
+nsresult JSStringEnumerator::Next(JSContext* aCx,
+ JS::MutableHandleValue aResult) {
+ RootedDictionary<IteratorResult> result(aCx);
+
+ nsAutoString elem;
+ if (NS_FAILED(mEnumerator->GetNext(elem))) {
+ result.mDone = true;
+ } else {
+ result.mDone = false;
+
+ if (!ToJSValue(
+ aCx, elem,
+ JS::MutableHandleValue::fromMarkedLocation(&result.mValue))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ if (!ToJSValue(aCx, result, aResult)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(JSStringEnumerator, nsIJSEnumerator)
+
+//
+// nsStringEnumeratorBase
+//
+
+nsresult nsStringEnumeratorBase::GetNext(nsAString& aResult) {
+ nsAutoCString str;
+ MOZ_TRY(GetNext(str));
+
+ CopyUTF8toUTF16(str, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringEnumeratorBase::StringIterator(nsIJSEnumerator** aRetVal) {
+ auto result = MakeRefPtr<JSStringEnumerator>(this);
+ result.forget(aRetVal);
+ return NS_OK;
+}
+
+//
+// nsStringEnumerator
+//
+
+class nsStringEnumerator final : public nsSimpleEnumerator,
+ public nsIStringEnumerator,
+ public nsIUTF8StringEnumerator {
+ public:
+ nsStringEnumerator(const nsTArray<nsString>* aArray, bool aOwnsArray)
+ : mArray(aArray), mIndex(0), mOwnsArray(aOwnsArray), mIsUnicode(true) {}
+
+ nsStringEnumerator(const nsTArray<nsCString>* aArray, bool aOwnsArray)
+ : mCArray(aArray), mIndex(0), mOwnsArray(aOwnsArray), mIsUnicode(false) {}
+
+ nsStringEnumerator(const nsTArray<nsString>* aArray, nsISupports* aOwner)
+ : mArray(aArray),
+ mIndex(0),
+ mOwner(aOwner),
+ mOwnsArray(false),
+ mIsUnicode(true) {}
+
+ nsStringEnumerator(const nsTArray<nsCString>* aArray, nsISupports* aOwner)
+ : mCArray(aArray),
+ mIndex(0),
+ mOwner(aOwner),
+ mOwnsArray(false),
+ mIsUnicode(false) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+ NS_DECL_NSISTRINGENUMERATORBASE
+
+ // have to declare nsIStringEnumerator manually, because of
+ // overlapping method names
+ NS_IMETHOD GetNext(nsAString& aResult) override;
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ const nsID& DefaultInterface() override {
+ if (mIsUnicode) {
+ return NS_GET_IID(nsISupportsString);
+ }
+ return NS_GET_IID(nsISupportsCString);
+ }
+
+ private:
+ ~nsStringEnumerator() {
+ if (mOwnsArray) {
+ // const-casting is safe here, because the NS_New*
+ // constructors make sure mOwnsArray is consistent with
+ // the constness of the objects
+ if (mIsUnicode) {
+ delete const_cast<nsTArray<nsString>*>(mArray);
+ } else {
+ delete const_cast<nsTArray<nsCString>*>(mCArray);
+ }
+ }
+ }
+
+ union {
+ const nsTArray<nsString>* mArray;
+ const nsTArray<nsCString>* mCArray;
+ };
+
+ inline uint32_t Count() {
+ return mIsUnicode ? mArray->Length() : mCArray->Length();
+ }
+
+ uint32_t mIndex;
+
+ // the owner allows us to hold a strong reference to the object
+ // that owns the array. Having a non-null value in mOwner implies
+ // that mOwnsArray is false, because we rely on the real owner
+ // to release the array
+ nsCOMPtr<nsISupports> mOwner;
+ bool mOwnsArray;
+ bool mIsUnicode;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(nsStringEnumerator, nsSimpleEnumerator,
+ nsIStringEnumerator, nsIUTF8StringEnumerator)
+
+NS_IMETHODIMP
+nsStringEnumerator::HasMore(bool* aResult) {
+ *aResult = mIndex < Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringEnumerator::HasMoreElements(bool* aResult) { return HasMore(aResult); }
+
+NS_IMETHODIMP
+nsStringEnumerator::GetNext(nsISupports** aResult) {
+ if (mIndex >= mArray->Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mIsUnicode) {
+ nsSupportsString* stringImpl = new nsSupportsString();
+
+ stringImpl->SetData(mArray->ElementAt(mIndex++));
+ *aResult = stringImpl;
+ } else {
+ nsSupportsCString* cstringImpl = new nsSupportsCString();
+
+ cstringImpl->SetData(mCArray->ElementAt(mIndex++));
+ *aResult = cstringImpl;
+ }
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringEnumerator::GetNext(nsAString& aResult) {
+ if (NS_WARN_IF(mIndex >= Count())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mIsUnicode) {
+ aResult = mArray->ElementAt(mIndex++);
+ } else {
+ CopyUTF8toUTF16(mCArray->ElementAt(mIndex++), aResult);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringEnumerator::GetNext(nsACString& aResult) {
+ if (NS_WARN_IF(mIndex >= Count())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mIsUnicode) {
+ CopyUTF16toUTF8(mArray->ElementAt(mIndex++), aResult);
+ } else {
+ aResult = mCArray->ElementAt(mIndex++);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringEnumerator::StringIterator(nsIJSEnumerator** aRetVal) {
+ auto result = MakeRefPtr<JSStringEnumerator>(this);
+ result.forget(aRetVal);
+ return NS_OK;
+}
+
+template <class T>
+static inline nsresult StringEnumeratorTail(T** aResult) {
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+//
+// constructors
+//
+
+nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult,
+ const nsTArray<nsString>* aArray,
+ nsISupports* aOwner) {
+ if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = new nsStringEnumerator(aArray, aOwner);
+ return StringEnumeratorTail(aResult);
+}
+
+nsresult NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult,
+ const nsTArray<nsCString>* aArray,
+ nsISupports* aOwner) {
+ if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = new nsStringEnumerator(aArray, aOwner);
+ return StringEnumeratorTail(aResult);
+}
+
+nsresult NS_NewAdoptingStringEnumerator(nsIStringEnumerator** aResult,
+ nsTArray<nsString>* aArray) {
+ if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = new nsStringEnumerator(aArray, true);
+ return StringEnumeratorTail(aResult);
+}
+
+nsresult NS_NewAdoptingUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult,
+ nsTArray<nsCString>* aArray) {
+ if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = new nsStringEnumerator(aArray, true);
+ return StringEnumeratorTail(aResult);
+}
+
+// const ones internally just forward to the non-const equivalents
+nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult,
+ const nsTArray<nsString>* aArray) {
+ if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = new nsStringEnumerator(aArray, false);
+ return StringEnumeratorTail(aResult);
+}
+
+nsresult NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult,
+ const nsTArray<nsCString>* aArray) {
+ if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = new nsStringEnumerator(aArray, false);
+ return StringEnumeratorTail(aResult);
+}
diff --git a/xpcom/ds/nsStringEnumerator.h b/xpcom/ds/nsStringEnumerator.h
new file mode 100644
index 0000000000..0061227c34
--- /dev/null
+++ b/xpcom/ds/nsStringEnumerator.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStringEnumerator_h
+#define nsStringEnumerator_h
+
+#include "nsIStringEnumerator.h"
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsStringEnumeratorBase : public nsIStringEnumerator,
+ public nsIUTF8StringEnumerator {
+ public:
+ NS_DECL_NSISTRINGENUMERATORBASE
+
+ NS_IMETHOD GetNext(nsAString&) override;
+
+ using nsIUTF8StringEnumerator::GetNext;
+
+ protected:
+ virtual ~nsStringEnumeratorBase() = default;
+};
+
+// nsIStringEnumerator/nsIUTF8StringEnumerator implementations
+//
+// Currently all implementations support both interfaces. The
+// constructors below provide the most common interface for the given
+// type (i.e. nsIStringEnumerator for char16_t* strings, and so
+// forth) but any resulting enumerators can be queried to the other
+// type. Internally, the enumerators will hold onto the type that was
+// passed in and do conversion if GetNext() for the other type of
+// string is called.
+
+// There are a few different types of enumerators:
+
+//
+// These enumerators hold a pointer to the array. Be careful
+// because modifying the array may confuse the iterator, especially if
+// you insert or remove elements in the middle of the array.
+//
+
+// The non-adopting enumerator requires that the array sticks around
+// at least as long as the enumerator does. These are for constant
+// string arrays that the enumerator does not own, this could be used
+// in VERY specialized cases such as when the provider KNOWS that the
+// string enumerator will be consumed immediately, or will at least
+// outlast the array.
+// For example:
+//
+// nsTArray<nsCString> array;
+// array.AppendCString("abc");
+// array.AppendCString("def");
+// NS_NewStringEnumerator(&enumerator, &array, true);
+//
+// // call some internal method which iterates the enumerator
+// InternalMethod(enumerator);
+// NS_RELEASE(enumerator);
+//
+[[nodiscard]] nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult,
+ const nsTArray<nsString>* aArray,
+ nsISupports* aOwner);
+[[nodiscard]] nsresult NS_NewUTF8StringEnumerator(
+ nsIUTF8StringEnumerator** aResult, const nsTArray<nsCString>* aArray);
+
+[[nodiscard]] nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult,
+ const nsTArray<nsString>* aArray);
+
+// Adopting string enumerators assume ownership of the array and will
+// call |operator delete| on the array when the enumerator is destroyed
+// this is useful when the provider creates an array solely for the
+// purpose of creating the enumerator.
+// For example:
+//
+// nsTArray<nsCString>* array = new nsTArray<nsCString>;
+// array->AppendString("abcd");
+// NS_NewAdoptingStringEnumerator(&result, array);
+[[nodiscard]] nsresult NS_NewAdoptingStringEnumerator(
+ nsIStringEnumerator** aResult, nsTArray<nsString>* aArray);
+
+[[nodiscard]] nsresult NS_NewAdoptingUTF8StringEnumerator(
+ nsIUTF8StringEnumerator** aResult, nsTArray<nsCString>* aArray);
+
+// these versions take a refcounted "owner" which will be addreffed
+// when the enumerator is created, and destroyed when the enumerator
+// is released. This allows providers to give non-owning pointers to
+// ns*StringArray member variables without worrying about lifetime
+// issues
+// For example:
+//
+// nsresult MyClass::Enumerate(nsIUTF8StringEnumerator** aResult) {
+// mCategoryList->AppendString("abcd");
+// return NS_NewStringEnumerator(aResult, mCategoryList, this);
+// }
+//
+[[nodiscard]] nsresult NS_NewUTF8StringEnumerator(
+ nsIUTF8StringEnumerator** aResult, const nsTArray<nsCString>* aArray,
+ nsISupports* aOwner);
+
+#endif // defined nsStringEnumerator_h
diff --git a/xpcom/ds/nsSupportsPrimitives.cpp b/xpcom/ds/nsSupportsPrimitives.cpp
new file mode 100644
index 0000000000..1d0db73187
--- /dev/null
+++ b/xpcom/ds/nsSupportsPrimitives.cpp
@@ -0,0 +1,603 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSupportsPrimitives.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Sprintf.h"
+#include <algorithm>
+
+template <typename T>
+static char* DataToString(const char* aFormat, T aData) {
+ static const int size = 32;
+ char buf[size];
+
+ SprintfLiteral(buf, aFormat, aData);
+
+ return moz_xstrdup(buf);
+}
+
+/*****************************************************************************
+ * nsSupportsCString
+ *****************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsCString, nsISupportsCString, nsISupportsPrimitive)
+
+NS_IMETHODIMP
+nsSupportsCString::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_CSTRING;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsCString::GetData(nsACString& aData) {
+ aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsCString::ToString(char** aResult) {
+ *aResult = ToNewCString(mData, mozilla::fallible);
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsCString::SetData(const nsACString& aData) {
+ bool ok = mData.Assign(aData, mozilla::fallible);
+ if (!ok) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+/*****************************************************************************
+ * nsSupportsString
+ *****************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsString, nsISupportsString, nsISupportsPrimitive)
+
+NS_IMETHODIMP
+nsSupportsString::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_STRING;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsString::GetData(nsAString& aData) {
+ aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsString::ToString(char16_t** aResult) {
+ *aResult = ToNewUnicode(mData, mozilla::fallible);
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsString::SetData(const nsAString& aData) {
+ bool ok = mData.Assign(aData, mozilla::fallible);
+ if (!ok) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsPRBool, nsISupportsPRBool, nsISupportsPrimitive)
+
+nsSupportsPRBool::nsSupportsPRBool() : mData(false) {}
+
+NS_IMETHODIMP
+nsSupportsPRBool::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_PRBOOL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRBool::GetData(bool* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRBool::SetData(bool aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRBool::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = moz_xstrdup(mData ? "true" : "false");
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsPRUint8, nsISupportsPRUint8, nsISupportsPrimitive)
+
+nsSupportsPRUint8::nsSupportsPRUint8() : mData(0) {}
+
+NS_IMETHODIMP
+nsSupportsPRUint8::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_PRUINT8;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint8::GetData(uint8_t* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint8::SetData(uint8_t aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint8::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = DataToString("%u", static_cast<unsigned int>(mData));
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsPRUint16, nsISupportsPRUint16, nsISupportsPrimitive)
+
+nsSupportsPRUint16::nsSupportsPRUint16() : mData(0) {}
+
+NS_IMETHODIMP
+nsSupportsPRUint16::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_PRUINT16;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint16::GetData(uint16_t* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint16::SetData(uint16_t aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint16::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = DataToString("%u", static_cast<unsigned int>(mData));
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsPRUint32, nsISupportsPRUint32, nsISupportsPrimitive)
+
+nsSupportsPRUint32::nsSupportsPRUint32() : mData(0) {}
+
+NS_IMETHODIMP
+nsSupportsPRUint32::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_PRUINT32;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint32::GetData(uint32_t* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint32::SetData(uint32_t aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint32::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = DataToString("%u", mData);
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsPRUint64, nsISupportsPRUint64, nsISupportsPrimitive)
+
+nsSupportsPRUint64::nsSupportsPRUint64() : mData(0) {}
+
+NS_IMETHODIMP
+nsSupportsPRUint64::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_PRUINT64;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint64::GetData(uint64_t* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint64::SetData(uint64_t aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRUint64::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = DataToString("%llu", mData);
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsPRTime, nsISupportsPRTime, nsISupportsPrimitive)
+
+nsSupportsPRTime::nsSupportsPRTime() : mData(0) {}
+
+NS_IMETHODIMP
+nsSupportsPRTime::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_PRTIME;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRTime::GetData(PRTime* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRTime::SetData(PRTime aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRTime::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = DataToString("%" PRIu64, mData);
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsChar, nsISupportsChar, nsISupportsPrimitive)
+
+nsSupportsChar::nsSupportsChar() : mData(0) {}
+
+NS_IMETHODIMP
+nsSupportsChar::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_CHAR;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsChar::GetData(char* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsChar::SetData(char aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsChar::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = static_cast<char*>(moz_xmalloc(2 * sizeof(char)));
+ *aResult[0] = mData;
+ *aResult[1] = '\0';
+
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsPRInt16, nsISupportsPRInt16, nsISupportsPrimitive)
+
+nsSupportsPRInt16::nsSupportsPRInt16() : mData(0) {}
+
+NS_IMETHODIMP
+nsSupportsPRInt16::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_PRINT16;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRInt16::GetData(int16_t* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRInt16::SetData(int16_t aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRInt16::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = DataToString("%d", static_cast<int>(mData));
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsPRInt32, nsISupportsPRInt32, nsISupportsPrimitive)
+
+nsSupportsPRInt32::nsSupportsPRInt32() : mData(0) {}
+
+NS_IMETHODIMP
+nsSupportsPRInt32::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_PRINT32;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRInt32::GetData(int32_t* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRInt32::SetData(int32_t aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRInt32::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = DataToString("%d", mData);
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsPRInt64, nsISupportsPRInt64, nsISupportsPrimitive)
+
+nsSupportsPRInt64::nsSupportsPRInt64() : mData(0) {}
+
+NS_IMETHODIMP
+nsSupportsPRInt64::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_PRINT64;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRInt64::GetData(int64_t* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRInt64::SetData(int64_t aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsPRInt64::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = DataToString("%" PRId64, mData);
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsFloat, nsISupportsFloat, nsISupportsPrimitive)
+
+nsSupportsFloat::nsSupportsFloat() : mData(float(0.0)) {}
+
+NS_IMETHODIMP
+nsSupportsFloat::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_FLOAT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsFloat::GetData(float* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsFloat::SetData(float aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsFloat::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = DataToString("%f", static_cast<double>(mData));
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsDouble, nsISupportsDouble, nsISupportsPrimitive)
+
+nsSupportsDouble::nsSupportsDouble() : mData(double(0.0)) {}
+
+NS_IMETHODIMP
+nsSupportsDouble::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_DOUBLE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsDouble::GetData(double* aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsDouble::SetData(double aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsDouble::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+ *aResult = DataToString("%f", mData);
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsInterfacePointer, nsISupportsInterfacePointer,
+ nsISupportsPrimitive)
+
+nsSupportsInterfacePointer::nsSupportsInterfacePointer() : mIID(nullptr) {}
+
+nsSupportsInterfacePointer::~nsSupportsInterfacePointer() {
+ if (mIID) {
+ free(mIID);
+ }
+}
+
+NS_IMETHODIMP
+nsSupportsInterfacePointer::GetType(uint16_t* aType) {
+ NS_ASSERTION(aType, "Bad pointer");
+ *aType = TYPE_INTERFACE_POINTER;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsInterfacePointer::GetData(nsISupports** aData) {
+ NS_ASSERTION(aData, "Bad pointer");
+ *aData = mData;
+ NS_IF_ADDREF(*aData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsInterfacePointer::SetData(nsISupports* aData) {
+ mData = aData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsInterfacePointer::GetDataIID(nsID** aIID) {
+ NS_ASSERTION(aIID, "Bad pointer");
+
+ *aIID = mIID ? mIID->Clone() : nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsInterfacePointer::SetDataIID(const nsID* aIID) {
+ if (mIID) {
+ free(mIID);
+ }
+
+ mIID = aIID ? aIID->Clone() : nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsInterfacePointer::ToString(char** aResult) {
+ NS_ASSERTION(aResult, "Bad pointer");
+
+ // jband sez: think about asking nsIInterfaceInfoManager whether
+ // the interface has a known human-readable name
+ *aResult = moz_xstrdup("[interface pointer]");
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsSupportsDependentCString, nsISupportsCString,
+ nsISupportsPrimitive)
+
+nsSupportsDependentCString::nsSupportsDependentCString(const char* aStr)
+ : mData(aStr) {}
+
+NS_IMETHODIMP
+nsSupportsDependentCString::GetType(uint16_t* aType) {
+ if (NS_WARN_IF(!aType)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aType = TYPE_CSTRING;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsDependentCString::GetData(nsACString& aData) {
+ aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsDependentCString::ToString(char** aResult) {
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = ToNewCString(mData, mozilla::fallible);
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSupportsDependentCString::SetData(const nsACString& aData) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/xpcom/ds/nsSupportsPrimitives.h b/xpcom/ds/nsSupportsPrimitives.h
new file mode 100644
index 0000000000..28d768a135
--- /dev/null
+++ b/xpcom/ds/nsSupportsPrimitives.h
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSupportsPrimitives_h__
+#define nsSupportsPrimitives_h__
+
+#include "mozilla/Attributes.h"
+
+#include "nsISupportsPrimitives.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+/***************************************************************************/
+
+class nsSupportsCString final : public nsISupportsCString {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSCSTRING
+
+ nsSupportsCString() = default;
+
+ private:
+ ~nsSupportsCString() = default;
+
+ nsCString mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsString final : public nsISupportsString {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSSTRING
+
+ nsSupportsString() = default;
+
+ private:
+ ~nsSupportsString() = default;
+
+ nsString mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsPRBool final : public nsISupportsPRBool {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSPRBOOL
+
+ nsSupportsPRBool();
+
+ private:
+ ~nsSupportsPRBool() = default;
+
+ bool mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsPRUint8 final : public nsISupportsPRUint8 {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSPRUINT8
+
+ nsSupportsPRUint8();
+
+ private:
+ ~nsSupportsPRUint8() = default;
+
+ uint8_t mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsPRUint16 final : public nsISupportsPRUint16 {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSPRUINT16
+
+ nsSupportsPRUint16();
+
+ private:
+ ~nsSupportsPRUint16() = default;
+
+ uint16_t mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsPRUint32 final : public nsISupportsPRUint32 {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSPRUINT32
+
+ nsSupportsPRUint32();
+
+ private:
+ ~nsSupportsPRUint32() = default;
+
+ uint32_t mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsPRUint64 final : public nsISupportsPRUint64 {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSPRUINT64
+
+ nsSupportsPRUint64();
+
+ private:
+ ~nsSupportsPRUint64() = default;
+
+ uint64_t mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsPRTime final : public nsISupportsPRTime {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSPRTIME
+
+ nsSupportsPRTime();
+
+ private:
+ ~nsSupportsPRTime() = default;
+
+ PRTime mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsChar final : public nsISupportsChar {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSCHAR
+
+ nsSupportsChar();
+
+ private:
+ ~nsSupportsChar() = default;
+
+ char mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsPRInt16 final : public nsISupportsPRInt16 {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSPRINT16
+
+ nsSupportsPRInt16();
+
+ private:
+ ~nsSupportsPRInt16() = default;
+
+ int16_t mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsPRInt32 final : public nsISupportsPRInt32 {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSPRINT32
+
+ nsSupportsPRInt32();
+
+ private:
+ ~nsSupportsPRInt32() = default;
+
+ int32_t mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsPRInt64 final : public nsISupportsPRInt64 {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSPRINT64
+
+ nsSupportsPRInt64();
+
+ private:
+ ~nsSupportsPRInt64() = default;
+
+ int64_t mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsFloat final : public nsISupportsFloat {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSFLOAT
+
+ nsSupportsFloat();
+
+ private:
+ ~nsSupportsFloat() = default;
+
+ float mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsDouble final : public nsISupportsDouble {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSDOUBLE
+
+ nsSupportsDouble();
+
+ private:
+ ~nsSupportsDouble() = default;
+
+ double mData;
+};
+
+/***************************************************************************/
+
+class nsSupportsInterfacePointer final : public nsISupportsInterfacePointer {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSINTERFACEPOINTER
+
+ nsSupportsInterfacePointer();
+
+ private:
+ ~nsSupportsInterfacePointer();
+
+ nsCOMPtr<nsISupports> mData;
+ nsID* mIID;
+};
+
+/***************************************************************************/
+
+/**
+ * Wraps a static const char* buffer for use with nsISupportsCString
+ *
+ * Only use this class with static buffers, or arena-allocated buffers of
+ * permanent lifetime!
+ */
+class nsSupportsDependentCString final : public nsISupportsCString {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSCSTRING
+
+ explicit nsSupportsDependentCString(const char* aStr);
+
+ private:
+ ~nsSupportsDependentCString() = default;
+
+ nsDependentCString mData;
+};
+
+#endif /* nsSupportsPrimitives_h__ */
diff --git a/xpcom/ds/nsTArray-inl.h b/xpcom/ds/nsTArray-inl.h
new file mode 100644
index 0000000000..801d3fab01
--- /dev/null
+++ b/xpcom/ds/nsTArray-inl.h
@@ -0,0 +1,691 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTArray_h__
+# error "Don't include this file directly"
+#endif
+
+// NOTE: We don't use MOZ_COUNT_CTOR/MOZ_COUNT_DTOR to perform leak checking of
+// nsTArray_base objects intentionally for the following reasons:
+// * The leak logging isn't as useful as other types of logging, as
+// nsTArray_base is frequently relocated without invoking a constructor, such
+// as when stored within another nsTArray. This means that
+// XPCOM_MEM_LOG_CLASSES cannot be used to identify specific leaks of nsTArray
+// objects.
+// * The nsTArray type is layout compatible with the ThinVec crate with the
+// correct flags, and ThinVec does not currently perform leak logging.
+// This means that if a large number of arrays are transferred between Rust
+// and C++ code using ThinVec, for example within another ThinVec, they
+// will not be logged correctly and might appear as e.g. negative leaks.
+// * Leaks which have been found thanks to the leak logging added by this
+// type have often not been significant, and/or have needed to be
+// circumvented using some other mechanism. Most leaks found with this type
+// in them also include other types which will continue to be tracked.
+
+template <class Alloc, class RelocationStrategy>
+nsTArray_base<Alloc, RelocationStrategy>::nsTArray_base() : mHdr(EmptyHdr()) {}
+
+template <class Alloc, class RelocationStrategy>
+nsTArray_base<Alloc, RelocationStrategy>::~nsTArray_base() {
+ if (!HasEmptyHeader() && !UsesAutoArrayBuffer()) {
+ Alloc::Free(mHdr);
+ }
+}
+
+template <class Alloc, class RelocationStrategy>
+nsTArray_base<Alloc, RelocationStrategy>::nsTArray_base(const nsTArray_base&)
+ : mHdr(EmptyHdr()) {
+ // Actual copying happens through nsTArray_CopyEnabler, we just need to do the
+ // initialization of mHdr.
+}
+
+template <class Alloc, class RelocationStrategy>
+nsTArray_base<Alloc, RelocationStrategy>&
+nsTArray_base<Alloc, RelocationStrategy>::operator=(const nsTArray_base&) {
+ // Actual copying happens through nsTArray_CopyEnabler, so do nothing here (do
+ // not copy mHdr).
+ return *this;
+}
+
+template <class Alloc, class RelocationStrategy>
+const nsTArrayHeader*
+nsTArray_base<Alloc, RelocationStrategy>::GetAutoArrayBufferUnsafe(
+ size_t aElemAlign) const {
+ // Assuming |this| points to an nsAutoArray, we want to get a pointer to
+ // mAutoBuf. So just cast |this| to nsAutoArray* and read &mAutoBuf!
+
+ const void* autoBuf =
+ &reinterpret_cast<const AutoTArray<nsTArray<uint32_t>, 1>*>(this)
+ ->mAutoBuf;
+
+ // If we're on a 32-bit system and aElemAlign is 8, we need to adjust our
+ // pointer to take into account the extra alignment in the auto array.
+
+ static_assert(
+ sizeof(void*) != 4 || (MOZ_ALIGNOF(mozilla::AlignedElem<8>) == 8 &&
+ sizeof(AutoTArray<mozilla::AlignedElem<8>, 1>) ==
+ sizeof(void*) + sizeof(nsTArrayHeader) + 4 +
+ sizeof(mozilla::AlignedElem<8>)),
+ "auto array padding wasn't what we expected");
+
+ // We don't support alignments greater than 8 bytes.
+ MOZ_ASSERT(aElemAlign <= 4 || aElemAlign == 8, "unsupported alignment.");
+ if (sizeof(void*) == 4 && aElemAlign == 8) {
+ autoBuf = reinterpret_cast<const char*>(autoBuf) + 4;
+ }
+
+ return reinterpret_cast<const Header*>(autoBuf);
+}
+
+template <class Alloc, class RelocationStrategy>
+bool nsTArray_base<Alloc, RelocationStrategy>::UsesAutoArrayBuffer() const {
+ if (!mHdr->mIsAutoArray) {
+ return false;
+ }
+
+ // This is nuts. If we were sane, we'd pass aElemAlign as a parameter to
+ // this function. Unfortunately this function is called in nsTArray_base's
+ // destructor, at which point we don't know value_type's alignment.
+ //
+ // We'll fall on our face and return true when we should say false if
+ //
+ // * we're not using our auto buffer,
+ // * aElemAlign == 4, and
+ // * mHdr == GetAutoArrayBuffer(8).
+ //
+ // This could happen if |*this| lives on the heap and malloc allocated our
+ // buffer on the heap adjacent to |*this|.
+ //
+ // However, we can show that this can't happen. If |this| is an auto array
+ // (as we ensured at the beginning of the method), GetAutoArrayBuffer(8)
+ // always points to memory owned by |*this|, because (as we assert below)
+ //
+ // * GetAutoArrayBuffer(8) is at most 4 bytes past GetAutoArrayBuffer(4),
+ // and
+ // * sizeof(nsTArrayHeader) > 4.
+ //
+ // Since AutoTArray always contains an nsTArrayHeader,
+ // GetAutoArrayBuffer(8) will always point inside the auto array object,
+ // even if it doesn't point at the beginning of the header.
+ //
+ // Note that this means that we can't store elements with alignment 16 in an
+ // nsTArray, because GetAutoArrayBuffer(16) could lie outside the memory
+ // owned by this AutoTArray. We statically assert that value_type's
+ // alignment is 8 bytes or less in AutoTArray.
+
+ static_assert(sizeof(nsTArrayHeader) > 4, "see comment above");
+
+#ifdef DEBUG
+ ptrdiff_t diff = reinterpret_cast<const char*>(GetAutoArrayBuffer(8)) -
+ reinterpret_cast<const char*>(GetAutoArrayBuffer(4));
+ MOZ_ASSERT(diff >= 0 && diff <= 4,
+ "GetAutoArrayBuffer doesn't do what we expect.");
+#endif
+
+ return mHdr == GetAutoArrayBuffer(4) || mHdr == GetAutoArrayBuffer(8);
+}
+
+// defined in nsTArray.cpp
+bool IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity,
+ size_t aElemSize);
+
+template <class Alloc, class RelocationStrategy>
+template <typename ActualAlloc>
+typename ActualAlloc::ResultTypeProxy
+nsTArray_base<Alloc, RelocationStrategy>::ExtendCapacity(size_type aLength,
+ size_type aCount,
+ size_type aElemSize) {
+ mozilla::CheckedInt<size_type> newLength = aLength;
+ newLength += aCount;
+
+ if (!newLength.isValid()) {
+ return ActualAlloc::FailureResult();
+ }
+
+ return this->EnsureCapacity<ActualAlloc>(newLength.value(), aElemSize);
+}
+
+template <class Alloc, class RelocationStrategy>
+template <typename ActualAlloc>
+typename ActualAlloc::ResultTypeProxy
+nsTArray_base<Alloc, RelocationStrategy>::EnsureCapacity(size_type aCapacity,
+ size_type aElemSize) {
+ // This should be the most common case so test this first
+ if (aCapacity <= mHdr->mCapacity) {
+ return ActualAlloc::SuccessResult();
+ }
+
+ // If the requested memory allocation exceeds size_type(-1)/2, then
+ // our doubling algorithm may not be able to allocate it.
+ // Additionally, if it exceeds uint32_t(-1) then we couldn't fit in the
+ // Header::mCapacity member. Just bail out in cases like that. We don't want
+ // to be allocating 2 GB+ arrays anyway.
+ if (!IsTwiceTheRequiredBytesRepresentableAsUint32(aCapacity, aElemSize)) {
+ ActualAlloc::SizeTooBig((size_t)aCapacity * aElemSize);
+ return ActualAlloc::FailureResult();
+ }
+
+ size_t reqSize = sizeof(Header) + aCapacity * aElemSize;
+
+ if (HasEmptyHeader()) {
+ // Malloc() new data
+ Header* header = static_cast<Header*>(ActualAlloc::Malloc(reqSize));
+ if (!header) {
+ return ActualAlloc::FailureResult();
+ }
+ header->mLength = 0;
+ header->mCapacity = aCapacity;
+ header->mIsAutoArray = 0;
+ mHdr = header;
+
+ return ActualAlloc::SuccessResult();
+ }
+
+ // We increase our capacity so that the allocated buffer grows exponentially,
+ // which gives us amortized O(1) appending. Below the threshold, we use
+ // powers-of-two. Above the threshold, we grow by at least 1.125, rounding up
+ // to the nearest MiB.
+ const size_t slowGrowthThreshold = 8 * 1024 * 1024;
+
+ size_t bytesToAlloc;
+ if (reqSize >= slowGrowthThreshold) {
+ size_t currSize = sizeof(Header) + Capacity() * aElemSize;
+ size_t minNewSize = currSize + (currSize >> 3); // multiply by 1.125
+ bytesToAlloc = reqSize > minNewSize ? reqSize : minNewSize;
+
+ // Round up to the next multiple of MiB.
+ const size_t MiB = 1 << 20;
+ bytesToAlloc = MiB * ((bytesToAlloc + MiB - 1) / MiB);
+ } else {
+ // Round up to the next power of two.
+ bytesToAlloc = mozilla::RoundUpPow2(reqSize);
+ }
+
+ Header* header;
+ if (UsesAutoArrayBuffer() || !RelocationStrategy::allowRealloc) {
+ // Malloc() and copy
+ header = static_cast<Header*>(ActualAlloc::Malloc(bytesToAlloc));
+ if (!header) {
+ return ActualAlloc::FailureResult();
+ }
+
+ RelocationStrategy::RelocateNonOverlappingRegionWithHeader(
+ header, mHdr, Length(), aElemSize);
+
+ if (!UsesAutoArrayBuffer()) {
+ ActualAlloc::Free(mHdr);
+ }
+ } else {
+ // Realloc() existing data
+ header = static_cast<Header*>(ActualAlloc::Realloc(mHdr, bytesToAlloc));
+ if (!header) {
+ return ActualAlloc::FailureResult();
+ }
+ }
+
+ // How many elements can we fit in bytesToAlloc?
+ size_t newCapacity = (bytesToAlloc - sizeof(Header)) / aElemSize;
+ MOZ_ASSERT(newCapacity >= aCapacity, "Didn't enlarge the array enough!");
+ header->mCapacity = newCapacity;
+
+ mHdr = header;
+
+ return ActualAlloc::SuccessResult();
+}
+
+// We don't need use Alloc template parameter specified here because failure to
+// shrink the capacity will leave the array unchanged.
+template <class Alloc, class RelocationStrategy>
+void nsTArray_base<Alloc, RelocationStrategy>::ShrinkCapacity(
+ size_type aElemSize, size_t aElemAlign) {
+ if (HasEmptyHeader() || UsesAutoArrayBuffer()) {
+ return;
+ }
+
+ if (mHdr->mLength >= mHdr->mCapacity) { // should never be greater than...
+ return;
+ }
+
+ size_type length = Length();
+
+ if (IsAutoArray() && GetAutoArrayBuffer(aElemAlign)->mCapacity >= length) {
+ Header* header = GetAutoArrayBuffer(aElemAlign);
+
+ // Move the data, but don't copy the header to avoid overwriting mCapacity.
+ header->mLength = length;
+ RelocationStrategy::RelocateNonOverlappingRegion(header + 1, mHdr + 1,
+ length, aElemSize);
+
+ nsTArrayFallibleAllocator::Free(mHdr);
+ mHdr = header;
+ return;
+ }
+
+ if (length == 0) {
+ MOZ_ASSERT(!IsAutoArray(), "autoarray should have fit 0 elements");
+ nsTArrayFallibleAllocator::Free(mHdr);
+ mHdr = EmptyHdr();
+ return;
+ }
+
+ size_type newSize = sizeof(Header) + length * aElemSize;
+
+ Header* newHeader;
+ if (!RelocationStrategy::allowRealloc) {
+ // Malloc() and copy.
+ newHeader =
+ static_cast<Header*>(nsTArrayFallibleAllocator::Malloc(newSize));
+ if (!newHeader) {
+ return;
+ }
+
+ RelocationStrategy::RelocateNonOverlappingRegionWithHeader(
+ newHeader, mHdr, Length(), aElemSize);
+
+ nsTArrayFallibleAllocator::Free(mHdr);
+ } else {
+ // Realloc() existing data.
+ newHeader =
+ static_cast<Header*>(nsTArrayFallibleAllocator::Realloc(mHdr, newSize));
+ if (!newHeader) {
+ return;
+ }
+ }
+
+ mHdr = newHeader;
+ mHdr->mCapacity = length;
+}
+
+template <class Alloc, class RelocationStrategy>
+void nsTArray_base<Alloc, RelocationStrategy>::ShrinkCapacityToZero(
+ size_type aElemSize, size_t aElemAlign) {
+ MOZ_ASSERT(mHdr->mLength == 0);
+
+ if (HasEmptyHeader() || UsesAutoArrayBuffer()) {
+ return;
+ }
+
+ const bool isAutoArray = IsAutoArray();
+
+ nsTArrayFallibleAllocator::Free(mHdr);
+
+ if (isAutoArray) {
+ mHdr = GetAutoArrayBufferUnsafe(aElemAlign);
+ mHdr->mLength = 0;
+ } else {
+ mHdr = EmptyHdr();
+ }
+}
+
+template <class Alloc, class RelocationStrategy>
+template <typename ActualAlloc>
+void nsTArray_base<Alloc, RelocationStrategy>::ShiftData(index_type aStart,
+ size_type aOldLen,
+ size_type aNewLen,
+ size_type aElemSize,
+ size_t aElemAlign) {
+ if (aOldLen == aNewLen) {
+ return;
+ }
+
+ // Determine how many elements need to be shifted
+ size_type num = mHdr->mLength - (aStart + aOldLen);
+
+ // Compute the resulting length of the array
+ mHdr->mLength += aNewLen - aOldLen;
+ if (mHdr->mLength == 0) {
+ ShrinkCapacityToZero(aElemSize, aElemAlign);
+ } else {
+ // Maybe nothing needs to be shifted
+ if (num == 0) {
+ return;
+ }
+ // Perform shift (change units to bytes first)
+ aStart *= aElemSize;
+ aNewLen *= aElemSize;
+ aOldLen *= aElemSize;
+ char* baseAddr = reinterpret_cast<char*>(mHdr + 1) + aStart;
+ RelocationStrategy::RelocateOverlappingRegion(
+ baseAddr + aNewLen, baseAddr + aOldLen, num, aElemSize);
+ }
+}
+
+template <class Alloc, class RelocationStrategy>
+template <typename ActualAlloc>
+void nsTArray_base<Alloc, RelocationStrategy>::SwapFromEnd(index_type aStart,
+ size_type aCount,
+ size_type aElemSize,
+ size_t aElemAlign) {
+ // This method is part of the implementation of
+ // nsTArray::SwapRemoveElement{s,}At. For more information, read the
+ // documentation on that method.
+ if (aCount == 0) {
+ return;
+ }
+
+ // We are going to be removing aCount elements. Update our length to point to
+ // the new end of the array.
+ size_type oldLength = mHdr->mLength;
+ mHdr->mLength -= aCount;
+
+ if (mHdr->mLength == 0) {
+ // If we have no elements remaining in the array, we can free our buffer.
+ ShrinkCapacityToZero(aElemSize, aElemAlign);
+ return;
+ }
+
+ // Determine how many elements we need to move from the end of the array into
+ // the now-removed section. This will either be the number of elements which
+ // were removed (if there are more elements in the tail of the array), or the
+ // entire tail of the array, whichever is smaller.
+ size_type relocCount = std::min(aCount, mHdr->mLength - aStart);
+ if (relocCount == 0) {
+ return;
+ }
+
+ // Move the elements which are now stranded after the end of the array back
+ // into the now-vacated memory.
+ index_type sourceBytes = (oldLength - relocCount) * aElemSize;
+ index_type destBytes = aStart * aElemSize;
+
+ // Perform the final copy. This is guaranteed to be a non-overlapping copy
+ // as our source contains only still-valid entries, and the destination
+ // contains only invalid entries which need to be overwritten.
+ MOZ_ASSERT(sourceBytes >= destBytes,
+ "The source should be after the destination.");
+ MOZ_ASSERT(sourceBytes - destBytes >= relocCount * aElemSize,
+ "The range should be nonoverlapping");
+
+ char* baseAddr = reinterpret_cast<char*>(mHdr + 1);
+ RelocationStrategy::RelocateNonOverlappingRegion(
+ baseAddr + destBytes, baseAddr + sourceBytes, relocCount, aElemSize);
+}
+
+template <class Alloc, class RelocationStrategy>
+template <typename ActualAlloc>
+typename ActualAlloc::ResultTypeProxy
+nsTArray_base<Alloc, RelocationStrategy>::InsertSlotsAt(index_type aIndex,
+ size_type aCount,
+ size_type aElemSize,
+ size_t aElemAlign) {
+ if (MOZ_UNLIKELY(aIndex > Length())) {
+ mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length());
+ }
+
+ if (!ActualAlloc::Successful(
+ this->ExtendCapacity<ActualAlloc>(Length(), aCount, aElemSize))) {
+ return ActualAlloc::FailureResult();
+ }
+
+ // Move the existing elements as needed. Note that this will
+ // change our mLength, so no need to call IncrementLength.
+ ShiftData<ActualAlloc>(aIndex, 0, aCount, aElemSize, aElemAlign);
+
+ return ActualAlloc::SuccessResult();
+}
+
+// nsTArray_base::IsAutoArrayRestorer is an RAII class which takes
+// |nsTArray_base &array| in its constructor. When it's destructed, it ensures
+// that
+//
+// * array.mIsAutoArray has the same value as it did when we started, and
+// * if array has an auto buffer and mHdr would otherwise point to
+// sEmptyTArrayHeader, array.mHdr points to array's auto buffer.
+
+template <class Alloc, class RelocationStrategy>
+nsTArray_base<Alloc, RelocationStrategy>::IsAutoArrayRestorer::
+ IsAutoArrayRestorer(nsTArray_base<Alloc, RelocationStrategy>& aArray,
+ size_t aElemAlign)
+ : mArray(aArray), mElemAlign(aElemAlign), mIsAuto(aArray.IsAutoArray()) {}
+
+template <class Alloc, class RelocationStrategy>
+nsTArray_base<Alloc,
+ RelocationStrategy>::IsAutoArrayRestorer::~IsAutoArrayRestorer() {
+ // Careful: We don't want to set mIsAutoArray = 1 on sEmptyTArrayHeader.
+ if (mIsAuto && mArray.HasEmptyHeader()) {
+ // Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts
+ // that mHdr->mIsAutoArray is true, which surely isn't the case here.
+ mArray.mHdr = mArray.GetAutoArrayBufferUnsafe(mElemAlign);
+ mArray.mHdr->mLength = 0;
+ } else if (!mArray.HasEmptyHeader()) {
+ mArray.mHdr->mIsAutoArray = mIsAuto;
+ }
+}
+
+template <class Alloc, class RelocationStrategy>
+template <typename ActualAlloc, class Allocator>
+typename ActualAlloc::ResultTypeProxy
+nsTArray_base<Alloc, RelocationStrategy>::SwapArrayElements(
+ nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize,
+ size_t aElemAlign) {
+ // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyTArrayHeader even if we
+ // have an auto buffer. We need to point mHdr back to our auto buffer before
+ // we return, otherwise we'll forget that we have an auto buffer at all!
+ // IsAutoArrayRestorer takes care of this for us.
+
+ IsAutoArrayRestorer ourAutoRestorer(*this, aElemAlign);
+ typename nsTArray_base<Allocator, RelocationStrategy>::IsAutoArrayRestorer
+ otherAutoRestorer(aOther, aElemAlign);
+
+ // If neither array uses an auto buffer which is big enough to store the
+ // other array's elements, then ensure that both arrays use malloc'ed storage
+ // and swap their mHdr pointers.
+ if ((!UsesAutoArrayBuffer() || Capacity() < aOther.Length()) &&
+ (!aOther.UsesAutoArrayBuffer() || aOther.Capacity() < Length())) {
+ if (!EnsureNotUsingAutoArrayBuffer<ActualAlloc>(aElemSize) ||
+ !aOther.template EnsureNotUsingAutoArrayBuffer<ActualAlloc>(
+ aElemSize)) {
+ return ActualAlloc::FailureResult();
+ }
+
+ Header* temp = mHdr;
+ mHdr = aOther.mHdr;
+ aOther.mHdr = temp;
+
+ return ActualAlloc::SuccessResult();
+ }
+
+ // Swap the two arrays by copying, since at least one is using an auto
+ // buffer which is large enough to hold all of the aOther's elements. We'll
+ // copy the shorter array into temporary storage.
+ //
+ // (We could do better than this in some circumstances. Suppose we're
+ // swapping arrays X and Y. X has space for 2 elements in its auto buffer,
+ // but currently has length 4, so it's using malloc'ed storage. Y has length
+ // 2. When we swap X and Y, we don't need to use a temporary buffer; we can
+ // write Y straight into X's auto buffer, write X's malloc'ed buffer on top
+ // of Y, and then switch X to using its auto buffer.)
+
+ if (!ActualAlloc::Successful(
+ EnsureCapacity<ActualAlloc>(aOther.Length(), aElemSize)) ||
+ !Allocator::Successful(
+ aOther.template EnsureCapacity<Allocator>(Length(), aElemSize))) {
+ return ActualAlloc::FailureResult();
+ }
+
+ // The EnsureCapacity calls above shouldn't have caused *both* arrays to
+ // switch from their auto buffers to malloc'ed space.
+ MOZ_ASSERT(UsesAutoArrayBuffer() || aOther.UsesAutoArrayBuffer(),
+ "One of the arrays should be using its auto buffer.");
+
+ size_type smallerLength = XPCOM_MIN(Length(), aOther.Length());
+ size_type largerLength = XPCOM_MAX(Length(), aOther.Length());
+ void* smallerElements;
+ void* largerElements;
+ if (Length() <= aOther.Length()) {
+ smallerElements = Hdr() + 1;
+ largerElements = aOther.Hdr() + 1;
+ } else {
+ smallerElements = aOther.Hdr() + 1;
+ largerElements = Hdr() + 1;
+ }
+
+ // Allocate temporary storage for the smaller of the two arrays. We want to
+ // allocate this space on the stack, if it's not too large. Sounds like a
+ // job for AutoTArray! (One of the two arrays we're swapping is using an
+ // auto buffer, so we're likely not allocating a lot of space here. But one
+ // could, in theory, allocate a huge AutoTArray on the heap.)
+ AutoTArray<uint8_t, 64 * sizeof(void*)> temp;
+ if (!ActualAlloc::Successful(temp.template EnsureCapacity<ActualAlloc>(
+ smallerLength * aElemSize, sizeof(uint8_t)))) {
+ return ActualAlloc::FailureResult();
+ }
+
+ RelocationStrategy::RelocateNonOverlappingRegion(
+ temp.Elements(), smallerElements, smallerLength, aElemSize);
+ RelocationStrategy::RelocateNonOverlappingRegion(
+ smallerElements, largerElements, largerLength, aElemSize);
+ RelocationStrategy::RelocateNonOverlappingRegion(
+ largerElements, temp.Elements(), smallerLength, aElemSize);
+
+ // Swap the arrays' lengths.
+ MOZ_ASSERT((aOther.Length() == 0 || !HasEmptyHeader()) &&
+ (Length() == 0 || !aOther.HasEmptyHeader()),
+ "Don't set sEmptyTArrayHeader's length.");
+ size_type tempLength = Length();
+
+ // Avoid writing to EmptyHdr, since it can trigger false
+ // positives with TSan.
+ if (!HasEmptyHeader()) {
+ mHdr->mLength = aOther.Length();
+ }
+ if (!aOther.HasEmptyHeader()) {
+ aOther.mHdr->mLength = tempLength;
+ }
+
+ return ActualAlloc::SuccessResult();
+}
+
+template <class Alloc, class RelocationStrategy>
+template <class Allocator>
+void nsTArray_base<Alloc, RelocationStrategy>::MoveInit(
+ nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize,
+ size_t aElemAlign) {
+ // This method is similar to SwapArrayElements, but specialized for the case
+ // where the target array is empty with no allocated heap storage. It is
+ // provided and used to simplify template instantiation and enable better code
+ // generation.
+
+ MOZ_ASSERT(Length() == 0);
+ MOZ_ASSERT(Capacity() == 0 || (IsAutoArray() && UsesAutoArrayBuffer()));
+
+ // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyTArrayHeader even if we
+ // have an auto buffer. We need to point mHdr back to our auto buffer before
+ // we return, otherwise we'll forget that we have an auto buffer at all!
+ // IsAutoArrayRestorer takes care of this for us.
+
+ IsAutoArrayRestorer ourAutoRestorer(*this, aElemAlign);
+ typename nsTArray_base<Allocator, RelocationStrategy>::IsAutoArrayRestorer
+ otherAutoRestorer(aOther, aElemAlign);
+
+ // If neither array uses an auto buffer which is big enough to store the
+ // other array's elements, then ensure that both arrays use malloc'ed storage
+ // and swap their mHdr pointers.
+ if ((!IsAutoArray() || Capacity() < aOther.Length()) &&
+ !aOther.UsesAutoArrayBuffer()) {
+ mHdr = aOther.mHdr;
+
+ aOther.mHdr = EmptyHdr();
+
+ return;
+ }
+
+ // Move the data by copying, since at least one has an auto
+ // buffer which is large enough to hold all of the aOther's elements.
+
+ EnsureCapacity<nsTArrayInfallibleAllocator>(aOther.Length(), aElemSize);
+
+ // The EnsureCapacity calls above shouldn't have caused *both* arrays to
+ // switch from their auto buffers to malloc'ed space.
+ MOZ_ASSERT(UsesAutoArrayBuffer() || aOther.UsesAutoArrayBuffer(),
+ "One of the arrays should be using its auto buffer.");
+
+ RelocationStrategy::RelocateNonOverlappingRegion(Hdr() + 1, aOther.Hdr() + 1,
+ aOther.Length(), aElemSize);
+
+ // Swap the arrays' lengths.
+ MOZ_ASSERT((aOther.Length() == 0 || !HasEmptyHeader()) &&
+ (Length() == 0 || !aOther.HasEmptyHeader()),
+ "Don't set sEmptyTArrayHeader's length.");
+
+ // Avoid writing to EmptyHdr, since it can trigger false
+ // positives with TSan.
+ if (!HasEmptyHeader()) {
+ mHdr->mLength = aOther.Length();
+ }
+ if (!aOther.HasEmptyHeader()) {
+ aOther.mHdr->mLength = 0;
+ }
+}
+
+template <class Alloc, class RelocationStrategy>
+template <class Allocator>
+void nsTArray_base<Alloc, RelocationStrategy>::MoveConstructNonAutoArray(
+ nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize,
+ size_t aElemAlign) {
+ // We know that we are not an (Copyable)AutoTArray and we know that we are
+ // empty, so don't use SwapArrayElements which doesn't know either of these
+ // facts and is very complex.
+
+ if (aOther.IsEmpty()) {
+ return;
+ }
+
+ // aOther might be an (Copyable)AutoTArray though, and it might use its inline
+ // buffer.
+ const bool otherUsesAutoArrayBuffer = aOther.UsesAutoArrayBuffer();
+ if (otherUsesAutoArrayBuffer) {
+ // Use nsTArrayInfallibleAllocator regardless of Alloc because this is
+ // called from a move constructor, which cannot report an error to the
+ // caller.
+ aOther.template EnsureNotUsingAutoArrayBuffer<nsTArrayInfallibleAllocator>(
+ aElemSize);
+ }
+
+ const bool otherIsAuto = otherUsesAutoArrayBuffer || aOther.IsAutoArray();
+ mHdr = aOther.mHdr;
+ // We might write to mHdr, so ensure it's not the static empty header. aOther
+ // shouldn't have been empty if we get here anyway.
+ MOZ_ASSERT(!HasEmptyHeader());
+
+ if (otherIsAuto) {
+ mHdr->mIsAutoArray = false;
+ aOther.mHdr = aOther.GetAutoArrayBufferUnsafe(aElemAlign);
+ aOther.mHdr->mLength = 0;
+ } else {
+ aOther.mHdr = aOther.EmptyHdr();
+ }
+}
+
+template <class Alloc, class RelocationStrategy>
+template <typename ActualAlloc>
+bool nsTArray_base<Alloc, RelocationStrategy>::EnsureNotUsingAutoArrayBuffer(
+ size_type aElemSize) {
+ if (UsesAutoArrayBuffer()) {
+ // If you call this on a 0-length array, we'll set that array's mHdr to
+ // sEmptyTArrayHeader, in flagrant violation of the AutoTArray invariants.
+ // It's up to you to set it back! (If you don't, the AutoTArray will
+ // forget that it has an auto buffer.)
+ if (Length() == 0) {
+ mHdr = EmptyHdr();
+ return true;
+ }
+
+ size_type size = sizeof(Header) + Length() * aElemSize;
+
+ Header* header = static_cast<Header*>(ActualAlloc::Malloc(size));
+ if (!header) {
+ return false;
+ }
+
+ RelocationStrategy::RelocateNonOverlappingRegionWithHeader(
+ header, mHdr, Length(), aElemSize);
+ header->mCapacity = Length();
+ mHdr = header;
+ }
+
+ return true;
+}
diff --git a/xpcom/ds/nsTArray.cpp b/xpcom/ds/nsTArray.cpp
new file mode 100644
index 0000000000..50326fb331
--- /dev/null
+++ b/xpcom/ds/nsTArray.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+#include "nsTArray.h"
+#include "nsXPCOM.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsDebug.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/IntegerPrintfMacros.h"
+
+// Ensure this is sufficiently aligned so that Elements() and co don't create
+// unaligned pointers, or slices with unaligned pointers for empty arrays, see
+// https://github.com/servo/servo/issues/22613.
+alignas(8) const nsTArrayHeader sEmptyTArrayHeader = {0, 0, 0};
+
+bool IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity,
+ size_t aElemSize) {
+ using mozilla::CheckedUint32;
+ return ((CheckedUint32(aCapacity) * aElemSize) * 2).isValid();
+}
+
+void ::detail::SetCycleCollectionArrayFlag(uint32_t& aFlags) {
+ aFlags |= CycleCollectionEdgeNameArrayFlag;
+}
diff --git a/xpcom/ds/nsTArray.h b/xpcom/ds/nsTArray.h
new file mode 100644
index 0000000000..ed8c05943c
--- /dev/null
+++ b/xpcom/ds/nsTArray.h
@@ -0,0 +1,3345 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTArray_h__
+#define nsTArray_h__
+
+#include <string.h>
+
+#include <algorithm>
+#include <functional>
+#include <initializer_list>
+#include <iterator>
+#include <new>
+#include <ostream>
+#include <type_traits>
+#include <utility>
+
+#include "mozilla/Alignment.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/FunctionTypeTraits.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/Span.h"
+#include "mozilla/fallible.h"
+#include "mozilla/mozalloc.h"
+#include "nsAlgorithm.h"
+#include "nsDebug.h"
+#include "nsISupports.h"
+#include "nsQuickSort.h"
+#include "nsRegionFwd.h"
+#include "nsTArrayForwardDeclare.h"
+
+namespace JS {
+template <class T>
+class Heap;
+} /* namespace JS */
+
+class nsCycleCollectionTraversalCallback;
+class nsRegion;
+
+namespace mozilla::a11y {
+class BatchData;
+}
+
+namespace mozilla {
+namespace layers {
+class Animation;
+class FrameStats;
+struct PropertyAnimationGroup;
+struct TileClient;
+} // namespace layers
+} // namespace mozilla
+
+namespace mozilla {
+struct SerializedStructuredCloneBuffer;
+class SourceBufferTask;
+} // namespace mozilla
+
+namespace mozilla::dom::binding_detail {
+template <typename, typename>
+class RecordEntry;
+}
+
+namespace mozilla::dom::ipc {
+class StructuredCloneData;
+} // namespace mozilla::dom::ipc
+
+namespace mozilla::dom {
+class ClonedMessageData;
+class MessageData;
+class MessagePortIdentifier;
+struct MozPluginParameter;
+template <typename T>
+struct Nullable;
+class OwningFileOrDirectory;
+class OwningStringOrBooleanOrObject;
+class OwningUTF8StringOrDouble;
+class Pref;
+class RefMessageData;
+class ResponsiveImageCandidate;
+class ServiceWorkerRegistrationData;
+namespace indexedDB {
+class SerializedStructuredCloneReadInfo;
+class ObjectStoreCursorResponse;
+class IndexCursorResponse;
+} // namespace indexedDB
+} // namespace mozilla::dom
+
+namespace mozilla::ipc {
+class ContentSecurityPolicy;
+template <class T>
+class Endpoint;
+} // namespace mozilla::ipc
+
+class JSStructuredCloneData;
+
+template <class T>
+class RefPtr;
+
+//
+// nsTArray<E> is a resizable array class, like std::vector.
+//
+// Unlike std::vector, which follows C++'s construction/destruction rules,
+// By default, nsTArray assumes that instances of E can be relocated safely
+// using memory utils (memcpy/memmove).
+//
+// The public classes defined in this header are
+//
+// nsTArray<E>,
+// CopyableTArray<E>,
+// FallibleTArray<E>,
+// AutoTArray<E, N>,
+// CopyableAutoTArray<E, N>
+//
+// nsTArray, CopyableTArray, AutoTArray and CopyableAutoTArray are infallible by
+// default. To opt-in to fallible behaviour, use the `mozilla::fallible`
+// parameter and check the return value.
+//
+// CopyableTArray and CopyableAutoTArray< are copy-constructible and
+// copy-assignable. Use these only when syntactically necessary to avoid implcit
+// unintentional copies. nsTArray/AutoTArray can be conveniently copied using
+// the Clone() member function. Consider using std::move where possible.
+//
+// If you just want to declare the nsTArray types (e.g., if you're in a header
+// file and don't need the full nsTArray definitions) consider including
+// nsTArrayForwardDeclare.h instead of nsTArray.h.
+//
+// The template parameter E specifies the type of the elements and has the
+// following requirements:
+//
+// E MUST be safely memmove()'able.
+// E MUST define a copy-constructor.
+// E MAY define operator< for sorting.
+// E MAY define operator== for searching.
+//
+// (Note that the memmove requirement may be relaxed for certain types - see
+// nsTArray_RelocationStrategy below.)
+//
+// There is a public type value_type defined as E within each array class, and
+// we reference the type under this name below.
+//
+// For member functions taking a Comparator instance, Comparator must be either
+// a functor with a tri-state comparison function with a signature compatible to
+//
+// /** @return negative iff a < b, 0 iff a == b, positive iff a > b */
+// int (const value_type& a, const value_type& b);
+//
+// or a class defining member functions with signatures compatible to:
+//
+// class Comparator {
+// public:
+// /** @return True if the elements are equals; false otherwise. */
+// bool Equals(const value_type& a, const value_type& b) const;
+//
+// /** @return True if (a < b); false otherwise. */
+// bool LessThan(const value_type& a, const value_type& b) const;
+// };
+//
+// The Equals member function is used for searching, and the LessThan member
+// function is used for searching and sorting. Note that some member functions,
+// e.g. Compare, are templates where a different type Item can be used for the
+// element to compare to. In that case, the signatures must be compatible to
+// allow those comparisons, but the details are not documented here.
+//
+
+//
+// nsTArrayFallibleResult and nsTArrayInfallibleResult types are proxy types
+// which are used because you cannot use a templated type which is bound to
+// void as an argument to a void function. In order to work around that, we
+// encode either a void or a boolean inside these proxy objects, and pass them
+// to the aforementioned function instead, and then use the type information to
+// decide what to do in the function.
+//
+// Note that public nsTArray methods should never return a proxy type. Such
+// types are only meant to be used in the internal nsTArray helper methods.
+// Public methods returning non-proxy types cannot be called from other
+// nsTArray members.
+//
+struct nsTArrayFallibleResult {
+ // Note: allows implicit conversions from and to bool
+ MOZ_IMPLICIT constexpr nsTArrayFallibleResult(bool aResult)
+ : mResult(aResult) {}
+
+ MOZ_IMPLICIT constexpr operator bool() { return mResult; }
+
+ private:
+ bool mResult;
+};
+
+struct nsTArrayInfallibleResult {};
+
+//
+// nsTArray*Allocators must all use the same |free()|, to allow swap()'ing
+// between fallible and infallible variants.
+//
+
+struct nsTArrayFallibleAllocatorBase {
+ typedef bool ResultType;
+ typedef nsTArrayFallibleResult ResultTypeProxy;
+
+ static constexpr ResultType Result(ResultTypeProxy aResult) {
+ return aResult;
+ }
+ static constexpr bool Successful(ResultTypeProxy aResult) { return aResult; }
+ static constexpr ResultTypeProxy SuccessResult() { return true; }
+ static constexpr ResultTypeProxy FailureResult() { return false; }
+ static constexpr ResultType ConvertBoolToResultType(bool aValue) {
+ return aValue;
+ }
+};
+
+struct nsTArrayInfallibleAllocatorBase {
+ typedef void ResultType;
+ typedef nsTArrayInfallibleResult ResultTypeProxy;
+
+ static constexpr ResultType Result(ResultTypeProxy aResult) {}
+ static constexpr bool Successful(ResultTypeProxy) { return true; }
+ static constexpr ResultTypeProxy SuccessResult() { return ResultTypeProxy(); }
+
+ [[noreturn]] static ResultTypeProxy FailureResult() {
+ MOZ_CRASH("Infallible nsTArray should never fail");
+ }
+
+ template <typename T>
+ static constexpr ResultType ConvertBoolToResultType(T aValue) {
+ if (!aValue) {
+ MOZ_CRASH("infallible nsTArray should never convert false to ResultType");
+ }
+ }
+
+ template <typename T>
+ static constexpr ResultType ConvertBoolToResultType(
+ const mozilla::NotNull<T>& aValue) {}
+};
+
+struct nsTArrayFallibleAllocator : nsTArrayFallibleAllocatorBase {
+ static void* Malloc(size_t aSize) { return malloc(aSize); }
+ static void* Realloc(void* aPtr, size_t aSize) {
+ return realloc(aPtr, aSize);
+ }
+
+ static void Free(void* aPtr) { free(aPtr); }
+ static void SizeTooBig(size_t) {}
+};
+
+struct nsTArrayInfallibleAllocator : nsTArrayInfallibleAllocatorBase {
+ static void* Malloc(size_t aSize) MOZ_NONNULL_RETURN {
+ return moz_xmalloc(aSize);
+ }
+ static void* Realloc(void* aPtr, size_t aSize) MOZ_NONNULL_RETURN {
+ return moz_xrealloc(aPtr, aSize);
+ }
+
+ static void Free(void* aPtr) { free(aPtr); }
+ static void SizeTooBig(size_t aSize) { NS_ABORT_OOM(aSize); }
+};
+
+// nsTArray_base stores elements into the space allocated beyond
+// sizeof(*this). This is done to minimize the size of the nsTArray
+// object when it is empty.
+struct nsTArrayHeader {
+ uint32_t mLength;
+ uint32_t mCapacity : 31;
+ uint32_t mIsAutoArray : 1;
+};
+
+extern "C" {
+extern const nsTArrayHeader sEmptyTArrayHeader;
+}
+
+namespace detail {
+// nsTArray_CopyDisabler disables copy operations.
+class nsTArray_CopyDisabler {
+ public:
+ nsTArray_CopyDisabler() = default;
+
+ nsTArray_CopyDisabler(const nsTArray_CopyDisabler&) = delete;
+ nsTArray_CopyDisabler& operator=(const nsTArray_CopyDisabler&) = delete;
+};
+
+} // namespace detail
+
+// This class provides a SafeElementAt method to nsTArray<E*> which does
+// not take a second default value parameter.
+template <class E, class Derived>
+struct nsTArray_SafeElementAtHelper : public ::detail::nsTArray_CopyDisabler {
+ typedef E* elem_type;
+ typedef size_t index_type;
+
+ // No implementation is provided for these two methods, and that is on
+ // purpose, since we don't support these functions on non-pointer type
+ // instantiations.
+ elem_type& SafeElementAt(index_type aIndex);
+ const elem_type& SafeElementAt(index_type aIndex) const;
+};
+
+template <class E, class Derived>
+struct nsTArray_SafeElementAtHelper<E*, Derived>
+ : public ::detail::nsTArray_CopyDisabler {
+ typedef E* elem_type;
+ // typedef const E* const_elem_type; XXX: see below
+ typedef size_t index_type;
+
+ elem_type SafeElementAt(index_type aIndex) {
+ return static_cast<Derived*>(this)->SafeElementAt(aIndex, nullptr);
+ }
+
+ // XXX: Probably should return const_elem_type, but callsites must be fixed.
+ // Also, the use of const_elem_type for nsTArray<xpcGCCallback> in
+ // xpcprivate.h causes build failures on Windows because xpcGCCallback is a
+ // function pointer and MSVC doesn't like qualifying it with |const|.
+ elem_type SafeElementAt(index_type aIndex) const {
+ return static_cast<const Derived*>(this)->SafeElementAt(aIndex, nullptr);
+ }
+};
+
+// E is a smart pointer type; the
+// smart pointer can act as its element_type*.
+template <class E, class Derived>
+struct nsTArray_SafeElementAtSmartPtrHelper
+ : public ::detail::nsTArray_CopyDisabler {
+ typedef typename E::element_type* elem_type;
+ typedef const typename E::element_type* const_elem_type;
+ typedef size_t index_type;
+
+ elem_type SafeElementAt(index_type aIndex) {
+ auto* derived = static_cast<Derived*>(this);
+ if (aIndex < derived->Length()) {
+ return derived->Elements()[aIndex];
+ }
+ return nullptr;
+ }
+
+ // XXX: Probably should return const_elem_type, but callsites must be fixed.
+ elem_type SafeElementAt(index_type aIndex) const {
+ auto* derived = static_cast<const Derived*>(this);
+ if (aIndex < derived->Length()) {
+ return derived->Elements()[aIndex];
+ }
+ return nullptr;
+ }
+};
+
+template <class T>
+class nsCOMPtr;
+
+template <class E, class Derived>
+struct nsTArray_SafeElementAtHelper<nsCOMPtr<E>, Derived>
+ : public nsTArray_SafeElementAtSmartPtrHelper<nsCOMPtr<E>, Derived> {};
+
+template <class E, class Derived>
+struct nsTArray_SafeElementAtHelper<RefPtr<E>, Derived>
+ : public nsTArray_SafeElementAtSmartPtrHelper<RefPtr<E>, Derived> {};
+
+namespace mozilla {
+template <class T>
+class OwningNonNull;
+} // namespace mozilla
+
+template <class E, class Derived>
+struct nsTArray_SafeElementAtHelper<mozilla::OwningNonNull<E>, Derived>
+ : public nsTArray_SafeElementAtSmartPtrHelper<mozilla::OwningNonNull<E>,
+ Derived> {};
+
+// Servo bindings.
+extern "C" void Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity,
+ size_t aElementSize);
+extern "C" void Gecko_ClearPODTArray(void* aArray, size_t aElementSize,
+ size_t aElementAlign);
+
+//
+// This class serves as a base class for nsTArray. It shouldn't be used
+// directly. It holds common implementation code that does not depend on the
+// element type of the nsTArray.
+//
+template <class Alloc, class RelocationStrategy>
+class nsTArray_base {
+ // Allow swapping elements with |nsTArray_base|s created using a
+ // different allocator. This is kosher because all allocators use
+ // the same free().
+ template <class XAlloc, class XRelocationStrategy>
+ friend class nsTArray_base;
+
+ // Needed for AppendElements from an array with a different allocator, which
+ // calls ShiftData.
+ template <class E, class XAlloc>
+ friend class nsTArray_Impl;
+
+ friend void Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity,
+ size_t aElemSize);
+ friend void Gecko_ClearPODTArray(void* aTArray, size_t aElementSize,
+ size_t aElementAlign);
+
+ protected:
+ typedef nsTArrayHeader Header;
+
+ public:
+ typedef size_t size_type;
+ typedef size_t index_type;
+
+ // @return The number of elements in the array.
+ size_type Length() const { return mHdr->mLength; }
+
+ // @return True if the array is empty or false otherwise.
+ bool IsEmpty() const { return Length() == 0; }
+
+ // @return The number of elements that can fit in the array without forcing
+ // the array to be re-allocated. The length of an array is always less
+ // than or equal to its capacity.
+ size_type Capacity() const { return mHdr->mCapacity; }
+
+#ifdef DEBUG
+ void* DebugGetHeader() const { return mHdr; }
+#endif
+
+ protected:
+ nsTArray_base();
+
+ ~nsTArray_base();
+
+ nsTArray_base(const nsTArray_base&);
+ nsTArray_base& operator=(const nsTArray_base&);
+
+ // Resize the storage if necessary to achieve the requested capacity.
+ // @param aCapacity The requested number of array elements.
+ // @param aElemSize The size of an array element.
+ // @return False if insufficient memory is available; true otherwise.
+ template <typename ActualAlloc>
+ typename ActualAlloc::ResultTypeProxy EnsureCapacity(size_type aCapacity,
+ size_type aElemSize);
+
+ // Extend the storage to accommodate aCount extra elements.
+ // @param aLength The current size of the array.
+ // @param aCount The number of elements to add.
+ // @param aElemSize The size of an array element.
+ // @return False if insufficient memory is available or the new length
+ // would overflow; true otherwise.
+ template <typename ActualAlloc>
+ typename ActualAlloc::ResultTypeProxy ExtendCapacity(size_type aLength,
+ size_type aCount,
+ size_type aElemSize);
+
+ // Tries to resize the storage to the minimum required amount. If this fails,
+ // the array is left as-is.
+ // @param aElemSize The size of an array element.
+ // @param aElemAlign The alignment in bytes of an array element.
+ void ShrinkCapacity(size_type aElemSize, size_t aElemAlign);
+
+ // Resizes the storage to 0. This may only be called when Length() is already
+ // 0.
+ // @param aElemSize The size of an array element.
+ // @param aElemAlign The alignment in bytes of an array element.
+ void ShrinkCapacityToZero(size_type aElemSize, size_t aElemAlign);
+
+ // This method may be called to resize a "gap" in the array by shifting
+ // elements around. It updates mLength appropriately. If the resulting
+ // array has zero elements, then the array's memory is free'd.
+ // @param aStart The starting index of the gap.
+ // @param aOldLen The current length of the gap.
+ // @param aNewLen The desired length of the gap.
+ // @param aElemSize The size of an array element.
+ // @param aElemAlign The alignment in bytes of an array element.
+ template <typename ActualAlloc>
+ void ShiftData(index_type aStart, size_type aOldLen, size_type aNewLen,
+ size_type aElemSize, size_t aElemAlign);
+
+ // This method may be called to swap elements from the end of the array to
+ // fill a "gap" in the array. If the resulting array has zero elements, then
+ // the array's memory is free'd.
+ // @param aStart The starting index of the gap.
+ // @param aCount The length of the gap.
+ // @param aElemSize The size of an array element.
+ // @param aElemAlign The alignment in bytes of an array element.
+ template <typename ActualAlloc>
+ void SwapFromEnd(index_type aStart, size_type aCount, size_type aElemSize,
+ size_t aElemAlign);
+
+ // This method increments the length member of the array's header.
+ // Note that mHdr may actually be sEmptyTArrayHeader in the case where a
+ // zero-length array is inserted into our array. But then aNum should
+ // always be 0.
+ void IncrementLength(size_t aNum) {
+ if (HasEmptyHeader()) {
+ if (MOZ_UNLIKELY(aNum != 0)) {
+ // Writing a non-zero length to the empty header would be extremely bad.
+ MOZ_CRASH();
+ }
+ } else {
+ mHdr->mLength += aNum;
+ }
+ }
+
+ // This method inserts blank slots into the array.
+ // @param aIndex the place to insert the new elements. This must be no
+ // greater than the current length of the array.
+ // @param aCount the number of slots to insert
+ // @param aElementSize the size of an array element.
+ // @param aElemAlign the alignment in bytes of an array element.
+ template <typename ActualAlloc>
+ typename ActualAlloc::ResultTypeProxy InsertSlotsAt(index_type aIndex,
+ size_type aCount,
+ size_type aElementSize,
+ size_t aElemAlign);
+
+ template <typename ActualAlloc, class Allocator>
+ typename ActualAlloc::ResultTypeProxy SwapArrayElements(
+ nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize,
+ size_t aElemAlign);
+
+ template <class Allocator>
+ void MoveConstructNonAutoArray(
+ nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize,
+ size_t aElemAlign);
+
+ template <class Allocator>
+ void MoveInit(nsTArray_base<Allocator, RelocationStrategy>& aOther,
+ size_type aElemSize, size_t aElemAlign);
+
+ // This is an RAII class used in SwapArrayElements.
+ class IsAutoArrayRestorer {
+ public:
+ IsAutoArrayRestorer(nsTArray_base<Alloc, RelocationStrategy>& aArray,
+ size_t aElemAlign);
+ ~IsAutoArrayRestorer();
+
+ private:
+ nsTArray_base<Alloc, RelocationStrategy>& mArray;
+ size_t mElemAlign;
+ bool mIsAuto;
+ };
+
+ // Helper function for SwapArrayElements. Ensures that if the array
+ // is an AutoTArray that it doesn't use the built-in buffer.
+ template <typename ActualAlloc>
+ bool EnsureNotUsingAutoArrayBuffer(size_type aElemSize);
+
+ // Returns true if this nsTArray is an AutoTArray with a built-in buffer.
+ bool IsAutoArray() const { return mHdr->mIsAutoArray; }
+
+ // Returns a Header for the built-in buffer of this AutoTArray.
+ Header* GetAutoArrayBuffer(size_t aElemAlign) {
+ MOZ_ASSERT(IsAutoArray(), "Should be an auto array to call this");
+ return GetAutoArrayBufferUnsafe(aElemAlign);
+ }
+ const Header* GetAutoArrayBuffer(size_t aElemAlign) const {
+ MOZ_ASSERT(IsAutoArray(), "Should be an auto array to call this");
+ return GetAutoArrayBufferUnsafe(aElemAlign);
+ }
+
+ // Returns a Header for the built-in buffer of this AutoTArray, but doesn't
+ // assert that we are an AutoTArray.
+ Header* GetAutoArrayBufferUnsafe(size_t aElemAlign) {
+ return const_cast<Header*>(
+ static_cast<const nsTArray_base<Alloc, RelocationStrategy>*>(this)
+ ->GetAutoArrayBufferUnsafe(aElemAlign));
+ }
+ const Header* GetAutoArrayBufferUnsafe(size_t aElemAlign) const;
+
+ // Returns true if this is an AutoTArray and it currently uses the
+ // built-in buffer to store its elements.
+ bool UsesAutoArrayBuffer() const;
+
+ // The array's elements (prefixed with a Header). This pointer is never
+ // null. If the array is empty, then this will point to sEmptyTArrayHeader.
+ Header* mHdr;
+
+ Header* Hdr() const MOZ_NONNULL_RETURN { return mHdr; }
+ Header** PtrToHdr() MOZ_NONNULL_RETURN { return &mHdr; }
+ static Header* EmptyHdr() MOZ_NONNULL_RETURN {
+ return const_cast<Header*>(&sEmptyTArrayHeader);
+ }
+
+ [[nodiscard]] bool HasEmptyHeader() const { return mHdr == EmptyHdr(); }
+};
+
+namespace detail {
+
+// Used for argument checking in nsTArrayElementTraits::Emplace.
+template <typename... T>
+struct ChooseFirst;
+
+template <>
+struct ChooseFirst<> {
+ // Choose a default type that is guaranteed to not match E* for any
+ // nsTArray<E>.
+ typedef void Type;
+};
+
+template <typename A, typename... Args>
+struct ChooseFirst<A, Args...> {
+ typedef A Type;
+};
+
+} // namespace detail
+
+//
+// This class defines convenience functions for element specific operations.
+// Specialize this template if necessary.
+//
+template <class E>
+class nsTArrayElementTraits {
+ public:
+ // Invoke the default constructor in place.
+ static inline void Construct(E* aE) {
+ // Do NOT call "E()"! That triggers C++ "default initialization"
+ // which zeroes out POD ("plain old data") types such as regular
+ // ints. We don't want that because it can be a performance issue
+ // and people don't expect it; nsTArray should work like a regular
+ // C/C++ array in this respect.
+ new (static_cast<void*>(aE)) E;
+ }
+ // Invoke the copy-constructor in place.
+ template <class A>
+ static inline void Construct(E* aE, A&& aArg) {
+ using E_NoCV = std::remove_cv_t<E>;
+ using A_NoCV = std::remove_cv_t<A>;
+ static_assert(!std::is_same_v<E_NoCV*, A_NoCV>,
+ "For safety, we disallow constructing nsTArray<E> elements "
+ "from E* pointers. See bug 960591.");
+ new (static_cast<void*>(aE)) E(std::forward<A>(aArg));
+ }
+ // Construct in place.
+ template <class... Args>
+ static inline void Emplace(E* aE, Args&&... aArgs) {
+ using E_NoCV = std::remove_cv_t<E>;
+ using A_NoCV =
+ std::remove_cv_t<typename ::detail::ChooseFirst<Args...>::Type>;
+ static_assert(!std::is_same_v<E_NoCV*, A_NoCV>,
+ "For safety, we disallow constructing nsTArray<E> elements "
+ "from E* pointers. See bug 960591.");
+ new (static_cast<void*>(aE)) E(std::forward<Args>(aArgs)...);
+ }
+ // Invoke the destructor in place.
+ static inline void Destruct(E* aE) { aE->~E(); }
+};
+
+// The default comparator used by nsTArray
+template <class A, class B>
+class nsDefaultComparator {
+ public:
+ bool Equals(const A& aA, const B& aB) const { return aA == aB; }
+ bool LessThan(const A& aA, const B& aB) const { return aA < aB; }
+};
+
+template <bool IsTriviallyCopyConstructible, bool IsSameType>
+struct AssignRangeAlgorithm {
+ template <class Item, class ElemType, class IndexType, class SizeType>
+ static void implementation(ElemType* aElements, IndexType aStart,
+ SizeType aCount, const Item* aValues) {
+ ElemType* iter = aElements + aStart;
+ ElemType* end = iter + aCount;
+ for (; iter != end; ++iter, ++aValues) {
+ nsTArrayElementTraits<ElemType>::Construct(iter, *aValues);
+ }
+ }
+};
+
+template <>
+struct AssignRangeAlgorithm<true, true> {
+ template <class Item, class ElemType, class IndexType, class SizeType>
+ static void implementation(ElemType* aElements, IndexType aStart,
+ SizeType aCount, const Item* aValues) {
+ if (aValues) {
+ memcpy(aElements + aStart, aValues, aCount * sizeof(ElemType));
+ }
+ }
+};
+
+//
+// Normally elements are copied with memcpy and memmove, but for some element
+// types that is problematic. The nsTArray_RelocationStrategy template class
+// can be specialized to ensure that copying calls constructors and destructors
+// instead, as is done below for JS::Heap<E> elements.
+//
+
+//
+// A class that defines how to copy elements using memcpy/memmove.
+//
+struct nsTArray_RelocateUsingMemutils {
+ const static bool allowRealloc = true;
+
+ static void RelocateNonOverlappingRegionWithHeader(void* aDest,
+ const void* aSrc,
+ size_t aCount,
+ size_t aElemSize) {
+ memcpy(aDest, aSrc, sizeof(nsTArrayHeader) + aCount * aElemSize);
+ }
+
+ static void RelocateOverlappingRegion(void* aDest, void* aSrc, size_t aCount,
+ size_t aElemSize) {
+ memmove(aDest, aSrc, aCount * aElemSize);
+ }
+
+ static void RelocateNonOverlappingRegion(void* aDest, void* aSrc,
+ size_t aCount, size_t aElemSize) {
+ memcpy(aDest, aSrc, aCount * aElemSize);
+ }
+};
+
+//
+// A template class that defines how to relocate elements using the type's move
+// constructor and destructor appropriately.
+//
+template <class ElemType>
+struct nsTArray_RelocateUsingMoveConstructor {
+ typedef nsTArrayElementTraits<ElemType> traits;
+
+ const static bool allowRealloc = false;
+
+ static void RelocateNonOverlappingRegionWithHeader(void* aDest, void* aSrc,
+ size_t aCount,
+ size_t aElemSize) {
+ nsTArrayHeader* destHeader = static_cast<nsTArrayHeader*>(aDest);
+ nsTArrayHeader* srcHeader = static_cast<nsTArrayHeader*>(aSrc);
+ *destHeader = *srcHeader;
+ RelocateNonOverlappingRegion(
+ static_cast<uint8_t*>(aDest) + sizeof(nsTArrayHeader),
+ static_cast<uint8_t*>(aSrc) + sizeof(nsTArrayHeader), aCount,
+ aElemSize);
+ }
+
+ // RelocateNonOverlappingRegion and RelocateOverlappingRegion are defined by
+ // analogy with memmove and memcpy that are used for relocation of
+ // trivially-relocatable types through nsTArray_RelocateUsingMemutils. What
+ // they actually do is slightly different: RelocateOverlappingRegion checks to
+ // see which direction the movement needs to take place, whether from
+ // back-to-front of the range to be moved or from front-to-back.
+ // RelocateNonOverlappingRegion assumes that relocating front-to-back is
+ // always valid. They use RelocateRegionForward and RelocateRegionBackward,
+ // which are analogous to std::move and std::move_backward respectively,
+ // except they don't move-assign the destination from the source but
+ // move-construct the destination from the source and destroy the source.
+ static void RelocateOverlappingRegion(void* aDest, void* aSrc, size_t aCount,
+ size_t aElemSize) {
+ ElemType* destBegin = static_cast<ElemType*>(aDest);
+ ElemType* srcBegin = static_cast<ElemType*>(aSrc);
+
+ // If destination and source are the same, this is a no-op.
+ // In practice, we don't do this.
+ if (destBegin == srcBegin) {
+ return;
+ }
+
+ ElemType* srcEnd = srcBegin + aCount;
+ ElemType* destEnd = destBegin + aCount;
+
+ // Figure out whether to relocate back-to-front or front-to-back.
+ if (srcEnd > destBegin && srcEnd < destEnd) {
+ RelocateRegionBackward(srcBegin, srcEnd, destEnd);
+ } else {
+ RelocateRegionForward(srcBegin, srcEnd, destBegin);
+ }
+ }
+
+ static void RelocateNonOverlappingRegion(void* aDest, void* aSrc,
+ size_t aCount, size_t aElemSize) {
+ ElemType* destBegin = static_cast<ElemType*>(aDest);
+ ElemType* srcBegin = static_cast<ElemType*>(aSrc);
+ ElemType* srcEnd = srcBegin + aCount;
+#ifdef DEBUG
+ ElemType* destEnd = destBegin + aCount;
+ MOZ_ASSERT(srcEnd <= destBegin || srcBegin >= destEnd);
+#endif
+ RelocateRegionForward(srcBegin, srcEnd, destBegin);
+ }
+
+ private:
+ static void RelocateRegionForward(ElemType* srcBegin, ElemType* srcEnd,
+ ElemType* destBegin) {
+ ElemType* srcElem = srcBegin;
+ ElemType* destElem = destBegin;
+
+ while (srcElem != srcEnd) {
+ RelocateElement(srcElem, destElem);
+ ++destElem;
+ ++srcElem;
+ }
+ }
+
+ static void RelocateRegionBackward(ElemType* srcBegin, ElemType* srcEnd,
+ ElemType* destEnd) {
+ ElemType* srcElem = srcEnd;
+ ElemType* destElem = destEnd;
+ while (srcElem != srcBegin) {
+ --destElem;
+ --srcElem;
+ RelocateElement(srcElem, destElem);
+ }
+ }
+
+ static void RelocateElement(ElemType* srcElem, ElemType* destElem) {
+ traits::Construct(destElem, std::move(*srcElem));
+ traits::Destruct(srcElem);
+ }
+};
+
+//
+// The default behaviour is to use memcpy/memmove for everything.
+//
+template <class E>
+struct MOZ_NEEDS_MEMMOVABLE_TYPE nsTArray_RelocationStrategy {
+ using Type = nsTArray_RelocateUsingMemutils;
+};
+
+//
+// Some classes require constructors/destructors to be called, so they are
+// specialized here.
+//
+#define MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(E) \
+ template <> \
+ struct nsTArray_RelocationStrategy<E> { \
+ using Type = nsTArray_RelocateUsingMoveConstructor<E>; \
+ };
+
+#define MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(T) \
+ template <typename S> \
+ struct nsTArray_RelocationStrategy<T<S>> { \
+ using Type = nsTArray_RelocateUsingMoveConstructor<T<S>>; \
+ };
+
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(JS::Heap)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(std::function)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(mozilla::ipc::Endpoint)
+
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(nsRegion)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(nsIntRegion)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::layers::TileClient)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(
+ mozilla::SerializedStructuredCloneBuffer)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(
+ mozilla::dom::ipc::StructuredCloneData)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::ClonedMessageData)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(
+ mozilla::dom::indexedDB::ObjectStoreCursorResponse)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(
+ mozilla::dom::indexedDB::IndexCursorResponse)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(
+ mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo);
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(JSStructuredCloneData)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::MessageData)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::RefMessageData)
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::SourceBufferTask)
+
+//
+// Base class for nsTArray_Impl that is templated on element type and derived
+// nsTArray_Impl class, to allow extra conversions to be added for specific
+// types.
+//
+template <class E, class Derived>
+struct nsTArray_TypedBase : public nsTArray_SafeElementAtHelper<E, Derived> {};
+
+//
+// Specialization of nsTArray_TypedBase for arrays containing JS::Heap<E>
+// elements.
+//
+// These conversions are safe because JS::Heap<E> and E share the same
+// representation, and since the result of the conversions are const references
+// we won't miss any barriers.
+//
+// The static_cast is necessary to obtain the correct address for the derived
+// class since we are a base class used in multiple inheritance.
+//
+template <class E, class Derived>
+struct nsTArray_TypedBase<JS::Heap<E>, Derived>
+ : public nsTArray_SafeElementAtHelper<JS::Heap<E>, Derived> {
+ operator const nsTArray<E>&() {
+ static_assert(sizeof(E) == sizeof(JS::Heap<E>),
+ "JS::Heap<E> must be binary compatible with E.");
+ Derived* self = static_cast<Derived*>(this);
+ return *reinterpret_cast<nsTArray<E>*>(self);
+ }
+
+ operator const FallibleTArray<E>&() {
+ Derived* self = static_cast<Derived*>(this);
+ return *reinterpret_cast<FallibleTArray<E>*>(self);
+ }
+};
+
+namespace detail {
+
+// These helpers allow us to differentiate between tri-state comparator
+// functions and classes with LessThan() and Equal() methods. If an object, when
+// called as a function with two instances of our element type, returns an int,
+// we treat it as a tri-state comparator.
+//
+// T is the type of the comparator object we want to check. U is the array
+// element type that we'll be comparing.
+//
+// V is never passed, and is only used to allow us to specialize on the return
+// value of the comparator function.
+template <typename T, typename U, typename V = int>
+struct IsCompareMethod : std::false_type {};
+
+template <typename T, typename U>
+struct IsCompareMethod<
+ T, U, decltype(std::declval<T>()(std::declval<U>(), std::declval<U>()))>
+ : std::true_type {};
+
+// These two wrappers allow us to use either a tri-state comparator, or an
+// object with Equals() and LessThan() methods interchangeably. They provide a
+// tri-state Compare() method, and Equals() method, and a LessThan() method.
+//
+// Depending on the type of the underlying comparator, they either pass these
+// through directly, or synthesize them from the methods available on the
+// comparator.
+//
+// Callers should always use the most-specific of these methods that match their
+// purpose.
+
+// Comparator wrapper for a tri-state comparator function
+template <typename T, typename U, bool IsCompare = IsCompareMethod<T, U>::value>
+struct CompareWrapper {
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable : 4180) /* Silence "qualifier applied to function \
+ type has no meaning" warning */
+#endif
+ MOZ_IMPLICIT CompareWrapper(const T& aComparator)
+ : mComparator(aComparator) {}
+
+ template <typename A, typename B>
+ int Compare(A& aLeft, B& aRight) const {
+ return mComparator(aLeft, aRight);
+ }
+
+ template <typename A, typename B>
+ bool Equals(A& aLeft, B& aRight) const {
+ return Compare(aLeft, aRight) == 0;
+ }
+
+ template <typename A, typename B>
+ bool LessThan(A& aLeft, B& aRight) const {
+ return Compare(aLeft, aRight) < 0;
+ }
+
+ const T& mComparator;
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+};
+
+// Comparator wrapper for a class with Equals() and LessThan() methods.
+template <typename T, typename U>
+struct CompareWrapper<T, U, false> {
+ MOZ_IMPLICIT CompareWrapper(const T& aComparator)
+ : mComparator(aComparator) {}
+
+ template <typename A, typename B>
+ int Compare(A& aLeft, B& aRight) const {
+ if (Equals(aLeft, aRight)) {
+ return 0;
+ }
+ return LessThan(aLeft, aRight) ? -1 : 1;
+ }
+
+ template <typename A, typename B>
+ bool Equals(A& aLeft, B& aRight) const {
+ return mComparator.Equals(aLeft, aRight);
+ }
+
+ template <typename A, typename B>
+ bool LessThan(A& aLeft, B& aRight) const {
+ return mComparator.LessThan(aLeft, aRight);
+ }
+
+ const T& mComparator;
+};
+
+} // namespace detail
+
+//
+// nsTArray_Impl contains most of the guts supporting nsTArray, FallibleTArray,
+// AutoTArray.
+//
+// The only situation in which you might need to use nsTArray_Impl in your code
+// is if you're writing code which mutates a TArray which may or may not be
+// infallible.
+//
+// Code which merely reads from a TArray which may or may not be infallible can
+// simply cast the TArray to |const nsTArray&|; both fallible and infallible
+// TArrays can be cast to |const nsTArray&|.
+//
+template <class E, class Alloc>
+class nsTArray_Impl
+ : public nsTArray_base<Alloc,
+ typename nsTArray_RelocationStrategy<E>::Type>,
+ public nsTArray_TypedBase<E, nsTArray_Impl<E, Alloc>> {
+ private:
+ friend class nsTArray<E>;
+
+ typedef nsTArrayFallibleAllocator FallibleAlloc;
+ typedef nsTArrayInfallibleAllocator InfallibleAlloc;
+
+ public:
+ typedef typename nsTArray_RelocationStrategy<E>::Type relocation_type;
+ typedef nsTArray_base<Alloc, relocation_type> base_type;
+ typedef typename base_type::size_type size_type;
+ typedef typename base_type::index_type index_type;
+ typedef E value_type;
+ typedef nsTArray_Impl<E, Alloc> self_type;
+ typedef nsTArrayElementTraits<E> elem_traits;
+ typedef nsTArray_SafeElementAtHelper<E, self_type> safeelementat_helper_type;
+ typedef mozilla::ArrayIterator<value_type&, self_type> iterator;
+ typedef mozilla::ArrayIterator<const value_type&, self_type> const_iterator;
+ typedef std::reverse_iterator<iterator> reverse_iterator;
+ typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+
+ using base_type::EmptyHdr;
+ using safeelementat_helper_type::SafeElementAt;
+
+ // A special value that is used to indicate an invalid or unknown index
+ // into the array.
+ static const index_type NoIndex = index_type(-1);
+
+ using base_type::Length;
+
+ //
+ // Finalization method
+ //
+
+ ~nsTArray_Impl() {
+ if (!base_type::IsEmpty()) {
+ ClearAndRetainStorage();
+ }
+ // mHdr cleanup will be handled by base destructor
+ }
+
+ //
+ // Initialization methods
+ //
+
+ nsTArray_Impl() = default;
+
+ // Initialize this array and pre-allocate some number of elements.
+ explicit nsTArray_Impl(size_type aCapacity) { SetCapacity(aCapacity); }
+
+ // Initialize this array with an r-value.
+ // Allow different types of allocators, since the allocator doesn't matter.
+ template <typename Allocator>
+ explicit nsTArray_Impl(nsTArray_Impl<E, Allocator>&& aOther) noexcept {
+ // We cannot be a (Copyable)AutoTArray because that overrides this ctor.
+ MOZ_ASSERT(!this->IsAutoArray());
+
+ // This does not use SwapArrayElements because that's unnecessarily complex.
+ this->MoveConstructNonAutoArray(aOther, sizeof(value_type),
+ MOZ_ALIGNOF(value_type));
+ }
+
+ // The array's copy-constructor performs a 'deep' copy of the given array.
+ // @param aOther The array object to copy.
+ //
+ // It's very important that we declare this method as taking |const
+ // self_type&| as opposed to taking |const nsTArray_Impl<E, OtherAlloc>| for
+ // an arbitrary OtherAlloc.
+ //
+ // If we don't declare a constructor taking |const self_type&|, C++ generates
+ // a copy-constructor for this class which merely copies the object's
+ // members, which is obviously wrong.
+ //
+ // You can pass an nsTArray_Impl<E, OtherAlloc> to this method because
+ // nsTArray_Impl<E, X> can be cast to const nsTArray_Impl<E, Y>&. So the
+ // effect on the API is the same as if we'd declared this method as taking
+ // |const nsTArray_Impl<E, OtherAlloc>&|.
+ nsTArray_Impl(const nsTArray_Impl&) = default;
+
+ // Allow converting to a const array with a different kind of allocator,
+ // Since the allocator doesn't matter for const arrays
+ template <typename Allocator>
+ [[nodiscard]] operator const nsTArray_Impl<E, Allocator>&() const& {
+ return *reinterpret_cast<const nsTArray_Impl<E, Allocator>*>(this);
+ }
+ // And we have to do this for our subclasses too
+ [[nodiscard]] operator const nsTArray<E>&() const& {
+ return *reinterpret_cast<const nsTArray<E>*>(this);
+ }
+ [[nodiscard]] operator const FallibleTArray<E>&() const& {
+ return *reinterpret_cast<const FallibleTArray<E>*>(this);
+ }
+
+ // The array's assignment operator performs a 'deep' copy of the given
+ // array. It is optimized to reuse existing storage if possible.
+ // @param aOther The array object to copy.
+ nsTArray_Impl& operator=(const nsTArray_Impl&) = default;
+
+ // The array's move assignment operator steals the underlying data from
+ // the other array.
+ // @param other The array object to move from.
+ self_type& operator=(self_type&& aOther) {
+ if (this != &aOther) {
+ Clear();
+ this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type));
+ }
+ return *this;
+ }
+
+ // Return true if this array has the same length and the same
+ // elements as |aOther|.
+ template <typename Allocator>
+ [[nodiscard]] bool operator==(
+ const nsTArray_Impl<E, Allocator>& aOther) const {
+ size_type len = Length();
+ if (len != aOther.Length()) {
+ return false;
+ }
+
+ // XXX std::equal would be as fast or faster here
+ for (index_type i = 0; i < len; ++i) {
+ if (!(operator[](i) == aOther[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Return true if this array does not have the same length and the same
+ // elements as |aOther|.
+ [[nodiscard]] bool operator!=(const self_type& aOther) const {
+ return !operator==(aOther);
+ }
+
+ // If Alloc == FallibleAlloc, ReplaceElementsAt might fail, without a way to
+ // signal this to the caller, so we disallow copying via operator=. Callers
+ // should use ReplaceElementsAt with a fallible argument instead, and check
+ // the result.
+ template <typename Allocator,
+ typename = std::enable_if_t<std::is_same_v<Alloc, InfallibleAlloc>,
+ Allocator>>
+ self_type& operator=(const nsTArray_Impl<E, Allocator>& aOther) {
+ AssignInternal<InfallibleAlloc>(aOther.Elements(), aOther.Length());
+ return *this;
+ }
+
+ template <typename Allocator>
+ self_type& operator=(nsTArray_Impl<E, Allocator>&& aOther) {
+ Clear();
+ this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type));
+ return *this;
+ }
+
+ // @return The amount of memory used by this nsTArray_Impl, excluding
+ // sizeof(*this). If you want to measure anything hanging off the array, you
+ // must iterate over the elements and measure them individually; hence the
+ // "Shallow" prefix.
+ [[nodiscard]] size_t ShallowSizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ if (this->UsesAutoArrayBuffer() || this->HasEmptyHeader()) {
+ return 0;
+ }
+ return aMallocSizeOf(this->Hdr());
+ }
+
+ // @return The amount of memory used by this nsTArray_Impl, including
+ // sizeof(*this). If you want to measure anything hanging off the array, you
+ // must iterate over the elements and measure them individually; hence the
+ // "Shallow" prefix.
+ [[nodiscard]] size_t ShallowSizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ //
+ // Accessor methods
+ //
+
+ // This method provides direct access to the array elements.
+ // @return A pointer to the first element of the array. If the array is
+ // empty, then this pointer must not be dereferenced.
+ [[nodiscard]] value_type* Elements() MOZ_NONNULL_RETURN {
+ return reinterpret_cast<value_type*>(Hdr() + 1);
+ }
+
+ // This method provides direct, readonly access to the array elements.
+ // @return A pointer to the first element of the array. If the array is
+ // empty, then this pointer must not be dereferenced.
+ [[nodiscard]] const value_type* Elements() const MOZ_NONNULL_RETURN {
+ return reinterpret_cast<const value_type*>(Hdr() + 1);
+ }
+
+ // This method provides direct access to an element of the array. The given
+ // index must be within the array bounds.
+ // @param aIndex The index of an element in the array.
+ // @return A reference to the i'th element of the array.
+ [[nodiscard]] value_type& ElementAt(index_type aIndex) {
+ if (MOZ_UNLIKELY(aIndex >= Length())) {
+ mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length());
+ }
+ return Elements()[aIndex];
+ }
+
+ // This method provides direct, readonly access to an element of the array
+ // The given index must be within the array bounds.
+ // @param aIndex The index of an element in the array.
+ // @return A const reference to the i'th element of the array.
+ [[nodiscard]] const value_type& ElementAt(index_type aIndex) const {
+ if (MOZ_UNLIKELY(aIndex >= Length())) {
+ mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length());
+ }
+ return Elements()[aIndex];
+ }
+
+ // This method provides direct access to an element of the array in a bounds
+ // safe manner. If the requested index is out of bounds the provided default
+ // value is returned.
+ // @param aIndex The index of an element in the array.
+ // @param aDef The value to return if the index is out of bounds.
+ [[nodiscard]] value_type& SafeElementAt(index_type aIndex, value_type& aDef) {
+ return aIndex < Length() ? Elements()[aIndex] : aDef;
+ }
+
+ // This method provides direct access to an element of the array in a bounds
+ // safe manner. If the requested index is out of bounds the provided default
+ // value is returned.
+ // @param aIndex The index of an element in the array.
+ // @param aDef The value to return if the index is out of bounds.
+ [[nodiscard]] const value_type& SafeElementAt(index_type aIndex,
+ const value_type& aDef) const {
+ return aIndex < Length() ? Elements()[aIndex] : aDef;
+ }
+
+ // Shorthand for ElementAt(aIndex)
+ [[nodiscard]] value_type& operator[](index_type aIndex) {
+ return ElementAt(aIndex);
+ }
+
+ // Shorthand for ElementAt(aIndex)
+ [[nodiscard]] const value_type& operator[](index_type aIndex) const {
+ return ElementAt(aIndex);
+ }
+
+ // Shorthand for ElementAt(length - 1)
+ [[nodiscard]] value_type& LastElement() { return ElementAt(Length() - 1); }
+
+ // Shorthand for ElementAt(length - 1)
+ [[nodiscard]] const value_type& LastElement() const {
+ return ElementAt(Length() - 1);
+ }
+
+ // Shorthand for SafeElementAt(length - 1, def)
+ [[nodiscard]] value_type& SafeLastElement(value_type& aDef) {
+ return SafeElementAt(Length() - 1, aDef);
+ }
+
+ // Shorthand for SafeElementAt(length - 1, def)
+ [[nodiscard]] const value_type& SafeLastElement(
+ const value_type& aDef) const {
+ return SafeElementAt(Length() - 1, aDef);
+ }
+
+ // Methods for range-based for loops.
+ [[nodiscard]] iterator begin() { return iterator(*this, 0); }
+ [[nodiscard]] const_iterator begin() const {
+ return const_iterator(*this, 0);
+ }
+ [[nodiscard]] const_iterator cbegin() const { return begin(); }
+ [[nodiscard]] iterator end() { return iterator(*this, Length()); }
+ [[nodiscard]] const_iterator end() const {
+ return const_iterator(*this, Length());
+ }
+ [[nodiscard]] const_iterator cend() const { return end(); }
+
+ // Methods for reverse iterating.
+ [[nodiscard]] reverse_iterator rbegin() { return reverse_iterator(end()); }
+ [[nodiscard]] const_reverse_iterator rbegin() const {
+ return const_reverse_iterator(end());
+ }
+ [[nodiscard]] const_reverse_iterator crbegin() const { return rbegin(); }
+ [[nodiscard]] reverse_iterator rend() { return reverse_iterator(begin()); }
+ [[nodiscard]] const_reverse_iterator rend() const {
+ return const_reverse_iterator(begin());
+ }
+ [[nodiscard]] const_reverse_iterator crend() const { return rend(); }
+
+ // Span integration
+
+ [[nodiscard]] operator mozilla::Span<value_type>() {
+ return mozilla::Span<value_type>(Elements(), Length());
+ }
+
+ [[nodiscard]] operator mozilla::Span<const value_type>() const {
+ return mozilla::Span<const value_type>(Elements(), Length());
+ }
+
+ //
+ // Search methods
+ //
+
+ // This method searches for the first element in this array that is equal
+ // to the given element.
+ // @param aItem The item to search for.
+ // @param aComp The Comparator used to determine element equality.
+ // @return true if the element was found.
+ template <class Item, class Comparator>
+ [[nodiscard]] bool Contains(const Item& aItem,
+ const Comparator& aComp) const {
+ return ApplyIf(
+ aItem, 0, aComp, []() { return true; }, []() { return false; });
+ }
+
+ // Like Contains(), but assumes a sorted array.
+ template <class Item, class Comparator>
+ [[nodiscard]] bool ContainsSorted(const Item& aItem,
+ const Comparator& aComp) const {
+ return BinaryIndexOf(aItem, aComp) != NoIndex;
+ }
+
+ // This method searches for the first element in this array that is equal
+ // to the given element. This method assumes that 'operator==' is defined
+ // for value_type.
+ // @param aItem The item to search for.
+ // @return true if the element was found.
+ template <class Item>
+ [[nodiscard]] bool Contains(const Item& aItem) const {
+ return Contains(aItem, nsDefaultComparator<value_type, Item>());
+ }
+
+ // Like Contains(), but assumes a sorted array.
+ template <class Item>
+ [[nodiscard]] bool ContainsSorted(const Item& aItem) const {
+ return BinaryIndexOf(aItem) != NoIndex;
+ }
+
+ // This method searches for the offset of the first element in this
+ // array that is equal to the given element.
+ // @param aItem The item to search for.
+ // @param aStart The index to start from.
+ // @param aComp The Comparator used to determine element equality.
+ // @return The index of the found element or NoIndex if not found.
+ template <class Item, class Comparator>
+ [[nodiscard]] index_type IndexOf(const Item& aItem, index_type aStart,
+ const Comparator& aComp) const {
+ ::detail::CompareWrapper<Comparator, Item> comp(aComp);
+
+ const value_type* iter = Elements() + aStart;
+ const value_type* iend = Elements() + Length();
+ for (; iter != iend; ++iter) {
+ if (comp.Equals(*iter, aItem)) {
+ return index_type(iter - Elements());
+ }
+ }
+ return NoIndex;
+ }
+
+ // This method searches for the offset of the first element in this
+ // array that is equal to the given element. This method assumes
+ // that 'operator==' is defined for value_type.
+ // @param aItem The item to search for.
+ // @param aStart The index to start from.
+ // @return The index of the found element or NoIndex if not found.
+ template <class Item>
+ [[nodiscard]] index_type IndexOf(const Item& aItem,
+ index_type aStart = 0) const {
+ return IndexOf(aItem, aStart, nsDefaultComparator<value_type, Item>());
+ }
+
+ // This method searches for the offset of the last element in this
+ // array that is equal to the given element.
+ // @param aItem The item to search for.
+ // @param aStart The index to start from. If greater than or equal to the
+ // length of the array, then the entire array is searched.
+ // @param aComp The Comparator used to determine element equality.
+ // @return The index of the found element or NoIndex if not found.
+ template <class Item, class Comparator>
+ [[nodiscard]] index_type LastIndexOf(const Item& aItem, index_type aStart,
+ const Comparator& aComp) const {
+ ::detail::CompareWrapper<Comparator, Item> comp(aComp);
+
+ size_type endOffset = aStart >= Length() ? Length() : aStart + 1;
+ const value_type* iend = Elements() - 1;
+ const value_type* iter = iend + endOffset;
+ for (; iter != iend; --iter) {
+ if (comp.Equals(*iter, aItem)) {
+ return index_type(iter - Elements());
+ }
+ }
+ return NoIndex;
+ }
+
+ // This method searches for the offset of the last element in this
+ // array that is equal to the given element. This method assumes
+ // that 'operator==' is defined for value_type.
+ // @param aItem The item to search for.
+ // @param aStart The index to start from. If greater than or equal to the
+ // length of the array, then the entire array is searched.
+ // @return The index of the found element or NoIndex if not found.
+ template <class Item>
+ [[nodiscard]] index_type LastIndexOf(const Item& aItem,
+ index_type aStart = NoIndex) const {
+ return LastIndexOf(aItem, aStart, nsDefaultComparator<value_type, Item>());
+ }
+
+ // This method searches for the offset for the element in this array
+ // that is equal to the given element. The array is assumed to be sorted.
+ // If there is more than one equivalent element, there is no guarantee
+ // on which one will be returned.
+ // @param aItem The item to search for.
+ // @param aComp The Comparator used.
+ // @return The index of the found element or NoIndex if not found.
+ template <class Item, class Comparator>
+ [[nodiscard]] index_type BinaryIndexOf(const Item& aItem,
+ const Comparator& aComp) const {
+ using mozilla::BinarySearchIf;
+ ::detail::CompareWrapper<Comparator, Item> comp(aComp);
+
+ size_t index;
+ bool found = BinarySearchIf(
+ Elements(), 0, Length(),
+ // Note: We pass the Compare() args here in reverse order and negate the
+ // results for compatibility reasons. Some existing callers use Equals()
+ // functions with first arguments which match aElement but not aItem, or
+ // second arguments that match aItem but not aElement. To accommodate
+ // those callers, we preserve the argument order of the older version of
+ // this API. These callers, however, should be fixed, and this special
+ // case removed.
+ [&](const value_type& aElement) {
+ return -comp.Compare(aElement, aItem);
+ },
+ &index);
+ return found ? index : NoIndex;
+ }
+
+ // This method searches for the offset for the element in this array
+ // that is equal to the given element. The array is assumed to be sorted.
+ // This method assumes that 'operator==' and 'operator<' are defined.
+ // @param aItem The item to search for.
+ // @return The index of the found element or NoIndex if not found.
+ template <class Item>
+ [[nodiscard]] index_type BinaryIndexOf(const Item& aItem) const {
+ return BinaryIndexOf(aItem, nsDefaultComparator<value_type, Item>());
+ }
+
+ //
+ // Mutation methods
+ //
+ private:
+ template <typename ActualAlloc, class Item>
+ typename ActualAlloc::ResultType AssignInternal(const Item* aArray,
+ size_type aArrayLen);
+
+ public:
+ template <class Allocator, typename ActualAlloc = Alloc>
+ [[nodiscard]] typename ActualAlloc::ResultType Assign(
+ const nsTArray_Impl<E, Allocator>& aOther) {
+ return AssignInternal<ActualAlloc>(aOther.Elements(), aOther.Length());
+ }
+
+ template <class Allocator>
+ [[nodiscard]] bool Assign(const nsTArray_Impl<E, Allocator>& aOther,
+ const mozilla::fallible_t&) {
+ return Assign<Allocator, FallibleAlloc>(aOther);
+ }
+
+ template <class Allocator>
+ void Assign(nsTArray_Impl<E, Allocator>&& aOther) {
+ Clear();
+ this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type));
+ }
+
+ // This method call the destructor on each element of the array, empties it,
+ // but does not shrink the array's capacity.
+ // See also SetLengthAndRetainStorage.
+ // Make sure to call Compact() if needed to avoid keeping a huge array
+ // around.
+ void ClearAndRetainStorage() {
+ if (this->HasEmptyHeader()) {
+ return;
+ }
+
+ DestructRange(0, Length());
+ base_type::mHdr->mLength = 0;
+ }
+
+ // This method modifies the length of the array, but unlike SetLength
+ // it doesn't deallocate/reallocate the current internal storage.
+ // The new length MUST be shorter than or equal to the current capacity.
+ // If the new length is larger than the existing length of the array,
+ // then new elements will be constructed using value_type's default
+ // constructor. If shorter, elements will be destructed and removed.
+ // See also ClearAndRetainStorage.
+ // @param aNewLen The desired length of this array.
+ void SetLengthAndRetainStorage(size_type aNewLen) {
+ MOZ_ASSERT(aNewLen <= base_type::Capacity());
+ size_type oldLen = Length();
+ if (aNewLen > oldLen) {
+ /// XXX(Bug 1631367) SetLengthAndRetainStorage should be disabled for
+ /// FallibleTArray.
+ InsertElementsAtInternal<InfallibleAlloc>(oldLen, aNewLen - oldLen);
+ return;
+ }
+ if (aNewLen < oldLen) {
+ DestructRange(aNewLen, oldLen - aNewLen);
+ base_type::mHdr->mLength = aNewLen;
+ }
+ }
+
+ // This method replaces a range of elements in this array.
+ // @param aStart The starting index of the elements to replace.
+ // @param aCount The number of elements to replace. This may be zero to
+ // insert elements without removing any existing elements.
+ // @param aArray The values to copy into this array. Must be non-null,
+ // and these elements must not already exist in the array
+ // being modified.
+ // @param aArrayLen The number of values to copy into this array.
+ // @return A pointer to the new elements in the array, or null if
+ // the operation failed due to insufficient memory.
+ private:
+ template <typename ActualAlloc, class Item>
+ value_type* ReplaceElementsAtInternal(index_type aStart, size_type aCount,
+ const Item* aArray,
+ size_type aArrayLen);
+
+ public:
+ template <class Item>
+ [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart,
+ size_type aCount,
+ const Item* aArray,
+ size_type aArrayLen,
+ const mozilla::fallible_t&) {
+ return ReplaceElementsAtInternal<FallibleAlloc>(aStart, aCount, aArray,
+ aArrayLen);
+ }
+
+ // A variation on the ReplaceElementsAt method defined above.
+ template <class Item>
+ [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart,
+ size_type aCount,
+ const nsTArray<Item>& aArray,
+ const mozilla::fallible_t&) {
+ return ReplaceElementsAtInternal<FallibleAlloc>(aStart, aCount, aArray);
+ }
+
+ template <class Item>
+ [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart,
+ size_type aCount,
+ mozilla::Span<Item> aSpan,
+ const mozilla::fallible_t&) {
+ return ReplaceElementsAtInternal<FallibleAlloc>(aStart, aCount, aSpan);
+ }
+
+ // A variation on the ReplaceElementsAt method defined above.
+ template <class Item>
+ [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart,
+ size_type aCount,
+ const Item& aItem,
+ const mozilla::fallible_t&) {
+ return ReplaceElementsAtInternal<FallibleAlloc>(aStart, aCount, aItem);
+ }
+
+ // A variation on the ReplaceElementsAt method defined above.
+ template <class Item>
+ mozilla::NotNull<value_type*> ReplaceElementAt(index_type aIndex,
+ Item&& aItem) {
+ value_type* const elem = &ElementAt(aIndex);
+ elem_traits::Destruct(elem);
+ elem_traits::Construct(elem, std::forward<Item>(aItem));
+ return mozilla::WrapNotNullUnchecked(elem);
+ }
+
+ // InsertElementsAt is ReplaceElementsAt with 0 elements to replace.
+ // XXX Provide a proper documentation of InsertElementsAt.
+ template <class Item>
+ [[nodiscard]] value_type* InsertElementsAt(index_type aIndex,
+ const Item* aArray,
+ size_type aArrayLen,
+ const mozilla::fallible_t&) {
+ return ReplaceElementsAtInternal<FallibleAlloc>(aIndex, 0, aArray,
+ aArrayLen);
+ }
+
+ template <class Item, class Allocator>
+ [[nodiscard]] value_type* InsertElementsAt(
+ index_type aIndex, const nsTArray_Impl<Item, Allocator>& aArray,
+ const mozilla::fallible_t&) {
+ return ReplaceElementsAtInternal<FallibleAlloc>(
+ aIndex, 0, aArray.Elements(), aArray.Length());
+ }
+
+ template <class Item>
+ [[nodiscard]] value_type* InsertElementsAt(index_type aIndex,
+ mozilla::Span<Item> aSpan,
+ const mozilla::fallible_t&) {
+ return ReplaceElementsAtInternal<FallibleAlloc>(aIndex, 0, aSpan.Elements(),
+ aSpan.Length());
+ }
+
+ private:
+ template <typename ActualAlloc>
+ value_type* InsertElementAtInternal(index_type aIndex);
+
+ // Insert a new element without copy-constructing. This is useful to avoid
+ // temporaries.
+ // @return A pointer to the newly inserted element, or null on OOM.
+ public:
+ [[nodiscard]] value_type* InsertElementAt(index_type aIndex,
+ const mozilla::fallible_t&) {
+ return InsertElementAtInternal<FallibleAlloc>(aIndex);
+ }
+
+ private:
+ template <typename ActualAlloc, class Item>
+ value_type* InsertElementAtInternal(index_type aIndex, Item&& aItem);
+
+ // Insert a new element, move constructing if possible.
+ public:
+ template <class Item>
+ [[nodiscard]] value_type* InsertElementAt(index_type aIndex, Item&& aItem,
+ const mozilla::fallible_t&) {
+ return InsertElementAtInternal<FallibleAlloc>(aIndex,
+ std::forward<Item>(aItem));
+ }
+
+ // Reconstruct the element at the given index, and return a pointer to the
+ // reconstructed element. This will destroy the existing element and
+ // default-construct a new one, giving you a state much like what single-arg
+ // InsertElementAt(), or no-arg AppendElement() does, but without changing the
+ // length of the array.
+ //
+ // array[idx] = value_type()
+ //
+ // would accomplish the same thing as long as value_type has the appropriate
+ // moving operator=, but some types don't for various reasons.
+ mozilla::NotNull<value_type*> ReconstructElementAt(index_type aIndex) {
+ value_type* elem = &ElementAt(aIndex);
+ elem_traits::Destruct(elem);
+ elem_traits::Construct(elem);
+ return mozilla::WrapNotNullUnchecked(elem);
+ }
+
+ // This method searches for the smallest index of an element that is strictly
+ // greater than |aItem|. If |aItem| is inserted at this index, the array will
+ // remain sorted and |aItem| would come after all elements that are equal to
+ // it. If |aItem| is greater than or equal to all elements in the array, the
+ // array length is returned.
+ //
+ // Note that consumers who want to know whether there are existing items equal
+ // to |aItem| in the array can just check that the return value here is > 0
+ // and indexing into the previous slot gives something equal to |aItem|.
+ //
+ //
+ // @param aItem The item to search for.
+ // @param aComp The Comparator used.
+ // @return The index of greatest element <= to |aItem|
+ // @precondition The array is sorted
+ template <class Item, class Comparator>
+ [[nodiscard]] index_type IndexOfFirstElementGt(
+ const Item& aItem, const Comparator& aComp) const {
+ using mozilla::BinarySearchIf;
+ ::detail::CompareWrapper<Comparator, Item> comp(aComp);
+
+ size_t index;
+ BinarySearchIf(
+ Elements(), 0, Length(),
+ [&](const value_type& aElement) {
+ return comp.Compare(aElement, aItem) <= 0 ? 1 : -1;
+ },
+ &index);
+ return index;
+ }
+
+ // A variation on the IndexOfFirstElementGt method defined above.
+ template <class Item>
+ [[nodiscard]] index_type IndexOfFirstElementGt(const Item& aItem) const {
+ return IndexOfFirstElementGt(aItem,
+ nsDefaultComparator<value_type, Item>());
+ }
+
+ private:
+ template <typename ActualAlloc, class Item, class Comparator>
+ value_type* InsertElementSortedInternal(Item&& aItem,
+ const Comparator& aComp) {
+ index_type index = IndexOfFirstElementGt<Item, Comparator>(aItem, aComp);
+ return InsertElementAtInternal<ActualAlloc>(index,
+ std::forward<Item>(aItem));
+ }
+
+ // Inserts |aItem| at such an index to guarantee that if the array
+ // was previously sorted, it will remain sorted after this
+ // insertion.
+ public:
+ template <class Item, class Comparator>
+ [[nodiscard]] value_type* InsertElementSorted(Item&& aItem,
+ const Comparator& aComp,
+ const mozilla::fallible_t&) {
+ return InsertElementSortedInternal<FallibleAlloc>(std::forward<Item>(aItem),
+ aComp);
+ }
+
+ // A variation on the InsertElementSorted method defined above.
+ public:
+ template <class Item>
+ [[nodiscard]] value_type* InsertElementSorted(Item&& aItem,
+ const mozilla::fallible_t&) {
+ return InsertElementSortedInternal<FallibleAlloc>(
+ std::forward<Item>(aItem), nsDefaultComparator<value_type, Item>{});
+ }
+
+ private:
+ template <typename ActualAlloc, class Item>
+ value_type* AppendElementsInternal(const Item* aArray, size_type aArrayLen);
+
+ // This method appends elements to the end of this array.
+ // @param aArray The elements to append to this array.
+ // @param aArrayLen The number of elements to append to this array.
+ // @return A pointer to the new elements in the array, or null if
+ // the operation failed due to insufficient memory.
+ public:
+ template <class Item>
+ [[nodiscard]] value_type* AppendElements(const Item* aArray,
+ size_type aArrayLen,
+ const mozilla::fallible_t&) {
+ return AppendElementsInternal<FallibleAlloc>(aArray, aArrayLen);
+ }
+
+ template <class Item>
+ [[nodiscard]] value_type* AppendElements(mozilla::Span<Item> aSpan,
+ const mozilla::fallible_t&) {
+ return AppendElementsInternal<FallibleAlloc>(aSpan.Elements(),
+ aSpan.Length());
+ }
+
+ // A variation on the AppendElements method defined above.
+ template <class Item, class Allocator>
+ [[nodiscard]] value_type* AppendElements(
+ const nsTArray_Impl<Item, Allocator>& aArray,
+ const mozilla::fallible_t&) {
+ return AppendElementsInternal<FallibleAlloc>(aArray.Elements(),
+ aArray.Length());
+ }
+
+ private:
+ template <typename ActualAlloc, class Item, class Allocator>
+ value_type* AppendElementsInternal(nsTArray_Impl<Item, Allocator>&& aArray);
+
+ // Move all elements from another array to the end of this array.
+ // @return A pointer to the newly appended elements, or null on OOM.
+ public:
+ template <class Item, class Allocator>
+ [[nodiscard]] value_type* AppendElements(
+ nsTArray_Impl<Item, Allocator>&& aArray, const mozilla::fallible_t&) {
+ return AppendElementsInternal<FallibleAlloc>(std::move(aArray));
+ }
+
+ // Append a new element, constructed in place from the provided arguments.
+ protected:
+ template <typename ActualAlloc, class... Args>
+ value_type* EmplaceBackInternal(Args&&... aItem);
+
+ public:
+ template <class... Args>
+ [[nodiscard]] value_type* EmplaceBack(const mozilla::fallible_t&,
+ Args&&... aArgs) {
+ return EmplaceBackInternal<FallibleAlloc, Args...>(
+ std::forward<Args>(aArgs)...);
+ }
+
+ private:
+ template <typename ActualAlloc, class Item>
+ value_type* AppendElementInternal(Item&& aItem);
+
+ // Append a new element, move constructing if possible.
+ public:
+ template <class Item>
+ [[nodiscard]] value_type* AppendElement(Item&& aItem,
+ const mozilla::fallible_t&) {
+ return AppendElementInternal<FallibleAlloc>(std::forward<Item>(aItem));
+ }
+
+ private:
+ template <typename ActualAlloc>
+ value_type* AppendElementsInternal(size_type aCount) {
+ if (!ActualAlloc::Successful(this->template ExtendCapacity<ActualAlloc>(
+ Length(), aCount, sizeof(value_type)))) {
+ return nullptr;
+ }
+ value_type* elems = Elements() + Length();
+ size_type i;
+ for (i = 0; i < aCount; ++i) {
+ elem_traits::Construct(elems + i);
+ }
+ this->IncrementLength(aCount);
+ return elems;
+ }
+
+ // Append new elements without copy-constructing. This is useful to avoid
+ // temporaries.
+ // @return A pointer to the newly appended elements, or null on OOM.
+ public:
+ [[nodiscard]] value_type* AppendElements(size_type aCount,
+ const mozilla::fallible_t&) {
+ return AppendElementsInternal<FallibleAlloc>(aCount);
+ }
+
+ private:
+ // Append a new element without copy-constructing. This is useful to avoid
+ // temporaries.
+ // @return A pointer to the newly appended element, or null on OOM.
+ public:
+ [[nodiscard]] value_type* AppendElement(const mozilla::fallible_t&) {
+ return AppendElements(1, mozilla::fallible);
+ }
+
+ // This method removes a single element from this array, like
+ // std::vector::erase.
+ // @param pos to the element to remove
+ const_iterator RemoveElementAt(const_iterator pos) {
+ MOZ_ASSERT(pos.GetArray() == this);
+
+ RemoveElementAt(pos.GetIndex());
+ return pos;
+ }
+
+ // This method removes a range of elements from this array, like
+ // std::vector::erase.
+ // @param first iterator to the first of elements to remove
+ // @param last iterator to the last of elements to remove
+ const_iterator RemoveElementsRange(const_iterator first,
+ const_iterator last) {
+ MOZ_ASSERT(first.GetArray() == this);
+ MOZ_ASSERT(last.GetArray() == this);
+ MOZ_ASSERT(last.GetIndex() >= first.GetIndex());
+
+ RemoveElementsAt(first.GetIndex(), last.GetIndex() - first.GetIndex());
+ return first;
+ }
+
+ // This method removes a range of elements from this array.
+ // @param aStart The starting index of the elements to remove.
+ // @param aCount The number of elements to remove.
+ void RemoveElementsAt(index_type aStart, size_type aCount);
+
+ private:
+ // Remove a range of elements from this array, but do not check that
+ // the range is in bounds.
+ // @param aStart The starting index of the elements to remove.
+ // @param aCount The number of elements to remove.
+ void RemoveElementsAtUnsafe(index_type aStart, size_type aCount);
+
+ public:
+ // A variation on the RemoveElementsAt method defined above.
+ void RemoveElementAt(index_type aIndex) { RemoveElementsAt(aIndex, 1); }
+
+ // A variation on RemoveElementAt that removes the last element.
+ void RemoveLastElement() { RemoveLastElements(1); }
+
+ // A variation on RemoveElementsAt that removes the last 'aCount' elements.
+ void RemoveLastElements(const size_type aCount) {
+ // This assertion is redundant, but produces a better error message than the
+ // release assertion within TruncateLength.
+ MOZ_ASSERT(aCount <= Length());
+ TruncateLength(Length() - aCount);
+ }
+
+ // Removes the last element of the array and returns a copy of it.
+ [[nodiscard]] value_type PopLastElement() {
+ // This function intentionally does not call ElementsAt and calls
+ // TruncateLengthUnsafe directly to avoid multiple release checks for
+ // non-emptiness.
+ // This debug assertion is redundant, but produces a better error message
+ // than the release assertion below.
+ MOZ_ASSERT(!base_type::IsEmpty());
+ const size_type oldLen = Length();
+ if (MOZ_UNLIKELY(0 == oldLen)) {
+ mozilla::detail::InvalidArrayIndex_CRASH(1, 0);
+ }
+ value_type elem = std::move(Elements()[oldLen - 1]);
+ TruncateLengthUnsafe(oldLen - 1);
+ return elem;
+ }
+
+ // This method performs index-based removals from an array without preserving
+ // the order of the array. This is useful if you are using the array as a
+ // set-like data structure.
+ //
+ // These removals are efficient, as they move as few elements as possible. At
+ // most N elements, where N is the number of removed elements, will have to
+ // be relocated.
+ //
+ // ## Examples
+ //
+ // When removing an element from the end of the array, it can be removed in
+ // place, by destroying it and decrementing the length.
+ //
+ // [ 1, 2, 3 ] => [ 1, 2 ]
+ // ^
+ //
+ // When removing any other single element, it is removed by swapping it with
+ // the last element, and then decrementing the length as before.
+ //
+ // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 6, 3, 4, 5 ]
+ // ^
+ //
+ // This method also supports efficiently removing a range of elements. If they
+ // are at the end, then they can all be removed like in the one element case.
+ //
+ // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 2 ]
+ // ^--------^
+ //
+ // If more elements are removed than exist after the removed section, the
+ // remaining elements will be shifted down like in a normal removal.
+ //
+ // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 2, 7, 8 ]
+ // ^--------^
+ //
+ // And if fewer elements are removed than exist after the removed section,
+ // elements will be moved from the end of the array to fill the vacated space.
+ //
+ // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 7, 8, 4, 5, 6 ]
+ // ^--^
+ //
+ // @param aStart The starting index of the elements to remove. @param aCount
+ // The number of elements to remove.
+ void UnorderedRemoveElementsAt(index_type aStart, size_type aCount);
+
+ // A variation on the UnorderedRemoveElementsAt method defined above to remove
+ // a single element. This operation is sometimes called `SwapRemove`.
+ //
+ // This method is O(1), but does not preserve the order of the elements.
+ void UnorderedRemoveElementAt(index_type aIndex) {
+ UnorderedRemoveElementsAt(aIndex, 1);
+ }
+
+ void Clear() {
+ ClearAndRetainStorage();
+ base_type::ShrinkCapacityToZero(sizeof(value_type),
+ MOZ_ALIGNOF(value_type));
+ }
+
+ // This method removes elements based on the return value of the
+ // callback function aPredicate. If the function returns true for
+ // an element, the element is removed. aPredicate will be called
+ // for each element in order. It is not safe to access the array
+ // inside aPredicate.
+ //
+ // Returns the number of elements removed.
+ template <typename Predicate>
+ size_type RemoveElementsBy(Predicate aPredicate);
+
+ // This helper function combines IndexOf with RemoveElementAt to "search
+ // and destroy" the first element that is equal to the given element.
+ // @param aItem The item to search for.
+ // @param aComp The Comparator used to determine element equality.
+ // @return true if the element was found
+ template <class Item, class Comparator>
+ bool RemoveElement(const Item& aItem, const Comparator& aComp) {
+ index_type i = IndexOf(aItem, 0, aComp);
+ if (i == NoIndex) {
+ return false;
+ }
+
+ RemoveElementsAtUnsafe(i, 1);
+ return true;
+ }
+
+ // A variation on the RemoveElement method defined above that assumes
+ // that 'operator==' is defined for value_type.
+ template <class Item>
+ bool RemoveElement(const Item& aItem) {
+ return RemoveElement(aItem, nsDefaultComparator<value_type, Item>());
+ }
+
+ // This helper function combines IndexOfFirstElementGt with
+ // RemoveElementAt to "search and destroy" the last element that
+ // is equal to the given element.
+ // @param aItem The item to search for.
+ // @param aComp The Comparator used to determine element equality.
+ // @return true if the element was found
+ template <class Item, class Comparator>
+ bool RemoveElementSorted(const Item& aItem, const Comparator& aComp) {
+ index_type index = IndexOfFirstElementGt(aItem, aComp);
+ if (index > 0 && aComp.Equals(ElementAt(index - 1), aItem)) {
+ RemoveElementsAtUnsafe(index - 1, 1);
+ return true;
+ }
+ return false;
+ }
+
+ // A variation on the RemoveElementSorted method defined above.
+ template <class Item>
+ bool RemoveElementSorted(const Item& aItem) {
+ return RemoveElementSorted(aItem, nsDefaultComparator<value_type, Item>());
+ }
+
+ // This method causes the elements contained in this array and the given
+ // array to be swapped.
+ template <class Allocator>
+ void SwapElements(nsTArray_Impl<E, Allocator>& aOther) {
+ // The only case this might fail were if someone called this with a
+ // AutoTArray upcast to nsTArray_Impl, under the conditions mentioned in the
+ // overload for AutoTArray below.
+ this->template SwapArrayElements<InfallibleAlloc>(
+ aOther, sizeof(value_type), MOZ_ALIGNOF(value_type));
+ }
+
+ template <size_t N>
+ void SwapElements(AutoTArray<E, N>& aOther) {
+ // Allocation might fail if Alloc==FallibleAlloc and
+ // Allocator==InfallibleAlloc and aOther uses auto storage. Allow this for
+ // small inline sizes, and crash in the rare case of a small OOM error.
+ static_assert(!std::is_same_v<Alloc, FallibleAlloc> ||
+ sizeof(E) * N <= 1024);
+ this->template SwapArrayElements<InfallibleAlloc>(
+ aOther, sizeof(value_type), MOZ_ALIGNOF(value_type));
+ }
+
+ template <class Allocator>
+ [[nodiscard]] auto SwapElements(nsTArray_Impl<E, Allocator>& aOther,
+ const mozilla::fallible_t&) {
+ // Allocation might fail if Alloc==FallibleAlloc and
+ // Allocator==InfallibleAlloc and aOther uses auto storage.
+ return FallibleAlloc::Result(
+ this->template SwapArrayElements<FallibleAlloc>(
+ aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)));
+ }
+
+ private:
+ // Used by ApplyIf functions to invoke a callable that takes either:
+ // - Nothing: F(void)
+ // - Index only: F(size_t)
+ // - Reference to element only: F(maybe-const value_type&)
+ // - Both index and reference: F(size_t, maybe-const value_type&)
+ // `value_type` must be const when called from const method.
+ template <typename T, typename Param0, typename Param1>
+ struct InvokeWithIndexAndOrReferenceHelper {
+ static constexpr bool valid = false;
+ };
+ template <typename T>
+ struct InvokeWithIndexAndOrReferenceHelper<T, void, void> {
+ static constexpr bool valid = true;
+ template <typename F>
+ static auto Invoke(F&& f, size_t, T&) {
+ return f();
+ }
+ };
+ template <typename T>
+ struct InvokeWithIndexAndOrReferenceHelper<T, size_t, void> {
+ static constexpr bool valid = true;
+ template <typename F>
+ static auto Invoke(F&& f, size_t i, T&) {
+ return f(i);
+ }
+ };
+ template <typename T>
+ struct InvokeWithIndexAndOrReferenceHelper<T, T&, void> {
+ static constexpr bool valid = true;
+ template <typename F>
+ static auto Invoke(F&& f, size_t, T& e) {
+ return f(e);
+ }
+ };
+ template <typename T>
+ struct InvokeWithIndexAndOrReferenceHelper<T, const T&, void> {
+ static constexpr bool valid = true;
+ template <typename F>
+ static auto Invoke(F&& f, size_t, T& e) {
+ return f(e);
+ }
+ };
+ template <typename T>
+ struct InvokeWithIndexAndOrReferenceHelper<T, size_t, T&> {
+ static constexpr bool valid = true;
+ template <typename F>
+ static auto Invoke(F&& f, size_t i, T& e) {
+ return f(i, e);
+ }
+ };
+ template <typename T>
+ struct InvokeWithIndexAndOrReferenceHelper<T, size_t, const T&> {
+ static constexpr bool valid = true;
+ template <typename F>
+ static auto Invoke(F&& f, size_t i, T& e) {
+ return f(i, e);
+ }
+ };
+ template <typename T, typename F>
+ static auto InvokeWithIndexAndOrReference(F&& f, size_t i, T& e) {
+ using Invoker = InvokeWithIndexAndOrReferenceHelper<
+ T, typename mozilla::FunctionTypeTraits<F>::template ParameterType<0>,
+ typename mozilla::FunctionTypeTraits<F>::template ParameterType<1>>;
+ static_assert(Invoker::valid,
+ "ApplyIf's Function parameters must match either: (void), "
+ "(size_t), (maybe-const value_type&), or "
+ "(size_t, maybe-const value_type&)");
+ return Invoker::Invoke(std::forward<F>(f), i, e);
+ }
+
+ public:
+ // 'Apply' family of methods.
+ //
+ // The advantages of using Apply methods with lambdas include:
+ // - Safety of accessing elements from within the call, when the array cannot
+ // have been modified between the iteration and the subsequent access.
+ // - Avoiding moot conversions: pointer->index during a search, followed by
+ // index->pointer after the search when accessing the element.
+ // - Embedding your code into the algorithm, giving the compiler more chances
+ // to optimize.
+
+ // Search for the first element comparing equal to aItem with the given
+ // comparator (`==` by default).
+ // If such an element exists, return the result of evaluating either:
+ // - `aFunction()`
+ // - `aFunction(index_type)`
+ // - `aFunction(maybe-const? value_type&)`
+ // - `aFunction(index_type, maybe-const? value_type&)`
+ // (`aFunction` must have one of the above signatures with these exact types,
+ // including references; implicit conversions or generic types not allowed.
+ // If `this` array is const, the referenced `value_type` must be const too;
+ // otherwise it may be either const or non-const.)
+ // But if the element is not found, return the result of evaluating
+ // `aFunctionElse()`.
+ template <class Item, class Comparator, class Function, class FunctionElse>
+ auto ApplyIf(const Item& aItem, index_type aStart, const Comparator& aComp,
+ Function&& aFunction, FunctionElse&& aFunctionElse) const {
+ static_assert(
+ std::is_same_v<
+ typename mozilla::FunctionTypeTraits<Function>::ReturnType,
+ typename mozilla::FunctionTypeTraits<FunctionElse>::ReturnType>,
+ "ApplyIf's `Function` and `FunctionElse` must return the same type.");
+
+ ::detail::CompareWrapper<Comparator, Item> comp(aComp);
+
+ const value_type* const elements = Elements();
+ const value_type* const iend = elements + Length();
+ for (const value_type* iter = elements + aStart; iter != iend; ++iter) {
+ if (comp.Equals(*iter, aItem)) {
+ return InvokeWithIndexAndOrReference<const value_type>(
+ std::forward<Function>(aFunction), iter - elements, *iter);
+ }
+ }
+ return aFunctionElse();
+ }
+ template <class Item, class Comparator, class Function, class FunctionElse>
+ auto ApplyIf(const Item& aItem, index_type aStart, const Comparator& aComp,
+ Function&& aFunction, FunctionElse&& aFunctionElse) {
+ static_assert(
+ std::is_same_v<
+ typename mozilla::FunctionTypeTraits<Function>::ReturnType,
+ typename mozilla::FunctionTypeTraits<FunctionElse>::ReturnType>,
+ "ApplyIf's `Function` and `FunctionElse` must return the same type.");
+
+ ::detail::CompareWrapper<Comparator, Item> comp(aComp);
+
+ value_type* const elements = Elements();
+ value_type* const iend = elements + Length();
+ for (value_type* iter = elements + aStart; iter != iend; ++iter) {
+ if (comp.Equals(*iter, aItem)) {
+ return InvokeWithIndexAndOrReference<value_type>(
+ std::forward<Function>(aFunction), iter - elements, *iter);
+ }
+ }
+ return aFunctionElse();
+ }
+ template <class Item, class Function, class FunctionElse>
+ auto ApplyIf(const Item& aItem, index_type aStart, Function&& aFunction,
+ FunctionElse&& aFunctionElse) const {
+ return ApplyIf(aItem, aStart, nsDefaultComparator<value_type, Item>(),
+ std::forward<Function>(aFunction),
+ std::forward<FunctionElse>(aFunctionElse));
+ }
+ template <class Item, class Function, class FunctionElse>
+ auto ApplyIf(const Item& aItem, index_type aStart, Function&& aFunction,
+ FunctionElse&& aFunctionElse) {
+ return ApplyIf(aItem, aStart, nsDefaultComparator<value_type, Item>(),
+ std::forward<Function>(aFunction),
+ std::forward<FunctionElse>(aFunctionElse));
+ }
+ template <class Item, class Function, class FunctionElse>
+ auto ApplyIf(const Item& aItem, Function&& aFunction,
+ FunctionElse&& aFunctionElse) const {
+ return ApplyIf(aItem, 0, std::forward<Function>(aFunction),
+ std::forward<FunctionElse>(aFunctionElse));
+ }
+ template <class Item, class Function, class FunctionElse>
+ auto ApplyIf(const Item& aItem, Function&& aFunction,
+ FunctionElse&& aFunctionElse) {
+ return ApplyIf(aItem, 0, std::forward<Function>(aFunction),
+ std::forward<FunctionElse>(aFunctionElse));
+ }
+
+ //
+ // Allocation
+ //
+
+ // This method may increase the capacity of this array object to the
+ // specified amount. This method may be called in advance of several
+ // AppendElement operations to minimize heap re-allocations. This method
+ // will not reduce the number of elements in this array.
+ // @param aCapacity The desired capacity of this array.
+ // @return True if the operation succeeded; false if we ran out of memory
+ protected:
+ template <typename ActualAlloc = Alloc>
+ typename ActualAlloc::ResultType SetCapacity(size_type aCapacity) {
+ return ActualAlloc::Result(this->template EnsureCapacity<ActualAlloc>(
+ aCapacity, sizeof(value_type)));
+ }
+
+ public:
+ [[nodiscard]] bool SetCapacity(size_type aCapacity,
+ const mozilla::fallible_t&) {
+ return SetCapacity<FallibleAlloc>(aCapacity);
+ }
+
+ // This method modifies the length of the array. If the new length is
+ // larger than the existing length of the array, then new elements will be
+ // constructed using value_type's default constructor. Otherwise, this call
+ // removes elements from the array (see also RemoveElementsAt).
+ // @param aNewLen The desired length of this array.
+ // @return True if the operation succeeded; false otherwise.
+ // See also TruncateLength for a more efficient variant if the new length is
+ // guaranteed to be smaller than the old.
+ protected:
+ template <typename ActualAlloc = Alloc>
+ typename ActualAlloc::ResultType SetLength(size_type aNewLen) {
+ const size_type oldLen = Length();
+ if (aNewLen > oldLen) {
+ return ActualAlloc::ConvertBoolToResultType(
+ InsertElementsAtInternal<ActualAlloc>(oldLen, aNewLen - oldLen) !=
+ nullptr);
+ }
+
+ TruncateLengthUnsafe(aNewLen);
+ return ActualAlloc::ConvertBoolToResultType(true);
+ }
+
+ public:
+ [[nodiscard]] bool SetLength(size_type aNewLen, const mozilla::fallible_t&) {
+ return SetLength<FallibleAlloc>(aNewLen);
+ }
+
+ // This method modifies the length of the array, but may only be
+ // called when the new length is shorter than the old. It can
+ // therefore be called when value_type has no default constructor,
+ // unlike SetLength. It removes elements from the array (see also
+ // RemoveElementsAt).
+ // @param aNewLen The desired length of this array.
+ void TruncateLength(size_type aNewLen) {
+ // This assertion is redundant, but produces a better error message than the
+ // release assertion below.
+ MOZ_ASSERT(aNewLen <= Length(), "caller should use SetLength instead");
+
+ if (MOZ_UNLIKELY(aNewLen > Length())) {
+ mozilla::detail::InvalidArrayIndex_CRASH(aNewLen, Length());
+ }
+
+ TruncateLengthUnsafe(aNewLen);
+ }
+
+ private:
+ void TruncateLengthUnsafe(size_type aNewLen) {
+ const size_type oldLen = Length();
+ if (oldLen) {
+ DestructRange(aNewLen, oldLen - aNewLen);
+ base_type::mHdr->mLength = aNewLen;
+ }
+ }
+
+ // This method ensures that the array has length at least the given
+ // length. If the current length is shorter than the given length,
+ // then new elements will be constructed using value_type's default
+ // constructor.
+ // @param aMinLen The desired minimum length of this array.
+ // @return True if the operation succeeded; false otherwise.
+ protected:
+ template <typename ActualAlloc = Alloc>
+ typename ActualAlloc::ResultType EnsureLengthAtLeast(size_type aMinLen) {
+ size_type oldLen = Length();
+ if (aMinLen > oldLen) {
+ return ActualAlloc::ConvertBoolToResultType(
+ !!InsertElementsAtInternal<ActualAlloc>(oldLen, aMinLen - oldLen));
+ }
+ return ActualAlloc::ConvertBoolToResultType(true);
+ }
+
+ public:
+ [[nodiscard]] bool EnsureLengthAtLeast(size_type aMinLen,
+ const mozilla::fallible_t&) {
+ return EnsureLengthAtLeast<FallibleAlloc>(aMinLen);
+ }
+
+ // This method inserts elements into the array, constructing
+ // them using value_type's default constructor.
+ // @param aIndex the place to insert the new elements. This must be no
+ // greater than the current length of the array.
+ // @param aCount the number of elements to insert
+ private:
+ template <typename ActualAlloc>
+ value_type* InsertElementsAtInternal(index_type aIndex, size_type aCount) {
+ if (!ActualAlloc::Successful(this->template InsertSlotsAt<ActualAlloc>(
+ aIndex, aCount, sizeof(value_type), MOZ_ALIGNOF(value_type)))) {
+ return nullptr;
+ }
+
+ // Initialize the extra array elements
+ value_type* iter = Elements() + aIndex;
+ value_type* iend = iter + aCount;
+ for (; iter != iend; ++iter) {
+ elem_traits::Construct(iter);
+ }
+
+ return Elements() + aIndex;
+ }
+
+ public:
+ [[nodiscard]] value_type* InsertElementsAt(index_type aIndex,
+ size_type aCount,
+ const mozilla::fallible_t&) {
+ return InsertElementsAtInternal<FallibleAlloc>(aIndex, aCount);
+ }
+
+ // This method inserts elements into the array, constructing them
+ // value_type's copy constructor (or whatever one-arg constructor
+ // happens to match the Item type).
+ // @param aIndex the place to insert the new elements. This must be no
+ // greater than the current length of the array.
+ // @param aCount the number of elements to insert.
+ // @param aItem the value to use when constructing the new elements.
+ private:
+ template <typename ActualAlloc, class Item>
+ value_type* InsertElementsAtInternal(index_type aIndex, size_type aCount,
+ const Item& aItem);
+
+ public:
+ template <class Item>
+ [[nodiscard]] value_type* InsertElementsAt(index_type aIndex,
+ size_type aCount,
+ const Item& aItem,
+ const mozilla::fallible_t&) {
+ return InsertElementsAt<Item, FallibleAlloc>(aIndex, aCount, aItem);
+ }
+
+ // This method may be called to minimize the memory used by this array.
+ void Compact() {
+ ShrinkCapacity(sizeof(value_type), MOZ_ALIGNOF(value_type));
+ }
+
+ //
+ // Sorting
+ //
+
+ // This function is meant to be used with the NS_QuickSort function. It
+ // maps the callback API expected by NS_QuickSort to the Comparator API
+ // used by nsTArray_Impl. See nsTArray_Impl::Sort.
+ template <class Comparator>
+ static int Compare(const void* aE1, const void* aE2, void* aData) {
+ const Comparator* c = reinterpret_cast<const Comparator*>(aData);
+ const value_type* a = static_cast<const value_type*>(aE1);
+ const value_type* b = static_cast<const value_type*>(aE2);
+ return c->Compare(*a, *b);
+ }
+
+ // This method sorts the elements of the array. It uses the LessThan
+ // method defined on the given Comparator object to collate elements.
+ // @param aComp The Comparator used to collate elements.
+ template <class Comparator>
+ void Sort(const Comparator& aComp) {
+ ::detail::CompareWrapper<Comparator, value_type> comp(aComp);
+
+ NS_QuickSort(Elements(), Length(), sizeof(value_type),
+ Compare<decltype(comp)>, &comp);
+ }
+
+ // A variation on the Sort method defined above that assumes that
+ // 'operator<' is defined for value_type.
+ void Sort() { Sort(nsDefaultComparator<value_type, value_type>()); }
+
+ // This method sorts the elements of the array in a stable way (i.e. not
+ // changing the relative order of elements considered equal by the
+ // Comparator). It uses the LessThan
+ // method defined on the given Comparator object to collate elements.
+ // @param aComp The Comparator used to collate elements.
+ template <class Comparator>
+ void StableSort(const Comparator& aComp) {
+ const ::detail::CompareWrapper<Comparator, value_type> comp(aComp);
+
+ std::stable_sort(Elements(), Elements() + Length(),
+ [&comp](const auto& lhs, const auto& rhs) {
+ return comp.LessThan(lhs, rhs);
+ });
+ }
+
+ // This method reverses the array in place.
+ void Reverse() {
+ value_type* elements = Elements();
+ const size_type len = Length();
+ for (index_type i = 0, iend = len / 2; i < iend; ++i) {
+ std::swap(elements[i], elements[len - i - 1]);
+ }
+ }
+
+ protected:
+ using base_type::Hdr;
+ using base_type::ShrinkCapacity;
+
+ // This method invokes value_type's destructor on a range of elements.
+ // @param aStart The index of the first element to destroy.
+ // @param aCount The number of elements to destroy.
+ void DestructRange(index_type aStart, size_type aCount) {
+ value_type* iter = Elements() + aStart;
+ value_type* iend = iter + aCount;
+ for (; iter != iend; ++iter) {
+ elem_traits::Destruct(iter);
+ }
+ }
+
+ // This method invokes value_type's copy-constructor on a range of elements.
+ // @param aStart The index of the first element to construct.
+ // @param aCount The number of elements to construct.
+ // @param aValues The array of elements to copy.
+ template <class Item>
+ void AssignRange(index_type aStart, size_type aCount, const Item* aValues) {
+ AssignRangeAlgorithm<
+ std::is_trivially_copy_constructible_v<Item>,
+ std::is_same_v<Item, value_type>>::implementation(Elements(), aStart,
+ aCount, aValues);
+ }
+};
+
+template <typename E, class Alloc>
+template <typename ActualAlloc, class Item>
+auto nsTArray_Impl<E, Alloc>::AssignInternal(const Item* aArray,
+ size_type aArrayLen) ->
+ typename ActualAlloc::ResultType {
+ static_assert(std::is_same_v<ActualAlloc, InfallibleAlloc> ||
+ std::is_same_v<ActualAlloc, FallibleAlloc>);
+
+ if constexpr (std::is_same_v<ActualAlloc, InfallibleAlloc>) {
+ ClearAndRetainStorage();
+ }
+ // Adjust memory allocation up-front to catch errors in the fallible case.
+ // We might relocate the elements to be destroyed unnecessarily. This could be
+ // optimized, but would make things more complicated.
+ if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>(
+ aArrayLen, sizeof(value_type)))) {
+ return ActualAlloc::ConvertBoolToResultType(false);
+ }
+
+ MOZ_ASSERT_IF(this->HasEmptyHeader(), aArrayLen == 0);
+ if (!this->HasEmptyHeader()) {
+ if constexpr (std::is_same_v<ActualAlloc, FallibleAlloc>) {
+ ClearAndRetainStorage();
+ }
+ AssignRange(0, aArrayLen, aArray);
+ base_type::mHdr->mLength = aArrayLen;
+ }
+
+ return ActualAlloc::ConvertBoolToResultType(true);
+}
+
+template <typename E, class Alloc>
+template <typename ActualAlloc, class Item>
+auto nsTArray_Impl<E, Alloc>::ReplaceElementsAtInternal(index_type aStart,
+ size_type aCount,
+ const Item* aArray,
+ size_type aArrayLen)
+ -> value_type* {
+ if (MOZ_UNLIKELY(aStart > Length())) {
+ mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length());
+ }
+ if (MOZ_UNLIKELY(aCount > Length() - aStart)) {
+ mozilla::detail::InvalidArrayIndex_CRASH(aStart + aCount, Length());
+ }
+
+ // Adjust memory allocation up-front to catch errors.
+ if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>(
+ Length() + aArrayLen - aCount, sizeof(value_type)))) {
+ return nullptr;
+ }
+ DestructRange(aStart, aCount);
+ this->template ShiftData<ActualAlloc>(
+ aStart, aCount, aArrayLen, sizeof(value_type), MOZ_ALIGNOF(value_type));
+ AssignRange(aStart, aArrayLen, aArray);
+ return Elements() + aStart;
+}
+
+template <typename E, class Alloc>
+void nsTArray_Impl<E, Alloc>::RemoveElementsAt(index_type aStart,
+ size_type aCount) {
+ MOZ_ASSERT(aCount == 0 || aStart < Length(), "Invalid aStart index");
+
+ mozilla::CheckedInt<index_type> rangeEnd = aStart;
+ rangeEnd += aCount;
+
+ if (MOZ_UNLIKELY(!rangeEnd.isValid() || rangeEnd.value() > Length())) {
+ mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length());
+ }
+
+ RemoveElementsAtUnsafe(aStart, aCount);
+}
+
+template <typename E, class Alloc>
+void nsTArray_Impl<E, Alloc>::RemoveElementsAtUnsafe(index_type aStart,
+ size_type aCount) {
+ DestructRange(aStart, aCount);
+ this->template ShiftData<InfallibleAlloc>(
+ aStart, aCount, 0, sizeof(value_type), MOZ_ALIGNOF(value_type));
+}
+
+template <typename E, class Alloc>
+void nsTArray_Impl<E, Alloc>::UnorderedRemoveElementsAt(index_type aStart,
+ size_type aCount) {
+ MOZ_ASSERT(aCount == 0 || aStart < Length(), "Invalid aStart index");
+
+ mozilla::CheckedInt<index_type> rangeEnd = aStart;
+ rangeEnd += aCount;
+
+ if (MOZ_UNLIKELY(!rangeEnd.isValid() || rangeEnd.value() > Length())) {
+ mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length());
+ }
+
+ // Destroy the elements which are being removed, and then swap elements in to
+ // replace them from the end. See the docs on the declaration of this
+ // function.
+ DestructRange(aStart, aCount);
+ this->template SwapFromEnd<InfallibleAlloc>(
+ aStart, aCount, sizeof(value_type), MOZ_ALIGNOF(value_type));
+}
+
+template <typename E, class Alloc>
+template <typename Predicate>
+auto nsTArray_Impl<E, Alloc>::RemoveElementsBy(Predicate aPredicate)
+ -> size_type {
+ if (this->HasEmptyHeader()) {
+ return 0;
+ }
+
+ index_type j = 0;
+ const index_type len = Length();
+ value_type* const elements = Elements();
+ for (index_type i = 0; i < len; ++i) {
+ const bool result = aPredicate(elements[i]);
+
+ // Check that the array has not been modified by the predicate.
+ MOZ_DIAGNOSTIC_ASSERT(len == base_type::mHdr->mLength &&
+ elements == Elements());
+
+ if (result) {
+ elem_traits::Destruct(elements + i);
+ } else {
+ if (j < i) {
+ relocation_type::RelocateNonOverlappingRegion(
+ elements + j, elements + i, 1, sizeof(value_type));
+ }
+ ++j;
+ }
+ }
+
+ base_type::mHdr->mLength = j;
+ return len - j;
+}
+
+template <typename E, class Alloc>
+template <typename ActualAlloc, class Item>
+auto nsTArray_Impl<E, Alloc>::InsertElementsAtInternal(index_type aIndex,
+ size_type aCount,
+ const Item& aItem)
+ -> value_type* {
+ if (!ActualAlloc::Successful(this->template InsertSlotsAt<ActualAlloc>(
+ aIndex, aCount, sizeof(value_type), MOZ_ALIGNOF(value_type)))) {
+ return nullptr;
+ }
+
+ // Initialize the extra array elements
+ value_type* iter = Elements() + aIndex;
+ value_type* iend = iter + aCount;
+ for (; iter != iend; ++iter) {
+ elem_traits::Construct(iter, aItem);
+ }
+
+ return Elements() + aIndex;
+}
+
+template <typename E, class Alloc>
+template <typename ActualAlloc>
+auto nsTArray_Impl<E, Alloc>::InsertElementAtInternal(index_type aIndex)
+ -> value_type* {
+ if (MOZ_UNLIKELY(aIndex > Length())) {
+ mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length());
+ }
+
+ // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK.
+ if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>(
+ Length() + 1, sizeof(value_type)))) {
+ return nullptr;
+ }
+ this->template ShiftData<ActualAlloc>(aIndex, 0, 1, sizeof(value_type),
+ MOZ_ALIGNOF(value_type));
+ value_type* elem = Elements() + aIndex;
+ elem_traits::Construct(elem);
+ return elem;
+}
+
+template <typename E, class Alloc>
+template <typename ActualAlloc, class Item>
+auto nsTArray_Impl<E, Alloc>::InsertElementAtInternal(index_type aIndex,
+ Item&& aItem)
+ -> value_type* {
+ if (MOZ_UNLIKELY(aIndex > Length())) {
+ mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length());
+ }
+
+ // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK.
+ if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>(
+ Length() + 1, sizeof(value_type)))) {
+ return nullptr;
+ }
+ this->template ShiftData<ActualAlloc>(aIndex, 0, 1, sizeof(value_type),
+ MOZ_ALIGNOF(value_type));
+ value_type* elem = Elements() + aIndex;
+ elem_traits::Construct(elem, std::forward<Item>(aItem));
+ return elem;
+}
+
+template <typename E, class Alloc>
+template <typename ActualAlloc, class Item>
+auto nsTArray_Impl<E, Alloc>::AppendElementsInternal(const Item* aArray,
+ size_type aArrayLen)
+ -> value_type* {
+ if (!ActualAlloc::Successful(this->template ExtendCapacity<ActualAlloc>(
+ Length(), aArrayLen, sizeof(value_type)))) {
+ return nullptr;
+ }
+ index_type len = Length();
+ AssignRange(len, aArrayLen, aArray);
+ this->IncrementLength(aArrayLen);
+ return Elements() + len;
+}
+
+template <typename E, class Alloc>
+template <typename ActualAlloc, class Item, class Allocator>
+auto nsTArray_Impl<E, Alloc>::AppendElementsInternal(
+ nsTArray_Impl<Item, Allocator>&& aArray) -> value_type* {
+ if constexpr (std::is_same_v<Alloc, Allocator>) {
+ MOZ_ASSERT(&aArray != this, "argument must be different aArray");
+ }
+ if (Length() == 0) {
+ // XXX This might still be optimized. If aArray uses auto-storage but we
+ // won't, we might better retain our storage if it's sufficiently large.
+ this->ShrinkCapacityToZero(sizeof(value_type), MOZ_ALIGNOF(value_type));
+ this->MoveInit(aArray, sizeof(value_type), MOZ_ALIGNOF(value_type));
+ return Elements();
+ }
+
+ index_type len = Length();
+ index_type otherLen = aArray.Length();
+ if (!ActualAlloc::Successful(this->template ExtendCapacity<ActualAlloc>(
+ len, otherLen, sizeof(value_type)))) {
+ return nullptr;
+ }
+ relocation_type::RelocateNonOverlappingRegion(
+ Elements() + len, aArray.Elements(), otherLen, sizeof(value_type));
+ this->IncrementLength(otherLen);
+ aArray.template ShiftData<ActualAlloc>(0, otherLen, 0, sizeof(value_type),
+ MOZ_ALIGNOF(value_type));
+ return Elements() + len;
+}
+
+template <typename E, class Alloc>
+template <typename ActualAlloc, class Item>
+auto nsTArray_Impl<E, Alloc>::AppendElementInternal(Item&& aItem)
+ -> value_type* {
+ // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK.
+ if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>(
+ Length() + 1, sizeof(value_type)))) {
+ return nullptr;
+ }
+ value_type* elem = Elements() + Length();
+ elem_traits::Construct(elem, std::forward<Item>(aItem));
+ this->mHdr->mLength += 1;
+ return elem;
+}
+
+template <typename E, class Alloc>
+template <typename ActualAlloc, class... Args>
+auto nsTArray_Impl<E, Alloc>::EmplaceBackInternal(Args&&... aArgs)
+ -> value_type* {
+ // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK.
+ if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>(
+ Length() + 1, sizeof(value_type)))) {
+ return nullptr;
+ }
+ value_type* elem = Elements() + Length();
+ elem_traits::Emplace(elem, std::forward<Args>(aArgs)...);
+ this->mHdr->mLength += 1;
+ return elem;
+}
+
+template <typename E, typename Alloc>
+inline void ImplCycleCollectionUnlink(nsTArray_Impl<E, Alloc>& aField) {
+ aField.Clear();
+}
+
+namespace detail {
+// This is defined in the cpp file to avoid including
+// nsCycleCollectionNoteChild.h in this header file.
+void SetCycleCollectionArrayFlag(uint32_t& aFlags);
+} // namespace detail
+
+template <typename E, typename Alloc>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ nsTArray_Impl<E, Alloc>& aField, const char* aName, uint32_t aFlags = 0) {
+ ::detail::SetCycleCollectionArrayFlag(aFlags);
+ size_t length = aField.Length();
+ for (size_t i = 0; i < length; ++i) {
+ ImplCycleCollectionTraverse(aCallback, aField[i], aName, aFlags);
+ }
+}
+
+//
+// nsTArray is an infallible vector class. See the comment at the top of this
+// file for more details.
+//
+template <class E>
+class nsTArray : public nsTArray_Impl<E, nsTArrayInfallibleAllocator> {
+ public:
+ using InfallibleAlloc = nsTArrayInfallibleAllocator;
+ using base_type = nsTArray_Impl<E, InfallibleAlloc>;
+ using self_type = nsTArray<E>;
+ using typename base_type::index_type;
+ using typename base_type::size_type;
+ using typename base_type::value_type;
+
+ nsTArray() {}
+ explicit nsTArray(size_type aCapacity) : base_type(aCapacity) {}
+ MOZ_IMPLICIT nsTArray(std::initializer_list<E> aIL) {
+ AppendElements(aIL.begin(), aIL.size());
+ }
+
+ template <class Item>
+ nsTArray(const Item* aArray, size_type aArrayLen) {
+ AppendElements(aArray, aArrayLen);
+ }
+
+ template <class Item>
+ explicit nsTArray(mozilla::Span<Item> aSpan) {
+ AppendElements(aSpan);
+ }
+
+ template <class Allocator>
+ explicit nsTArray(const nsTArray_Impl<E, Allocator>& aOther)
+ : base_type(aOther) {}
+ template <class Allocator>
+ MOZ_IMPLICIT nsTArray(nsTArray_Impl<E, Allocator>&& aOther)
+ : base_type(std::move(aOther)) {}
+
+ template <class Allocator>
+ self_type& operator=(const nsTArray_Impl<E, Allocator>& aOther) {
+ base_type::operator=(aOther);
+ return *this;
+ }
+ template <class Allocator>
+ self_type& operator=(nsTArray_Impl<E, Allocator>&& aOther) {
+ // This is quite complex, since we don't know if we are an AutoTArray. While
+ // AutoTArray overrides this operator=, this might be called on a nsTArray&
+ // bound to an AutoTArray.
+ base_type::operator=(std::move(aOther));
+ return *this;
+ }
+
+ using base_type::AppendElement;
+ using base_type::AppendElements;
+ using base_type::EmplaceBack;
+ using base_type::EnsureLengthAtLeast;
+ using base_type::InsertElementAt;
+ using base_type::InsertElementsAt;
+ using base_type::InsertElementSorted;
+ using base_type::ReplaceElementsAt;
+ using base_type::SetCapacity;
+ using base_type::SetLength;
+
+ template <class Item>
+ mozilla::NotNull<value_type*> AppendElements(const Item* aArray,
+ size_type aArrayLen) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template AppendElementsInternal<InfallibleAlloc>(aArray,
+ aArrayLen));
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> AppendElements(mozilla::Span<Item> aSpan) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template AppendElementsInternal<InfallibleAlloc>(aSpan.Elements(),
+ aSpan.Length()));
+ }
+
+ template <class Item, class Allocator>
+ mozilla::NotNull<value_type*> AppendElements(
+ const nsTArray_Impl<Item, Allocator>& aArray) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template AppendElementsInternal<InfallibleAlloc>(
+ aArray.Elements(), aArray.Length()));
+ }
+
+ template <class Item, class Allocator>
+ mozilla::NotNull<value_type*> AppendElements(
+ nsTArray_Impl<Item, Allocator>&& aArray) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template AppendElementsInternal<InfallibleAlloc>(
+ std::move(aArray)));
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> AppendElement(Item&& aItem) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template AppendElementInternal<InfallibleAlloc>(
+ std::forward<Item>(aItem)));
+ }
+
+ mozilla::NotNull<value_type*> AppendElements(size_type aCount) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template AppendElementsInternal<InfallibleAlloc>(aCount));
+ }
+
+ mozilla::NotNull<value_type*> AppendElement() {
+ return mozilla::WrapNotNullUnchecked(
+ this->template AppendElementsInternal<InfallibleAlloc>(1));
+ }
+
+ self_type Clone() const {
+ self_type result;
+ result.Assign(*this);
+ return result;
+ }
+
+ mozilla::NotNull<value_type*> InsertElementsAt(index_type aIndex,
+ size_type aCount) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template InsertElementsAtInternal<InfallibleAlloc>(aIndex,
+ aCount));
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> InsertElementsAt(index_type aIndex,
+ size_type aCount,
+ const Item& aItem) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template InsertElementsAtInternal<InfallibleAlloc>(aIndex, aCount,
+ aItem));
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> InsertElementsAt(index_type aIndex,
+ const Item* aArray,
+ size_type aArrayLen) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template ReplaceElementsAtInternal<InfallibleAlloc>(
+ aIndex, 0, aArray, aArrayLen));
+ }
+
+ template <class Item, class Allocator>
+ mozilla::NotNull<value_type*> InsertElementsAt(
+ index_type aIndex, const nsTArray_Impl<Item, Allocator>& aArray) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template ReplaceElementsAtInternal<InfallibleAlloc>(
+ aIndex, 0, aArray.Elements(), aArray.Length()));
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> InsertElementsAt(index_type aIndex,
+ mozilla::Span<Item> aSpan) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template ReplaceElementsAtInternal<InfallibleAlloc>(
+ aIndex, 0, aSpan.Elements(), aSpan.Length()));
+ }
+
+ mozilla::NotNull<value_type*> InsertElementAt(index_type aIndex) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template InsertElementAtInternal<InfallibleAlloc>(aIndex));
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> InsertElementAt(index_type aIndex,
+ Item&& aItem) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template InsertElementAtInternal<InfallibleAlloc>(
+ aIndex, std::forward<Item>(aItem)));
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> ReplaceElementsAt(index_type aStart,
+ size_type aCount,
+ const Item* aArray,
+ size_type aArrayLen) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template ReplaceElementsAtInternal<InfallibleAlloc>(
+ aStart, aCount, aArray, aArrayLen));
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> ReplaceElementsAt(
+ index_type aStart, size_type aCount, const nsTArray<Item>& aArray) {
+ return ReplaceElementsAt(aStart, aCount, aArray.Elements(),
+ aArray.Length());
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> ReplaceElementsAt(index_type aStart,
+ size_type aCount,
+ mozilla::Span<Item> aSpan) {
+ return ReplaceElementsAt(aStart, aCount, aSpan.Elements(), aSpan.Length());
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> ReplaceElementsAt(index_type aStart,
+ size_type aCount,
+ const Item& aItem) {
+ return ReplaceElementsAt(aStart, aCount, &aItem, 1);
+ }
+
+ template <class Item, class Comparator>
+ mozilla::NotNull<value_type*> InsertElementSorted(Item&& aItem,
+ const Comparator& aComp) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template InsertElementSortedInternal<InfallibleAlloc>(
+ std::forward<Item>(aItem), aComp));
+ }
+
+ template <class Item>
+ mozilla::NotNull<value_type*> InsertElementSorted(Item&& aItem) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template InsertElementSortedInternal<InfallibleAlloc>(
+ std::forward<Item>(aItem),
+ nsDefaultComparator<value_type, Item>{}));
+ }
+
+ template <class... Args>
+ mozilla::NotNull<value_type*> EmplaceBack(Args&&... aArgs) {
+ return mozilla::WrapNotNullUnchecked(
+ this->template EmplaceBackInternal<InfallibleAlloc, Args...>(
+ std::forward<Args>(aArgs)...));
+ }
+};
+
+template <class E>
+class CopyableTArray : public nsTArray<E> {
+ public:
+ using nsTArray<E>::nsTArray;
+
+ CopyableTArray(const CopyableTArray& aOther) : nsTArray<E>() {
+ this->Assign(aOther);
+ }
+ CopyableTArray& operator=(const CopyableTArray& aOther) {
+ if (this != &aOther) {
+ this->Assign(aOther);
+ }
+ return *this;
+ }
+ template <typename Allocator>
+ MOZ_IMPLICIT CopyableTArray(const nsTArray_Impl<E, Allocator>& aOther) {
+ this->Assign(aOther);
+ }
+ template <typename Allocator>
+ CopyableTArray& operator=(const nsTArray_Impl<E, Allocator>& aOther) {
+ if constexpr (std::is_same_v<Allocator, nsTArrayInfallibleAllocator>) {
+ if (this == &aOther) {
+ return *this;
+ }
+ }
+ this->Assign(aOther);
+ return *this;
+ }
+ template <typename Allocator>
+ MOZ_IMPLICIT CopyableTArray(nsTArray_Impl<E, Allocator>&& aOther)
+ : nsTArray<E>{std::move(aOther)} {}
+ template <typename Allocator>
+ CopyableTArray& operator=(nsTArray_Impl<E, Allocator>&& aOther) {
+ static_cast<nsTArray<E>&>(*this) = std::move(aOther);
+ return *this;
+ }
+
+ CopyableTArray(CopyableTArray&&) = default;
+ CopyableTArray& operator=(CopyableTArray&&) = default;
+};
+
+//
+// FallibleTArray is a fallible vector class.
+//
+template <class E>
+class FallibleTArray : public nsTArray_Impl<E, nsTArrayFallibleAllocator> {
+ public:
+ typedef nsTArray_Impl<E, nsTArrayFallibleAllocator> base_type;
+ typedef FallibleTArray<E> self_type;
+ typedef typename base_type::size_type size_type;
+
+ FallibleTArray() = default;
+ explicit FallibleTArray(size_type aCapacity) : base_type(aCapacity) {}
+
+ template <class Allocator>
+ explicit FallibleTArray(const nsTArray_Impl<E, Allocator>& aOther)
+ : base_type(aOther) {}
+ template <class Allocator>
+ explicit FallibleTArray(nsTArray_Impl<E, Allocator>&& aOther)
+ : base_type(std::move(aOther)) {}
+
+ template <class Allocator>
+ self_type& operator=(const nsTArray_Impl<E, Allocator>& aOther) {
+ base_type::operator=(aOther);
+ return *this;
+ }
+ template <class Allocator>
+ self_type& operator=(nsTArray_Impl<E, Allocator>&& aOther) {
+ base_type::operator=(std::move(aOther));
+ return *this;
+ }
+};
+
+//
+// AutoTArray<E, N> is like nsTArray<E>, but with N elements of inline storage.
+// Storing more than N elements is fine, but it will cause a heap allocation.
+//
+template <class E, size_t N>
+class MOZ_NON_MEMMOVABLE AutoTArray : public nsTArray<E> {
+ static_assert(N != 0, "AutoTArray<E, 0> should be specialized");
+
+ public:
+ typedef AutoTArray<E, N> self_type;
+ typedef nsTArray<E> base_type;
+ typedef typename base_type::Header Header;
+ typedef typename base_type::value_type value_type;
+
+ AutoTArray() : mAlign() { Init(); }
+
+ AutoTArray(self_type&& aOther) : nsTArray<E>() {
+ Init();
+ this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type));
+ }
+
+ explicit AutoTArray(base_type&& aOther) : mAlign() {
+ Init();
+ this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type));
+ }
+
+ template <typename Allocator>
+ explicit AutoTArray(nsTArray_Impl<value_type, Allocator>&& aOther) {
+ Init();
+ this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type));
+ }
+
+ MOZ_IMPLICIT AutoTArray(std::initializer_list<E> aIL) : mAlign() {
+ Init();
+ this->AppendElements(aIL.begin(), aIL.size());
+ }
+
+ self_type& operator=(self_type&& aOther) {
+ base_type::operator=(std::move(aOther));
+ return *this;
+ }
+
+ template <typename Allocator>
+ self_type& operator=(nsTArray_Impl<value_type, Allocator>&& aOther) {
+ base_type::operator=(std::move(aOther));
+ return *this;
+ }
+
+ // Intentionally hides nsTArray_Impl::Clone to make clones usually be
+ // AutoTArray as well.
+ self_type Clone() const {
+ self_type result;
+ result.Assign(*this);
+ return result;
+ }
+
+ private:
+ // nsTArray_base casts itself as an nsAutoArrayBase in order to get a pointer
+ // to mAutoBuf.
+ template <class Allocator, class RelocationStrategy>
+ friend class nsTArray_base;
+
+ void Init() {
+ static_assert(MOZ_ALIGNOF(value_type) <= 8,
+ "can't handle alignments greater than 8, "
+ "see nsTArray_base::UsesAutoArrayBuffer()");
+ // Temporary work around for VS2012 RC compiler crash
+ Header** phdr = base_type::PtrToHdr();
+ *phdr = reinterpret_cast<Header*>(&mAutoBuf);
+ (*phdr)->mLength = 0;
+ (*phdr)->mCapacity = N;
+ (*phdr)->mIsAutoArray = 1;
+
+ MOZ_ASSERT(base_type::GetAutoArrayBuffer(MOZ_ALIGNOF(value_type)) ==
+ reinterpret_cast<Header*>(&mAutoBuf),
+ "GetAutoArrayBuffer needs to be fixed");
+ }
+
+ // Declare mAutoBuf aligned to the maximum of the header's alignment and
+ // value_type's alignment. We need to use a union rather than
+ // MOZ_ALIGNED_DECL because GCC is picky about what goes into
+ // __attribute__((aligned(foo))).
+ union {
+ char mAutoBuf[sizeof(nsTArrayHeader) + N * sizeof(value_type)];
+ // Do the max operation inline to ensure that it is a compile-time constant.
+ mozilla::AlignedElem<(MOZ_ALIGNOF(Header) > MOZ_ALIGNOF(value_type))
+ ? MOZ_ALIGNOF(Header)
+ : MOZ_ALIGNOF(value_type)>
+ mAlign;
+ };
+};
+
+//
+// Specialization of AutoTArray<E, N> for the case where N == 0.
+// AutoTArray<E, 0> behaves exactly like nsTArray<E>, but without this
+// specialization, it stores a useless inline header.
+//
+// We do have many AutoTArray<E, 0> objects in memory: about 2,000 per tab as
+// of May 2014. These are typically not explicitly AutoTArray<E, 0> but rather
+// AutoTArray<E, N> for some value N depending on template parameters, in
+// generic code.
+//
+// For that reason, we optimize this case with the below partial specialization,
+// which ensures that AutoTArray<E, 0> is just like nsTArray<E>, without any
+// inline header overhead.
+//
+template <class E>
+class AutoTArray<E, 0> : public nsTArray<E> {
+ using nsTArray<E>::nsTArray;
+};
+
+template <class E, size_t N>
+struct nsTArray_RelocationStrategy<AutoTArray<E, N>> {
+ using Type = nsTArray_RelocateUsingMoveConstructor<AutoTArray<E, N>>;
+};
+
+template <class E, size_t N>
+class CopyableAutoTArray : public AutoTArray<E, N> {
+ public:
+ typedef CopyableAutoTArray<E, N> self_type;
+ using AutoTArray<E, N>::AutoTArray;
+
+ CopyableAutoTArray(const CopyableAutoTArray& aOther) : AutoTArray<E, N>() {
+ this->Assign(aOther);
+ }
+ CopyableAutoTArray& operator=(const CopyableAutoTArray& aOther) {
+ if (this != &aOther) {
+ this->Assign(aOther);
+ }
+ return *this;
+ }
+ template <typename Allocator>
+ MOZ_IMPLICIT CopyableAutoTArray(const nsTArray_Impl<E, Allocator>& aOther) {
+ this->Assign(aOther);
+ }
+ template <typename Allocator>
+ CopyableAutoTArray& operator=(const nsTArray_Impl<E, Allocator>& aOther) {
+ if constexpr (std::is_same_v<Allocator, nsTArrayInfallibleAllocator>) {
+ if (this == &aOther) {
+ return *this;
+ }
+ }
+ this->Assign(aOther);
+ return *this;
+ }
+ template <typename Allocator>
+ MOZ_IMPLICIT CopyableAutoTArray(nsTArray_Impl<E, Allocator>&& aOther)
+ : AutoTArray<E, N>{std::move(aOther)} {}
+ template <typename Allocator>
+ CopyableAutoTArray& operator=(nsTArray_Impl<E, Allocator>&& aOther) {
+ static_cast<AutoTArray<E, N>&>(*this) = std::move(aOther);
+ return *this;
+ }
+
+ // CopyableTArray exists for cases where an explicit Clone is not possible.
+ // These uses should not be mixed, so we delete Clone() here.
+ self_type Clone() const = delete;
+
+ CopyableAutoTArray(CopyableAutoTArray&&) = default;
+ CopyableAutoTArray& operator=(CopyableAutoTArray&&) = default;
+};
+
+namespace mozilla {
+template <typename E, typename ArrayT>
+class nsTArrayBackInserter {
+ ArrayT* mArray;
+
+ class Proxy {
+ ArrayT& mArray;
+
+ public:
+ explicit Proxy(ArrayT& aArray) : mArray{aArray} {}
+
+ template <typename E2>
+ void operator=(E2&& aValue) {
+ mArray.AppendElement(std::forward<E2>(aValue));
+ }
+ };
+
+ public:
+ using iterator_category = std::output_iterator_tag;
+ using value_type = void;
+ using difference_type = void;
+ using pointer = void;
+ using reference = void;
+ explicit nsTArrayBackInserter(ArrayT& aArray) : mArray{&aArray} {}
+
+ // Return a proxy so that nsTArrayBackInserter has the default special member
+ // functions, and the operator= template is defined in Proxy rather than this
+ // class (which otherwise breaks with recent MS STL versions).
+ // See also Bug 1331137, comment 11.
+ Proxy operator*() { return Proxy(*mArray); }
+
+ nsTArrayBackInserter& operator++() { return *this; }
+ nsTArrayBackInserter& operator++(int) { return *this; }
+};
+} // namespace mozilla
+
+template <typename E>
+auto MakeBackInserter(nsTArray<E>& aArray) {
+ return mozilla::nsTArrayBackInserter<E, nsTArray<E>>{aArray};
+}
+
+// Span integration
+namespace mozilla {
+template <typename E, class Alloc>
+Span(nsTArray_Impl<E, Alloc>&) -> Span<E>;
+
+template <typename E, class Alloc>
+Span(const nsTArray_Impl<E, Alloc>&) -> Span<const E>;
+
+// Provides a view on a nsTArray through which the existing array elements can
+// be accessed in a non-const way, but the array itself cannot be modified, so
+// that references to elements are guaranteed to be stable.
+template <typename E>
+class nsTArrayView {
+ public:
+ using element_type = E;
+ using pointer = element_type*;
+ using reference = element_type&;
+ using index_type = typename Span<element_type>::index_type;
+ using size_type = typename Span<element_type>::index_type;
+
+ explicit nsTArrayView(nsTArray<element_type> aArray)
+ : mArray(std::move(aArray)), mSpan(mArray) {}
+
+ element_type& operator[](index_type aIndex) { return mSpan[aIndex]; }
+
+ const element_type& operator[](index_type aIndex) const {
+ return mSpan[aIndex];
+ }
+
+ size_type Length() const { return mSpan.Length(); }
+
+ auto begin() { return mSpan.begin(); }
+ auto end() { return mSpan.end(); }
+ auto begin() const { return mSpan.begin(); }
+ auto end() const { return mSpan.end(); }
+ auto cbegin() const { return mSpan.cbegin(); }
+ auto cend() const { return mSpan.cend(); }
+
+ Span<element_type> AsSpan() { return mSpan; }
+ Span<const element_type> AsSpan() const { return mSpan; }
+
+ private:
+ nsTArray<element_type> mArray;
+ const Span<element_type> mSpan;
+};
+
+template <typename Range, typename = std::enable_if_t<std::is_same_v<
+ typename std::iterator_traits<
+ typename Range::iterator>::iterator_category,
+ std::random_access_iterator_tag>>>
+auto RangeSize(const Range& aRange) {
+ // See https://en.cppreference.com/w/cpp/iterator/begin, section 'User-defined
+ // overloads'.
+ using std::begin;
+ using std::end;
+
+ return std::distance(begin(aRange), end(aRange));
+}
+
+/**
+ * Materialize a range as a nsTArray (or a compatible variant, like AutoTArray)
+ * of an explicitly specified type. The array value type must be implicitly
+ * convertible from the range's value type.
+ */
+template <typename Array, typename Range>
+auto ToTArray(const Range& aRange) {
+ using std::begin;
+ using std::end;
+
+ Array res;
+ res.SetCapacity(RangeSize(aRange));
+ std::copy(begin(aRange), end(aRange), MakeBackInserter(res));
+ return res;
+}
+
+/**
+ * Materialize a range as a nsTArray of its (decayed) value type.
+ */
+template <typename Range>
+auto ToArray(const Range& aRange) {
+ return ToTArray<nsTArray<std::decay_t<
+ typename std::iterator_traits<typename Range::iterator>::value_type>>>(
+ aRange);
+}
+
+/**
+ * Appends all elements from a range to an array.
+ */
+template <typename Array, typename Range>
+void AppendToArray(Array& aArray, const Range& aRange) {
+ using std::begin;
+ using std::end;
+
+ aArray.SetCapacity(aArray.Length() + RangeSize(aRange));
+ std::copy(begin(aRange), end(aRange), MakeBackInserter(aArray));
+}
+
+} // namespace mozilla
+
+// MOZ_DBG support
+
+template <class E, class Alloc>
+std::ostream& operator<<(std::ostream& aOut,
+ const nsTArray_Impl<E, Alloc>& aTArray) {
+ return aOut << mozilla::Span(aTArray);
+}
+
+// Assert that AutoTArray doesn't have any extra padding inside.
+//
+// It's important that the data stored in this auto array takes up a multiple of
+// 8 bytes; e.g. AutoTArray<uint32_t, 1> wouldn't work. Since AutoTArray
+// contains a pointer, its size must be a multiple of alignof(void*). (This is
+// because any type may be placed into an array, and there's no padding between
+// elements of an array.) The compiler pads the end of the structure to
+// enforce this rule.
+//
+// If we used AutoTArray<uint32_t, 1> below, this assertion would fail on a
+// 64-bit system, where the compiler inserts 4 bytes of padding at the end of
+// the auto array to make its size a multiple of alignof(void*) == 8 bytes.
+
+static_assert(sizeof(AutoTArray<uint32_t, 2>) ==
+ sizeof(void*) + sizeof(nsTArrayHeader) + sizeof(uint32_t) * 2,
+ "AutoTArray shouldn't contain any extra padding, "
+ "see the comment");
+
+// Definitions of nsTArray_Impl methods
+#include "nsTArray-inl.h"
+
+#endif // nsTArray_h__
diff --git a/xpcom/ds/nsTArrayForwardDeclare.h b/xpcom/ds/nsTArrayForwardDeclare.h
new file mode 100644
index 0000000000..f888550803
--- /dev/null
+++ b/xpcom/ds/nsTArrayForwardDeclare.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTArrayForwardDeclare_h__
+#define nsTArrayForwardDeclare_h__
+
+//
+// This simple header file contains forward declarations for the TArray family
+// of classes.
+//
+// Including this header is preferable to writing
+//
+// template<class E> class nsTArray;
+//
+// yourself, since this header gives us flexibility to e.g. change the default
+// template parameters.
+//
+
+#include <stddef.h>
+
+template <class E>
+class nsTArray;
+
+template <class E>
+class FallibleTArray;
+
+template <class E>
+class CopyableTArray;
+
+template <class E, size_t N>
+class AutoTArray;
+
+template <class E, size_t N>
+class CopyableAutoTArray;
+
+#endif
diff --git a/xpcom/ds/nsTHashMap.h b/xpcom/ds/nsTHashMap.h
new file mode 100644
index 0000000000..8357d2ae7b
--- /dev/null
+++ b/xpcom/ds/nsTHashMap.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef XPCOM_DS_NSTHASHMAP_H_
+#define XPCOM_DS_NSTHASHMAP_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsBaseHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsHashtablesFwd.h"
+#include <type_traits>
+
+namespace mozilla::detail {
+template <class KeyType>
+struct nsKeyClass<
+ KeyType, std::enable_if_t<sizeof(KeyType) &&
+ std::is_base_of_v<PLDHashEntryHdr, KeyType>>> {
+ using type = KeyType;
+};
+
+template <typename KeyType>
+struct nsKeyClass<KeyType*> {
+ using type = nsPtrHashKey<KeyType>;
+};
+
+template <>
+struct nsKeyClass<nsCString> {
+ using type = nsCStringHashKey;
+};
+
+// This uses the case-sensitive hash key class, if you want the
+// case-insensitive hash key, use nsStringCaseInsensitiveHashKey explicitly.
+template <>
+struct nsKeyClass<nsString> {
+ using type = nsStringHashKey;
+};
+
+template <typename KeyType>
+struct nsKeyClass<KeyType, std::enable_if_t<std::is_integral_v<KeyType> ||
+ std::is_enum_v<KeyType>>> {
+ using type = nsIntegralHashKey<KeyType>;
+};
+
+template <>
+struct nsKeyClass<nsCOMPtr<nsISupports>> {
+ using type = nsISupportsHashKey;
+};
+
+template <typename T>
+struct nsKeyClass<RefPtr<T>> {
+ using type = nsRefPtrHashKey<T>;
+};
+
+template <>
+struct nsKeyClass<nsID> {
+ using type = nsIDHashKey;
+};
+
+} // namespace mozilla::detail
+
+// The actual definition of nsTHashMap is in nsHashtablesFwd.h, since it is a
+// type alias.
+
+#endif // XPCOM_DS_NSTHASHMAP_H_
diff --git a/xpcom/ds/nsTHashSet.h b/xpcom/ds/nsTHashSet.h
new file mode 100644
index 0000000000..4d905299db
--- /dev/null
+++ b/xpcom/ds/nsTHashSet.h
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef XPCOM_DS_NSTHASHSET_H_
+#define XPCOM_DS_NSTHASHSET_H_
+
+#include "nsHashtablesFwd.h"
+#include "nsTHashMap.h" // for nsKeyClass
+
+/**
+ * Templated hash set. Don't use this directly, but use nsTHashSet instead
+ * (defined as a type alias in nsHashtablesFwd.h).
+ *
+ * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h
+ * for a complete specification.
+ */
+template <class KeyClass>
+class nsTBaseHashSet : protected nsTHashtable<KeyClass> {
+ using Base = nsTHashtable<KeyClass>;
+ typedef mozilla::fallible_t fallible_t;
+
+ public:
+ // XXX We have a similar situation here like with DataType/UserDataType in
+ // nsBaseHashtable. It's less problematic here due to the more constrained
+ // interface, but it may still be confusing. KeyType is not the stored key
+ // type, but the one exposed to the user, i.e. as a parameter type, and as the
+ // value type when iterating. It is currently impossible to move-insert a
+ // RefPtr<T>, e.g., since the KeyType is T* in that case.
+ using ValueType = typename KeyClass::KeyType;
+
+ // KeyType is defined just for compatibility with nsTHashMap. For a set, the
+ // key type is conceptually equivalent to the value type.
+ using KeyType = typename KeyClass::KeyType;
+
+ using Base::Base;
+
+ // Query operations.
+
+ using Base::Contains;
+ using Base::GetGeneration;
+ using Base::ShallowSizeOfExcludingThis;
+ using Base::ShallowSizeOfIncludingThis;
+ using Base::SizeOfExcludingThis;
+ using Base::SizeOfIncludingThis;
+
+ /**
+ * Return the number of entries in the table.
+ * @return number of entries
+ */
+ [[nodiscard]] uint32_t Count() const { return Base::Count(); }
+
+ /**
+ * Return whether the table is empty.
+ * @return whether empty
+ */
+ [[nodiscard]] bool IsEmpty() const { return Base::IsEmpty(); }
+
+ using iterator = ::detail::nsTHashtableKeyIterator<KeyClass>;
+ using const_iterator = iterator;
+
+ [[nodiscard]] auto begin() const { return Base::Keys().begin(); }
+
+ [[nodiscard]] auto end() const { return Base::Keys().end(); }
+
+ [[nodiscard]] auto cbegin() const { return Base::Keys().cbegin(); }
+
+ [[nodiscard]] auto cend() const { return Base::Keys().cend(); }
+
+ // Mutating operations.
+
+ using Base::Clear;
+ using Base::MarkImmutable;
+
+ /**
+ * Inserts a value into the set. Has no effect if the value is already in the
+ * set. This overload is infallible and crashes if memory is exhausted.
+ *
+ * \note For strict consistency with nsTHashtable::EntryHandle method naming,
+ * this should rather be called OrInsert, as it is legal to call it when the
+ * value is already in the set. For simplicity, as we don't have two methods,
+ * we still use "Insert" instead.
+ */
+ void Insert(ValueType aValue) { Base::PutEntry(aValue); }
+
+ /**
+ * Inserts a value into the set. Has no effect if the value is already in the
+ * set. This overload is fallible and returns false if memory is exhausted.
+ *
+ * \note See note on infallible overload.
+ */
+ [[nodiscard]] bool Insert(ValueType aValue, const mozilla::fallible_t&) {
+ return Base::PutEntry(aValue, mozilla::fallible);
+ }
+
+ /**
+ * Inserts a value into the set. Has no effect if the value is already in the
+ * set. This member function is infallible and crashes if memory is exhausted.
+ *
+ * \return true if the value was actually inserted, false if it was already in
+ * the set.
+ */
+ bool EnsureInserted(ValueType aValue) { return Base::EnsureInserted(aValue); }
+
+ using Base::Remove;
+
+ /**
+ * Removes a value from the set. Has no effect if the value is not in the set.
+ *
+ * \note For strict consistency with nsTHashtable::EntryHandle method naming,
+ * this should rather be called OrRemove, as it is legal to call it when the
+ * value is not in the set. For simplicity, as we don't have two methods,
+ * we still use "Remove" instead.
+ */
+ void Remove(ValueType aValue) { Base::RemoveEntry(aValue); }
+
+ using Base::EnsureRemoved;
+
+ /**
+ * Removes all elements matching a predicate.
+ *
+ * The predicate must be compatible with signature bool (ValueType).
+ */
+ template <typename Pred>
+ void RemoveIf(Pred&& aPred) {
+ for (auto it = cbegin(), end = cend(); it != end; ++it) {
+ if (aPred(static_cast<ValueType>(*it))) {
+ Remove(it);
+ }
+ }
+ }
+};
+
+template <typename KeyClass>
+auto RangeSize(const nsTBaseHashSet<KeyClass>& aRange) {
+ return aRange.Count();
+}
+
+class nsCycleCollectionTraversalCallback;
+
+template <class KeyClass>
+inline void ImplCycleCollectionUnlink(nsTBaseHashSet<KeyClass>& aField) {
+ aField.Clear();
+}
+
+template <class KeyClass>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ const nsTBaseHashSet<KeyClass>& aField, const char* aName,
+ uint32_t aFlags = 0) {
+ for (const auto& entry : aField) {
+ CycleCollectionNoteChild(aCallback, mozilla::detail::PtrGetWeak(entry),
+ aName, aFlags);
+ }
+}
+
+namespace mozilla {
+template <typename SetT>
+class nsTSetInserter {
+ SetT* mSet;
+
+ class Proxy {
+ SetT& mSet;
+
+ public:
+ explicit Proxy(SetT& aSet) : mSet{aSet} {}
+
+ template <typename E2>
+ void operator=(E2&& aValue) {
+ mSet.Insert(std::forward<E2>(aValue));
+ }
+ };
+
+ public:
+ using iterator_category = std::output_iterator_tag;
+
+ explicit nsTSetInserter(SetT& aSet) : mSet{&aSet} {}
+
+ Proxy operator*() { return Proxy(*mSet); }
+
+ nsTSetInserter& operator++() { return *this; }
+ nsTSetInserter& operator++(int) { return *this; }
+};
+} // namespace mozilla
+
+template <typename E>
+auto MakeInserter(nsTBaseHashSet<E>& aSet) {
+ return mozilla::nsTSetInserter<nsTBaseHashSet<E>>{aSet};
+}
+
+#endif // XPCOM_DS_NSTHASHSET_H_
diff --git a/xpcom/ds/nsTHashtable.h b/xpcom/ds/nsTHashtable.h
new file mode 100644
index 0000000000..d4a385551f
--- /dev/null
+++ b/xpcom/ds/nsTHashtable.h
@@ -0,0 +1,966 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// See the comment at the top of mfbt/HashTable.h for a comparison between
+// PLDHashTable and mozilla::HashTable.
+
+#ifndef nsTHashtable_h__
+#define nsTHashtable_h__
+
+#include <iterator>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include "PLDHashTable.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/fallible.h"
+#include "nsPointerHashKeys.h"
+#include "nsTArrayForwardDeclare.h"
+
+template <class EntryType>
+class nsTHashtable;
+
+namespace detail {
+class nsTHashtableIteratorBase {
+ public:
+ using EndIteratorTag = PLDHashTable::Iterator::EndIteratorTag;
+
+ nsTHashtableIteratorBase(nsTHashtableIteratorBase&& aOther) = default;
+
+ nsTHashtableIteratorBase& operator=(nsTHashtableIteratorBase&& aOther) {
+ // User-defined because the move assignment operator is deleted in
+ // PLDHashtable::Iterator.
+ return operator=(static_cast<const nsTHashtableIteratorBase&>(aOther));
+ }
+
+ nsTHashtableIteratorBase(const nsTHashtableIteratorBase& aOther)
+ : mIterator{aOther.mIterator.Clone()} {}
+ nsTHashtableIteratorBase& operator=(const nsTHashtableIteratorBase& aOther) {
+ // Since PLDHashTable::Iterator has no assignment operator, we destroy and
+ // recreate mIterator.
+ mIterator.~Iterator();
+ new (&mIterator) PLDHashTable::Iterator(aOther.mIterator.Clone());
+ return *this;
+ }
+
+ explicit nsTHashtableIteratorBase(PLDHashTable::Iterator aFrom)
+ : mIterator{std::move(aFrom)} {}
+
+ explicit nsTHashtableIteratorBase(const PLDHashTable& aTable)
+ : mIterator{&const_cast<PLDHashTable&>(aTable)} {}
+
+ nsTHashtableIteratorBase(const PLDHashTable& aTable, EndIteratorTag aTag)
+ : mIterator{&const_cast<PLDHashTable&>(aTable), aTag} {}
+
+ bool operator==(const nsTHashtableIteratorBase& aRhs) const {
+ return mIterator == aRhs.mIterator;
+ }
+ bool operator!=(const nsTHashtableIteratorBase& aRhs) const {
+ return !(*this == aRhs);
+ }
+
+ protected:
+ PLDHashTable::Iterator mIterator;
+};
+
+// STL-style iterators to allow the use in range-based for loops, e.g.
+template <typename T>
+class nsTHashtableEntryIterator : public nsTHashtableIteratorBase {
+ friend class nsTHashtable<std::remove_const_t<T>>;
+
+ public:
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = T;
+ using difference_type = int32_t;
+ using pointer = value_type*;
+ using reference = value_type&;
+
+ using iterator_type = nsTHashtableEntryIterator;
+ using const_iterator_type = nsTHashtableEntryIterator<const T>;
+
+ using nsTHashtableIteratorBase::nsTHashtableIteratorBase;
+
+ value_type* operator->() const {
+ return static_cast<value_type*>(mIterator.Get());
+ }
+ value_type& operator*() const {
+ return *static_cast<value_type*>(mIterator.Get());
+ }
+
+ iterator_type& operator++() {
+ mIterator.Next();
+ return *this;
+ }
+ iterator_type operator++(int) {
+ iterator_type it = *this;
+ ++*this;
+ return it;
+ }
+
+ operator const_iterator_type() const {
+ return const_iterator_type{mIterator.Clone()};
+ }
+};
+
+template <typename EntryType>
+class nsTHashtableKeyIterator : public nsTHashtableIteratorBase {
+ friend class nsTHashtable<EntryType>;
+
+ public:
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = const std::decay_t<typename EntryType::KeyType>;
+ using difference_type = int32_t;
+ using pointer = value_type*;
+ using reference = value_type&;
+
+ using iterator_type = nsTHashtableKeyIterator;
+ using const_iterator_type = nsTHashtableKeyIterator;
+
+ using nsTHashtableIteratorBase::nsTHashtableIteratorBase;
+
+ value_type* operator->() const {
+ return &static_cast<const EntryType*>(mIterator.Get())->GetKey();
+ }
+ decltype(auto) operator*() const {
+ return static_cast<const EntryType*>(mIterator.Get())->GetKey();
+ }
+
+ iterator_type& operator++() {
+ mIterator.Next();
+ return *this;
+ }
+ iterator_type operator++(int) {
+ iterator_type it = *this;
+ ++*this;
+ return it;
+ }
+};
+
+template <typename EntryType>
+class nsTHashtableKeyRange {
+ public:
+ using IteratorType = nsTHashtableKeyIterator<EntryType>;
+ using iterator = IteratorType;
+
+ explicit nsTHashtableKeyRange(const PLDHashTable& aHashtable)
+ : mHashtable{aHashtable} {}
+
+ auto begin() const { return IteratorType{mHashtable}; }
+ auto end() const {
+ return IteratorType{mHashtable, typename IteratorType::EndIteratorTag{}};
+ }
+ auto cbegin() const { return begin(); }
+ auto cend() const { return end(); }
+
+ uint32_t Count() const { return mHashtable.EntryCount(); }
+
+ private:
+ const PLDHashTable& mHashtable;
+};
+
+template <typename EntryType>
+auto RangeSize(const ::detail::nsTHashtableKeyRange<EntryType>& aRange) {
+ return aRange.Count();
+}
+
+} // namespace detail
+
+/**
+ * a base class for templated hashtables.
+ *
+ * Clients will rarely need to use this class directly. Check the derived
+ * classes first, to see if they will meet your needs.
+ *
+ * @param EntryType the templated entry-type class that is managed by the
+ * hashtable. <code>EntryType</code> must extend the following declaration,
+ * and <strong>must not declare any virtual functions or derive from classes
+ * with virtual functions.</strong> Any vtable pointer would break the
+ * PLDHashTable code.
+ *<pre> class EntryType : public PLDHashEntryHdr
+ * {
+ * public: or friend nsTHashtable<EntryType>;
+ * // KeyType is what we use when Get()ing or Put()ing this entry
+ * // this should either be a simple datatype (uint32_t, nsISupports*) or
+ * // a const reference (const nsAString&)
+ * typedef something KeyType;
+ * // KeyTypePointer is the pointer-version of KeyType, because
+ * // PLDHashTable.h requires keys to cast to <code>const void*</code>
+ * typedef const something* KeyTypePointer;
+ *
+ * EntryType(KeyTypePointer aKey);
+ *
+ * // A copy or C++11 Move constructor must be defined, even if
+ * // AllowMemMove() == true, otherwise you will cause link errors.
+ * EntryType(const EntryType& aEnt); // Either this...
+ * EntryType(EntryType&& aEnt); // ...or this
+ *
+ * // the destructor must be defined... or you will cause link errors!
+ * ~EntryType();
+ *
+ * // KeyEquals(): does this entry match this key?
+ * bool KeyEquals(KeyTypePointer aKey) const;
+ *
+ * // KeyToPointer(): Convert KeyType to KeyTypePointer
+ * static KeyTypePointer KeyToPointer(KeyType aKey);
+ *
+ * // HashKey(): calculate the hash number
+ * static PLDHashNumber HashKey(KeyTypePointer aKey);
+ *
+ * // ALLOW_MEMMOVE can we move this class with memmove(), or do we have
+ * // to use the copy constructor?
+ * enum { ALLOW_MEMMOVE = true/false };
+ * }</pre>
+ *
+ * @see nsInterfaceHashtable
+ * @see nsClassHashtable
+ * @see nsTHashMap
+ * @author "Benjamin Smedberg <bsmedberg@covad.net>"
+ */
+
+template <class EntryType>
+class MOZ_NEEDS_NO_VTABLE_TYPE nsTHashtable {
+ typedef mozilla::fallible_t fallible_t;
+ static_assert(std::is_pointer_v<typename EntryType::KeyTypePointer>,
+ "KeyTypePointer should be a pointer");
+
+ public:
+ // Separate constructors instead of default aInitLength parameter since
+ // otherwise the default no-arg constructor isn't found.
+ nsTHashtable()
+ : mTable(Ops(), sizeof(EntryType), PLDHashTable::kDefaultInitialLength) {}
+ explicit nsTHashtable(uint32_t aInitLength)
+ : mTable(Ops(), sizeof(EntryType), aInitLength) {}
+
+ /**
+ * destructor, cleans up and deallocates
+ */
+ ~nsTHashtable() = default;
+
+ nsTHashtable(nsTHashtable<EntryType>&& aOther);
+ nsTHashtable<EntryType>& operator=(nsTHashtable<EntryType>&& aOther);
+
+ nsTHashtable(const nsTHashtable<EntryType>&) = delete;
+ nsTHashtable& operator=(const nsTHashtable<EntryType>&) = delete;
+
+ /**
+ * Return the generation number for the table. This increments whenever
+ * the table data items are moved.
+ */
+ uint32_t GetGeneration() const { return mTable.Generation(); }
+
+ /**
+ * KeyType is typedef'ed for ease of use.
+ */
+ typedef typename EntryType::KeyType KeyType;
+
+ /**
+ * KeyTypePointer is typedef'ed for ease of use.
+ */
+ typedef typename EntryType::KeyTypePointer KeyTypePointer;
+
+ /**
+ * Return the number of entries in the table.
+ * @return number of entries
+ */
+ uint32_t Count() const { return mTable.EntryCount(); }
+
+ /**
+ * Return true if the hashtable is empty.
+ */
+ bool IsEmpty() const { return Count() == 0; }
+
+ /**
+ * Get the entry associated with a key.
+ * @param aKey the key to retrieve
+ * @return pointer to the entry class, if the key exists; nullptr if the
+ * key doesn't exist
+ */
+ EntryType* GetEntry(KeyType aKey) const {
+ return static_cast<EntryType*>(
+ mTable.Search(EntryType::KeyToPointer(aKey)));
+ }
+
+ /**
+ * Return true if an entry for the given key exists, false otherwise.
+ * @param aKey the key to retrieve
+ * @return true if the key exists, false if the key doesn't exist
+ */
+ bool Contains(KeyType aKey) const { return !!GetEntry(aKey); }
+
+ /**
+ * Infallibly get the entry associated with a key, or create a new entry,
+ * @param aKey the key to retrieve
+ * @return pointer to the entry retrieved; never nullptr
+ */
+ EntryType* PutEntry(KeyType aKey) {
+ // Infallible WithEntryHandle.
+ return WithEntryHandle(
+ aKey, [](auto entryHandle) { return entryHandle.OrInsert(); });
+ }
+
+ /**
+ * Fallibly get the entry associated with a key, or create a new entry,
+ * @param aKey the key to retrieve
+ * @return pointer to the entry retrieved; nullptr only if memory can't
+ * be allocated
+ */
+ [[nodiscard]] EntryType* PutEntry(KeyType aKey, const fallible_t& aFallible) {
+ return WithEntryHandle(aKey, aFallible, [](auto maybeEntryHandle) {
+ return maybeEntryHandle ? maybeEntryHandle->OrInsert() : nullptr;
+ });
+ }
+
+ /**
+ * Get the entry associated with a key, or create a new entry using infallible
+ * allocation and insert that.
+ * @param aKey the key to retrieve
+ * @param aEntry will be assigned (if non-null) to the entry that was
+ * found or created
+ * @return true if a new entry was created, or false if an existing entry
+ * was found
+ */
+ [[nodiscard]] bool EnsureInserted(KeyType aKey,
+ EntryType** aEntry = nullptr) {
+ auto oldCount = Count();
+ EntryType* entry = PutEntry(aKey);
+ if (aEntry) {
+ *aEntry = entry;
+ }
+ return oldCount != Count();
+ }
+
+ /**
+ * Remove the entry associated with a key.
+ * @param aKey of the entry to remove
+ */
+ void RemoveEntry(KeyType aKey) {
+ mTable.Remove(EntryType::KeyToPointer(aKey));
+ }
+
+ /**
+ * Lookup the entry associated with aKey and remove it if found, otherwise
+ * do nothing.
+ * @param aKey of the entry to remove
+ * @return true if an entry was found and removed, or false if no entry
+ * was found for aKey
+ */
+ bool EnsureRemoved(KeyType aKey) {
+ auto* entry = GetEntry(aKey);
+ if (entry) {
+ RemoveEntry(entry);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Remove the entry associated with a key.
+ * @param aEntry the entry-pointer to remove (obtained from GetEntry)
+ */
+ void RemoveEntry(EntryType* aEntry) { mTable.RemoveEntry(aEntry); }
+
+ /**
+ * Remove the entry associated with a key, but don't resize the hashtable.
+ * This is a low-level method, and is not recommended unless you know what
+ * you're doing. If you use it, please add a comment explaining why you
+ * didn't use RemoveEntry().
+ * @param aEntry the entry-pointer to remove (obtained from GetEntry)
+ */
+ void RawRemoveEntry(EntryType* aEntry) { mTable.RawRemove(aEntry); }
+
+ protected:
+ class EntryHandle {
+ public:
+ EntryHandle(EntryHandle&& aOther) = default;
+ ~EntryHandle() = default;
+
+ EntryHandle(const EntryHandle&) = delete;
+ EntryHandle& operator=(const EntryHandle&) = delete;
+ EntryHandle& operator=(const EntryHandle&&) = delete;
+
+ KeyType Key() const { return mKey; }
+
+ bool HasEntry() const { return mEntryHandle.HasEntry(); }
+
+ explicit operator bool() const { return mEntryHandle.operator bool(); }
+
+ EntryType* Entry() { return static_cast<EntryType*>(mEntryHandle.Entry()); }
+
+ void Insert() { InsertInternal(); }
+
+ EntryType* OrInsert() {
+ if (!HasEntry()) {
+ Insert();
+ }
+ return Entry();
+ }
+
+ void Remove() { mEntryHandle.Remove(); }
+
+ void OrRemove() { mEntryHandle.OrRemove(); }
+
+ protected:
+ template <typename... Args>
+ void InsertInternal(Args&&... aArgs) {
+ MOZ_RELEASE_ASSERT(!HasEntry());
+ mEntryHandle.Insert([&](PLDHashEntryHdr* entry) {
+ new (mozilla::KnownNotNull, entry) EntryType(
+ EntryType::KeyToPointer(mKey), std::forward<Args>(aArgs)...);
+ });
+ }
+
+ private:
+ friend class nsTHashtable;
+
+ EntryHandle(KeyType aKey, PLDHashTable::EntryHandle&& aEntryHandle)
+ : mKey(aKey), mEntryHandle(std::move(aEntryHandle)) {}
+
+ KeyType mKey;
+ PLDHashTable::EntryHandle mEntryHandle;
+ };
+
+ template <class F>
+ auto WithEntryHandle(KeyType aKey, F&& aFunc)
+ -> std::invoke_result_t<F, EntryHandle&&> {
+ return this->mTable.WithEntryHandle(
+ EntryType::KeyToPointer(aKey),
+ [&aKey, &aFunc](auto entryHandle) -> decltype(auto) {
+ return std::forward<F>(aFunc)(
+ EntryHandle{aKey, std::move(entryHandle)});
+ });
+ }
+
+ template <class F>
+ auto WithEntryHandle(KeyType aKey, const mozilla::fallible_t& aFallible,
+ F&& aFunc)
+ -> std::invoke_result_t<F, mozilla::Maybe<EntryHandle>&&> {
+ return this->mTable.WithEntryHandle(
+ EntryType::KeyToPointer(aKey), aFallible,
+ [&aKey, &aFunc](auto maybeEntryHandle) {
+ return std::forward<F>(aFunc)(
+ maybeEntryHandle
+ ? mozilla::Some(EntryHandle{aKey, maybeEntryHandle.extract()})
+ : mozilla::Nothing());
+ });
+ }
+
+ public:
+ class ConstIterator {
+ public:
+ explicit ConstIterator(nsTHashtable* aTable)
+ : mBaseIterator(&aTable->mTable) {}
+ ~ConstIterator() = default;
+
+ KeyType Key() const { return Get()->GetKey(); }
+
+ const EntryType* Get() const {
+ return static_cast<const EntryType*>(mBaseIterator.Get());
+ }
+
+ bool Done() const { return mBaseIterator.Done(); }
+ void Next() { mBaseIterator.Next(); }
+
+ ConstIterator() = delete;
+ ConstIterator(const ConstIterator&) = delete;
+ ConstIterator(ConstIterator&& aOther) = delete;
+ ConstIterator& operator=(const ConstIterator&) = delete;
+ ConstIterator& operator=(ConstIterator&&) = delete;
+
+ protected:
+ PLDHashTable::Iterator mBaseIterator;
+ };
+
+ // This is an iterator that also allows entry removal. Example usage:
+ //
+ // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
+ // Entry* entry = iter.Get();
+ // // ... do stuff with |entry| ...
+ // // ... possibly call iter.Remove() once ...
+ // }
+ //
+ class Iterator final : public ConstIterator {
+ public:
+ using ConstIterator::ConstIterator;
+
+ using ConstIterator::Get;
+
+ EntryType* Get() const {
+ return static_cast<EntryType*>(this->mBaseIterator.Get());
+ }
+
+ void Remove() { this->mBaseIterator.Remove(); }
+ };
+
+ Iterator Iter() { return Iterator(this); }
+
+ ConstIterator ConstIter() const {
+ return ConstIterator(const_cast<nsTHashtable*>(this));
+ }
+
+ using const_iterator = ::detail::nsTHashtableEntryIterator<const EntryType>;
+ using iterator = ::detail::nsTHashtableEntryIterator<EntryType>;
+
+ iterator begin() { return iterator{mTable}; }
+ const_iterator begin() const { return const_iterator{mTable}; }
+ const_iterator cbegin() const { return begin(); }
+ iterator end() {
+ return iterator{mTable, typename iterator::EndIteratorTag{}};
+ }
+ const_iterator end() const {
+ return const_iterator{mTable, typename const_iterator::EndIteratorTag{}};
+ }
+ const_iterator cend() const { return end(); }
+
+ void Remove(const_iterator& aIter) { aIter.mIterator.Remove(); }
+
+ /**
+ * Return a range of the keys (of KeyType). Note this range iterates over the
+ * keys in place, so modifications to the nsTHashtable invalidate the range
+ * while it's iterated, except when calling Remove() with a key iterator
+ * derived from that range.
+ */
+ auto Keys() const {
+ return ::detail::nsTHashtableKeyRange<EntryType>{mTable};
+ }
+
+ /**
+ * Remove an entry from a key range, specified via a key iterator, e.g.
+ *
+ * for (auto it = hash.Keys().begin(), end = hash.Keys().end();
+ * it != end; * ++it) {
+ * if (*it > 42) { hash.Remove(it); }
+ * }
+ */
+ void Remove(::detail::nsTHashtableKeyIterator<EntryType>& aIter) {
+ aIter.mIterator.Remove();
+ }
+
+ /**
+ * Remove all entries, return hashtable to "pristine" state. It's
+ * conceptually the same as calling the destructor and then re-calling the
+ * constructor.
+ */
+ void Clear() { mTable.Clear(); }
+
+ /**
+ * Measure the size of the table's entry storage. Does *not* measure anything
+ * hanging off table entries; hence the "Shallow" prefix. To measure that,
+ * either use SizeOfExcludingThis() or iterate manually over the entries,
+ * calling SizeOfExcludingThis() on each one.
+ *
+ * @param aMallocSizeOf the function used to measure heap-allocated blocks
+ * @return the measured shallow size of the table
+ */
+ size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ /**
+ * Like ShallowSizeOfExcludingThis, but includes sizeof(*this).
+ */
+ size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ /**
+ * This is a "deep" measurement of the table. To use it, |EntryType| must
+ * define SizeOfExcludingThis, and that method will be called on all live
+ * entries.
+ */
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = ConstIter(); !iter.Done(); iter.Next()) {
+ n += (*iter.Get()).SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+ }
+
+ /**
+ * Like SizeOfExcludingThis, but includes sizeof(*this).
+ */
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ /**
+ * Swap the elements in this hashtable with the elements in aOther.
+ */
+ void SwapElements(nsTHashtable<EntryType>& aOther) {
+ MOZ_ASSERT_IF(this->mTable.Ops() && aOther.mTable.Ops(),
+ this->mTable.Ops() == aOther.mTable.Ops());
+ std::swap(this->mTable, aOther.mTable);
+ }
+
+ /**
+ * Mark the table as constant after initialization.
+ *
+ * This will prevent assertions when a read-only hash is accessed on multiple
+ * threads without synchronization.
+ */
+ void MarkImmutable() { mTable.MarkImmutable(); }
+
+ protected:
+ PLDHashTable mTable;
+
+ static PLDHashNumber s_HashKey(const void* aKey);
+
+ static bool s_MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey);
+
+ static void s_CopyEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom,
+ PLDHashEntryHdr* aTo);
+
+ static void s_ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry);
+
+ private:
+ // copy constructor, not implemented
+ nsTHashtable(nsTHashtable<EntryType>& aToCopy) = delete;
+
+ /**
+ * Gets the table's ops.
+ */
+ static const PLDHashTableOps* Ops();
+
+ // assignment operator, not implemented
+ nsTHashtable<EntryType>& operator=(nsTHashtable<EntryType>& aToEqual) =
+ delete;
+};
+
+namespace mozilla {
+namespace detail {
+
+// Like PLDHashTable::MoveEntryStub, but specialized for fixed N (i.e. the size
+// of the entries in the hashtable). Saves a memory read to figure out the size
+// from the table and gives the compiler the opportunity to inline the memcpy.
+//
+// We define this outside of nsTHashtable so only one copy exists for every N,
+// rather than separate copies for every EntryType used with nsTHashtable.
+template <size_t N>
+static void FixedSizeEntryMover(PLDHashTable*, const PLDHashEntryHdr* aFrom,
+ PLDHashEntryHdr* aTo) {
+ memcpy(aTo, aFrom, N);
+}
+
+} // namespace detail
+} // namespace mozilla
+
+//
+// template definitions
+//
+
+template <class EntryType>
+nsTHashtable<EntryType>::nsTHashtable(nsTHashtable<EntryType>&& aOther)
+ : mTable(std::move(aOther.mTable)) {}
+
+template <class EntryType>
+nsTHashtable<EntryType>& nsTHashtable<EntryType>::operator=(
+ nsTHashtable<EntryType>&& aOther) {
+ mTable = std::move(aOther.mTable);
+ return *this;
+}
+
+template <class EntryType>
+/* static */ const PLDHashTableOps* nsTHashtable<EntryType>::Ops() {
+ // If this variable is a global variable, we get strange start-up failures on
+ // WindowsCrtPatch.h (see bug 1166598 comment 20). But putting it inside a
+ // function avoids that problem.
+ static const PLDHashTableOps sOps = {
+ s_HashKey, s_MatchEntry,
+ EntryType::ALLOW_MEMMOVE
+ ? mozilla::detail::FixedSizeEntryMover<sizeof(EntryType)>
+ : s_CopyEntry,
+ // Simplify hashtable clearing in case our entries are trivially
+ // destructible.
+ std::is_trivially_destructible_v<EntryType> ? nullptr : s_ClearEntry,
+ // We don't use a generic initEntry hook because we want to allow
+ // initialization of data members defined in derived classes directly
+ // in the entry constructor (for example when a member can't be default
+ // constructed).
+ nullptr};
+ return &sOps;
+}
+
+// static definitions
+
+template <class EntryType>
+PLDHashNumber nsTHashtable<EntryType>::s_HashKey(const void* aKey) {
+ return EntryType::HashKey(static_cast<KeyTypePointer>(aKey));
+}
+
+template <class EntryType>
+bool nsTHashtable<EntryType>::s_MatchEntry(const PLDHashEntryHdr* aEntry,
+ const void* aKey) {
+ return (static_cast<const EntryType*>(aEntry))
+ ->KeyEquals(static_cast<KeyTypePointer>(aKey));
+}
+
+template <class EntryType>
+void nsTHashtable<EntryType>::s_CopyEntry(PLDHashTable* aTable,
+ const PLDHashEntryHdr* aFrom,
+ PLDHashEntryHdr* aTo) {
+ auto* fromEntry = const_cast<std::remove_const_t<EntryType>*>(
+ static_cast<const EntryType*>(aFrom));
+
+ new (mozilla::KnownNotNull, aTo) EntryType(std::move(*fromEntry));
+
+ fromEntry->~EntryType();
+}
+
+template <class EntryType>
+void nsTHashtable<EntryType>::s_ClearEntry(PLDHashTable* aTable,
+ PLDHashEntryHdr* aEntry) {
+ static_cast<EntryType*>(aEntry)->~EntryType();
+}
+
+class nsCycleCollectionTraversalCallback;
+
+template <class EntryType>
+inline void ImplCycleCollectionUnlink(nsTHashtable<EntryType>& aField) {
+ aField.Clear();
+}
+
+template <class EntryType>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ nsTHashtable<EntryType>& aField, const char* aName, uint32_t aFlags = 0) {
+ for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) {
+ EntryType* entry = iter.Get();
+ ImplCycleCollectionTraverse(aCallback, *entry, aName, aFlags);
+ }
+}
+
+/**
+ * For nsTHashtable with pointer entries, we can have a template specialization
+ * that layers a typed T* interface on top of a common implementation that
+ * works internally with void pointers. This arrangement saves code size and
+ * might slightly improve performance as well.
+ */
+
+/**
+ * We need a separate entry type class for the inheritance structure of the
+ * nsTHashtable specialization below; nsVoidPtrHashKey is simply typedefed to a
+ * specialization of nsPtrHashKey, and the formulation:
+ *
+ * class nsTHashtable<nsPtrHashKey<T>> :
+ * protected nsTHashtable<nsPtrHashKey<const void>
+ *
+ * is not going to turn out very well, since we'd wind up with an nsTHashtable
+ * instantiation that is its own base class.
+ */
+namespace detail {
+
+class VoidPtrHashKey : public nsPtrHashKey<const void> {
+ typedef nsPtrHashKey<const void> Base;
+
+ public:
+ explicit VoidPtrHashKey(const void* aKey) : Base(aKey) {}
+};
+
+} // namespace detail
+
+/**
+ * See the main nsTHashtable documentation for descriptions of this class's
+ * methods.
+ */
+template <typename T>
+class nsTHashtable<nsPtrHashKey<T>>
+ : protected nsTHashtable<::detail::VoidPtrHashKey> {
+ typedef nsTHashtable<::detail::VoidPtrHashKey> Base;
+ typedef nsPtrHashKey<T> EntryType;
+
+ // We play games with reinterpret_cast'ing between these two classes, so
+ // try to ensure that playing said games is reasonable.
+ static_assert(sizeof(nsPtrHashKey<T>) == sizeof(::detail::VoidPtrHashKey),
+ "hash keys must be the same size");
+
+ nsTHashtable(const nsTHashtable& aOther) = delete;
+ nsTHashtable& operator=(const nsTHashtable& aOther) = delete;
+
+ public:
+ nsTHashtable() = default;
+ explicit nsTHashtable(uint32_t aInitLength) : Base(aInitLength) {}
+
+ ~nsTHashtable() = default;
+
+ nsTHashtable(nsTHashtable&&) = default;
+
+ using Base::Clear;
+ using Base::Count;
+ using Base::GetGeneration;
+ using Base::IsEmpty;
+
+ using Base::MarkImmutable;
+ using Base::ShallowSizeOfExcludingThis;
+ using Base::ShallowSizeOfIncludingThis;
+
+ /* Wrapper functions */
+ EntryType* GetEntry(T* aKey) const {
+ return reinterpret_cast<EntryType*>(Base::GetEntry(aKey));
+ }
+
+ bool Contains(T* aKey) const { return Base::Contains(aKey); }
+
+ EntryType* PutEntry(T* aKey) {
+ return reinterpret_cast<EntryType*>(Base::PutEntry(aKey));
+ }
+
+ [[nodiscard]] EntryType* PutEntry(T* aKey,
+ const mozilla::fallible_t& aFallible) {
+ return reinterpret_cast<EntryType*>(Base::PutEntry(aKey, aFallible));
+ }
+
+ [[nodiscard]] bool EnsureInserted(T* aKey, EntryType** aEntry = nullptr) {
+ return Base::EnsureInserted(
+ aKey, reinterpret_cast<::detail::VoidPtrHashKey**>(aEntry));
+ }
+
+ void RemoveEntry(T* aKey) { Base::RemoveEntry(aKey); }
+
+ bool EnsureRemoved(T* aKey) { return Base::EnsureRemoved(aKey); }
+
+ void RemoveEntry(EntryType* aEntry) {
+ Base::RemoveEntry(reinterpret_cast<::detail::VoidPtrHashKey*>(aEntry));
+ }
+
+ void RawRemoveEntry(EntryType* aEntry) {
+ Base::RawRemoveEntry(reinterpret_cast<::detail::VoidPtrHashKey*>(aEntry));
+ }
+
+ protected:
+ class EntryHandle : protected Base::EntryHandle {
+ public:
+ using Base = nsTHashtable::Base::EntryHandle;
+
+ EntryHandle(EntryHandle&& aOther) = default;
+ ~EntryHandle() = default;
+
+ EntryHandle(const EntryHandle&) = delete;
+ EntryHandle& operator=(const EntryHandle&) = delete;
+ EntryHandle& operator=(const EntryHandle&&) = delete;
+
+ using Base::Key;
+
+ using Base::HasEntry;
+
+ using Base::operator bool;
+
+ EntryType* Entry() { return reinterpret_cast<EntryType*>(Base::Entry()); }
+
+ using Base::Insert;
+
+ EntryType* OrInsert() {
+ if (!HasEntry()) {
+ Insert();
+ }
+ return Entry();
+ }
+
+ using Base::Remove;
+
+ using Base::OrRemove;
+
+ private:
+ friend class nsTHashtable;
+
+ explicit EntryHandle(Base&& aBase) : Base(std::move(aBase)) {}
+ };
+
+ template <class F>
+ auto WithEntryHandle(KeyType aKey, F aFunc)
+ -> std::invoke_result_t<F, EntryHandle&&> {
+ return Base::WithEntryHandle(aKey, [&aFunc](auto entryHandle) {
+ return aFunc(EntryHandle{std::move(entryHandle)});
+ });
+ }
+
+ template <class F>
+ auto WithEntryHandle(KeyType aKey, const mozilla::fallible_t& aFallible,
+ F aFunc)
+ -> std::invoke_result_t<F, mozilla::Maybe<EntryHandle>&&> {
+ return Base::WithEntryHandle(
+ aKey, aFallible, [&aFunc](auto maybeEntryHandle) {
+ return aFunc(maybeEntryHandle ? mozilla::Some(EntryHandle{
+ maybeEntryHandle.extract()})
+ : mozilla::Nothing());
+ });
+ }
+
+ public:
+ class ConstIterator {
+ public:
+ explicit ConstIterator(nsTHashtable* aTable)
+ : mBaseIterator(&aTable->mTable) {}
+ ~ConstIterator() = default;
+
+ KeyType Key() const { return Get()->GetKey(); }
+
+ const EntryType* Get() const {
+ return static_cast<const EntryType*>(mBaseIterator.Get());
+ }
+
+ bool Done() const { return mBaseIterator.Done(); }
+ void Next() { mBaseIterator.Next(); }
+
+ ConstIterator() = delete;
+ ConstIterator(const ConstIterator&) = delete;
+ ConstIterator(ConstIterator&& aOther) = delete;
+ ConstIterator& operator=(const ConstIterator&) = delete;
+ ConstIterator& operator=(ConstIterator&&) = delete;
+
+ protected:
+ PLDHashTable::Iterator mBaseIterator;
+ };
+
+ class Iterator final : public ConstIterator {
+ public:
+ using ConstIterator::ConstIterator;
+
+ using ConstIterator::Get;
+
+ EntryType* Get() const {
+ return static_cast<EntryType*>(this->mBaseIterator.Get());
+ }
+
+ void Remove() { this->mBaseIterator.Remove(); }
+ };
+
+ Iterator Iter() { return Iterator(this); }
+
+ ConstIterator ConstIter() const {
+ return ConstIterator(const_cast<nsTHashtable*>(this));
+ }
+
+ using const_iterator = ::detail::nsTHashtableEntryIterator<const EntryType>;
+ using iterator = ::detail::nsTHashtableEntryIterator<EntryType>;
+
+ iterator begin() { return iterator{mTable}; }
+ const_iterator begin() const { return const_iterator{mTable}; }
+ const_iterator cbegin() const { return begin(); }
+ iterator end() {
+ return iterator{mTable, typename iterator::EndIteratorTag{}};
+ }
+ const_iterator end() const {
+ return const_iterator{mTable, typename const_iterator::EndIteratorTag{}};
+ }
+ const_iterator cend() const { return end(); }
+
+ auto Keys() const {
+ return ::detail::nsTHashtableKeyRange<nsPtrHashKey<T>>{mTable};
+ }
+
+ void Remove(::detail::nsTHashtableKeyIterator<EntryType>& aIter) {
+ aIter.mIterator.Remove();
+ }
+
+ void SwapElements(nsTHashtable& aOther) { Base::SwapElements(aOther); }
+};
+
+#endif // nsTHashtable_h__
diff --git a/xpcom/ds/nsTObserverArray.cpp b/xpcom/ds/nsTObserverArray.cpp
new file mode 100644
index 0000000000..68211873dd
--- /dev/null
+++ b/xpcom/ds/nsTObserverArray.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTObserverArray.h"
+
+void nsTObserverArray_base::AdjustIterators(index_type aModPos,
+ diff_type aAdjustment) {
+ MOZ_ASSERT(aAdjustment == -1 || aAdjustment == 1, "invalid adjustment");
+ Iterator_base* iter = mIterators;
+ while (iter) {
+ if (iter->mPosition > aModPos) {
+ iter->mPosition += aAdjustment;
+ }
+ iter = iter->mNext;
+ }
+}
+
+void nsTObserverArray_base::ClearIterators() {
+ Iterator_base* iter = mIterators;
+ while (iter) {
+ iter->mPosition = 0;
+ iter = iter->mNext;
+ }
+}
diff --git a/xpcom/ds/nsTObserverArray.h b/xpcom/ds/nsTObserverArray.h
new file mode 100644
index 0000000000..4d3e4087e0
--- /dev/null
+++ b/xpcom/ds/nsTObserverArray.h
@@ -0,0 +1,583 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTObserverArray_h___
+#define nsTObserverArray_h___
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ReverseIterator.h"
+#include "nsTArray.h"
+#include "nsCycleCollectionNoteChild.h"
+
+/**
+ * An array of observers. Like a normal array, but supports iterators that are
+ * stable even if the array is modified during iteration.
+ * The template parameter T is the observer type the array will hold;
+ * N is the number of built-in storage slots that come with the array.
+ * NOTE: You probably want to use nsTObserverArray, unless you specifically
+ * want built-in storage. See below.
+ * @see nsTObserverArray, nsTArray
+ */
+
+class nsTObserverArray_base {
+ public:
+ typedef size_t index_type;
+ typedef size_t size_type;
+ typedef ptrdiff_t diff_type;
+
+ protected:
+ class Iterator_base {
+ public:
+ Iterator_base(const Iterator_base&) = delete;
+
+ protected:
+ friend class nsTObserverArray_base;
+
+ Iterator_base(index_type aPosition, Iterator_base* aNext)
+ : mPosition(aPosition), mNext(aNext) {}
+
+ // The current position of the iterator. Its exact meaning differs
+ // depending on iterator. See nsTObserverArray<T>::ForwardIterator.
+ index_type mPosition;
+
+ // The next iterator currently iterating the same array
+ Iterator_base* mNext;
+ };
+
+ nsTObserverArray_base() : mIterators(nullptr) {}
+
+ ~nsTObserverArray_base() {
+ NS_ASSERTION(mIterators == nullptr, "iterators outlasting array");
+ }
+
+ /**
+ * Adjusts iterators after an element has been inserted or removed
+ * from the array.
+ * @param aModPos Position where elements were added or removed.
+ * @param aAdjustment -1 if an element was removed, 1 if an element was
+ * added.
+ */
+ void AdjustIterators(index_type aModPos, diff_type aAdjustment);
+
+ /**
+ * Clears iterators when the array is destroyed.
+ */
+ void ClearIterators();
+
+ mutable Iterator_base* mIterators;
+};
+
+template <class T, size_t N>
+class nsAutoTObserverArray : protected nsTObserverArray_base {
+ public:
+ typedef T value_type;
+ typedef nsTArray<T> array_type;
+
+ nsAutoTObserverArray() = default;
+
+ //
+ // Accessor methods
+ //
+
+ // @return The number of elements in the array.
+ size_type Length() const { return mArray.Length(); }
+
+ // @return True if the array is empty or false otherwise.
+ bool IsEmpty() const { return mArray.IsEmpty(); }
+
+ // This method provides direct, readonly access to the array elements.
+ // @return A pointer to the first element of the array. If the array is
+ // empty, then this pointer must not be dereferenced.
+ const value_type* Elements() const { return mArray.Elements(); }
+ value_type* Elements() { return mArray.Elements(); }
+
+ // This method provides direct access to an element of the array. The given
+ // index must be within the array bounds. If the underlying array may change
+ // during iteration, use an iterator instead of this function.
+ // @param aIndex The index of an element in the array.
+ // @return A reference to the i'th element of the array.
+ value_type& ElementAt(index_type aIndex) { return mArray.ElementAt(aIndex); }
+
+ // Same as above, but readonly.
+ const value_type& ElementAt(index_type aIndex) const {
+ return mArray.ElementAt(aIndex);
+ }
+
+ // This method provides direct access to an element of the array in a bounds
+ // safe manner. If the requested index is out of bounds the provided default
+ // value is returned.
+ // @param aIndex The index of an element in the array.
+ // @param aDef The value to return if the index is out of bounds.
+ value_type& SafeElementAt(index_type aIndex, value_type& aDef) {
+ return mArray.SafeElementAt(aIndex, aDef);
+ }
+
+ // Same as above, but readonly.
+ const value_type& SafeElementAt(index_type aIndex,
+ const value_type& aDef) const {
+ return mArray.SafeElementAt(aIndex, aDef);
+ }
+
+ // No operator[] is provided because the point of this class is to support
+ // allow modifying the array during iteration, and ElementAt() is not safe
+ // in those conditions.
+
+ //
+ // Search methods
+ //
+
+ // This method searches, starting from the beginning of the array,
+ // for the first element in this array that is equal to the given element.
+ // 'operator==' must be defined for value_type.
+ // @param aItem The item to search for.
+ // @return true if the element was found.
+ template <class Item>
+ bool Contains(const Item& aItem) const {
+ return IndexOf(aItem) != array_type::NoIndex;
+ }
+
+ // This method searches for the offset of the first element in this
+ // array that is equal to the given element.
+ // 'operator==' must be defined for value_type.
+ // @param aItem The item to search for.
+ // @param aStart The index to start from.
+ // @return The index of the found element or NoIndex if not found.
+ template <class Item>
+ index_type IndexOf(const Item& aItem, index_type aStart = 0) const {
+ return mArray.IndexOf(aItem, aStart);
+ }
+
+ //
+ // Mutation methods
+ //
+
+ // Insert a given element at the given index.
+ // @param aIndex The index at which to insert item.
+ // @param aItem The item to insert,
+ template <class Item>
+ void InsertElementAt(index_type aIndex, const Item& aItem) {
+ mArray.InsertElementAt(aIndex, aItem);
+ AdjustIterators(aIndex, 1);
+ }
+
+ // Same as above but without copy constructing.
+ // This is useful to avoid temporaries.
+ value_type* InsertElementAt(index_type aIndex) {
+ value_type* item = mArray.InsertElementAt(aIndex);
+ AdjustIterators(aIndex, 1);
+ return item;
+ }
+
+ // Prepend an element to the array unless it already exists in the array.
+ // 'operator==' must be defined for value_type.
+ // @param aItem The item to prepend.
+ template <class Item>
+ void PrependElementUnlessExists(const Item& aItem) {
+ if (!Contains(aItem)) {
+ mArray.InsertElementAt(0, aItem);
+ AdjustIterators(0, 1);
+ }
+ }
+
+ // Append an element to the array.
+ // @param aItem The item to append.
+ template <class Item>
+ void AppendElement(Item&& aItem) {
+ mArray.AppendElement(std::forward<Item>(aItem));
+ }
+
+ // Same as above, but without copy-constructing. This is useful to avoid
+ // temporaries.
+ value_type* AppendElement() { return mArray.AppendElement(); }
+
+ // Append an element to the array unless it already exists in the array.
+ // 'operator==' must be defined for value_type.
+ // @param aItem The item to append.
+ template <class Item>
+ void AppendElementUnlessExists(const Item& aItem) {
+ if (!Contains(aItem)) {
+ mArray.AppendElement(aItem);
+ }
+ }
+
+ // Remove an element from the array.
+ // @param aIndex The index of the item to remove.
+ void RemoveElementAt(index_type aIndex) {
+ NS_ASSERTION(aIndex < mArray.Length(), "invalid index");
+ mArray.RemoveElementAt(aIndex);
+ AdjustIterators(aIndex, -1);
+ }
+
+ // This helper function combines IndexOf with RemoveElementAt to "search
+ // and destroy" the first element that is equal to the given element.
+ // 'operator==' must be defined for value_type.
+ // @param aItem The item to search for.
+ // @return true if the element was found and removed.
+ template <class Item>
+ bool RemoveElement(const Item& aItem) {
+ index_type index = mArray.IndexOf(aItem, 0);
+ if (index == array_type::NoIndex) {
+ return false;
+ }
+
+ mArray.RemoveElementAt(index);
+ AdjustIterators(index, -1);
+ return true;
+ }
+
+ // See nsTArray::RemoveElementsBy. Neither the predicate nor the removal of
+ // elements from the array must have any side effects that modify the array.
+ template <typename Predicate>
+ void NonObservingRemoveElementsBy(Predicate aPredicate) {
+ index_type i = 0;
+ mArray.RemoveElementsBy([&](const value_type& aItem) {
+ if (aPredicate(aItem)) {
+ // This element is going to be removed.
+ AdjustIterators(i, -1);
+ return true;
+ }
+ ++i;
+ return false;
+ });
+ }
+
+ // Removes all observers and collapses all iterators to the beginning of
+ // the array. The result is that forward iterators will see all elements
+ // in the array.
+ void Clear() {
+ mArray.Clear();
+ ClearIterators();
+ }
+
+ // Compact the array to minimize the memory it uses
+ void Compact() { mArray.Compact(); }
+
+ // Returns the number of bytes on the heap taken up by this object, not
+ // including sizeof(*this). If you want to measure anything hanging off the
+ // array, you must iterate over the elements and measure them individually;
+ // hence the "Shallow" prefix.
+ size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ //
+ // Iterators
+ //
+
+ // Base class for iterators. Do not use this directly.
+ class Iterator : public Iterator_base {
+ protected:
+ friend class nsAutoTObserverArray;
+ typedef nsAutoTObserverArray<T, N> array_type;
+
+ Iterator(const Iterator& aOther)
+ : Iterator(aOther.mPosition, aOther.mArray) {}
+
+ Iterator(index_type aPosition, const array_type& aArray)
+ : Iterator_base(aPosition, aArray.mIterators),
+ mArray(const_cast<array_type&>(aArray)) {
+ aArray.mIterators = this;
+ }
+
+ ~Iterator() {
+ NS_ASSERTION(mArray.mIterators == this,
+ "Iterators must currently be destroyed in opposite order "
+ "from the construction order. It is suggested that you "
+ "simply put them on the stack");
+ mArray.mIterators = mNext;
+ }
+
+ // The array we're iterating
+ array_type& mArray;
+ };
+
+ // Iterates the array forward from beginning to end. mPosition points
+ // to the element that will be returned on next call to GetNext.
+ // Elements:
+ // - prepended to the array during iteration *will not* be traversed
+ // - appended during iteration *will* be traversed
+ // - removed during iteration *will not* be traversed.
+ // @see EndLimitedIterator
+ class ForwardIterator : protected Iterator {
+ public:
+ typedef nsAutoTObserverArray<T, N> array_type;
+ typedef Iterator base_type;
+
+ explicit ForwardIterator(const array_type& aArray) : Iterator(0, aArray) {}
+
+ ForwardIterator(const array_type& aArray, index_type aPos)
+ : Iterator(aPos, aArray) {}
+
+ bool operator<(const ForwardIterator& aOther) const {
+ NS_ASSERTION(&this->mArray == &aOther.mArray,
+ "not iterating the same array");
+ return base_type::mPosition < aOther.mPosition;
+ }
+
+ // Returns true if there are more elements to iterate.
+ // This must precede a call to GetNext(). If false is
+ // returned, GetNext() must not be called.
+ bool HasMore() const {
+ return base_type::mPosition < base_type::mArray.Length();
+ }
+
+ // Returns the next element and steps one step. This must
+ // be preceded by a call to HasMore().
+ // @return The next observer.
+ value_type& GetNext() {
+ NS_ASSERTION(HasMore(), "iterating beyond end of array");
+ return base_type::mArray.Elements()[base_type::mPosition++];
+ }
+
+ // Removes the element at the current iterator position.
+ // (the last element returned from |GetNext()|)
+ // This will not affect the next call to |GetNext()|
+ void Remove() {
+ return base_type::mArray.RemoveElementAt(base_type::mPosition - 1);
+ }
+ };
+
+ // EndLimitedIterator works like ForwardIterator, but will not iterate new
+ // observers appended to the array after the iterator was created.
+ class EndLimitedIterator : protected ForwardIterator {
+ public:
+ typedef nsAutoTObserverArray<T, N> array_type;
+ typedef Iterator base_type;
+
+ explicit EndLimitedIterator(const array_type& aArray)
+ : ForwardIterator(aArray), mEnd(aArray, aArray.Length()) {}
+
+ // Returns true if there are more elements to iterate.
+ // This must precede a call to GetNext(). If false is
+ // returned, GetNext() must not be called.
+ bool HasMore() const { return *this < mEnd; }
+
+ // Returns the next element and steps one step. This must
+ // be preceded by a call to HasMore().
+ // @return The next observer.
+ value_type& GetNext() {
+ NS_ASSERTION(HasMore(), "iterating beyond end of array");
+ return base_type::mArray.Elements()[base_type::mPosition++];
+ }
+
+ // Removes the element at the current iterator position.
+ // (the last element returned from |GetNext()|)
+ // This will not affect the next call to |GetNext()|
+ void Remove() {
+ return base_type::mArray.RemoveElementAt(base_type::mPosition - 1);
+ }
+
+ private:
+ ForwardIterator mEnd;
+ };
+
+ // Iterates the array backward from end to start. mPosition points
+ // to the element that was returned last.
+ // Elements:
+ // - prepended to the array during iteration *will* be traversed,
+ // unless the iteration already arrived at the first element
+ // - appended during iteration *will not* be traversed
+ // - removed during iteration *will not* be traversed.
+ class BackwardIterator : protected Iterator {
+ public:
+ typedef nsAutoTObserverArray<T, N> array_type;
+ typedef Iterator base_type;
+
+ explicit BackwardIterator(const array_type& aArray)
+ : Iterator(aArray.Length(), aArray) {}
+
+ // Returns true if there are more elements to iterate.
+ // This must precede a call to GetNext(). If false is
+ // returned, GetNext() must not be called.
+ bool HasMore() const { return base_type::mPosition > 0; }
+
+ // Returns the next element and steps one step. This must
+ // be preceded by a call to HasMore().
+ // @return The next observer.
+ value_type& GetNext() {
+ NS_ASSERTION(HasMore(), "iterating beyond start of array");
+ return base_type::mArray.Elements()[--base_type::mPosition];
+ }
+
+ // Removes the element at the current iterator position.
+ // (the last element returned from |GetNext()|)
+ // This will not affect the next call to |GetNext()|
+ void Remove() {
+ return base_type::mArray.RemoveElementAt(base_type::mPosition);
+ }
+ };
+
+ struct EndSentinel {};
+
+ // Internal type, do not use directly, see
+ // ForwardRange()/EndLimitedRange()/BackwardRange().
+ template <typename Iterator, typename U>
+ struct STLIterator {
+ using value_type = std::remove_reference_t<U>;
+
+ explicit STLIterator(const nsAutoTObserverArray<T, N>& aArray)
+ : mIterator{aArray} {
+ operator++();
+ }
+
+ bool operator!=(const EndSentinel&) const {
+ // We are a non-end-sentinel and the other is an end-sentinel, so we are
+ // still valid if mCurrent is valid.
+ return mCurrent;
+ }
+
+ STLIterator& operator++() {
+ mCurrent = mIterator.HasMore() ? &mIterator.GetNext() : nullptr;
+ return *this;
+ }
+
+ value_type* operator->() { return mCurrent; }
+ U& operator*() { return *mCurrent; }
+
+ private:
+ Iterator mIterator;
+ value_type* mCurrent;
+ };
+
+ // Internal type, do not use directly, see
+ // ForwardRange()/EndLimitedRange()/BackwardRange().
+ template <typename Iterator, typename U>
+ class STLIteratorRange {
+ public:
+ using iterator = STLIterator<Iterator, U>;
+
+ explicit STLIteratorRange(const nsAutoTObserverArray<T, N>& aArray)
+ : mArray{aArray} {}
+
+ STLIteratorRange(const STLIteratorRange& aOther) = delete;
+
+ iterator begin() const { return iterator{mArray}; }
+ EndSentinel end() const { return {}; }
+
+ private:
+ const nsAutoTObserverArray<T, N>& mArray;
+ };
+
+ template <typename U>
+ using STLForwardIteratorRange = STLIteratorRange<ForwardIterator, U>;
+
+ template <typename U>
+ using STLEndLimitedIteratorRange = STLIteratorRange<EndLimitedIterator, U>;
+
+ template <typename U>
+ using STLBackwardIteratorRange = STLIteratorRange<BackwardIterator, U>;
+
+ // Constructs a range (usable with range-based for) based on the
+ // ForwardIterator semantics. Note that this range does not provide
+ // full-feature STL-style iterators usable with STL-style algorithms.
+ auto ForwardRange() { return STLForwardIteratorRange<T>{*this}; }
+
+ // Constructs a const range (usable with range-based for) based on the
+ // ForwardIterator semantics. Note that this range does not provide
+ // full-feature STL-style iterators usable with STL-style algorithms.
+ auto ForwardRange() const { return STLForwardIteratorRange<const T>{*this}; }
+
+ // Constructs a range (usable with range-based for) based on the
+ // EndLimitedIterator semantics. Note that this range does not provide
+ // full-feature STL-style iterators usable with STL-style algorithms.
+ auto EndLimitedRange() { return STLEndLimitedIteratorRange<T>{*this}; }
+
+ // Constructs a const range (usable with range-based for) based on the
+ // EndLimitedIterator semantics. Note that this range does not provide
+ // full-feature STL-style iterators usable with STL-style algorithms.
+ auto EndLimitedRange() const {
+ return STLEndLimitedIteratorRange<const T>{*this};
+ }
+
+ // Constructs a range (usable with range-based for) based on the
+ // BackwardIterator semantics. Note that this range does not provide
+ // full-feature STL-style iterators usable with STL-style algorithms.
+ auto BackwardRange() { return STLBackwardIteratorRange<T>{*this}; }
+
+ // Constructs a const range (usable with range-based for) based on the
+ // BackwardIterator semantics. Note that this range does not provide
+ // full-feature STL-style iterators usable with STL-style algorithms.
+ auto BackwardRange() const {
+ return STLBackwardIteratorRange<const T>{*this};
+ }
+
+ // Constructs a const range (usable with range-based for and STL-style
+ // algorithms) based on a non-observing iterator. The array must not be
+ // modified during iteration.
+ auto NonObservingRange() const {
+ return mozilla::detail::IteratorRange<
+ typename AutoTArray<T, N>::const_iterator,
+ typename AutoTArray<T, N>::const_reverse_iterator>{mArray.cbegin(),
+ mArray.cend()};
+ }
+
+ protected:
+ AutoTArray<T, N> mArray;
+};
+
+template <class T>
+class nsTObserverArray : public nsAutoTObserverArray<T, 0> {
+ public:
+ typedef nsAutoTObserverArray<T, 0> base_type;
+ typedef nsTObserverArray_base::size_type size_type;
+
+ //
+ // Initialization methods
+ //
+
+ nsTObserverArray() = default;
+
+ // Initialize this array and pre-allocate some number of elements.
+ explicit nsTObserverArray(size_type aCapacity) {
+ base_type::mArray.SetCapacity(aCapacity);
+ }
+
+ nsTObserverArray Clone() const {
+ auto result = nsTObserverArray{};
+ result.mArray.Assign(this->mArray);
+ return result;
+ }
+};
+
+template <typename T, size_t N>
+auto MakeBackInserter(nsAutoTObserverArray<T, N>& aArray) {
+ return mozilla::nsTArrayBackInserter<T, nsAutoTObserverArray<T, N>>{aArray};
+}
+
+template <typename T, size_t N>
+inline void ImplCycleCollectionUnlink(nsAutoTObserverArray<T, N>& aField) {
+ aField.Clear();
+}
+
+template <typename T, size_t N>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ nsAutoTObserverArray<T, N>& aField, const char* aName,
+ uint32_t aFlags = 0) {
+ aFlags |= CycleCollectionEdgeNameArrayFlag;
+ size_t length = aField.Length();
+ for (size_t i = 0; i < length; ++i) {
+ ImplCycleCollectionTraverse(aCallback, aField.ElementAt(i), aName, aFlags);
+ }
+}
+
+// Note that this macro only works if the array holds pointers to XPCOM objects.
+#define NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(array_, func_, params_) \
+ do { \
+ for (RefPtr obs_ : (array_).ForwardRange()) { \
+ obs_->func_ params_; \
+ } \
+ } while (0)
+
+// Note that this macro only works if the array holds pointers to XPCOM objects.
+#define NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(array_, func_, params_) \
+ do { \
+ for (auto* obs_ : (array_).ForwardRange()) { \
+ obs_->func_ params_; \
+ } \
+ } while (0)
+
+#endif // nsTObserverArray_h___
diff --git a/xpcom/ds/nsTPriorityQueue.h b/xpcom/ds/nsTPriorityQueue.h
new file mode 100644
index 0000000000..1fa1cc1e50
--- /dev/null
+++ b/xpcom/ds/nsTPriorityQueue.h
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_TPRIORITY_QUEUE_H_
+#define NS_TPRIORITY_QUEUE_H_
+
+#include "mozilla/Assertions.h"
+#include "nsTArray.h"
+
+/**
+ * A templatized priority queue data structure that uses an nsTArray to serve as
+ * a binary heap. The default comparator causes this to act like a min-heap.
+ * Only the LessThan method of the comparator is used.
+ */
+template <class T, class Compare = nsDefaultComparator<T, T>>
+class nsTPriorityQueue {
+ public:
+ typedef typename nsTArray<T>::size_type size_type;
+
+ /**
+ * Default constructor also creates a comparator object using the default
+ * constructor for type Compare.
+ */
+ nsTPriorityQueue() : mCompare(Compare()) {}
+
+ /**
+ * Constructor to allow a specific instance of a comparator object to be
+ * used.
+ */
+ explicit nsTPriorityQueue(const Compare& aComp) : mCompare(aComp) {}
+
+ nsTPriorityQueue(nsTPriorityQueue&&) = default;
+ nsTPriorityQueue& operator=(nsTPriorityQueue&&) = default;
+
+ /**
+ * @return True if the queue is empty or false otherwise.
+ */
+ bool IsEmpty() const { return mElements.IsEmpty(); }
+
+ /**
+ * @return The number of elements in the queue.
+ */
+ size_type Length() const { return mElements.Length(); }
+
+ /**
+ * @return The topmost element in the queue without changing the queue. This
+ * is the element 'a' such that there is no other element 'b' in the queue for
+ * which Compare(b, a) returns true. (Since this container does not check
+ * for duplicate entries there may exist 'b' for which Compare(a, b) returns
+ * false.)
+ */
+ const T& Top() const {
+ MOZ_ASSERT(!mElements.IsEmpty(), "Empty queue");
+ return mElements[0];
+ }
+
+ /**
+ * Adds an element to the queue
+ * @param aElement The element to add
+ * @return true on success, false on out of memory.
+ */
+ void Push(T&& aElement) {
+ mElements.AppendElement(std::move(aElement));
+
+ // Sift up
+ size_type i = mElements.Length() - 1;
+ while (i) {
+ size_type parent = (size_type)((i - 1) / 2);
+ if (mCompare.LessThan(mElements[parent], mElements[i])) {
+ break;
+ }
+ std::swap(mElements[i], mElements[parent]);
+ i = parent;
+ }
+ }
+
+ /**
+ * Removes and returns the top-most element from the queue.
+ * @return The topmost element, that is, the element 'a' such that there is no
+ * other element 'b' in the queue for which Compare(b, a) returns true.
+ * @see Top()
+ */
+ T Pop() {
+ MOZ_ASSERT(!mElements.IsEmpty(), "Empty queue");
+ T pop = std::move(mElements[0]);
+
+ const size_type newLength = mElements.Length() - 1;
+ if (newLength == 0) {
+ mElements.Clear();
+ return pop;
+ }
+
+ // Move last to front
+ mElements[0] = mElements.PopLastElement();
+
+ // Sift down
+ size_type i = 0;
+ while (2 * i + 1 < newLength) {
+ size_type swap = i;
+ size_type l_child = 2 * i + 1;
+ if (mCompare.LessThan(mElements[l_child], mElements[i])) {
+ swap = l_child;
+ }
+ size_type r_child = l_child + 1;
+ if (r_child < newLength &&
+ mCompare.LessThan(mElements[r_child], mElements[swap])) {
+ swap = r_child;
+ }
+ if (swap == i) {
+ break;
+ }
+ std::swap(mElements[i], mElements[swap]);
+ i = swap;
+ }
+
+ return pop;
+ }
+
+ /**
+ * Removes all elements from the queue.
+ */
+ void Clear() { mElements.Clear(); }
+
+ /**
+ * Provides readonly access to the queue elements as an array. Generally this
+ * should be avoided but may be needed in some situations such as when the
+ * elements contained in the queue need to be enumerated for cycle-collection.
+ * @return A pointer to the first element of the array. If the array is
+ * empty, then this pointer must not be dereferenced.
+ */
+ const T* Elements() const { return mElements.Elements(); }
+
+ nsTPriorityQueue Clone() const {
+ auto res = nsTPriorityQueue{mCompare};
+ res.mElements = mElements.Clone();
+ return res;
+ }
+
+ protected:
+ nsTArray<T> mElements;
+ Compare mCompare; // Comparator object
+};
+
+#endif // NS_TPRIORITY_QUEUE_H_
diff --git a/xpcom/ds/nsVariant.cpp b/xpcom/ds/nsVariant.cpp
new file mode 100644
index 0000000000..3ccfc7fa31
--- /dev/null
+++ b/xpcom/ds/nsVariant.cpp
@@ -0,0 +1,1881 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsVariant.h"
+#include "prprf.h"
+#include "prdtoa.h"
+#include <math.h>
+#include "nsCycleCollectionParticipant.h"
+#include "xptinfo.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsCRTGlue.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Printf.h"
+
+/***************************************************************************/
+// Helpers for static convert functions...
+
+static nsresult String2Double(const char* aString, double* aResult) {
+ char* next;
+ double value = PR_strtod(aString, &next);
+ if (next == aString) {
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+ *aResult = value;
+ return NS_OK;
+}
+
+static nsresult AString2Double(const nsAString& aString, double* aResult) {
+ char* pChars = ToNewCString(aString, mozilla::fallible);
+ if (!pChars) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nsresult rv = String2Double(pChars, aResult);
+ free(pChars);
+ return rv;
+}
+
+static nsresult AUTF8String2Double(const nsAUTF8String& aString,
+ double* aResult) {
+ return String2Double(PromiseFlatUTF8String(aString).get(), aResult);
+}
+
+static nsresult ACString2Double(const nsACString& aString, double* aResult) {
+ return String2Double(PromiseFlatCString(aString).get(), aResult);
+}
+
+// Fills aOutData with double, uint32_t, or int32_t.
+// Returns NS_OK, an error code, or a non-NS_OK success code
+nsresult nsDiscriminatedUnion::ToManageableNumber(
+ nsDiscriminatedUnion* aOutData) const {
+ nsresult rv;
+
+ switch (mType) {
+ // This group results in a int32_t...
+
+#define CASE__NUMBER_INT32(type_, member_) \
+ case nsIDataType::type_: \
+ aOutData->u.mInt32Value = u.member_; \
+ aOutData->mType = nsIDataType::VTYPE_INT32; \
+ return NS_OK;
+
+ CASE__NUMBER_INT32(VTYPE_INT8, mInt8Value)
+ CASE__NUMBER_INT32(VTYPE_INT16, mInt16Value)
+ CASE__NUMBER_INT32(VTYPE_INT32, mInt32Value)
+ CASE__NUMBER_INT32(VTYPE_UINT8, mUint8Value)
+ CASE__NUMBER_INT32(VTYPE_UINT16, mUint16Value)
+ CASE__NUMBER_INT32(VTYPE_BOOL, mBoolValue)
+ CASE__NUMBER_INT32(VTYPE_CHAR, mCharValue)
+ CASE__NUMBER_INT32(VTYPE_WCHAR, mWCharValue)
+
+#undef CASE__NUMBER_INT32
+
+ // This group results in a uint32_t...
+
+ case nsIDataType::VTYPE_UINT32:
+ aOutData->u.mUint32Value = u.mUint32Value;
+ aOutData->mType = nsIDataType::VTYPE_UINT32;
+ return NS_OK;
+
+ // This group results in a double...
+
+ case nsIDataType::VTYPE_INT64:
+ case nsIDataType::VTYPE_UINT64:
+ // XXX Need boundary checking here.
+ // We may need to return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
+ aOutData->u.mDoubleValue = double(u.mInt64Value);
+ aOutData->mType = nsIDataType::VTYPE_DOUBLE;
+ return NS_OK;
+ case nsIDataType::VTYPE_FLOAT:
+ aOutData->u.mDoubleValue = u.mFloatValue;
+ aOutData->mType = nsIDataType::VTYPE_DOUBLE;
+ return NS_OK;
+ case nsIDataType::VTYPE_DOUBLE:
+ aOutData->u.mDoubleValue = u.mDoubleValue;
+ aOutData->mType = nsIDataType::VTYPE_DOUBLE;
+ return NS_OK;
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ rv = String2Double(u.str.mStringValue, &aOutData->u.mDoubleValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aOutData->mType = nsIDataType::VTYPE_DOUBLE;
+ return NS_OK;
+ case nsIDataType::VTYPE_ASTRING:
+ rv = AString2Double(*u.mAStringValue, &aOutData->u.mDoubleValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aOutData->mType = nsIDataType::VTYPE_DOUBLE;
+ return NS_OK;
+ case nsIDataType::VTYPE_UTF8STRING:
+ rv = AUTF8String2Double(*u.mUTF8StringValue, &aOutData->u.mDoubleValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aOutData->mType = nsIDataType::VTYPE_DOUBLE;
+ return NS_OK;
+ case nsIDataType::VTYPE_CSTRING:
+ rv = ACString2Double(*u.mCStringValue, &aOutData->u.mDoubleValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aOutData->mType = nsIDataType::VTYPE_DOUBLE;
+ return NS_OK;
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ rv = AString2Double(nsDependentString(u.wstr.mWStringValue),
+ &aOutData->u.mDoubleValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aOutData->mType = nsIDataType::VTYPE_DOUBLE;
+ return NS_OK;
+
+ // This group fails...
+
+ case nsIDataType::VTYPE_VOID:
+ case nsIDataType::VTYPE_ID:
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ case nsIDataType::VTYPE_ARRAY:
+ case nsIDataType::VTYPE_EMPTY_ARRAY:
+ case nsIDataType::VTYPE_EMPTY:
+ default:
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+}
+
+/***************************************************************************/
+// Array helpers...
+
+void nsDiscriminatedUnion::FreeArray() {
+ NS_ASSERTION(mType == nsIDataType::VTYPE_ARRAY, "bad FreeArray call");
+ NS_ASSERTION(u.array.mArrayValue, "bad array");
+ NS_ASSERTION(u.array.mArrayCount, "bad array count");
+
+#define CASE__FREE_ARRAY_PTR(type_, ctype_) \
+ case nsIDataType::type_: { \
+ ctype_** p = (ctype_**)u.array.mArrayValue; \
+ for (uint32_t i = u.array.mArrayCount; i > 0; p++, i--) \
+ if (*p) free((char*)*p); \
+ break; \
+ }
+
+#define CASE__FREE_ARRAY_IFACE(type_, ctype_) \
+ case nsIDataType::type_: { \
+ ctype_** p = (ctype_**)u.array.mArrayValue; \
+ for (uint32_t i = u.array.mArrayCount; i > 0; p++, i--) \
+ if (*p) (*p)->Release(); \
+ break; \
+ }
+
+ switch (u.array.mArrayType) {
+ case nsIDataType::VTYPE_INT8:
+ case nsIDataType::VTYPE_INT16:
+ case nsIDataType::VTYPE_INT32:
+ case nsIDataType::VTYPE_INT64:
+ case nsIDataType::VTYPE_UINT8:
+ case nsIDataType::VTYPE_UINT16:
+ case nsIDataType::VTYPE_UINT32:
+ case nsIDataType::VTYPE_UINT64:
+ case nsIDataType::VTYPE_FLOAT:
+ case nsIDataType::VTYPE_DOUBLE:
+ case nsIDataType::VTYPE_BOOL:
+ case nsIDataType::VTYPE_CHAR:
+ case nsIDataType::VTYPE_WCHAR:
+ break;
+
+ // XXX We ASSUME that "array of nsID" means "array of pointers to nsID".
+ CASE__FREE_ARRAY_PTR(VTYPE_ID, nsID)
+ CASE__FREE_ARRAY_PTR(VTYPE_CHAR_STR, char)
+ CASE__FREE_ARRAY_PTR(VTYPE_WCHAR_STR, char16_t)
+ CASE__FREE_ARRAY_IFACE(VTYPE_INTERFACE, nsISupports)
+ CASE__FREE_ARRAY_IFACE(VTYPE_INTERFACE_IS, nsISupports)
+
+ // The rest are illegal.
+ case nsIDataType::VTYPE_VOID:
+ case nsIDataType::VTYPE_ASTRING:
+ case nsIDataType::VTYPE_UTF8STRING:
+ case nsIDataType::VTYPE_CSTRING:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ case nsIDataType::VTYPE_ARRAY:
+ case nsIDataType::VTYPE_EMPTY_ARRAY:
+ case nsIDataType::VTYPE_EMPTY:
+ default:
+ NS_ERROR("bad type in array!");
+ break;
+ }
+
+ // Free the array memory.
+ free((char*)u.array.mArrayValue);
+
+#undef CASE__FREE_ARRAY_PTR
+#undef CASE__FREE_ARRAY_IFACE
+}
+
+static nsresult CloneArray(uint16_t aInType, const nsIID* aInIID,
+ uint32_t aInCount, void* aInValue,
+ uint16_t* aOutType, nsIID* aOutIID,
+ uint32_t* aOutCount, void** aOutValue) {
+ NS_ASSERTION(aInCount, "bad param");
+ NS_ASSERTION(aInValue, "bad param");
+ NS_ASSERTION(aOutType, "bad param");
+ NS_ASSERTION(aOutCount, "bad param");
+ NS_ASSERTION(aOutValue, "bad param");
+
+ uint32_t i;
+
+ // First we figure out the size of the elements for the new u.array.
+
+ size_t elementSize;
+ size_t allocSize;
+
+ switch (aInType) {
+ case nsIDataType::VTYPE_INT8:
+ elementSize = sizeof(int8_t);
+ break;
+ case nsIDataType::VTYPE_INT16:
+ elementSize = sizeof(int16_t);
+ break;
+ case nsIDataType::VTYPE_INT32:
+ elementSize = sizeof(int32_t);
+ break;
+ case nsIDataType::VTYPE_INT64:
+ elementSize = sizeof(int64_t);
+ break;
+ case nsIDataType::VTYPE_UINT8:
+ elementSize = sizeof(uint8_t);
+ break;
+ case nsIDataType::VTYPE_UINT16:
+ elementSize = sizeof(uint16_t);
+ break;
+ case nsIDataType::VTYPE_UINT32:
+ elementSize = sizeof(uint32_t);
+ break;
+ case nsIDataType::VTYPE_UINT64:
+ elementSize = sizeof(uint64_t);
+ break;
+ case nsIDataType::VTYPE_FLOAT:
+ elementSize = sizeof(float);
+ break;
+ case nsIDataType::VTYPE_DOUBLE:
+ elementSize = sizeof(double);
+ break;
+ case nsIDataType::VTYPE_BOOL:
+ elementSize = sizeof(bool);
+ break;
+ case nsIDataType::VTYPE_CHAR:
+ elementSize = sizeof(char);
+ break;
+ case nsIDataType::VTYPE_WCHAR:
+ elementSize = sizeof(char16_t);
+ break;
+
+ // XXX We ASSUME that "array of nsID" means "array of pointers to nsID".
+ case nsIDataType::VTYPE_ID:
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ elementSize = sizeof(void*);
+ break;
+
+ // The rest are illegal.
+ case nsIDataType::VTYPE_ASTRING:
+ case nsIDataType::VTYPE_UTF8STRING:
+ case nsIDataType::VTYPE_CSTRING:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ case nsIDataType::VTYPE_VOID:
+ case nsIDataType::VTYPE_ARRAY:
+ case nsIDataType::VTYPE_EMPTY_ARRAY:
+ case nsIDataType::VTYPE_EMPTY:
+ default:
+ NS_ERROR("bad type in array!");
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ // Alloc the u.array.
+
+ allocSize = aInCount * elementSize;
+ *aOutValue = moz_xmalloc(allocSize);
+
+ // Clone the elements.
+
+ switch (aInType) {
+ case nsIDataType::VTYPE_INT8:
+ case nsIDataType::VTYPE_INT16:
+ case nsIDataType::VTYPE_INT32:
+ case nsIDataType::VTYPE_INT64:
+ case nsIDataType::VTYPE_UINT8:
+ case nsIDataType::VTYPE_UINT16:
+ case nsIDataType::VTYPE_UINT32:
+ case nsIDataType::VTYPE_UINT64:
+ case nsIDataType::VTYPE_FLOAT:
+ case nsIDataType::VTYPE_DOUBLE:
+ case nsIDataType::VTYPE_BOOL:
+ case nsIDataType::VTYPE_CHAR:
+ case nsIDataType::VTYPE_WCHAR:
+ memcpy(*aOutValue, aInValue, allocSize);
+ break;
+
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ if (aOutIID) {
+ *aOutIID = *aInIID;
+ }
+ [[fallthrough]];
+
+ case nsIDataType::VTYPE_INTERFACE: {
+ memcpy(*aOutValue, aInValue, allocSize);
+
+ nsISupports** p = (nsISupports**)*aOutValue;
+ for (i = aInCount; i > 0; ++p, --i)
+ if (*p) {
+ (*p)->AddRef();
+ }
+ break;
+ }
+
+ // XXX We ASSUME that "array of nsID" means "array of pointers to nsID".
+ case nsIDataType::VTYPE_ID: {
+ nsID** inp = (nsID**)aInValue;
+ nsID** outp = (nsID**)*aOutValue;
+ for (i = aInCount; i > 0; --i) {
+ nsID* idp = *(inp++);
+ if (idp) {
+ *(outp++) = idp->Clone();
+ } else {
+ *(outp++) = nullptr;
+ }
+ }
+ break;
+ }
+
+ case nsIDataType::VTYPE_CHAR_STR: {
+ char** inp = (char**)aInValue;
+ char** outp = (char**)*aOutValue;
+ for (i = aInCount; i > 0; i--) {
+ char* str = *(inp++);
+ if (str) {
+ *(outp++) = moz_xstrdup(str);
+ } else {
+ *(outp++) = nullptr;
+ }
+ }
+ break;
+ }
+
+ case nsIDataType::VTYPE_WCHAR_STR: {
+ char16_t** inp = (char16_t**)aInValue;
+ char16_t** outp = (char16_t**)*aOutValue;
+ for (i = aInCount; i > 0; i--) {
+ char16_t* str = *(inp++);
+ if (str) {
+ *(outp++) = NS_xstrdup(str);
+ } else {
+ *(outp++) = nullptr;
+ }
+ }
+ break;
+ }
+
+ // The rest are illegal.
+ case nsIDataType::VTYPE_VOID:
+ case nsIDataType::VTYPE_ARRAY:
+ case nsIDataType::VTYPE_EMPTY_ARRAY:
+ case nsIDataType::VTYPE_EMPTY:
+ case nsIDataType::VTYPE_ASTRING:
+ case nsIDataType::VTYPE_UTF8STRING:
+ case nsIDataType::VTYPE_CSTRING:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ default:
+ NS_ERROR("bad type in array!");
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ *aOutType = aInType;
+ *aOutCount = aInCount;
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+#define TRIVIAL_DATA_CONVERTER(type_, member_, retval_) \
+ if (mType == nsIDataType::type_) { \
+ *retval_ = u.member_; \
+ return NS_OK; \
+ }
+
+#define NUMERIC_CONVERSION_METHOD_BEGIN(type_, Ctype_, name_) \
+ nsresult nsDiscriminatedUnion::ConvertTo##name_(Ctype_* aResult) const { \
+ TRIVIAL_DATA_CONVERTER(type_, m##name_##Value, aResult) \
+ nsDiscriminatedUnion tempData; \
+ nsresult rv = ToManageableNumber(&tempData); \
+ /* */ \
+ /* NOTE: rv may indicate a success code that we want to preserve */ \
+ /* For the final return. So all the return cases below should return */ \
+ /* this rv when indicating success. */ \
+ /* */ \
+ if (NS_FAILED(rv)) return rv; \
+ switch (tempData.mType) {
+#define CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(Ctype_) \
+ case nsIDataType::VTYPE_INT32: \
+ *aResult = (Ctype_)tempData.u.mInt32Value; \
+ return rv;
+
+#define CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(Ctype_, min_, max_) \
+ case nsIDataType::VTYPE_INT32: { \
+ int32_t value = tempData.u.mInt32Value; \
+ if (value < (min_) || value > (max_)) { \
+ return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \
+ } \
+ *aResult = (Ctype_)value; \
+ return rv; \
+ }
+
+#define CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(Ctype_) \
+ case nsIDataType::VTYPE_UINT32: \
+ *aResult = (Ctype_)tempData.u.mUint32Value; \
+ return rv;
+
+#define CASE__NUMERIC_CONVERSION_UINT32_MAX(Ctype_, max_) \
+ case nsIDataType::VTYPE_UINT32: { \
+ uint32_t value = tempData.u.mUint32Value; \
+ if (value > (max_)) return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \
+ *aResult = (Ctype_)value; \
+ return rv; \
+ }
+
+#define CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(Ctype_) \
+ case nsIDataType::VTYPE_DOUBLE: \
+ *aResult = (Ctype_)tempData.u.mDoubleValue; \
+ return rv;
+
+#define CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(Ctype_, min_, max_) \
+ case nsIDataType::VTYPE_DOUBLE: { \
+ double value = tempData.u.mDoubleValue; \
+ if (mozilla::IsNaN(value) || value < (min_) || value > (max_)) { \
+ return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \
+ } \
+ *aResult = (Ctype_)value; \
+ return (0.0 == fmod(value, 1.0)) ? rv \
+ : NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA; \
+ }
+
+#define CASES__NUMERIC_CONVERSION_NORMAL(Ctype_, min_, max_) \
+ CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(Ctype_, min_, max_) \
+ CASE__NUMERIC_CONVERSION_UINT32_MAX(Ctype_, max_) \
+ CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(Ctype_, min_, max_)
+
+#define NUMERIC_CONVERSION_METHOD_END \
+ default: \
+ NS_ERROR("bad type returned from ToManageableNumber"); \
+ return NS_ERROR_CANNOT_CONVERT_DATA; \
+ } \
+ }
+
+#define NUMERIC_CONVERSION_METHOD_NORMAL(type_, Ctype_, name_, min_, max_) \
+ NUMERIC_CONVERSION_METHOD_BEGIN(type_, Ctype_, name_) \
+ CASES__NUMERIC_CONVERSION_NORMAL(Ctype_, min_, max_) \
+ NUMERIC_CONVERSION_METHOD_END
+
+/***************************************************************************/
+// These expand into full public methods...
+
+NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_INT8, uint8_t, Int8, (-127 - 1), 127)
+NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_INT16, int16_t, Int16, (-32767 - 1),
+ 32767)
+
+NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_INT32, int32_t, Int32)
+CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(int32_t)
+CASE__NUMERIC_CONVERSION_UINT32_MAX(int32_t, 2147483647)
+CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(int32_t, (-2147483647 - 1),
+ 2147483647)
+NUMERIC_CONVERSION_METHOD_END
+
+NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_UINT8, uint8_t, Uint8, 0, 255)
+NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_UINT16, uint16_t, Uint16, 0, 65535)
+
+NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_UINT32, uint32_t, Uint32)
+CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(uint32_t, 0, 2147483647)
+CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(uint32_t)
+CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(uint32_t, 0, 4294967295U)
+NUMERIC_CONVERSION_METHOD_END
+
+// XXX toFloat conversions need to be fixed!
+NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_FLOAT, float, Float)
+CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(float)
+CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(float)
+CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(float)
+NUMERIC_CONVERSION_METHOD_END
+
+NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_DOUBLE, double, Double)
+CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(double)
+CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(double)
+CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(double)
+NUMERIC_CONVERSION_METHOD_END
+
+// XXX toChar conversions need to be fixed!
+NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_CHAR, char, Char, CHAR_MIN, CHAR_MAX)
+
+// XXX toWChar conversions need to be fixed!
+NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_WCHAR, char16_t, WChar, 0,
+ std::numeric_limits<char16_t>::max())
+
+#undef NUMERIC_CONVERSION_METHOD_BEGIN
+#undef CASE__NUMERIC_CONVERSION_INT32_JUST_CAST
+#undef CASE__NUMERIC_CONVERSION_INT32_MIN_MAX
+#undef CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST
+#undef CASE__NUMERIC_CONVERSION_UINT32_MIN_MAX
+#undef CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST
+#undef CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX
+#undef CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT
+#undef CASES__NUMERIC_CONVERSION_NORMAL
+#undef NUMERIC_CONVERSION_METHOD_END
+#undef NUMERIC_CONVERSION_METHOD_NORMAL
+
+/***************************************************************************/
+
+// Just leverage a numeric converter for bool (but restrict the values).
+// XXX Is this really what we want to do?
+
+nsresult nsDiscriminatedUnion::ConvertToBool(bool* aResult) const {
+ TRIVIAL_DATA_CONVERTER(VTYPE_BOOL, mBoolValue, aResult)
+
+ double val;
+ nsresult rv = ConvertToDouble(&val);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // NaN is falsy in JS, so we might as well make it false here.
+ if (mozilla::IsNaN(val)) {
+ *aResult = false;
+ } else {
+ *aResult = 0.0 != val;
+ }
+ return rv;
+}
+
+/***************************************************************************/
+
+nsresult nsDiscriminatedUnion::ConvertToInt64(int64_t* aResult) const {
+ TRIVIAL_DATA_CONVERTER(VTYPE_INT64, mInt64Value, aResult)
+ TRIVIAL_DATA_CONVERTER(VTYPE_UINT64, mUint64Value, aResult)
+
+ nsDiscriminatedUnion tempData;
+ nsresult rv = ToManageableNumber(&tempData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ switch (tempData.mType) {
+ case nsIDataType::VTYPE_INT32:
+ *aResult = tempData.u.mInt32Value;
+ return rv;
+ case nsIDataType::VTYPE_UINT32:
+ *aResult = tempData.u.mUint32Value;
+ return rv;
+ case nsIDataType::VTYPE_DOUBLE: {
+ double value = tempData.u.mDoubleValue;
+ if (mozilla::IsNaN(value)) {
+ return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
+ }
+ // XXX should check for data loss here!
+ *aResult = value;
+ return rv;
+ }
+ default:
+ NS_ERROR("bad type returned from ToManageableNumber");
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+}
+
+nsresult nsDiscriminatedUnion::ConvertToUint64(uint64_t* aResult) const {
+ return ConvertToInt64((int64_t*)aResult);
+}
+
+/***************************************************************************/
+
+bool nsDiscriminatedUnion::String2ID(nsID* aPid) const {
+ nsAutoString tempString;
+ nsAString* pString;
+
+ switch (mType) {
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ return aPid->Parse(u.str.mStringValue);
+ case nsIDataType::VTYPE_CSTRING:
+ return aPid->Parse(PromiseFlatCString(*u.mCStringValue).get());
+ case nsIDataType::VTYPE_UTF8STRING:
+ return aPid->Parse(PromiseFlatUTF8String(*u.mUTF8StringValue).get());
+ case nsIDataType::VTYPE_ASTRING:
+ pString = u.mAStringValue;
+ break;
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ tempString.Assign(u.wstr.mWStringValue);
+ pString = &tempString;
+ break;
+ default:
+ NS_ERROR("bad type in call to String2ID");
+ return false;
+ }
+
+ char* pChars = ToNewCString(*pString, mozilla::fallible);
+ if (!pChars) {
+ return false;
+ }
+ bool result = aPid->Parse(pChars);
+ free(pChars);
+ return result;
+}
+
+nsresult nsDiscriminatedUnion::ConvertToID(nsID* aResult) const {
+ nsID id;
+
+ switch (mType) {
+ case nsIDataType::VTYPE_ID:
+ *aResult = u.mIDValue;
+ return NS_OK;
+ case nsIDataType::VTYPE_INTERFACE:
+ *aResult = NS_GET_IID(nsISupports);
+ return NS_OK;
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ *aResult = u.iface.mInterfaceID;
+ return NS_OK;
+ case nsIDataType::VTYPE_ASTRING:
+ case nsIDataType::VTYPE_UTF8STRING:
+ case nsIDataType::VTYPE_CSTRING:
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ if (!String2ID(&id)) {
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+ *aResult = id;
+ return NS_OK;
+ default:
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+}
+
+/***************************************************************************/
+
+nsresult nsDiscriminatedUnion::ToString(nsACString& aOutString) const {
+ mozilla::SmprintfPointer pptr;
+
+ switch (mType) {
+ // all the stuff we don't handle...
+ case nsIDataType::VTYPE_ASTRING:
+ case nsIDataType::VTYPE_UTF8STRING:
+ case nsIDataType::VTYPE_CSTRING:
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ case nsIDataType::VTYPE_WCHAR:
+ NS_ERROR("ToString being called for a string type - screwy logic!");
+ [[fallthrough]];
+
+ // XXX We might want stringified versions of these... ???
+
+ case nsIDataType::VTYPE_VOID:
+ case nsIDataType::VTYPE_EMPTY:
+ aOutString.SetIsVoid(true);
+ return NS_OK;
+
+ case nsIDataType::VTYPE_EMPTY_ARRAY:
+ case nsIDataType::VTYPE_ARRAY:
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ default:
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+
+ // nsID has its own text formatter.
+
+ case nsIDataType::VTYPE_ID: {
+ char* ptr = u.mIDValue.ToString();
+ if (!ptr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aOutString.Assign(ptr);
+ free(ptr);
+ return NS_OK;
+ }
+
+ // Can't use Smprintf for floats, since it's locale-dependent
+#define CASE__APPENDFLOAT_NUMBER(type_, member_) \
+ case nsIDataType::type_: { \
+ nsAutoCString str; \
+ str.AppendFloat(u.member_); \
+ aOutString.Assign(str); \
+ return NS_OK; \
+ }
+
+ CASE__APPENDFLOAT_NUMBER(VTYPE_FLOAT, mFloatValue)
+ CASE__APPENDFLOAT_NUMBER(VTYPE_DOUBLE, mDoubleValue)
+
+#undef CASE__APPENDFLOAT_NUMBER
+
+ // the rest can be Smprintf'd and use common code.
+
+#define CASE__SMPRINTF_NUMBER(type_, format_, cast_, member_) \
+ case nsIDataType::type_: \
+ static_assert(sizeof(cast_) >= sizeof(u.member_), \
+ "size of type should be at least as big as member"); \
+ pptr = mozilla::Smprintf(format_, (cast_)u.member_); \
+ break;
+
+ CASE__SMPRINTF_NUMBER(VTYPE_INT8, "%d", int, mInt8Value)
+ CASE__SMPRINTF_NUMBER(VTYPE_INT16, "%d", int, mInt16Value)
+ CASE__SMPRINTF_NUMBER(VTYPE_INT32, "%d", int, mInt32Value)
+ CASE__SMPRINTF_NUMBER(VTYPE_INT64, "%" PRId64, int64_t, mInt64Value)
+
+ CASE__SMPRINTF_NUMBER(VTYPE_UINT8, "%u", unsigned, mUint8Value)
+ CASE__SMPRINTF_NUMBER(VTYPE_UINT16, "%u", unsigned, mUint16Value)
+ CASE__SMPRINTF_NUMBER(VTYPE_UINT32, "%u", unsigned, mUint32Value)
+ CASE__SMPRINTF_NUMBER(VTYPE_UINT64, "%" PRIu64, int64_t, mUint64Value)
+
+ // XXX Would we rather print "true" / "false" ?
+ CASE__SMPRINTF_NUMBER(VTYPE_BOOL, "%d", int, mBoolValue)
+
+ CASE__SMPRINTF_NUMBER(VTYPE_CHAR, "%c", char, mCharValue)
+
+#undef CASE__SMPRINTF_NUMBER
+ }
+
+ if (!pptr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aOutString.Assign(pptr.get());
+ return NS_OK;
+}
+
+nsresult nsDiscriminatedUnion::ConvertToAString(nsAString& aResult) const {
+ switch (mType) {
+ case nsIDataType::VTYPE_ASTRING:
+ aResult.Assign(*u.mAStringValue);
+ return NS_OK;
+ case nsIDataType::VTYPE_CSTRING:
+ CopyASCIItoUTF16(*u.mCStringValue, aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_UTF8STRING:
+ CopyUTF8toUTF16(*u.mUTF8StringValue, aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_CHAR_STR:
+ CopyASCIItoUTF16(mozilla::MakeStringSpan(u.str.mStringValue), aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_WCHAR_STR:
+ aResult.Assign(u.wstr.mWStringValue);
+ return NS_OK;
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ CopyASCIItoUTF16(
+ nsDependentCString(u.str.mStringValue, u.str.mStringLength), aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ aResult.Assign(u.wstr.mWStringValue, u.wstr.mWStringLength);
+ return NS_OK;
+ case nsIDataType::VTYPE_WCHAR:
+ aResult.Assign(u.mWCharValue);
+ return NS_OK;
+ default: {
+ nsAutoCString tempCString;
+ nsresult rv = ToString(tempCString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ CopyASCIItoUTF16(tempCString, aResult);
+ return NS_OK;
+ }
+ }
+}
+
+nsresult nsDiscriminatedUnion::ConvertToACString(nsACString& aResult) const {
+ switch (mType) {
+ case nsIDataType::VTYPE_ASTRING:
+ LossyCopyUTF16toASCII(*u.mAStringValue, aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_CSTRING:
+ aResult.Assign(*u.mCStringValue);
+ return NS_OK;
+ case nsIDataType::VTYPE_UTF8STRING:
+ // XXX This is an extra copy that should be avoided
+ // once Jag lands support for UTF8String and associated
+ // conversion methods.
+ LossyCopyUTF16toASCII(NS_ConvertUTF8toUTF16(*u.mUTF8StringValue),
+ aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_CHAR_STR:
+ aResult.Assign(*u.str.mStringValue);
+ return NS_OK;
+ case nsIDataType::VTYPE_WCHAR_STR:
+ LossyCopyUTF16toASCII(nsDependentString(u.wstr.mWStringValue), aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ aResult.Assign(u.str.mStringValue, u.str.mStringLength);
+ return NS_OK;
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ LossyCopyUTF16toASCII(
+ nsDependentString(u.wstr.mWStringValue, u.wstr.mWStringLength),
+ aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_WCHAR: {
+ const char16_t* str = &u.mWCharValue;
+ LossyCopyUTF16toASCII(Substring(str, 1), aResult);
+ return NS_OK;
+ }
+ default:
+ return ToString(aResult);
+ }
+}
+
+nsresult nsDiscriminatedUnion::ConvertToAUTF8String(
+ nsAUTF8String& aResult) const {
+ switch (mType) {
+ case nsIDataType::VTYPE_ASTRING:
+ CopyUTF16toUTF8(*u.mAStringValue, aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_CSTRING:
+ // XXX Extra copy, can be removed if we're sure CSTRING can
+ // only contain ASCII.
+ CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(*u.mCStringValue), aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_UTF8STRING:
+ aResult.Assign(*u.mUTF8StringValue);
+ return NS_OK;
+ case nsIDataType::VTYPE_CHAR_STR:
+ // XXX Extra copy, can be removed if we're sure CHAR_STR can
+ // only contain ASCII.
+ CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(u.str.mStringValue), aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_WCHAR_STR:
+ CopyUTF16toUTF8(mozilla::MakeStringSpan(u.wstr.mWStringValue), aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ // XXX Extra copy, can be removed if we're sure CHAR_STR can
+ // only contain ASCII.
+ CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(nsDependentCString(
+ u.str.mStringValue, u.str.mStringLength)),
+ aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ CopyUTF16toUTF8(
+ nsDependentString(u.wstr.mWStringValue, u.wstr.mWStringLength),
+ aResult);
+ return NS_OK;
+ case nsIDataType::VTYPE_WCHAR: {
+ const char16_t* str = &u.mWCharValue;
+ CopyUTF16toUTF8(Substring(str, 1), aResult);
+ return NS_OK;
+ }
+ default: {
+ nsAutoCString tempCString;
+ nsresult rv = ToString(tempCString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // XXX Extra copy, can be removed if we're sure tempCString can
+ // only contain ASCII.
+ CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(tempCString), aResult);
+ return NS_OK;
+ }
+ }
+}
+
+nsresult nsDiscriminatedUnion::ConvertToString(char** aResult) const {
+ uint32_t ignored;
+ return ConvertToStringWithSize(&ignored, aResult);
+}
+
+nsresult nsDiscriminatedUnion::ConvertToWString(char16_t** aResult) const {
+ uint32_t ignored;
+ return ConvertToWStringWithSize(&ignored, aResult);
+}
+
+nsresult nsDiscriminatedUnion::ConvertToStringWithSize(uint32_t* aSize,
+ char** aStr) const {
+ nsAutoString tempString;
+ nsAutoCString tempCString;
+ nsresult rv;
+
+ switch (mType) {
+ case nsIDataType::VTYPE_ASTRING:
+ *aSize = u.mAStringValue->Length();
+ *aStr = ToNewCString(*u.mAStringValue);
+ break;
+ case nsIDataType::VTYPE_CSTRING:
+ *aSize = u.mCStringValue->Length();
+ *aStr = ToNewCString(*u.mCStringValue);
+ break;
+ case nsIDataType::VTYPE_UTF8STRING: {
+ // XXX This is doing 1 extra copy. Need to fix this
+ // when Jag lands UTF8String
+ // we want:
+ // *aSize = *mUTF8StringValue->Length();
+ // *aStr = ToNewCString(*mUTF8StringValue);
+ // But this will have to do for now.
+ const NS_ConvertUTF8toUTF16 tempString16(*u.mUTF8StringValue);
+ *aSize = tempString16.Length();
+ *aStr = ToNewCString(tempString16);
+ break;
+ }
+ case nsIDataType::VTYPE_CHAR_STR: {
+ nsDependentCString cString(u.str.mStringValue);
+ *aSize = cString.Length();
+ *aStr = ToNewCString(cString);
+ break;
+ }
+ case nsIDataType::VTYPE_WCHAR_STR: {
+ nsDependentString string(u.wstr.mWStringValue);
+ *aSize = string.Length();
+ *aStr = ToNewCString(string);
+ break;
+ }
+ case nsIDataType::VTYPE_STRING_SIZE_IS: {
+ nsDependentCString cString(u.str.mStringValue, u.str.mStringLength);
+ *aSize = cString.Length();
+ *aStr = ToNewCString(cString);
+ break;
+ }
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS: {
+ nsDependentString string(u.wstr.mWStringValue, u.wstr.mWStringLength);
+ *aSize = string.Length();
+ *aStr = ToNewCString(string);
+ break;
+ }
+ case nsIDataType::VTYPE_WCHAR:
+ tempString.Assign(u.mWCharValue);
+ *aSize = tempString.Length();
+ *aStr = ToNewCString(tempString);
+ break;
+ default:
+ rv = ToString(tempCString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aSize = tempCString.Length();
+ *aStr = ToNewCString(tempCString);
+ break;
+ }
+
+ return *aStr ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+nsresult nsDiscriminatedUnion::ConvertToWStringWithSize(uint32_t* aSize,
+ char16_t** aStr) const {
+ nsAutoString tempString;
+ nsAutoCString tempCString;
+ nsresult rv;
+
+ switch (mType) {
+ case nsIDataType::VTYPE_ASTRING:
+ *aSize = u.mAStringValue->Length();
+ *aStr = ToNewUnicode(*u.mAStringValue);
+ break;
+ case nsIDataType::VTYPE_CSTRING:
+ *aSize = u.mCStringValue->Length();
+ *aStr = ToNewUnicode(*u.mCStringValue);
+ break;
+ case nsIDataType::VTYPE_UTF8STRING: {
+ *aStr = UTF8ToNewUnicode(*u.mUTF8StringValue, aSize);
+ break;
+ }
+ case nsIDataType::VTYPE_CHAR_STR: {
+ nsDependentCString cString(u.str.mStringValue);
+ *aSize = cString.Length();
+ *aStr = ToNewUnicode(cString);
+ break;
+ }
+ case nsIDataType::VTYPE_WCHAR_STR: {
+ nsDependentString string(u.wstr.mWStringValue);
+ *aSize = string.Length();
+ *aStr = ToNewUnicode(string);
+ break;
+ }
+ case nsIDataType::VTYPE_STRING_SIZE_IS: {
+ nsDependentCString cString(u.str.mStringValue, u.str.mStringLength);
+ *aSize = cString.Length();
+ *aStr = ToNewUnicode(cString);
+ break;
+ }
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS: {
+ nsDependentString string(u.wstr.mWStringValue, u.wstr.mWStringLength);
+ *aSize = string.Length();
+ *aStr = ToNewUnicode(string);
+ break;
+ }
+ case nsIDataType::VTYPE_WCHAR:
+ tempString.Assign(u.mWCharValue);
+ *aSize = tempString.Length();
+ *aStr = ToNewUnicode(tempString);
+ break;
+ default:
+ rv = ToString(tempCString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aSize = tempCString.Length();
+ *aStr = ToNewUnicode(tempCString);
+ break;
+ }
+
+ return *aStr ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult nsDiscriminatedUnion::ConvertToISupports(nsISupports** aResult) const {
+ switch (mType) {
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ if (u.iface.mInterfaceValue) {
+ return u.iface.mInterfaceValue->QueryInterface(NS_GET_IID(nsISupports),
+ (void**)aResult);
+ } else {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+ default:
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+}
+
+nsresult nsDiscriminatedUnion::ConvertToInterface(nsIID** aIID,
+ void** aInterface) const {
+ const nsIID* piid;
+
+ switch (mType) {
+ case nsIDataType::VTYPE_INTERFACE:
+ piid = &NS_GET_IID(nsISupports);
+ break;
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ piid = &u.iface.mInterfaceID;
+ break;
+ default:
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ *aIID = piid->Clone();
+
+ if (u.iface.mInterfaceValue) {
+ return u.iface.mInterfaceValue->QueryInterface(*piid, aInterface);
+ }
+
+ *aInterface = nullptr;
+ return NS_OK;
+}
+
+nsresult nsDiscriminatedUnion::ConvertToArray(uint16_t* aType, nsIID* aIID,
+ uint32_t* aCount,
+ void** aPtr) const {
+ // XXX perhaps we'd like to add support for converting each of the various
+ // types into an array containing one element of that type. We can leverage
+ // CloneArray to do this if we want to support this.
+
+ if (mType == nsIDataType::VTYPE_ARRAY) {
+ return CloneArray(u.array.mArrayType, &u.array.mArrayInterfaceID,
+ u.array.mArrayCount, u.array.mArrayValue, aType, aIID,
+ aCount, aPtr);
+ }
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+/***************************************************************************/
+// static setter functions...
+
+#define DATA_SETTER_PROLOGUE Cleanup()
+
+#define DATA_SETTER_EPILOGUE(type_) mType = nsIDataType::type_;
+
+#define DATA_SETTER(type_, member_, value_) \
+ DATA_SETTER_PROLOGUE; \
+ u.member_ = value_; \
+ DATA_SETTER_EPILOGUE(type_)
+
+#define DATA_SETTER_WITH_CAST(type_, member_, cast_, value_) \
+ DATA_SETTER_PROLOGUE; \
+ u.member_ = cast_ value_; \
+ DATA_SETTER_EPILOGUE(type_)
+
+/********************************************/
+
+#define CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) {
+#define CASE__SET_FROM_VARIANT_VTYPE__GETTER(member_, name_) \
+ rv = aValue->GetAs##name_(&(u.member_));
+
+#define CASE__SET_FROM_VARIANT_VTYPE__GETTER_CAST(cast_, member_, name_) \
+ rv = aValue->GetAs##name_(cast_ & (u.member_));
+
+#define CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_) \
+ if (NS_SUCCEEDED(rv)) { \
+ mType = nsIDataType::type_; \
+ } \
+ break; \
+ }
+
+#define CASE__SET_FROM_VARIANT_TYPE(type_, member_, name_) \
+ case nsIDataType::type_: \
+ CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) \
+ CASE__SET_FROM_VARIANT_VTYPE__GETTER(member_, name_) \
+ CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_)
+
+#define CASE__SET_FROM_VARIANT_VTYPE_CAST(type_, cast_, member_, name_) \
+ case nsIDataType::type_: \
+ CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) \
+ CASE__SET_FROM_VARIANT_VTYPE__GETTER_CAST(cast_, member_, name_) \
+ CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_)
+
+nsresult nsDiscriminatedUnion::SetFromVariant(nsIVariant* aValue) {
+ nsresult rv = NS_OK;
+
+ Cleanup();
+
+ uint16_t type = aValue->GetDataType();
+
+ switch (type) {
+ CASE__SET_FROM_VARIANT_VTYPE_CAST(VTYPE_INT8, (uint8_t*), mInt8Value, Int8)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_INT16, mInt16Value, Int16)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_INT32, mInt32Value, Int32)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT8, mUint8Value, Uint8)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT16, mUint16Value, Uint16)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT32, mUint32Value, Uint32)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_FLOAT, mFloatValue, Float)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_DOUBLE, mDoubleValue, Double)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_BOOL, mBoolValue, Bool)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_CHAR, mCharValue, Char)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_WCHAR, mWCharValue, WChar)
+ CASE__SET_FROM_VARIANT_TYPE(VTYPE_ID, mIDValue, ID)
+
+ case nsIDataType::VTYPE_ASTRING:
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_ASTRING);
+ u.mAStringValue = new nsString();
+
+ rv = aValue->GetAsAString(*u.mAStringValue);
+ if (NS_FAILED(rv)) {
+ delete u.mAStringValue;
+ }
+ CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_ASTRING)
+
+ case nsIDataType::VTYPE_CSTRING:
+ CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_CSTRING);
+ u.mCStringValue = new nsCString();
+
+ rv = aValue->GetAsACString(*u.mCStringValue);
+ if (NS_FAILED(rv)) {
+ delete u.mCStringValue;
+ }
+ CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_CSTRING)
+
+ case nsIDataType::VTYPE_UTF8STRING:
+ CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_UTF8STRING);
+ u.mUTF8StringValue = new nsUTF8String();
+
+ rv = aValue->GetAsAUTF8String(*u.mUTF8StringValue);
+ if (NS_FAILED(rv)) {
+ delete u.mUTF8StringValue;
+ }
+ CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_UTF8STRING)
+
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_STRING_SIZE_IS);
+ rv = aValue->GetAsStringWithSize(&u.str.mStringLength,
+ &u.str.mStringValue);
+ CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_STRING_SIZE_IS)
+
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_INTERFACE_IS);
+ // XXX This iid handling is ugly!
+ nsIID* iid;
+ rv = aValue->GetAsInterface(&iid, (void**)&u.iface.mInterfaceValue);
+ if (NS_SUCCEEDED(rv)) {
+ u.iface.mInterfaceID = *iid;
+ free((char*)iid);
+ }
+ CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_INTERFACE_IS)
+
+ case nsIDataType::VTYPE_ARRAY:
+ CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_ARRAY);
+ rv = aValue->GetAsArray(&u.array.mArrayType, &u.array.mArrayInterfaceID,
+ &u.array.mArrayCount, &u.array.mArrayValue);
+ CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_ARRAY)
+
+ case nsIDataType::VTYPE_VOID:
+ SetToVoid();
+ rv = NS_OK;
+ break;
+ case nsIDataType::VTYPE_EMPTY_ARRAY:
+ SetToEmptyArray();
+ rv = NS_OK;
+ break;
+ case nsIDataType::VTYPE_EMPTY:
+ SetToEmpty();
+ rv = NS_OK;
+ break;
+ default:
+ NS_ERROR("bad type in variant!");
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ return rv;
+}
+
+void nsDiscriminatedUnion::SetFromInt8(uint8_t aValue) {
+ DATA_SETTER_WITH_CAST(VTYPE_INT8, mInt8Value, (uint8_t), aValue);
+}
+void nsDiscriminatedUnion::SetFromInt16(int16_t aValue) {
+ DATA_SETTER(VTYPE_INT16, mInt16Value, aValue);
+}
+void nsDiscriminatedUnion::SetFromInt32(int32_t aValue) {
+ DATA_SETTER(VTYPE_INT32, mInt32Value, aValue);
+}
+void nsDiscriminatedUnion::SetFromInt64(int64_t aValue) {
+ DATA_SETTER(VTYPE_INT64, mInt64Value, aValue);
+}
+void nsDiscriminatedUnion::SetFromUint8(uint8_t aValue) {
+ DATA_SETTER(VTYPE_UINT8, mUint8Value, aValue);
+}
+void nsDiscriminatedUnion::SetFromUint16(uint16_t aValue) {
+ DATA_SETTER(VTYPE_UINT16, mUint16Value, aValue);
+}
+void nsDiscriminatedUnion::SetFromUint32(uint32_t aValue) {
+ DATA_SETTER(VTYPE_UINT32, mUint32Value, aValue);
+}
+void nsDiscriminatedUnion::SetFromUint64(uint64_t aValue) {
+ DATA_SETTER(VTYPE_UINT64, mUint64Value, aValue);
+}
+void nsDiscriminatedUnion::SetFromFloat(float aValue) {
+ DATA_SETTER(VTYPE_FLOAT, mFloatValue, aValue);
+}
+void nsDiscriminatedUnion::SetFromDouble(double aValue) {
+ DATA_SETTER(VTYPE_DOUBLE, mDoubleValue, aValue);
+}
+void nsDiscriminatedUnion::SetFromBool(bool aValue) {
+ DATA_SETTER(VTYPE_BOOL, mBoolValue, aValue);
+}
+void nsDiscriminatedUnion::SetFromChar(char aValue) {
+ DATA_SETTER(VTYPE_CHAR, mCharValue, aValue);
+}
+void nsDiscriminatedUnion::SetFromWChar(char16_t aValue) {
+ DATA_SETTER(VTYPE_WCHAR, mWCharValue, aValue);
+}
+void nsDiscriminatedUnion::SetFromID(const nsID& aValue) {
+ DATA_SETTER(VTYPE_ID, mIDValue, aValue);
+}
+void nsDiscriminatedUnion::SetFromAString(const nsAString& aValue) {
+ DATA_SETTER_PROLOGUE;
+ u.mAStringValue = new nsString(aValue);
+ DATA_SETTER_EPILOGUE(VTYPE_ASTRING);
+}
+
+void nsDiscriminatedUnion::SetFromACString(const nsACString& aValue) {
+ DATA_SETTER_PROLOGUE;
+ u.mCStringValue = new nsCString(aValue);
+ DATA_SETTER_EPILOGUE(VTYPE_CSTRING);
+}
+
+void nsDiscriminatedUnion::SetFromAUTF8String(const nsAUTF8String& aValue) {
+ DATA_SETTER_PROLOGUE;
+ u.mUTF8StringValue = new nsUTF8String(aValue);
+ DATA_SETTER_EPILOGUE(VTYPE_UTF8STRING);
+}
+
+nsresult nsDiscriminatedUnion::SetFromString(const char* aValue) {
+ DATA_SETTER_PROLOGUE;
+ if (!aValue) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ return SetFromStringWithSize(strlen(aValue), aValue);
+}
+nsresult nsDiscriminatedUnion::SetFromWString(const char16_t* aValue) {
+ DATA_SETTER_PROLOGUE;
+ if (!aValue) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ return SetFromWStringWithSize(NS_strlen(aValue), aValue);
+}
+void nsDiscriminatedUnion::SetFromISupports(nsISupports* aValue) {
+ return SetFromInterface(NS_GET_IID(nsISupports), aValue);
+}
+void nsDiscriminatedUnion::SetFromInterface(const nsIID& aIID,
+ nsISupports* aValue) {
+ DATA_SETTER_PROLOGUE;
+ NS_IF_ADDREF(aValue);
+ u.iface.mInterfaceValue = aValue;
+ u.iface.mInterfaceID = aIID;
+ DATA_SETTER_EPILOGUE(VTYPE_INTERFACE_IS);
+}
+nsresult nsDiscriminatedUnion::SetFromArray(uint16_t aType, const nsIID* aIID,
+ uint32_t aCount, void* aValue) {
+ DATA_SETTER_PROLOGUE;
+ if (!aValue || !aCount) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsresult rv = CloneArray(aType, aIID, aCount, aValue, &u.array.mArrayType,
+ &u.array.mArrayInterfaceID, &u.array.mArrayCount,
+ &u.array.mArrayValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ DATA_SETTER_EPILOGUE(VTYPE_ARRAY);
+ return NS_OK;
+}
+nsresult nsDiscriminatedUnion::SetFromStringWithSize(uint32_t aSize,
+ const char* aValue) {
+ DATA_SETTER_PROLOGUE;
+ if (!aValue) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ u.str.mStringValue = (char*)moz_xmemdup(aValue, (aSize + 1) * sizeof(char));
+ u.str.mStringLength = aSize;
+ DATA_SETTER_EPILOGUE(VTYPE_STRING_SIZE_IS);
+ return NS_OK;
+}
+nsresult nsDiscriminatedUnion::SetFromWStringWithSize(uint32_t aSize,
+ const char16_t* aValue) {
+ DATA_SETTER_PROLOGUE;
+ if (!aValue) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ u.wstr.mWStringValue =
+ (char16_t*)moz_xmemdup(aValue, (aSize + 1) * sizeof(char16_t));
+ u.wstr.mWStringLength = aSize;
+ DATA_SETTER_EPILOGUE(VTYPE_WSTRING_SIZE_IS);
+ return NS_OK;
+}
+void nsDiscriminatedUnion::AllocateWStringWithSize(uint32_t aSize) {
+ DATA_SETTER_PROLOGUE;
+ u.wstr.mWStringValue = (char16_t*)moz_xmalloc((aSize + 1) * sizeof(char16_t));
+ u.wstr.mWStringValue[aSize] = '\0';
+ u.wstr.mWStringLength = aSize;
+ DATA_SETTER_EPILOGUE(VTYPE_WSTRING_SIZE_IS);
+}
+void nsDiscriminatedUnion::SetToVoid() {
+ DATA_SETTER_PROLOGUE;
+ DATA_SETTER_EPILOGUE(VTYPE_VOID);
+}
+void nsDiscriminatedUnion::SetToEmpty() {
+ DATA_SETTER_PROLOGUE;
+ DATA_SETTER_EPILOGUE(VTYPE_EMPTY);
+}
+void nsDiscriminatedUnion::SetToEmptyArray() {
+ DATA_SETTER_PROLOGUE;
+ DATA_SETTER_EPILOGUE(VTYPE_EMPTY_ARRAY);
+}
+
+/***************************************************************************/
+
+void nsDiscriminatedUnion::Cleanup() {
+ switch (mType) {
+ case nsIDataType::VTYPE_INT8:
+ case nsIDataType::VTYPE_INT16:
+ case nsIDataType::VTYPE_INT32:
+ case nsIDataType::VTYPE_INT64:
+ case nsIDataType::VTYPE_UINT8:
+ case nsIDataType::VTYPE_UINT16:
+ case nsIDataType::VTYPE_UINT32:
+ case nsIDataType::VTYPE_UINT64:
+ case nsIDataType::VTYPE_FLOAT:
+ case nsIDataType::VTYPE_DOUBLE:
+ case nsIDataType::VTYPE_BOOL:
+ case nsIDataType::VTYPE_CHAR:
+ case nsIDataType::VTYPE_WCHAR:
+ case nsIDataType::VTYPE_VOID:
+ case nsIDataType::VTYPE_ID:
+ break;
+ case nsIDataType::VTYPE_ASTRING:
+ delete u.mAStringValue;
+ break;
+ case nsIDataType::VTYPE_CSTRING:
+ delete u.mCStringValue;
+ break;
+ case nsIDataType::VTYPE_UTF8STRING:
+ delete u.mUTF8StringValue;
+ break;
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ free((char*)u.str.mStringValue);
+ break;
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ free((char*)u.wstr.mWStringValue);
+ break;
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ NS_IF_RELEASE(u.iface.mInterfaceValue);
+ break;
+ case nsIDataType::VTYPE_ARRAY:
+ FreeArray();
+ break;
+ case nsIDataType::VTYPE_EMPTY_ARRAY:
+ case nsIDataType::VTYPE_EMPTY:
+ break;
+ default:
+ NS_ERROR("bad type in variant!");
+ break;
+ }
+
+ mType = nsIDataType::VTYPE_EMPTY;
+}
+
+void nsDiscriminatedUnion::Traverse(
+ nsCycleCollectionTraversalCallback& aCb) const {
+ switch (mType) {
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mData");
+ aCb.NoteXPCOMChild(u.iface.mInterfaceValue);
+ break;
+ case nsIDataType::VTYPE_ARRAY:
+ switch (u.array.mArrayType) {
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS: {
+ nsISupports** p = (nsISupports**)u.array.mArrayValue;
+ for (uint32_t i = u.array.mArrayCount; i > 0; ++p, --i) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mData[i]");
+ aCb.NoteXPCOMChild(*p);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/***************************************************************************/
+/***************************************************************************/
+// members...
+
+nsVariantBase::nsVariantBase() : mWritable(true) {}
+
+// For all the data getters we just forward to the static (and sharable)
+// 'ConvertTo' functions.
+
+uint16_t nsVariantBase::GetDataType() { return mData.GetType(); }
+
+NS_IMETHODIMP
+nsVariantBase::GetAsInt8(uint8_t* aResult) {
+ return mData.ConvertToInt8(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsInt16(int16_t* aResult) {
+ return mData.ConvertToInt16(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsInt32(int32_t* aResult) {
+ return mData.ConvertToInt32(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsInt64(int64_t* aResult) {
+ return mData.ConvertToInt64(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsUint8(uint8_t* aResult) {
+ return mData.ConvertToUint8(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsUint16(uint16_t* aResult) {
+ return mData.ConvertToUint16(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsUint32(uint32_t* aResult) {
+ return mData.ConvertToUint32(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsUint64(uint64_t* aResult) {
+ return mData.ConvertToUint64(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsFloat(float* aResult) {
+ return mData.ConvertToFloat(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsDouble(double* aResult) {
+ return mData.ConvertToDouble(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsBool(bool* aResult) { return mData.ConvertToBool(aResult); }
+
+NS_IMETHODIMP
+nsVariantBase::GetAsChar(char* aResult) { return mData.ConvertToChar(aResult); }
+
+NS_IMETHODIMP
+nsVariantBase::GetAsWChar(char16_t* aResult) {
+ return mData.ConvertToWChar(aResult);
+}
+
+NS_IMETHODIMP_(nsresult)
+nsVariantBase::GetAsID(nsID* aResult) { return mData.ConvertToID(aResult); }
+
+NS_IMETHODIMP
+nsVariantBase::GetAsAString(nsAString& aResult) {
+ return mData.ConvertToAString(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsACString(nsACString& aResult) {
+ return mData.ConvertToACString(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsAUTF8String(nsAUTF8String& aResult) {
+ return mData.ConvertToAUTF8String(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsString(char** aResult) {
+ return mData.ConvertToString(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsWString(char16_t** aResult) {
+ return mData.ConvertToWString(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsISupports(nsISupports** aResult) {
+ return mData.ConvertToISupports(aResult);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsJSVal(JS::MutableHandleValue) {
+ // Can only get the jsval from an XPCVariant.
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsInterface(nsIID** aIID, void** aInterface) {
+ return mData.ConvertToInterface(aIID, aInterface);
+}
+
+NS_IMETHODIMP_(nsresult)
+nsVariantBase::GetAsArray(uint16_t* aType, nsIID* aIID, uint32_t* aCount,
+ void** aPtr) {
+ return mData.ConvertToArray(aType, aIID, aCount, aPtr);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsStringWithSize(uint32_t* aSize, char** aStr) {
+ return mData.ConvertToStringWithSize(aSize, aStr);
+}
+
+NS_IMETHODIMP
+nsVariantBase::GetAsWStringWithSize(uint32_t* aSize, char16_t** aStr) {
+ return mData.ConvertToWStringWithSize(aSize, aStr);
+}
+
+/***************************************************************************/
+
+NS_IMETHODIMP
+nsVariantBase::GetWritable(bool* aWritable) {
+ *aWritable = mWritable;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsVariantBase::SetWritable(bool aWritable) {
+ if (!mWritable && aWritable) {
+ return NS_ERROR_FAILURE;
+ }
+ mWritable = aWritable;
+ return NS_OK;
+}
+
+/***************************************************************************/
+
+// For all the data setters we just forward to the static (and sharable)
+// 'SetFrom' functions.
+
+NS_IMETHODIMP
+nsVariantBase::SetAsInt8(uint8_t aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromInt8(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsInt16(int16_t aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromInt16(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsInt32(int32_t aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromInt32(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsInt64(int64_t aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromInt64(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsUint8(uint8_t aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromUint8(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsUint16(uint16_t aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromUint16(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsUint32(uint32_t aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromUint32(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsUint64(uint64_t aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromUint64(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsFloat(float aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromFloat(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsDouble(double aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromDouble(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsBool(bool aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromBool(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsChar(char aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromChar(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsWChar(char16_t aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromWChar(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsID(const nsID& aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromID(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsAString(const nsAString& aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromAString(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsACString(const nsACString& aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromACString(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsAUTF8String(const nsAUTF8String& aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromAUTF8String(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsString(const char* aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ return mData.SetFromString(aValue);
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsWString(const char16_t* aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ return mData.SetFromWString(aValue);
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsISupports(nsISupports* aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromISupports(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsInterface(const nsIID& aIID, void* aInterface) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetFromInterface(aIID, (nsISupports*)aInterface);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsArray(uint16_t aType, const nsIID* aIID, uint32_t aCount,
+ void* aPtr) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ return mData.SetFromArray(aType, aIID, aCount, aPtr);
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsStringWithSize(uint32_t aSize, const char* aStr) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ return mData.SetFromStringWithSize(aSize, aStr);
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsWStringWithSize(uint32_t aSize, const char16_t* aStr) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ return mData.SetFromWStringWithSize(aSize, aStr);
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsVoid() {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetToVoid();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsEmpty() {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetToEmpty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetAsEmptyArray() {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ mData.SetToEmptyArray();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVariantBase::SetFromVariant(nsIVariant* aValue) {
+ if (!mWritable) {
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+ return mData.SetFromVariant(aValue);
+}
+
+/* nsVariant implementation */
+
+NS_IMPL_ISUPPORTS(nsVariant, nsIVariant, nsIWritableVariant)
+
+/* nsVariantCC implementation */
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsVariantCC)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIVariant)
+ NS_INTERFACE_MAP_ENTRY(nsIWritableVariant)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsVariantCC)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsVariantCC)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsVariantCC)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsVariantCC)
+ tmp->mData.Traverse(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsVariantCC)
+ tmp->mData.Cleanup();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
diff --git a/xpcom/ds/nsVariant.h b/xpcom/ds/nsVariant.h
new file mode 100644
index 0000000000..8de1fe4618
--- /dev/null
+++ b/xpcom/ds/nsVariant.h
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsVariant_h
+#define nsVariant_h
+
+#include "nsIVariant.h"
+#include "nsStringFwd.h"
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+
+/**
+ * Map the nsAUTF8String, nsUTF8String classes to the nsACString and
+ * nsCString classes respectively for now. These defines need to be removed
+ * once Jag lands his nsUTF8String implementation.
+ */
+#define nsAUTF8String nsACString
+#define nsUTF8String nsCString
+#define PromiseFlatUTF8String PromiseFlatCString
+
+/**
+ * nsDiscriminatedUnion is a class that nsIVariant implementors can use
+ * to hold the underlying data.
+ */
+
+class nsDiscriminatedUnion {
+ public:
+ nsDiscriminatedUnion() : mType(nsIDataType::VTYPE_EMPTY) {
+ u.mInt8Value = '\0';
+ }
+ nsDiscriminatedUnion(const nsDiscriminatedUnion&) = delete;
+ nsDiscriminatedUnion(nsDiscriminatedUnion&&) = delete;
+
+ ~nsDiscriminatedUnion() { Cleanup(); }
+
+ nsDiscriminatedUnion& operator=(const nsDiscriminatedUnion&) = delete;
+ nsDiscriminatedUnion& operator=(nsDiscriminatedUnion&&) = delete;
+
+ void Cleanup();
+
+ uint16_t GetType() const { return mType; }
+
+ [[nodiscard]] nsresult ConvertToInt8(uint8_t* aResult) const;
+ [[nodiscard]] nsresult ConvertToInt16(int16_t* aResult) const;
+ [[nodiscard]] nsresult ConvertToInt32(int32_t* aResult) const;
+ [[nodiscard]] nsresult ConvertToInt64(int64_t* aResult) const;
+ [[nodiscard]] nsresult ConvertToUint8(uint8_t* aResult) const;
+ [[nodiscard]] nsresult ConvertToUint16(uint16_t* aResult) const;
+ [[nodiscard]] nsresult ConvertToUint32(uint32_t* aResult) const;
+ [[nodiscard]] nsresult ConvertToUint64(uint64_t* aResult) const;
+ [[nodiscard]] nsresult ConvertToFloat(float* aResult) const;
+ [[nodiscard]] nsresult ConvertToDouble(double* aResult) const;
+ [[nodiscard]] nsresult ConvertToBool(bool* aResult) const;
+ [[nodiscard]] nsresult ConvertToChar(char* aResult) const;
+ [[nodiscard]] nsresult ConvertToWChar(char16_t* aResult) const;
+
+ [[nodiscard]] nsresult ConvertToID(nsID* aResult) const;
+
+ [[nodiscard]] nsresult ConvertToAString(nsAString& aResult) const;
+ [[nodiscard]] nsresult ConvertToAUTF8String(nsAUTF8String& aResult) const;
+ [[nodiscard]] nsresult ConvertToACString(nsACString& aResult) const;
+ [[nodiscard]] nsresult ConvertToString(char** aResult) const;
+ [[nodiscard]] nsresult ConvertToWString(char16_t** aResult) const;
+ [[nodiscard]] nsresult ConvertToStringWithSize(uint32_t* aSize,
+ char** aStr) const;
+ [[nodiscard]] nsresult ConvertToWStringWithSize(uint32_t* aSize,
+ char16_t** aStr) const;
+
+ [[nodiscard]] nsresult ConvertToISupports(nsISupports** aResult) const;
+ [[nodiscard]] nsresult ConvertToInterface(nsIID** aIID,
+ void** aInterface) const;
+ [[nodiscard]] nsresult ConvertToArray(uint16_t* aType, nsIID* aIID,
+ uint32_t* aCount, void** aPtr) const;
+
+ [[nodiscard]] nsresult SetFromVariant(nsIVariant* aValue);
+
+ void SetFromInt8(uint8_t aValue);
+ void SetFromInt16(int16_t aValue);
+ void SetFromInt32(int32_t aValue);
+ void SetFromInt64(int64_t aValue);
+ void SetFromUint8(uint8_t aValue);
+ void SetFromUint16(uint16_t aValue);
+ void SetFromUint32(uint32_t aValue);
+ void SetFromUint64(uint64_t aValue);
+ void SetFromFloat(float aValue);
+ void SetFromDouble(double aValue);
+ void SetFromBool(bool aValue);
+ void SetFromChar(char aValue);
+ void SetFromWChar(char16_t aValue);
+ void SetFromID(const nsID& aValue);
+ void SetFromAString(const nsAString& aValue);
+ void SetFromAUTF8String(const nsAUTF8String& aValue);
+ void SetFromACString(const nsACString& aValue);
+ [[nodiscard]] nsresult SetFromString(const char* aValue);
+ [[nodiscard]] nsresult SetFromWString(const char16_t* aValue);
+ void SetFromISupports(nsISupports* aValue);
+ void SetFromInterface(const nsIID& aIID, nsISupports* aValue);
+ [[nodiscard]] nsresult SetFromArray(uint16_t aType, const nsIID* aIID,
+ uint32_t aCount, void* aValue);
+ [[nodiscard]] nsresult SetFromStringWithSize(uint32_t aSize,
+ const char* aValue);
+ [[nodiscard]] nsresult SetFromWStringWithSize(uint32_t aSize,
+ const char16_t* aValue);
+
+ // Like SetFromWStringWithSize, but leaves the string uninitialized. It does
+ // does write the null-terminator.
+ void AllocateWStringWithSize(uint32_t aSize);
+
+ void SetToVoid();
+ void SetToEmpty();
+ void SetToEmptyArray();
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
+
+ private:
+ [[nodiscard]] nsresult ToManageableNumber(
+ nsDiscriminatedUnion* aOutData) const;
+ void FreeArray();
+ [[nodiscard]] bool String2ID(nsID* aPid) const;
+ [[nodiscard]] nsresult ToString(nsACString& aOutString) const;
+
+ public:
+ union {
+ int8_t mInt8Value;
+ int16_t mInt16Value;
+ int32_t mInt32Value;
+ int64_t mInt64Value;
+ uint8_t mUint8Value;
+ uint16_t mUint16Value;
+ uint32_t mUint32Value;
+ uint64_t mUint64Value;
+ float mFloatValue;
+ double mDoubleValue;
+ bool mBoolValue;
+ char mCharValue;
+ char16_t mWCharValue;
+ nsIID mIDValue;
+ nsAString* mAStringValue;
+ nsAUTF8String* mUTF8StringValue;
+ nsACString* mCStringValue;
+ struct {
+ // This is an owning reference that cannot be an nsCOMPtr because
+ // nsDiscriminatedUnion needs to be POD. AddRef/Release are manually
+ // called on this.
+ nsISupports* MOZ_OWNING_REF mInterfaceValue;
+ nsIID mInterfaceID;
+ } iface;
+ struct {
+ nsIID mArrayInterfaceID;
+ void* mArrayValue;
+ uint32_t mArrayCount;
+ uint16_t mArrayType;
+ } array;
+ struct {
+ char* mStringValue;
+ uint32_t mStringLength;
+ } str;
+ struct {
+ char16_t* mWStringValue;
+ uint32_t mWStringLength;
+ } wstr;
+ } u;
+ uint16_t mType;
+};
+
+/**
+ * nsVariant implements the generic variant support. The xpcom module registers
+ * a factory (see NS_VARIANT_CONTRACTID in nsIVariant.idl) that will create
+ * these objects. They are created 'empty' and 'writable'.
+ *
+ * nsIVariant users won't usually need to see this class.
+ */
+class nsVariantBase : public nsIWritableVariant {
+ public:
+ NS_DECL_NSIVARIANT
+ NS_DECL_NSIWRITABLEVARIANT
+
+ nsVariantBase();
+
+ protected:
+ ~nsVariantBase() = default;
+
+ nsDiscriminatedUnion mData;
+ bool mWritable;
+};
+
+class nsVariant final : public nsVariantBase {
+ public:
+ NS_DECL_ISUPPORTS
+
+ nsVariant() = default;
+
+ private:
+ ~nsVariant() = default;
+};
+
+class nsVariantCC final : public nsVariantBase {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsVariantCC)
+
+ nsVariantCC() = default;
+
+ private:
+ ~nsVariantCC() = default;
+};
+
+/**
+ * Users of nsIVariant should be using the contractID and not this CID.
+ * - see NS_VARIANT_CONTRACTID in nsIVariant.idl.
+ */
+
+#define NS_VARIANT_CID \
+ { /* 0D6EA1D0-879C-11d5-90EF-0010A4E73D9A */ \
+ 0xd6ea1d0, 0x879c, 0x11d5, { \
+ 0x90, 0xef, 0x0, 0x10, 0xa4, 0xe7, 0x3d, 0x9a \
+ } \
+ }
+
+#endif // nsVariant_h
diff --git a/xpcom/ds/nsWhitespaceTokenizer.h b/xpcom/ds/nsWhitespaceTokenizer.h
new file mode 100644
index 0000000000..77fea70850
--- /dev/null
+++ b/xpcom/ds/nsWhitespaceTokenizer.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsWhitespaceTokenizer_h
+#define __nsWhitespaceTokenizer_h
+
+#include "mozilla/RangedPtr.h"
+#include "nsDependentSubstring.h"
+#include "nsCRTGlue.h"
+
+template <typename DependentSubstringType, bool IsWhitespace(char16_t)>
+class nsTWhitespaceTokenizer {
+ typedef typename DependentSubstringType::char_type CharType;
+ typedef typename DependentSubstringType::substring_type SubstringType;
+
+ public:
+ explicit nsTWhitespaceTokenizer(const SubstringType& aSource)
+ : mIter(aSource.Data(), aSource.Length()),
+ mEnd(aSource.Data() + aSource.Length(), aSource.Data(),
+ aSource.Length()),
+ mWhitespaceBeforeFirstToken(false),
+ mWhitespaceAfterCurrentToken(false) {
+ while (mIter < mEnd && IsWhitespace(*mIter)) {
+ mWhitespaceBeforeFirstToken = true;
+ ++mIter;
+ }
+ }
+
+ /**
+ * Checks if any more tokens are available.
+ */
+ bool hasMoreTokens() const { return mIter < mEnd; }
+
+ /*
+ * Returns true if there is whitespace prior to the first token.
+ */
+ bool whitespaceBeforeFirstToken() const {
+ return mWhitespaceBeforeFirstToken;
+ }
+
+ /*
+ * Returns true if there is any whitespace after the current token.
+ * This is always true unless we're reading the last token.
+ */
+ bool whitespaceAfterCurrentToken() const {
+ return mWhitespaceAfterCurrentToken;
+ }
+
+ /**
+ * Returns the next token.
+ */
+ const DependentSubstringType nextToken() {
+ const mozilla::RangedPtr<const CharType> tokenStart = mIter;
+ while (mIter < mEnd && !IsWhitespace(*mIter)) {
+ ++mIter;
+ }
+ const mozilla::RangedPtr<const CharType> tokenEnd = mIter;
+ mWhitespaceAfterCurrentToken = false;
+ while (mIter < mEnd && IsWhitespace(*mIter)) {
+ mWhitespaceAfterCurrentToken = true;
+ ++mIter;
+ }
+ return Substring(tokenStart.get(), tokenEnd.get());
+ }
+
+ private:
+ mozilla::RangedPtr<const CharType> mIter;
+ const mozilla::RangedPtr<const CharType> mEnd;
+ bool mWhitespaceBeforeFirstToken;
+ bool mWhitespaceAfterCurrentToken;
+};
+
+template <bool IsWhitespace(char16_t) = NS_IsAsciiWhitespace>
+class nsWhitespaceTokenizerTemplate
+ : public nsTWhitespaceTokenizer<nsDependentSubstring, IsWhitespace> {
+ public:
+ explicit nsWhitespaceTokenizerTemplate(const nsAString& aSource)
+ : nsTWhitespaceTokenizer<nsDependentSubstring, IsWhitespace>(aSource) {}
+};
+
+typedef nsWhitespaceTokenizerTemplate<> nsWhitespaceTokenizer;
+
+template <bool IsWhitespace(char16_t) = NS_IsAsciiWhitespace>
+class nsCWhitespaceTokenizerTemplate
+ : public nsTWhitespaceTokenizer<nsDependentCSubstring, IsWhitespace> {
+ public:
+ explicit nsCWhitespaceTokenizerTemplate(const nsACString& aSource)
+ : nsTWhitespaceTokenizer<nsDependentCSubstring, IsWhitespace>(aSource) {}
+};
+
+typedef nsCWhitespaceTokenizerTemplate<> nsCWhitespaceTokenizer;
+
+#endif /* __nsWhitespaceTokenizer_h */
diff --git a/xpcom/ds/nsWindowsRegKey.cpp b/xpcom/ds/nsWindowsRegKey.cpp
new file mode 100644
index 0000000000..1f9bb58ac6
--- /dev/null
+++ b/xpcom/ds/nsWindowsRegKey.cpp
@@ -0,0 +1,523 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include <shlwapi.h>
+#include <stdlib.h>
+#include "nsWindowsRegKey.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+//-----------------------------------------------------------------------------
+
+// According to MSDN, the following limits apply (in characters excluding room
+// for terminating null character):
+#define MAX_KEY_NAME_LEN 255
+#define MAX_VALUE_NAME_LEN 16383
+
+class nsWindowsRegKey final : public nsIWindowsRegKey {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWINDOWSREGKEY
+
+ nsWindowsRegKey()
+ : mKey(nullptr), mWatchEvent(nullptr), mWatchRecursive(FALSE) {}
+
+ private:
+ ~nsWindowsRegKey() { Close(); }
+
+ HKEY mKey;
+ HANDLE mWatchEvent;
+ BOOL mWatchRecursive;
+};
+
+NS_IMPL_ISUPPORTS(nsWindowsRegKey, nsIWindowsRegKey)
+
+NS_IMETHODIMP
+nsWindowsRegKey::GetKey(HKEY* aKey) {
+ *aKey = mKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::SetKey(HKEY aKey) {
+ // We do not close the older aKey!
+ StopWatching();
+
+ mKey = aKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::Close() {
+ StopWatching();
+
+ if (mKey) {
+ RegCloseKey(mKey);
+ mKey = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::Open(uint32_t aRootKey, const nsAString& aPath,
+ uint32_t aMode) {
+ Close();
+
+ LONG rv =
+ RegOpenKeyExW((HKEY)(intptr_t)aRootKey, PromiseFlatString(aPath).get(), 0,
+ (REGSAM)aMode, &mKey);
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::Create(uint32_t aRootKey, const nsAString& aPath,
+ uint32_t aMode) {
+ Close();
+
+ DWORD disposition;
+ LONG rv = RegCreateKeyExW(
+ (HKEY)(intptr_t)aRootKey, PromiseFlatString(aPath).get(), 0, nullptr,
+ REG_OPTION_NON_VOLATILE, (REGSAM)aMode, nullptr, &mKey, &disposition);
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::OpenChild(const nsAString& aPath, uint32_t aMode,
+ nsIWindowsRegKey** aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIWindowsRegKey> child = new nsWindowsRegKey();
+
+ nsresult rv = child->Open((uintptr_t)mKey, aPath, aMode);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ child.swap(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::CreateChild(const nsAString& aPath, uint32_t aMode,
+ nsIWindowsRegKey** aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIWindowsRegKey> child = new nsWindowsRegKey();
+
+ nsresult rv = child->Create((uintptr_t)mKey, aPath, aMode);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ child.swap(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::GetChildCount(uint32_t* aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ DWORD numSubKeys;
+ LONG rv =
+ RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, &numSubKeys, nullptr,
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
+ if (rv != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = numSubKeys;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::GetChildName(uint32_t aIndex, nsAString& aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ FILETIME lastWritten;
+
+ wchar_t nameBuf[MAX_KEY_NAME_LEN + 1];
+ DWORD nameLen = sizeof(nameBuf) / sizeof(nameBuf[0]);
+
+ LONG rv = RegEnumKeyExW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr,
+ nullptr, &lastWritten);
+ if (rv != ERROR_SUCCESS) {
+ return NS_ERROR_NOT_AVAILABLE; // XXX what's the best error code here?
+ }
+
+ aResult.Assign(nameBuf, nameLen);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::HasChild(const nsAString& aName, bool* aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Check for the existence of a child key by opening the key with minimal
+ // rights. Perhaps there is a more efficient way to do this?
+
+ HKEY key;
+ LONG rv = RegOpenKeyExW(mKey, PromiseFlatString(aName).get(), 0,
+ STANDARD_RIGHTS_READ, &key);
+
+ if ((*aResult = (rv == ERROR_SUCCESS && key))) {
+ RegCloseKey(key);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::GetValueCount(uint32_t* aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ DWORD numValues;
+ LONG rv =
+ RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, &numValues, nullptr, nullptr, nullptr, nullptr);
+ if (rv != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = numValues;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::GetValueName(uint32_t aIndex, nsAString& aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ wchar_t nameBuf[MAX_VALUE_NAME_LEN];
+ DWORD nameLen = sizeof(nameBuf) / sizeof(nameBuf[0]);
+
+ LONG rv = RegEnumValueW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr,
+ nullptr, nullptr);
+ if (rv != ERROR_SUCCESS) {
+ return NS_ERROR_NOT_AVAILABLE; // XXX what's the best error code here?
+ }
+
+ aResult.Assign(nameBuf, nameLen);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::HasValue(const nsAString& aName, bool* aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr,
+ nullptr, nullptr);
+
+ *aResult = (rv == ERROR_SUCCESS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::RemoveChild(const nsAString& aName) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LONG rv = RegDeleteKeyW(mKey, PromiseFlatString(aName).get());
+
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::RemoveValue(const nsAString& aName) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LONG rv = RegDeleteValueW(mKey, PromiseFlatString(aName).get());
+
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::GetValueType(const nsAString& aName, uint32_t* aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0,
+ (LPDWORD)aResult, nullptr, nullptr);
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::ReadStringValue(const nsAString& aName, nsAString& aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ DWORD type, size;
+
+ const nsString& flatName = PromiseFlatString(aName);
+
+ LONG rv = RegQueryValueExW(mKey, flatName.get(), 0, &type, nullptr, &size);
+ if (rv != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // This must be a string type in order to fetch the value as a string.
+ // We're being a bit forgiving here by allowing types other than REG_SZ.
+ if (type != REG_SZ && type != REG_EXPAND_SZ && type != REG_MULTI_SZ) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // The buffer size must be a multiple of 2.
+ if (size % 2 != 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (size == 0) {
+ aResult.Truncate();
+ return NS_OK;
+ }
+
+ // |size| may or may not include the terminating null character.
+ DWORD resultLen = size / 2;
+
+ if (!aResult.SetLength(resultLen, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ auto begin = aResult.BeginWriting();
+
+ rv = RegQueryValueExW(mKey, flatName.get(), 0, &type, (LPBYTE)begin, &size);
+
+ if (!aResult.CharAt(resultLen - 1)) {
+ // The string passed to us had a null terminator in the final position.
+ aResult.Truncate(resultLen - 1);
+ }
+
+ // Expand the environment variables if needed
+ if (type == REG_EXPAND_SZ) {
+ const nsString& flatSource = PromiseFlatString(aResult);
+ resultLen = ExpandEnvironmentStringsW(flatSource.get(), nullptr, 0);
+ if (resultLen > 1) {
+ nsAutoString expandedResult;
+ // |resultLen| includes the terminating null character
+ --resultLen;
+ if (!expandedResult.SetLength(resultLen, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ resultLen = ExpandEnvironmentStringsW(
+ flatSource.get(), expandedResult.get(), resultLen + 1);
+ if (resultLen <= 0) {
+ rv = ERROR_UNKNOWN_FEATURE;
+ aResult.Truncate();
+ } else {
+ rv = ERROR_SUCCESS;
+ aResult = expandedResult;
+ }
+ } else if (resultLen == 1) {
+ // It apparently expands to nothing (just a null terminator).
+ aResult.Truncate();
+ }
+ }
+
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::ReadIntValue(const nsAString& aName, uint32_t* aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ DWORD size = sizeof(*aResult);
+ LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr,
+ (LPBYTE)aResult, &size);
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::ReadInt64Value(const nsAString& aName, uint64_t* aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ DWORD size = sizeof(*aResult);
+ LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr,
+ (LPBYTE)aResult, &size);
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::ReadBinaryValue(const nsAString& aName, nsACString& aResult) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ DWORD size;
+ LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr,
+ nullptr, &size);
+
+ if (rv != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!size) {
+ aResult.Truncate();
+ return NS_OK;
+ }
+
+ if (!aResult.SetLength(size, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ auto begin = aResult.BeginWriting();
+
+ rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr,
+ (LPBYTE)begin, &size);
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::WriteStringValue(const nsAString& aName,
+ const nsAString& aValue) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Need to indicate complete size of buffer including null terminator.
+ const nsString& flatValue = PromiseFlatString(aValue);
+
+ LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_SZ,
+ (const BYTE*)flatValue.get(),
+ (flatValue.Length() + 1) * sizeof(char16_t));
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::WriteIntValue(const nsAString& aName, uint32_t aValue) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_DWORD,
+ (const BYTE*)&aValue, sizeof(aValue));
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::WriteInt64Value(const nsAString& aName, uint64_t aValue) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_QWORD,
+ (const BYTE*)&aValue, sizeof(aValue));
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::WriteBinaryValue(const nsAString& aName,
+ const nsACString& aValue) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ const nsCString& flatValue = PromiseFlatCString(aValue);
+ LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_BINARY,
+ (const BYTE*)flatValue.get(), flatValue.Length());
+ return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::StartWatching(bool aRecurse) {
+ if (NS_WARN_IF(!mKey)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mWatchEvent) {
+ return NS_OK;
+ }
+
+ mWatchEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
+ if (!mWatchEvent) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ DWORD filter = REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_ATTRIBUTES |
+ REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY;
+
+ LONG rv = RegNotifyChangeKeyValue(mKey, aRecurse, filter, mWatchEvent, TRUE);
+ if (rv != ERROR_SUCCESS) {
+ StopWatching();
+ // On older versions of Windows, this call is not implemented, so simply
+ // return NS_OK in those cases and pretend that the watching is happening.
+ return (rv == ERROR_CALL_NOT_IMPLEMENTED) ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ mWatchRecursive = aRecurse;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::StopWatching() {
+ if (mWatchEvent) {
+ CloseHandle(mWatchEvent);
+ mWatchEvent = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::HasChanged(bool* aResult) {
+ if (mWatchEvent && WaitForSingleObject(mWatchEvent, 0) == WAIT_OBJECT_0) {
+ // An event only gets signaled once, then it's done, so we have to set up
+ // another event to watch.
+ StopWatching();
+ StartWatching(mWatchRecursive);
+ *aResult = true;
+ } else {
+ *aResult = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsRegKey::IsWatching(bool* aResult) {
+ *aResult = (mWatchEvent != nullptr);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+void NS_NewWindowsRegKey(nsIWindowsRegKey** aResult) {
+ RefPtr<nsWindowsRegKey> key = new nsWindowsRegKey();
+ key.forget(aResult);
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult nsWindowsRegKeyConstructor(const nsIID& aIID, void** aResult) {
+ nsCOMPtr<nsIWindowsRegKey> key;
+ NS_NewWindowsRegKey(getter_AddRefs(key));
+ return key->QueryInterface(aIID, aResult);
+}
diff --git a/xpcom/ds/nsWindowsRegKey.h b/xpcom/ds/nsWindowsRegKey.h
new file mode 100644
index 0000000000..c8032540ab
--- /dev/null
+++ b/xpcom/ds/nsWindowsRegKey.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWindowsRegKey_h__
+#define nsWindowsRegKey_h__
+
+//-----------------------------------------------------------------------------
+
+#include "nsIWindowsRegKey.h"
+
+/**
+ * This ContractID may be used to instantiate a windows registry key object
+ * via the XPCOM component manager.
+ */
+#define NS_WINDOWSREGKEY_CONTRACTID "@mozilla.org/windows-registry-key;1"
+
+/**
+ * This function may be used to instantiate a windows registry key object prior
+ * to XPCOM being initialized.
+ */
+extern "C" void NS_NewWindowsRegKey(nsIWindowsRegKey** aResult);
+
+//-----------------------------------------------------------------------------
+
+#ifdef IMPL_LIBXUL
+
+// a53bc624-d577-4839-b8ec-bb5040a52ff4
+# define NS_WINDOWSREGKEY_CID \
+ { \
+ 0xa53bc624, 0xd577, 0x4839, { \
+ 0xb8, 0xec, 0xbb, 0x50, 0x40, 0xa5, 0x2f, 0xf4 \
+ } \
+ }
+
+[[nodiscard]] extern nsresult nsWindowsRegKeyConstructor(const nsIID& aIID,
+ void** aResult);
+
+#endif // IMPL_LIBXUL
+
+//-----------------------------------------------------------------------------
+
+#endif // nsWindowsRegKey_h__
diff --git a/xpcom/ds/test/python.ini b/xpcom/ds/test/python.ini
new file mode 100644
index 0000000000..680185c496
--- /dev/null
+++ b/xpcom/ds/test/python.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+subsuite = xpcom
+
+[test_dafsa.py]
diff --git a/xpcom/ds/test/test_dafsa.py b/xpcom/ds/test/test_dafsa.py
new file mode 100644
index 0000000000..9becdd6c06
--- /dev/null
+++ b/xpcom/ds/test/test_dafsa.py
@@ -0,0 +1,546 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import unittest
+from io import StringIO
+
+import mozunit
+from incremental_dafsa import Dafsa, Node
+
+
+def _node_to_string(node: Node, prefix, buffer, cache):
+ if not node.is_end_node:
+ prefix += (
+ str(ord(node.character)) if ord(node.character) < 10 else node.character
+ )
+ else:
+ prefix += "$"
+ cached = cache.get(id(node))
+ buffer.write("{}{}".format(prefix, "=" if cached else "").strip() + "\n")
+
+ if not cached:
+ cache[id(node)] = node
+ if node:
+ for node in sorted(node.children.values(), key=lambda n: n.character):
+ _node_to_string(node, prefix, buffer, cache)
+
+
+def _dafsa_to_string(dafsa: Dafsa):
+ """Encodes the dafsa into a string notation.
+
+ Each node is printed on its own line with all the nodes that precede it.
+ The end node is designated with the "$" character.
+ If it joins into an existing node, the end of the line is adorned with a "=".
+ Though this doesn't carry information about which other prefix it has joined with,
+ it has seemed to be precise enough for testing.
+
+ For example, with the input data of:
+ * a1
+ * ac1
+ * bc1
+
+ [root] --- a ---- 1 --- [end]
+ | | /
+ -- b -- c---
+
+ The output will be:
+ a
+ a1
+ a1$ <- end node was found
+ ac
+ ac1= <- joins with the "a1" prefix
+ b
+ bc= <- joins with the "ac" prefix
+ """
+ buffer = StringIO()
+ cache = {}
+
+ for node in sorted(dafsa.root_node.children.values(), key=lambda n: n.character):
+ _node_to_string(node, "", buffer, cache)
+ return buffer.getvalue().strip()
+
+
+def _to_words(data):
+ return [line.strip() for line in data.strip().split("\n")]
+
+
+def _assert_dafsa(data, expected):
+ words = _to_words(data)
+ dafsa = Dafsa.from_tld_data(words)
+
+ expected = expected.strip()
+ expected = "\n".join([line.strip() for line in expected.split("\n")])
+ as_string = _dafsa_to_string(dafsa)
+ assert as_string == expected
+
+
+class TestDafsa(unittest.TestCase):
+ def test_1(self):
+ _assert_dafsa(
+ """
+ a1
+ ac1
+ acc1
+ bd1
+ bc1
+ bcc1
+ """,
+ """
+ a
+ a1
+ a1$
+ ac
+ ac1=
+ acc
+ acc1=
+ b
+ bc=
+ bd
+ bd1=
+ """,
+ )
+
+ def test_2(self):
+ _assert_dafsa(
+ """
+ ab1
+ b1
+ bb1
+ bbb1
+ """,
+ """
+ a
+ ab
+ ab1
+ ab1$
+ b
+ b1=
+ bb
+ bb1=
+ bbb=
+ """,
+ )
+
+ def test_3(self):
+ _assert_dafsa(
+ """
+ a.ca1
+ a.com1
+ c.corg1
+ b.ca1
+ b.com1
+ b.corg1
+ """,
+ """
+ a
+ a.
+ a.c
+ a.ca
+ a.ca1
+ a.ca1$
+ a.co
+ a.com
+ a.com1=
+ b
+ b.
+ b.c
+ b.ca=
+ b.co
+ b.com=
+ b.cor
+ b.corg
+ b.corg1=
+ c
+ c.
+ c.c
+ c.co
+ c.cor=
+ """,
+ )
+
+ def test_4(self):
+ _assert_dafsa(
+ """
+ acom1
+ bcomcom1
+ acomcom1
+ """,
+ """
+ a
+ ac
+ aco
+ acom
+ acom1
+ acom1$
+ acomc
+ acomco
+ acomcom
+ acomcom1=
+ b
+ bc
+ bco
+ bcom
+ bcomc=
+ """,
+ )
+
+ def test_5(self):
+ _assert_dafsa(
+ """
+ a.d1
+ a.c.d1
+ b.d1
+ b.c.d1
+ """,
+ """
+ a
+ a.
+ a.c
+ a.c.
+ a.c.d
+ a.c.d1
+ a.c.d1$
+ a.d=
+ b
+ b.=
+ """,
+ )
+
+ def test_6(self):
+ _assert_dafsa(
+ """
+ a61
+ a661
+ b61
+ b661
+ """,
+ """
+ a
+ a6
+ a61
+ a61$
+ a66
+ a661=
+ b
+ b6=
+ """,
+ )
+
+ def test_7(self):
+ _assert_dafsa(
+ """
+ a61
+ a6661
+ b61
+ b6661
+ """,
+ """
+ a
+ a6
+ a61
+ a61$
+ a66
+ a666
+ a6661=
+ b
+ b6=
+ """,
+ )
+
+ def test_8(self):
+ _assert_dafsa(
+ """
+ acc1
+ bc1
+ bccc1
+ """,
+ """
+ a
+ ac
+ acc
+ acc1
+ acc1$
+ b
+ bc
+ bc1=
+ bcc=
+ """,
+ )
+
+ def test_9(self):
+ _assert_dafsa(
+ """
+ acc1
+ bc1
+ bcc1
+ """,
+ """
+ a
+ ac
+ acc
+ acc1
+ acc1$
+ b
+ bc
+ bc1=
+ bcc=
+ """,
+ )
+
+ def test_10(self):
+ _assert_dafsa(
+ """
+ acc1
+ cc1
+ cccc1
+ """,
+ """
+ a
+ ac
+ acc
+ acc1
+ acc1$
+ c
+ cc
+ cc1=
+ ccc=
+ """,
+ )
+
+ def test_11(self):
+ _assert_dafsa(
+ """
+ ac1
+ acc1
+ bc1
+ bcc1
+ """,
+ """
+ a
+ ac
+ ac1
+ ac1$
+ acc
+ acc1=
+ b
+ bc=
+ """,
+ )
+
+ def test_12(self):
+ _assert_dafsa(
+ """
+ acd1
+ bcd1
+ bcdd1
+ """,
+ """
+ a
+ ac
+ acd
+ acd1
+ acd1$
+ b
+ bc
+ bcd
+ bcd1=
+ bcdd=
+ """,
+ )
+
+ def test_13(self):
+ _assert_dafsa(
+ """
+ ac1
+ acc1
+ bc1
+ bcc1
+ bccc1
+ """,
+ """
+ a
+ ac
+ ac1
+ ac1$
+ acc
+ acc1=
+ b
+ bc
+ bc1=
+ bcc=
+ """,
+ )
+
+ def test_14(self):
+ _assert_dafsa(
+ """
+ acc1
+ acccc1
+ bcc1
+ bcccc1
+ bcccccc1
+ """,
+ """
+ a
+ ac
+ acc
+ acc1
+ acc1$
+ accc
+ acccc
+ acccc1=
+ b
+ bc
+ bcc
+ bcc1=
+ bccc=
+ """,
+ )
+
+ def test_15(self):
+ _assert_dafsa(
+ """
+ ac1
+ bc1
+ acac1
+ """,
+ """
+ a
+ ac
+ ac1
+ ac1$
+ aca
+ acac
+ acac1=
+ b
+ bc=
+ """,
+ )
+
+ def test_16(self):
+ _assert_dafsa(
+ """
+ bat1
+ t1
+ tbat1
+ """,
+ """
+ b
+ ba
+ bat
+ bat1
+ bat1$
+ t
+ t1=
+ tb=
+ """,
+ )
+
+ def test_17(self):
+ _assert_dafsa(
+ """
+ acow1
+ acat1
+ t1
+ tcat1
+ acatcat1
+ """,
+ """
+ a
+ ac
+ aca
+ acat
+ acat1
+ acat1$
+ acatc
+ acatca
+ acatcat
+ acatcat1=
+ aco
+ acow
+ acow1=
+ t=
+ """,
+ )
+
+ def test_18(self):
+ _assert_dafsa(
+ """
+ bc1
+ abc1
+ abcxyzc1
+ """,
+ """
+ a
+ ab
+ abc
+ abc1
+ abc1$
+ abcx
+ abcxy
+ abcxyz
+ abcxyzc
+ abcxyzc1=
+ b
+ bc=
+ """,
+ )
+
+ def test_19(self):
+ _assert_dafsa(
+ """
+ a.z1
+ a.y1
+ c.z1
+ d.z1
+ d.y1
+ """,
+ """
+ a
+ a.
+ a.y
+ a.y1
+ a.y1$
+ a.z
+ a.z1=
+ c
+ c.
+ c.z=
+ d
+ d.=
+ """,
+ )
+
+ def test_20(self):
+ _assert_dafsa(
+ """
+ acz1
+ acy1
+ accz1
+ acccz1
+ bcz1
+ bcy1
+ bccz1
+ bcccz1
+ """,
+ """
+ a
+ ac
+ acc
+ accc
+ acccz
+ acccz1
+ acccz1$
+ accz=
+ acy
+ acy1=
+ acz=
+ b
+ bc=
+ """,
+ )
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/xpcom/ds/tools/incremental_dafsa.py b/xpcom/ds/tools/incremental_dafsa.py
new file mode 100644
index 0000000000..1157c26850
--- /dev/null
+++ b/xpcom/ds/tools/incremental_dafsa.py
@@ -0,0 +1,509 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Incremental algorithm for creating a "deterministic acyclic finite state
+automaton" (DAFSA). At the time of writing this algorithm, there was existing logic
+that depended on a different format for the DAFSA, so this contains convenience
+functions for converting to a compatible structure. This legacy format is defined
+in make_dafsa.py.
+"""
+
+from typing import Callable, Dict, List, Optional
+
+
+class Node:
+ children: Dict[str, "Node"]
+ parents: Dict[str, List["Node"]]
+ character: str
+ is_root_node: bool
+ is_end_node: bool
+
+ def __init__(self, character, is_root_node=False, is_end_node=False):
+ self.children = {}
+ self.parents = {}
+ self.character = character
+ self.is_root_node = is_root_node
+ self.is_end_node = is_end_node
+
+ def __str__(self):
+ """Produce a helpful string representation of this node.
+
+ This is expected to only be used for debugging.
+ The produced output is:
+
+ "c[def.] <123>"
+ ^ ^ ^
+ | | Internal python ID of the node (used for de-duping)
+ | |
+ | One possible path through the tree to the end
+ |
+ Current node character
+ """
+
+ if self.is_root_node:
+ return "<root>"
+ elif self.is_end_node:
+ return "<end>"
+
+ first_potential_match = ""
+ node = self
+ while node.children:
+ first_character = next(iter(node.children))
+ if first_character:
+ first_potential_match += first_character
+ node = node.children[first_character]
+
+ return "%s[%s] <%d>" % (self.character, first_potential_match, id(self))
+
+ def add_child(self, child):
+ self.children[child.character] = child
+ child.parents.setdefault(self.character, [])
+ child.parents[self.character].append(self)
+
+ def remove(self):
+ # remove() must only be called when this node has only a single parent, and that
+ # parent doesn't need this child anymore.
+ # The caller is expected to have performed this validation.
+ # (placing asserts here add a non-trivial performance hit)
+
+ # There's only a single parent, so only one list should be in the "parents" map
+ parent_list = next(iter(self.parents.values()))
+ self.remove_parent(parent_list[0])
+ for child in list(self.children.values()):
+ child.remove_parent(self)
+
+ def remove_parent(self, parent_node: "Node"):
+ parent_node.children.pop(self.character)
+ parents_for_character = self.parents[parent_node.character]
+ parents_for_character.remove(parent_node)
+ if not parents_for_character:
+ self.parents.pop(parent_node.character)
+
+ def copy_fork_node(self, fork_node: "Node", child_to_avoid: Optional["Node"]):
+ """Shallow-copy a node's children.
+
+ When adding a new word, sometimes previously-joined suffixes aren't perfect
+ matches any more. When this happens, some nodes need to be "copied" out.
+ For all non-end nodes, there's a child to avoid in the shallow-copy.
+ """
+
+ for child in fork_node.children.values():
+ if child is not child_to_avoid:
+ self.add_child(child)
+
+ def is_fork(self):
+ """Check if this node has multiple parents"""
+
+ if len(self.parents) == 0:
+ return False
+
+ if len(self.parents) > 1:
+ return True
+
+ return len(next(iter(self.parents.values()))) > 1
+
+ def is_replacement_for_prefix_end_node(self, old: "Node"):
+ """Check if this node is a valid replacement for an old end node.
+
+ A node is a valid replacement if it maintains all existing child paths while
+ adding the new child path needed for the new word.
+
+ Args:
+ old: node being replaced
+
+ Returns: True if this node is a valid replacement node.
+ """
+
+ if len(self.children) != len(old.children) + 1:
+ return False
+
+ for character, other_node in old.children.items():
+ this_node = self.children.get(character)
+ if other_node is not this_node:
+ return False
+
+ return True
+
+ def is_replacement_for_prefix_node(self, old: "Node"):
+ """Check if this node is a valid replacement for a non-end node.
+
+ A node is a valid replacement if it:
+ * Has one new child that the old node doesn't
+ * Is missing a child that the old node has
+ * Shares all other children
+
+ Returns: True if this node is a valid replacement node.
+ """
+
+ if len(self.children) != len(old.children):
+ return False
+
+ found_extra_child = False
+
+ for character, other_node in old.children.items():
+ this_node = self.children.get(character)
+ if other_node is not this_node:
+ if found_extra_child:
+ # Found two children in the old node that aren't in the new one,
+ # this isn't a valid replacement
+ return False
+ else:
+ found_extra_child = True
+
+ return found_extra_child
+
+
+class SuffixCursor:
+ index: int # Current position of the cursor within the DAFSA.
+ node: Node
+
+ def __init__(self, index, node):
+ self.index = index
+ self.node = node
+
+ def _query(self, character: str, check: Callable[[Node], bool]):
+ for node in self.node.parents.get(character, []):
+ if check(node):
+ self.index -= 1
+ self.node = node
+ return True
+ return False
+
+ def find_single_child(self, character):
+ """Find the next matching suffix node that has a single child.
+
+ Return True if such a node is found."""
+ return self._query(character, lambda node: len(node.children) == 1)
+
+ def find_end_of_prefix_replacement(self, end_of_prefix: Node):
+ """Find the next matching suffix node that replaces the old prefix-end node.
+
+ Return True if such a node is found."""
+ return self._query(
+ end_of_prefix.character,
+ lambda node: node.is_replacement_for_prefix_end_node(end_of_prefix),
+ )
+
+ def find_inside_of_prefix_replacement(self, prefix_node: Node):
+ """Find the next matching suffix node that replaces a node within the prefix.
+
+ Return True if such a node is found."""
+ return self._query(
+ prefix_node.character,
+ lambda node: node.is_replacement_for_prefix_node(prefix_node),
+ )
+
+
+class DafsaAppendStateMachine:
+ """State machine for adding a word to a Dafsa.
+
+ Each state returns a function reference to the "next state". States should be
+ invoked until "None" is returned, in which case the new word has been appended.
+
+ The prefix and suffix indexes are placed according to the currently-known valid
+ value (not the next value being investigated). Additionally, they are 0-indexed
+ against the root node (which sits behind the beginning of the string).
+
+ Let's imagine we're at the following state when adding, for example, the
+ word "mozilla.org":
+
+ mozilla.org
+ ^ ^ ^ ^
+ | | | |
+ / | | \
+ [root] | | [end] node
+ node | \
+ | suffix
+ \
+ prefix
+
+ In this state, the furthest prefix match we could find was:
+ [root] - m - o - z - i - l
+ The index of the prefix match is "5".
+
+ Additionally, we've been looking for suffix nodes, and we've already found:
+ r - g - [end]
+ The current suffix index is "10".
+ The next suffix node we'll attempt to find is at index "9".
+ """
+
+ root_node: Node
+ prefix_index: int
+ suffix_cursor: SuffixCursor
+ stack: List[Node]
+ word: str
+ suffix_overlaps_prefix: bool
+ first_fork_index: Optional[int]
+ _state: Callable
+
+ def __init__(self, word, root_node, end_node):
+ self.root_node = root_node
+ self.prefix_index = 0
+ self.suffix_cursor = SuffixCursor(len(word) + 1, end_node)
+ self.stack = [root_node]
+ self.word = word
+ self.suffix_overlaps_prefix = False
+ self.first_fork_index = None
+ self._state = self._find_prefix
+
+ def run(self):
+ """Run this state machine to completion, adding the new word."""
+ while self._state is not None:
+ self._state = self._state()
+
+ def _find_prefix(self):
+ """Find the longest existing prefix that matches the current word."""
+ prefix_node = self.root_node
+ while self.prefix_index < len(self.word):
+ next_character = self.word[self.prefix_index]
+ next_node = prefix_node.children.get(next_character)
+ if not next_node:
+ # We're finished finding the prefix, let's find the longest suffix
+ # match now.
+ return self._find_suffix_nodes_after_prefix
+
+ self.prefix_index += 1
+ prefix_node = next_node
+ self.stack.append(next_node)
+
+ if not self.first_fork_index and next_node.is_fork():
+ self.first_fork_index = self.prefix_index
+
+ # Deja vu, we've appended this string before. Since this string has
+ # already been appended, we don't have to do anything.
+ return None
+
+ def _find_suffix_nodes_after_prefix(self):
+ """Find the chain of suffix nodes for characters after the prefix."""
+ while self.suffix_cursor.index - 1 > self.prefix_index:
+ # To fetch the next character, we need to subtract two from the current
+ # suffix index. This is because:
+ # * The next suffix node is 1 node before our current node (subtract 1)
+ # * The suffix index includes the root node before the beginning of the
+ # string - it's like the string is 1-indexed (subtract 1 again).
+ next_character = self.word[self.suffix_cursor.index - 2]
+ if not self.suffix_cursor.find_single_child(next_character):
+ return self._add_new_nodes
+
+ if self.suffix_cursor.node is self.stack[-1]:
+ # The suffix match is overlapping with the prefix! This can happen in
+ # cases like:
+ # * "ab"
+ # * "abb"
+ # The suffix cursor is at the same node as the prefix match, but they're
+ # at different positions in the word.
+ #
+ # [root] - a - b - [end]
+ # ^
+ # / \
+ # / \
+ # prefix suffix
+ # \ /
+ # \ /
+ # VV
+ # "abb"
+ if not self.first_fork_index:
+ # There hasn't been a fork, so our prefix isn't shared. So, we
+ # can mark this node as a fork, since the repetition means
+ # that there's two paths that are now using this node
+ self.first_fork_index = self.prefix_index
+ return self._add_new_nodes
+
+ # Removes the link between the unique part of the prefix and the
+ # shared part of the prefix.
+ self.stack[self.first_fork_index].remove_parent(
+ self.stack[self.first_fork_index - 1]
+ )
+ self.suffix_overlaps_prefix = True
+
+ if self.first_fork_index is None:
+ return self._find_next_suffix_nodes
+ elif self.suffix_cursor.index - 1 == self.first_fork_index:
+ return self._find_next_suffix_node_at_prefix_end_at_fork
+ else:
+ return self._find_next_suffix_node_at_prefix_end_after_fork
+
+ def _find_next_suffix_node_at_prefix_end_at_fork(self):
+ """Find the next suffix node that replaces the end of the prefix.
+
+ In this state, the prefix_end node is the same as the first fork node.
+ Therefore, if a match can be found, the old prefix node can't be entirely
+ deleted since it's used elsewhere. Instead, just the link between our
+ unique prefix and the end of the fork is removed.
+ """
+ existing_node = self.stack[self.prefix_index]
+ if not self.suffix_cursor.find_end_of_prefix_replacement(existing_node):
+ return self._add_new_nodes
+
+ self.prefix_index -= 1
+ self.first_fork_index = None
+
+ if not self.suffix_overlaps_prefix:
+ existing_node.remove_parent(self.stack[self.prefix_index])
+ else:
+ # When the suffix overlaps the prefix, the old "parent link" was removed
+ # earlier in the "find_suffix_nodes_after_prefix" step.
+ self.suffix_overlaps_prefix = False
+
+ return self._find_next_suffix_nodes
+
+ def _find_next_suffix_node_at_prefix_end_after_fork(self):
+ """Find the next suffix node that replaces the end of the prefix.
+
+ In this state, the prefix_end node is after the first fork node.
+ Therefore, even if a match is found, we don't want to modify the replaced
+ prefix node since an unrelated word chain uses it.
+ """
+ existing_node = self.stack[self.prefix_index]
+ if not self.suffix_cursor.find_end_of_prefix_replacement(existing_node):
+ return self._add_new_nodes
+
+ self.prefix_index -= 1
+ if self.prefix_index == self.first_fork_index:
+ return self._find_next_suffix_node_within_prefix_at_fork
+ else:
+ return self._find_next_suffix_nodes_within_prefix_after_fork
+
+ def _find_next_suffix_node_within_prefix_at_fork(self):
+ """Find the next suffix node within a prefix.
+
+ In this state, we've already worked our way back and found nodes in the suffix
+ to replace prefix nodes after the fork node. We have now reached the fork node,
+ and if we find a replacement for it, then we can remove the link between it
+ and our then-unique prefix and clear the fork status.
+ """
+ existing_node = self.stack[self.prefix_index]
+ if not self.suffix_cursor.find_inside_of_prefix_replacement(existing_node):
+ return self._add_new_nodes
+
+ self.prefix_index -= 1
+ self.first_fork_index = None
+
+ if not self.suffix_overlaps_prefix:
+ existing_node.remove_parent(self.stack[self.prefix_index])
+ else:
+ # When the suffix overlaps the prefix, the old "parent link" was removed
+ # earlier in the "find_suffix_nodes_after_prefix" step.
+ self.suffix_overlaps_prefix = False
+
+ return self._find_next_suffix_nodes
+
+ def _find_next_suffix_nodes_within_prefix_after_fork(self):
+ """Find the next suffix nodes within a prefix.
+
+ Finds suffix nodes to replace prefix nodes, but doesn't modify the prefix
+ nodes since they're after a fork (so, we're sharing prefix nodes with
+ other words and can't modify them).
+ """
+ while True:
+ existing_node = self.stack[self.prefix_index]
+ if not self.suffix_cursor.find_inside_of_prefix_replacement(existing_node):
+ return self._add_new_nodes
+
+ self.prefix_index -= 1
+ if self.prefix_index == self.first_fork_index:
+ return self._find_next_suffix_node_within_prefix_at_fork
+
+ def _find_next_suffix_nodes(self):
+ """Find all remaining suffix nodes in the chain.
+
+ In this state, there's no (longer) any fork, so there's no other words
+ using our current prefix. Therefore, as we find replacement nodes as we
+ work our way backwards, we can remove the now-unused prefix nodes.
+ """
+ while True:
+ existing_node = self.stack[self.prefix_index]
+ if not self.suffix_cursor.find_end_of_prefix_replacement(existing_node):
+ return self._add_new_nodes
+
+ # This prefix node is wholly replaced by the new suffix node, so it can
+ # be deleted.
+ existing_node.remove()
+ self.prefix_index -= 1
+
+ def _add_new_nodes(self):
+ """Adds new nodes to support the new word.
+
+ Duplicates forked nodes to make room for new links, adds new nodes for new
+ characters, and splices the prefix to the suffix to finish embedding the new
+ word into the DAFSA.
+ """
+ if self.first_fork_index is not None:
+ front_node = _duplicate_fork_nodes(
+ self.stack,
+ self.first_fork_index,
+ self.prefix_index,
+ # if suffix_overlaps_parent, the parent link was removed
+ # earlier in the word-adding process.
+ remove_parent_link=not self.suffix_overlaps_prefix,
+ )
+ else:
+ front_node = self.stack[self.prefix_index]
+
+ new_text = self.word[self.prefix_index : self.suffix_cursor.index - 1]
+ for character in new_text:
+ new_node = Node(character)
+ front_node.add_child(new_node)
+ front_node = new_node
+
+ front_node.add_child(self.suffix_cursor.node)
+ return None # Done!
+
+
+def _duplicate_fork_nodes(stack, fork_index, prefix_index, remove_parent_link=True):
+ parent_node = stack[fork_index - 1]
+ if remove_parent_link:
+ # remove link to old chain that we're going to be copying
+ stack[fork_index].remove_parent(parent_node)
+
+ for index in range(fork_index, prefix_index + 1):
+ fork_node = stack[index]
+ replacement_node = Node(fork_node.character)
+ child_to_avoid = None
+ if index < len(stack) - 1:
+ # We're going to be manually replacing the next node in the stack,
+ # so don't connect it as a child.
+ child_to_avoid = stack[index + 1]
+
+ replacement_node.copy_fork_node(fork_node, child_to_avoid)
+ parent_node.add_child(replacement_node)
+ parent_node = replacement_node
+
+ return parent_node
+
+
+class Dafsa:
+ root_node: Node
+ end_node: Node
+
+ def __init__(self):
+ self.root_node = Node(None, is_root_node=True)
+ self.end_node = Node(None, is_end_node=True)
+
+ @classmethod
+ def from_tld_data(cls, lines):
+ """Create a dafsa for TLD data.
+
+ TLD data has a domain and a "type" enum. The source data encodes the type as a
+ text number, but the dafsa-consuming code assumes that the type is a raw byte
+ number (e.g.: "1" => 0x01).
+
+ This function acts as a helper, processing this TLD detail before creating a
+ standard dafsa.
+ """
+
+ dafsa = cls()
+ for i, word in enumerate(lines):
+ domain_number = word[-1]
+ # Convert type from string to byte representation
+ raw_domain_number = chr(ord(domain_number) & 0x0F)
+
+ word = "%s%s" % (word[:-1], raw_domain_number)
+ dafsa.append(word)
+ return dafsa
+
+ def append(self, word):
+ state_machine = DafsaAppendStateMachine(word, self.root_node, self.end_node)
+ state_machine.run()
diff --git a/xpcom/ds/tools/make_dafsa.py b/xpcom/ds/tools/make_dafsa.py
new file mode 100644
index 0000000000..a9cedf78a8
--- /dev/null
+++ b/xpcom/ds/tools/make_dafsa.py
@@ -0,0 +1,372 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+A Deterministic acyclic finite state automaton (DAFSA) is a compact
+representation of an unordered word list (dictionary).
+
+http://en.wikipedia.org/wiki/Deterministic_acyclic_finite_state_automaton
+
+This python program converts a list of strings to a byte array in C++.
+This python program fetches strings and return values from a gperf file
+and generates a C++ file with a byte array representing graph that can be
+used as a memory efficient replacement for the perfect hash table.
+
+The input strings are assumed to consist of printable 7-bit ASCII characters
+and the return values are assumed to be one digit integers.
+
+In this program a DAFSA is a diamond shaped graph starting at a common
+root node and ending at a common end node. All internal nodes contain
+a character and each word is represented by the characters in one path from
+the root node to the end node.
+
+The order of the operations is crucial since lookups will be performed
+starting from the source with no backtracking. Thus a node must have at
+most one child with a label starting by the same character. The output
+is also arranged so that all jumps are to increasing addresses, thus forward
+in memory.
+
+The generated output has suffix free decoding so that the sign of leading
+bits in a link (a reference to a child node) indicate if it has a size of one,
+two or three bytes and if it is the last outgoing link from the actual node.
+A node label is terminated by a byte with the leading bit set.
+
+The generated byte array can described by the following BNF:
+
+<byte> ::= < 8-bit value in range [0x00-0xFF] >
+
+<char> ::= < printable 7-bit ASCII character, byte in range [0x20-0x7F] >
+<end_char> ::= < char + 0x80, byte in range [0xA0-0xFF] >
+<return value> ::= < value + 0x80, byte in range [0x80-0x8F] >
+
+<offset1> ::= < byte in range [0x00-0x3F] >
+<offset2> ::= < byte in range [0x40-0x5F] >
+<offset3> ::= < byte in range [0x60-0x7F] >
+
+<end_offset1> ::= < byte in range [0x80-0xBF] >
+<end_offset2> ::= < byte in range [0xC0-0xDF] >
+<end_offset3> ::= < byte in range [0xE0-0xFF] >
+
+<prefix> ::= <char>
+
+<label> ::= <end_char>
+ | <char> <label>
+
+<end_label> ::= <return_value>
+ | <char> <end_label>
+
+<offset> ::= <offset1>
+ | <offset2> <byte>
+ | <offset3> <byte> <byte>
+
+<end_offset> ::= <end_offset1>
+ | <end_offset2> <byte>
+ | <end_offset3> <byte> <byte>
+
+<offsets> ::= <end_offset>
+ | <offset> <offsets>
+
+<source> ::= <offsets>
+
+<node> ::= <label> <offsets>
+ | <prefix> <node>
+ | <end_label>
+
+<dafsa> ::= <source>
+ | <dafsa> <node>
+
+Decoding:
+
+<char> -> printable 7-bit ASCII character
+<end_char> & 0x7F -> printable 7-bit ASCII character
+<return value> & 0x0F -> integer
+<offset1 & 0x3F> -> integer
+((<offset2> & 0x1F>) << 8) + <byte> -> integer
+((<offset3> & 0x1F>) << 16) + (<byte> << 8) + <byte> -> integer
+
+end_offset1, end_offset2 and and_offset3 are decoded same as offset1,
+offset2 and offset3 respectively.
+
+The first offset in a list of offsets is the distance in bytes between the
+offset itself and the first child node. Subsequent offsets are the distance
+between previous child node and next child node. Thus each offset links a node
+to a child node. The distance is always counted between start addresses, i.e.
+first byte in decoded offset or first byte in child node.
+
+Example 1:
+
+%%
+aa, 1
+a, 2
+%%
+
+The input is first parsed to a list of words:
+["aa1", "a2"]
+
+This produces the following graph:
+[root] --- a --- 0x02 --- [end]
+ | /
+ | /
+ - a --- 0x01
+
+A C++ representation of the compressed graph is generated:
+
+const unsigned char dafsa[7] = {
+ 0x81, 0xE1, 0x02, 0x81, 0x82, 0x61, 0x81,
+};
+
+The bytes in the generated array has the following meaning:
+
+ 0: 0x81 <end_offset1> child at position 0 + (0x81 & 0x3F) -> jump to 1
+
+ 1: 0xE1 <end_char> label character (0xE1 & 0x7F) -> match "a"
+ 2: 0x02 <offset1> child at position 2 + (0x02 & 0x3F) -> jump to 4
+
+ 3: 0x81 <end_offset1> child at position 4 + (0x81 & 0x3F) -> jump to 5
+ 4: 0x82 <return_value> 0x82 & 0x0F -> return 2
+
+ 5: 0x61 <char> label character 0x61 -> match "a"
+ 6: 0x81 <return_value> 0x81 & 0x0F -> return 1
+
+Example 2:
+
+%%
+aa, 1
+bbb, 2
+baa, 1
+%%
+
+The input is first parsed to a list of words:
+["aa1", "bbb2", "baa1"]
+
+This produces the following graph:
+[root] --- a --- a --- 0x01 --- [end]
+ | / / /
+ | / / /
+ - b --- b --- b --- 0x02
+
+A C++ representation of the compressed graph is generated:
+
+const unsigned char dafsa[11] = {
+ 0x02, 0x83, 0xE2, 0x02, 0x83, 0x61, 0x61, 0x81, 0x62, 0x62, 0x82,
+};
+
+The bytes in the generated array has the following meaning:
+
+ 0: 0x02 <offset1> child at position 0 + (0x02 & 0x3F) -> jump to 2
+ 1: 0x83 <end_offset1> child at position 2 + (0x83 & 0x3F) -> jump to 5
+
+ 2: 0xE2 <end_char> label character (0xE2 & 0x7F) -> match "b"
+ 3: 0x02 <offset1> child at position 3 + (0x02 & 0x3F) -> jump to 5
+ 4: 0x83 <end_offset1> child at position 5 + (0x83 & 0x3F) -> jump to 8
+
+ 5: 0x61 <char> label character 0x61 -> match "a"
+ 6: 0x61 <char> label character 0x61 -> match "a"
+ 7: 0x81 <return_value> 0x81 & 0x0F -> return 1
+
+ 8: 0x62 <char> label character 0x62 -> match "b"
+ 9: 0x62 <char> label character 0x62 -> match "b"
+10: 0x82 <return_value> 0x82 & 0x0F -> return 2
+"""
+import struct
+import sys
+
+from incremental_dafsa import Dafsa, Node
+
+
+class InputError(Exception):
+ """Exception raised for errors in the input file."""
+
+
+def top_sort(dafsa: Dafsa):
+ """Generates list of nodes in topological sort order."""
+ incoming = {}
+
+ def count_incoming(node: Node):
+ """Counts incoming references."""
+ if not node.is_end_node:
+ if id(node) not in incoming:
+ incoming[id(node)] = 1
+ for child in node.children.values():
+ count_incoming(child)
+ else:
+ incoming[id(node)] += 1
+
+ for node in dafsa.root_node.children.values():
+ count_incoming(node)
+
+ for node in dafsa.root_node.children.values():
+ incoming[id(node)] -= 1
+
+ waiting = [
+ node for node in dafsa.root_node.children.values() if incoming[id(node)] == 0
+ ]
+ nodes = []
+
+ while waiting:
+ node = waiting.pop()
+ assert incoming[id(node)] == 0
+ nodes.append(node)
+ for child in node.children.values():
+ if not child.is_end_node:
+ incoming[id(child)] -= 1
+ if incoming[id(child)] == 0:
+ waiting.append(child)
+ return nodes
+
+
+def encode_links(node: Node, offsets, current):
+ """Encodes a list of children as one, two or three byte offsets."""
+ if next(iter(node.children.values())).is_end_node:
+ # This is an <end_label> node and no links follow such nodes
+ return []
+ guess = 3 * len(node.children)
+ assert node.children
+
+ children = sorted(node.children.values(), key=lambda x: -offsets[id(x)])
+ while True:
+ offset = current + guess
+ buf = []
+ for child in children:
+ last = len(buf)
+ distance = offset - offsets[id(child)]
+ assert distance > 0 and distance < (1 << 21)
+
+ if distance < (1 << 6):
+ # A 6-bit offset: "s0xxxxxx"
+ buf.append(distance)
+ elif distance < (1 << 13):
+ # A 13-bit offset: "s10xxxxxxxxxxxxx"
+ buf.append(0x40 | (distance >> 8))
+ buf.append(distance & 0xFF)
+ else:
+ # A 21-bit offset: "s11xxxxxxxxxxxxxxxxxxxxx"
+ buf.append(0x60 | (distance >> 16))
+ buf.append((distance >> 8) & 0xFF)
+ buf.append(distance & 0xFF)
+ # Distance in first link is relative to following record.
+ # Distance in other links are relative to previous link.
+ offset -= distance
+ if len(buf) == guess:
+ break
+ guess = len(buf)
+ # Set most significant bit to mark end of links in this node.
+ buf[last] |= 1 << 7
+ buf.reverse()
+ return buf
+
+
+def encode_prefix(label):
+ """Encodes a node label as a list of bytes without a trailing high byte.
+
+ This method encodes a node if there is exactly one child and the
+ child follows immediately after so that no jump is needed. This label
+ will then be a prefix to the label in the child node.
+ """
+ assert label
+ return [ord(c) for c in reversed(label)]
+
+
+def encode_label(label):
+ """Encodes a node label as a list of bytes with a trailing high byte >0x80."""
+ buf = encode_prefix(label)
+ # Set most significant bit to mark end of label in this node.
+ buf[0] |= 1 << 7
+ return buf
+
+
+def encode(dafsa: Dafsa):
+ """Encodes a DAFSA to a list of bytes"""
+ output = []
+ offsets = {}
+
+ for node in reversed(top_sort(dafsa)):
+ if (
+ len(node.children) == 1
+ and not next(iter(node.children.values())).is_end_node
+ and (offsets[id(next(iter(node.children.values())))] == len(output))
+ ):
+ output.extend(encode_prefix(node.character))
+ else:
+ output.extend(encode_links(node, offsets, len(output)))
+ output.extend(encode_label(node.character))
+ offsets[id(node)] = len(output)
+
+ output.extend(encode_links(dafsa.root_node, offsets, len(output)))
+ output.reverse()
+ return output
+
+
+def to_cxx(data, preamble=None):
+ """Generates C++ code from a list of encoded bytes."""
+ text = "/* This file is generated. DO NOT EDIT!\n\n"
+ text += "The byte array encodes a dictionary of strings and values. See "
+ text += "make_dafsa.py for documentation."
+ text += "*/\n\n"
+
+ if preamble:
+ text += preamble
+ text += "\n\n"
+
+ text += "const unsigned char kDafsa[%s] = {\n" % len(data)
+ for i in range(0, len(data), 12):
+ text += " "
+ text += ", ".join("0x%02x" % byte for byte in data[i : i + 12])
+ text += ",\n"
+ text += "};\n"
+ return text
+
+
+def words_to_cxx(words, preamble=None):
+ """Generates C++ code from a word list"""
+ dafsa = Dafsa.from_tld_data(words)
+ return to_cxx(encode(dafsa), preamble)
+
+
+def words_to_bin(words):
+ """Generates bytes from a word list"""
+ dafsa = Dafsa.from_tld_data(words)
+ data = encode(dafsa)
+ return struct.pack("%dB" % len(data), *data)
+
+
+def parse_gperf(infile):
+ """Parses gperf file and extract strings and return code"""
+ lines = [line.strip() for line in infile]
+
+ # Extract the preamble.
+ first_delimeter = lines.index("%%")
+ preamble = "\n".join(lines[0:first_delimeter])
+
+ # Extract strings after the first '%%' and before the second '%%'.
+ begin = first_delimeter + 1
+ end = lines.index("%%", begin)
+ lines = lines[begin:end]
+ for line in lines:
+ if line[-3:-1] != ", ":
+ raise InputError('Expected "domainname, <digit>", found "%s"' % line)
+ # Technically the DAFSA format could support return values in range [0-31],
+ # but the values below are the only with a defined meaning.
+ if line[-1] not in "0124":
+ raise InputError(
+ 'Expected value to be one of {0,1,2,4}, found "%s"' % line[-1]
+ )
+ return (preamble, [line[:-3] + line[-1] for line in lines])
+
+
+def main(outfile, infile):
+ with open(infile, "r") as infile:
+ preamble, words = parse_gperf(infile)
+ outfile.write(words_to_cxx(words, preamble))
+ return 0
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print("usage: %s infile outfile" % sys.argv[0])
+ sys.exit(1)
+
+ with open(sys.argv[2], "w") as outfile:
+ sys.exit(main(outfile, sys.argv[1]))
diff --git a/xpcom/ds/tools/perfecthash.py b/xpcom/ds/tools/perfecthash.py
new file mode 100644
index 0000000000..b4e964a125
--- /dev/null
+++ b/xpcom/ds/tools/perfecthash.py
@@ -0,0 +1,371 @@
+# perfecthash.py - Helper for generating perfect hash functions
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+A perfect hash function (PHF) is a function which maps distinct elements from a
+source set to a sequence of integers with no collisions.
+
+PHFs generated by this module use a 32-bit FNV hash to index an intermediate
+table. The value from that table is then used to run a second 32-bit FNV hash to
+get a final index.
+
+The algorithm works by starting with the largest set of conflicts, and testing
+intermediate table values until no conflicts are generated, allowing efficient
+index lookup at runtime. (see also: http://stevehanov.ca/blog/index.php?id=119)
+
+NOTE: This perfect hash generator was designed to be simple, easy to follow, and
+maintainable, rather than being as optimized as possible, due to the relatively
+small dataset it was designed for. In the future we may want to optimize further.
+"""
+
+import textwrap
+from collections import namedtuple
+
+import six
+from mozbuild.util import ensure_bytes
+
+
+# Iteration over bytestrings works differently in Python 2 and 3; this function
+# captures the two possibilities. Returns an 'int' given the output of iterating
+# through a bytestring regardless of the input.
+def _ord(c):
+ if six.PY3:
+ return c
+ return ord(c)
+
+
+class PerfectHash(object):
+ """PerfectHash objects represent a computed perfect hash function, which
+ can be generated at compile time to provide highly efficient and compact
+ static HashTables.
+
+ Consumers must provide an intermediate table size to store the generated
+ hash function. Larger tables will generate more quickly, while smaller
+ tables will consume less space on disk."""
+
+ # 32-bit FNV offset basis and prime value.
+ # NOTE: Must match values in |PerfectHash.h|
+ FNV_OFFSET_BASIS = 0x811C9DC5
+ FNV_PRIME = 16777619
+ U32_MAX = 0xFFFFFFFF
+
+ # Bucket of entries which map into a given intermediate index.
+ Bucket = namedtuple("Bucket", "index entries")
+
+ def __init__(self, entries, size, validate=True, key=lambda e: e[0]):
+ """Create a new PerfectHash
+
+ @param entries set of entries to generate a PHF for
+ @param size size of the PHF intermediate table
+ @param validate test the generated hash function after generation
+ @param key function to get 'memoryview'-compatible key for an
+ entry. defaults to extracting the first element of an
+ entry 2-tuple.
+ """
+
+ assert 0 < len(entries) < self.U32_MAX, "bad # of entries!"
+ self._key = key
+
+ # Allocate the intermediate table and keys.
+ self.table = [0] * size
+ self.entries = [None] * len(entries)
+
+ # A set of Bucket values. Each bucket contains the index into the
+ # intermediate table, and a list of Entries which map into that bucket.
+ buckets = [self.Bucket(idx, []) for idx in range(size)]
+
+ # Determine which input strings map to which buckets in the table.
+ for entry in entries:
+ assert entry is not None, "None entry in entries"
+ assert self.key(entry).itemsize == 1, "non-byte key elements"
+ buckets[self._hash(self.key(entry)) % size].entries.append(entry)
+
+ # Sort the buckets such that the largest one comes first.
+ buckets.sort(key=lambda b: len(b.entries), reverse=True)
+
+ for bucket in buckets:
+ # Once we've reached an empty bucket, we're done.
+ if len(bucket.entries) == 0:
+ break
+
+ # Try values for the basis until we find one with no conflicts.
+ idx = 0
+ basis = 1
+ slots = []
+ while idx < len(bucket.entries):
+ slot = self._hash(self.key(bucket.entries[idx]), basis) % len(
+ self.entries
+ )
+ if self.entries[slot] is not None or slot in slots:
+ # There was a conflict, try the next basis.
+ basis += 1
+ idx = 0
+ del slots[:]
+ assert basis < self.U32_MAX, "table too small"
+ else:
+ slots.append(slot)
+ idx += 1
+
+ assert basis < self.U32_MAX
+
+ # We've found a basis which doesn't conflict
+ self.table[bucket.index] = basis
+ for slot, entry in zip(slots, bucket.entries):
+ self.entries[slot] = entry
+
+ # Validate that looking up each key succeeds
+ if validate:
+ for entry in entries:
+ assert self.get_entry(self.key(entry)), "get_entry(%s)" % repr(entry)
+
+ @classmethod
+ def _hash(cls, key, basis=FNV_OFFSET_BASIS):
+ """A basic FNV-based hash function. key is the memoryview to hash.
+ 32-bit FNV is used for indexing into the first table, and the value
+ stored in that table is used as the offset basis for indexing into the
+ values table."""
+ for byte in memoryview(ensure_bytes(key)):
+ obyte = _ord(byte)
+ basis ^= obyte # xor-in the byte
+ basis *= cls.FNV_PRIME # Multiply by the FNV prime
+ basis &= cls.U32_MAX # clamp to 32-bits
+ return basis
+
+ def key(self, entry):
+ return memoryview(ensure_bytes(self._key(entry)))
+
+ def get_raw_index(self, key):
+ """Determine the index in self.entries without validating"""
+ mid = self.table[self._hash(key) % len(self.table)]
+ return self._hash(key, mid) % len(self.entries)
+
+ def get_index(self, key):
+ """Given a key, determine the index in self.entries"""
+ idx = self.get_raw_index(key)
+ if memoryview(ensure_bytes(key)) != self.key(self.entries[idx]):
+ return None
+ return idx
+
+ def get_entry(self, key):
+ """Given a key, get the corresponding entry"""
+ idx = self.get_index(key)
+ if idx is None:
+ return None
+ return self.entries[idx]
+
+ @staticmethod
+ def _indent(text, amount=1):
+ return text.replace("\n", "\n" + (" " * amount))
+
+ def codegen(self, name, entry_type):
+ """Create a helper for codegening PHF logic"""
+ return CGHelper(self, name, entry_type)
+
+ def cxx_codegen(
+ self,
+ name,
+ entry_type,
+ lower_entry,
+ entries_name=None,
+ return_type=None,
+ return_entry="return entry;",
+ key_type="const char*",
+ key_bytes="aKey",
+ key_length="strlen(aKey)",
+ ):
+ """Generate complete C++ code for a get_entry-style method.
+
+ @param name Name for the entry getter function.
+ @param entry_type C++ type of each entry in static memory.
+ @param lower_entry Function called with each entry to convert it to a
+ C++ literal of type 'entry_type'.
+
+ # Optional Keyword Parameters:
+ @param entries_name Name for the generated entry table.
+
+ @param return_type C++ return type, default: 'const entry_type&'
+ @param return_entry Override the default behaviour for returning the
+ found entry. 'entry' is a reference to the found
+ entry, and 'aKey' is the lookup key. 'return_entry'
+ can be used for additional checks, e.g. for keys
+ not in the table.
+
+ @param key_type C++ key type, default: 'const char*'
+ @param key_bytes 'const char*' expression to get bytes for 'aKey'
+ @param key_length 'size_t' expression to get length of 'aKey'"""
+
+ if entries_name is None:
+ entries_name = "s%sEntries" % name
+
+ cg = self.codegen(entries_name, entry_type)
+ entries = cg.gen_entries(lower_entry)
+ getter = cg.gen_getter(
+ name, return_type, return_entry, key_type, key_bytes, key_length
+ )
+
+ return "%s\n\n%s" % (entries, getter)
+
+
+class CGHelper(object):
+ """Helper object for generating C++ code for a PerfectHash.
+ Created using PerfectHash.codegen()."""
+
+ def __init__(self, phf, entries_name, entry_type):
+ self.phf = phf
+ self.entries_name = entries_name
+ self.entry_type = entry_type
+
+ @staticmethod
+ def _indent(text, amount=1):
+ return text.replace("\n", "\n" + (" " * amount))
+
+ def basis_ty(self):
+ """Determine how big of an integer is needed for bases table."""
+ max_base = max(self.phf.table)
+ if max_base <= 0xFF:
+ return "uint8_t"
+ elif max_base <= 0xFFFF:
+ return "uint16_t"
+ return "uint32_t"
+
+ def basis_table(self, name="BASES"):
+ """Generate code for a static basis table"""
+ bases = ""
+ for idx, base in enumerate(self.phf.table):
+ if idx and idx % 16 == 0: # 16 bases per line
+ bases += "\n "
+ bases += "%4d," % base
+ return (
+ textwrap.dedent(
+ """\
+ static const %s %s[] = {
+ %s
+ };
+ """
+ )
+ % (self.basis_ty(), name, bases)
+ )
+
+ def gen_entries(self, lower_entry):
+ """Generate code for an entries table"""
+ entries = self._indent(
+ ",\n".join(lower_entry(entry).rstrip() for entry in self.phf.entries)
+ )
+ return (
+ textwrap.dedent(
+ """\
+ const %s %s[] = {
+ %s
+ };
+ """
+ )
+ % (self.entry_type, self.entries_name, entries)
+ )
+
+ def gen_getter(
+ self,
+ name,
+ return_type=None,
+ return_entry="return entry;",
+ key_type="const char*",
+ key_bytes="aKey",
+ key_length="strlen(aKey)",
+ ):
+ """Generate the code for a C++ getter.
+
+ @param name Name for the entry getter function.
+ @param return_type C++ return type, default: 'const entry_type&'
+ @param return_entry Override the default behaviour for returning the
+ found entry. 'entry' is a reference to the found
+ entry, and 'aKey' is the lookup key. 'return_entry'
+ can be used for additional checks, e.g. for keys
+ not in the table.
+
+ @param key_type C++ key type, default: 'const char*'
+ @param key_bytes 'const char*' expression to get bytes for 'aKey'
+ @param key_length 'size_t' expression to get length of 'aKey'"""
+
+ if return_type is None:
+ return_type = "const %s&" % self.entry_type
+
+ return textwrap.dedent(
+ """
+ %(return_type)s
+ %(name)s(%(key_type)s aKey)
+ {
+ %(basis_table)s
+
+ const char* bytes = %(key_bytes)s;
+ size_t length = %(key_length)s;
+ auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES,
+ %(entries_name)s);
+ %(return_entry)s
+ }
+ """
+ ) % {
+ "name": name,
+ "basis_table": self._indent(self.basis_table()),
+ "entries_name": self.entries_name,
+ "return_type": return_type,
+ "return_entry": self._indent(return_entry),
+ "key_type": key_type,
+ "key_bytes": key_bytes,
+ "key_length": key_length,
+ }
+
+ def gen_jslinearstr_getter(
+ self, name, return_type=None, return_entry="return entry;"
+ ):
+ """Generate code for a specialized getter taking JSLinearStrings.
+ This getter avoids copying the JS string, but only supports ASCII keys.
+
+ @param name Name for the entry getter function.
+ @param return_type C++ return type, default: 'const entry_type&'
+ @param return_entry Override the default behaviour for returning the
+ found entry. 'entry' is a reference to the found
+ entry, and 'aKey' is the lookup key. 'return_entry'
+ can be used for additional checks, e.g. for keys
+ not in the table."""
+
+ assert all(
+ _ord(b) <= 0x7F for e in self.phf.entries for b in self.phf.key(e)
+ ), "non-ASCII key"
+
+ if return_type is None:
+ return_type = "const %s&" % self.entry_type
+
+ return textwrap.dedent(
+ """
+ %(return_type)s
+ %(name)s(JSLinearString* aKey)
+ {
+ %(basis_table)s
+
+ size_t length = JS::GetLinearStringLength(aKey);
+
+ JS::AutoCheckCannotGC nogc;
+ if (JS::LinearStringHasLatin1Chars(aKey)) {
+ auto& entry = mozilla::perfecthash::Lookup(
+ JS::GetLatin1LinearStringChars(nogc, aKey),
+ length, BASES, %(entries_name)s);
+
+ %(return_entry)s
+ } else {
+ auto& entry = mozilla::perfecthash::Lookup(
+ JS::GetTwoByteLinearStringChars(nogc, aKey),
+ length, BASES, %(entries_name)s);
+
+ %(return_entry)s
+ }
+ }
+ """
+ ) % {
+ "name": name,
+ "basis_table": self._indent(self.basis_table()),
+ "entries_name": self.entries_name,
+ "return_type": return_type,
+ "return_entry": self._indent(return_entry, 2),
+ }