diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /xpcom/ds | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/ds')
118 files changed, 31162 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/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..67e4d7fdcf --- /dev/null +++ b/xpcom/ds/PLDHashTable.h @@ -0,0 +1,807 @@ +/* -*- 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) const { + 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..fb41a00bd1 --- /dev/null +++ b/xpcom/ds/StaticAtoms.py @@ -0,0 +1,2636 @@ +# 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("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("invertedColors", "inverted-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("generic", "generic"), + 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("_module", "module"), + 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("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("popover", "popover"), + Atom("popovertarget", "popovertarget"), + Atom("popovertargetaction", "popovertargetaction"), + 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("prefersReducedTransparency", "prefers-reduced-transparency"), + 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("scripting", "scripting"), + 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("scrollbarInlineSize", "scrollbar-inline-size"), + 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("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"), + Atom("onbeforetoggle", "onbeforetoggle"), + # 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("beforePseudoProperty", "beforePseudoProperty"), # nsXMLElement* + Atom("cssPseudoElementBeforeProperty", "CSSPseudoElementBeforeProperty"), # CSSPseudoElement* + Atom("cssPseudoElementAfterProperty", "CSSPseudoElementAfterProperty"), # CSSPseudoElement* + Atom("cssPseudoElementMarkerProperty", "CSSPseudoElementMarkerProperty"), # CSSPseudoElement* + 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("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_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_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"), + Atom("_moz_popover_enabled", "-moz-popover-enabled"), + # 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_DefaultScreenshotDirectory", "Scrnshts"), + 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_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_highlight", ":highlight"), + 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..ce4e8c1e69 --- /dev/null +++ b/xpcom/ds/StickyTimeDuration.h @@ -0,0 +1,239 @@ +/* -*- 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 <cmath> +#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 || (!std::isinf(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 || std::isinf(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(std::isinf(aB) == std::isinf(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..e861f7bd8d --- /dev/null +++ b/xpcom/ds/moz.build @@ -0,0 +1,156 @@ +# -*- 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", + "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..1b53b7be2e --- /dev/null +++ b/xpcom/ds/nsAtom.h @@ -0,0 +1,309 @@ +/* -*- 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 nsAutoAtomString : public nsAutoString { + public: + explicit nsAutoAtomString(const nsAtom* aAtom) { aAtom->ToString(*this); } +}; + +class nsAtomCString : public nsCString { + public: + explicit nsAtomCString(const nsAtom* aAtom) { aAtom->ToUTF8String(*this); } +}; + +class nsAutoAtomCString : public nsAutoCString { + public: + explicit nsAutoAtomCString(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(¤t)) || + !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..5ed81b01a7 --- /dev/null +++ b/xpcom/ds/nsObserverService.cpp @@ -0,0 +1,357 @@ +/* -*- 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" + +// 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)); + + 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); + } + + 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..f8a40431c7 --- /dev/null +++ b/xpcom/ds/nsVariant.cpp @@ -0,0 +1,1876 @@ +/* -*- 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 (std::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 (std::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 (std::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: { + aOutString.Assign(u.mIDValue.ToString().get()); + 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), + } |