Bug 1471025: Part 5 - Add a range iterator helper for iterating both static and dynamic preferences. r=njn
For memory efficiency in content processes, we need to be able to store
changed preferences in a separate dynamic hashtable when their values don't
match the snapshot values.
That makes iteration over the full set of preferences somewhat more
complicated, since not only do we need to iterate over two tables, but we also
need to ignore preferences in the snapshot table if they also exist in the
dynamic hashtable.
This patch solves that problem by adding an iterator helper which iterates
over values in both tables, and skips values in the static table if they also
exist in the dynamic table.
In order to support completely deleting preferences that exist in the base
table, it also ignores all dynamic entries with the None type, so that they
can completely mask deleted base table values.
MozReview-Commit-ID: LCIwyPJMByj
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -1237,16 +1237,241 @@ static bool gCallbacksInProgress = false
static bool gShouldCleanupDeadNodes = false;
static PLDHashTableOps pref_HashTableOps = {
PLDHashTable::HashStringKey, PrefEntry::MatchEntry,
PLDHashTable::MoveEntryStub, PrefEntry::ClearEntry,
PrefEntry::InitEntry,
};
+class PrefsHashIter
+{
+ using Iterator = decltype(gHashTable->Iter());
+ using ElemType = Pref*;
+
+ Iterator mIter;
+
+public:
+ explicit PrefsHashIter(PLDHashTable* aTable)
+ : mIter(aTable->Iter())
+ {
+ }
+
+ class Elem
+ {
+ friend class PrefsHashIter;
+
+ PrefsHashIter& mParent;
+ bool mDone;
+
+ Elem(PrefsHashIter& aIter, bool aDone)
+ : mParent(aIter)
+ , mDone(aDone)
+ {
+ }
+
+ Iterator& Iter() { return mParent.mIter; }
+
+ public:
+ Elem& operator*() { return *this; }
+
+ ElemType get()
+ {
+ if (mDone) {
+ return nullptr;
+ }
+ return static_cast<PrefEntry*>(Iter().Get())->mPref;
+ }
+ ElemType get() const { return const_cast<Elem*>(this)->get(); }
+
+ ElemType operator->() { return get(); }
+ ElemType operator->() const { return get(); }
+
+ operator ElemType() { return get(); }
+
+ void Remove() { Iter().Remove(); }
+
+ Elem& operator++()
+ {
+ MOZ_ASSERT(!mDone);
+ Iter().Next();
+ mDone = Iter().Done();
+ return *this;
+ }
+
+ bool operator!=(Elem& other)
+ {
+ return mDone != other.mDone || this->get() != other.get();
+ }
+ };
+
+ Elem begin() { return Elem(*this, mIter.Done()); }
+
+ Elem end() { return Elem(*this, true); }
+};
+
+class PrefsIter
+{
+ using Iterator = decltype(gHashTable->Iter());
+ using ElemType = PrefWrapper;
+
+ using HashElem = PrefsHashIter::Elem;
+ using SharedElem = SharedPrefMap::Pref;
+
+ using ElemTypeVariant = Variant<HashElem, SharedElem>;
+
+ SharedPrefMap* mSharedMap;
+ PLDHashTable* mHashTable;
+ PrefsHashIter mIter;
+
+ ElemTypeVariant mPos;
+ ElemTypeVariant mEnd;
+
+ Maybe<PrefWrapper> mEntry;
+
+public:
+ PrefsIter(PLDHashTable* aHashTable, SharedPrefMap* aSharedMap)
+ : mSharedMap(aSharedMap)
+ , mHashTable(aHashTable)
+ , mIter(aHashTable)
+ , mPos(AsVariant(mIter.begin()))
+ , mEnd(AsVariant(mIter.end()))
+ {
+ if (Done()) {
+ NextIterator();
+ }
+ }
+
+private:
+#define MATCH(type, ...) \
+ do { \
+ struct Matcher \
+ { \
+ PrefsIter& mIter; \
+ type match(HashElem& pos) \
+ { \
+ HashElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<HashElem>(); \
+ __VA_ARGS__; \
+ } \
+ type match(SharedElem& pos) \
+ { \
+ SharedElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<SharedElem>(); \
+ __VA_ARGS__; \
+ } \
+ }; \
+ return mPos.match(Matcher{ *this }); \
+ } while (0);
+
+ bool Done() { MATCH(bool, return pos == end); }
+
+ PrefWrapper MakeEntry() { MATCH(PrefWrapper, return PrefWrapper(pos)); }
+
+ void NextEntry()
+ {
+ mEntry.reset();
+ MATCH(void, ++pos);
+ }
+#undef MATCH
+
+ bool Next()
+ {
+ NextEntry();
+ return !Done() || NextIterator();
+ }
+
+ bool NextIterator()
+ {
+ if (mPos.is<HashElem>() && mSharedMap) {
+ mPos = AsVariant(mSharedMap->begin());
+ mEnd = AsVariant(mSharedMap->end());
+ return !Done();
+ }
+ return false;
+ }
+
+ bool IteratingBase() { return mPos.is<SharedElem>(); }
+
+ PrefWrapper& Entry()
+ {
+ MOZ_ASSERT(!Done());
+
+ if (!mEntry.isSome()) {
+ mEntry.emplace(MakeEntry());
+ }
+ return mEntry.ref();
+ }
+
+public:
+ class Elem
+ {
+ friend class PrefsIter;
+
+ PrefsIter& mParent;
+ bool mDone;
+
+ Elem(PrefsIter& aIter, bool aDone)
+ : mParent(aIter)
+ , mDone(aDone)
+ {
+ SkipDuplicates();
+ }
+
+ void Next() { mDone = !mParent.Next(); }
+
+ void SkipDuplicates()
+ {
+ while (!mDone && (mParent.IteratingBase()
+ ? !!mParent.mHashTable->Search(ref().Name())
+ : ref().IsTypeNone())) {
+ Next();
+ }
+ }
+
+ public:
+ Elem& operator*() { return *this; }
+
+ ElemType& ref() { return mParent.Entry(); }
+ const ElemType& ref() const { return const_cast<Elem*>(this)->ref(); }
+
+ ElemType* operator->() { return &ref(); }
+ const ElemType* operator->() const { return &ref(); }
+
+ operator ElemType() { return ref(); }
+
+ void Remove()
+ {
+ MOZ_ASSERT(!mParent.IteratingBase());
+ mParent.mPos.as<HashElem>().Remove();
+ }
+
+ Elem& operator++()
+ {
+ MOZ_ASSERT(!mDone);
+ Next();
+ SkipDuplicates();
+ return *this;
+ }
+
+ bool operator!=(Elem& other)
+ {
+ if (mDone != other.mDone) {
+ return true;
+ }
+ if (mDone) {
+ return false;
+ }
+ return &this->ref() != &other.ref();
+ }
+ };
+
+ Elem begin() { return { *this, Done() }; }
+
+ Elem end() { return { *this, true }; }
+};
+
static Pref*
pref_HashTableLookup(const char* aPrefName);
static void
NotifyCallbacks(const char* aPrefName);
#define PREF_HASHTABLE_INITIAL_LENGTH 1024
--- a/modules/libpref/SharedPrefMap.h
+++ b/modules/libpref/SharedPrefMap.h
@@ -389,16 +389,31 @@ public:
{
return GetStringValue(aKind).get();
}
// Returns the entry's index in the map, as understood by GetKeyAt() and
// GetValueAt().
size_t Index() const { return mEntry - mMap->Entries().get(); }
+ bool operator==(const Pref& aPref) const { return mEntry == aPref.mEntry; }
+ bool operator!=(const Pref& aPref) const { return !(*this == aPref); }
+
+ // This is odd, but necessary in order for the C++ range iterator protocol
+ // to work here.
+ Pref& operator*() { return *this; }
+
+ // Updates this wrapper to point to the next entry in the map. This should
+ // not be attempted unless Index() is less than the map's Count().
+ Pref& operator++()
+ {
+ mEntry++;
+ return *this;
+ }
+
Pref(const Pref& aPref) = default;
protected:
friend class SharedPrefMap;
Pref(const SharedPrefMap* aPrefMap, const Entry* aEntry)
: mMap(aPrefMap)
, mEntry(aEntry)
@@ -452,19 +467,45 @@ public:
// Returns the value for the entry at the given index.
//
// The given index *must* be less than the value returned by Count().
//
// The returned value is valid for the lifetime of this map instance.
const Pref GetValueAt(uint32_t aIndex) const
{
MOZ_ASSERT(aIndex < Count());
+ return UncheckedGetValueAt(aIndex);
+ }
+
+private:
+ // Returns a wrapper with a pointer to an entry without checking its bounds.
+ // This should only be used by range iterators, to check their end positions.
+ //
+ // Note: In debug builds, the RangePtr returned by entries will still assert
+ // that aIndex is no more than 1 past the last element in the array, since it
+ // also takes into account the ranged iteration use case.
+ Pref UncheckedGetValueAt(uint32_t aIndex) const
+ {
return { this, (Entries() + aIndex).get() };
}
+public:
+ // C++ range iterator protocol. begin() and end() return references to the
+ // first and last entries in the array. The begin wrapper can be incremented
+ // until it matches the last element in the array, at which point it becomes
+ // invalid and the iteration is over.
+ Pref begin() const { return UncheckedGetValueAt(0); }
+ Pref end() const { return UncheckedGetValueAt(Count()); }
+
+ // A cosmetic helper for range iteration. Returns a reference value from a
+ // pointer to this instance so that its .begin() and .end() methods can be
+ // accessed in a ranged for loop. `map->Iter()` is equivalent to `*map`, but
+ // makes its purpose slightly clearer.
+ const SharedPrefMap& Iter() const { return *this; }
+
// Returns a copy of the read-only file descriptor which backs the shared
// memory region for this map. The file descriptor may be passed between
// processes, and used to construct new instances of SharedPrefMap with
// the same data as this instance.
FileDescriptor CloneFileDescriptor() const;
// Returns the size of the mapped memory region. This size must be passed to
// the constructor when mapping the shared region in another process.