--- a/dom/ipc/MemMapSnapshot.h
+++ b/dom/ipc/MemMapSnapshot.h
@@ -2,17 +2,17 @@
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* 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 dom_ipc_MemMapSnapshot_h
#define dom_ipc_MemMapSnapshot_h
-#include "AutoMemMap.h"
+#include "mozilla/AutoMemMap.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/RangedPtr.h"
#include "mozilla/Result.h"
#ifdef XP_WIN
# include "mozilla/ipc/FileDescriptor.h"
#endif
--- a/dom/ipc/SharedStringMap.cpp
+++ b/dom/ipc/SharedStringMap.cpp
@@ -85,18 +85,17 @@ SharedStringMap::Find(const nsCString& a
},
aIndex);
}
void
SharedStringMapBuilder::Add(const nsCString& aKey, const nsString& aValue)
{
- mEntries.Put(aKey, {{mKeyTable.Add(aKey), aKey.Length()},
- {mValueTable.Add(aValue), aValue.Length()}});
+ mEntries.Put(aKey, {mKeyTable.Add(aKey), mValueTable.Add(aValue)});
}
Result<Ok, nsresult>
SharedStringMapBuilder::Finalize(loader::AutoMemMap& aMap)
{
using Header = SharedStringMap::Header;
MOZ_ASSERT(mEntries.Count() == mKeyTable.Count());
--- a/dom/ipc/SharedStringMap.h
+++ b/dom/ipc/SharedStringMap.h
@@ -4,17 +4,17 @@
* 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 dom_ipc_SharedStringMap_h
#define dom_ipc_SharedStringMap_h
#include "mozilla/AutoMemMap.h"
#include "mozilla/Result.h"
-#include "mozilla/TypeTraits.h"
+#include "mozilla/dom/ipc/StringTable.h"
#include "nsDataHashtable.h"
namespace mozilla {
namespace dom {
namespace ipc {
class SharedStringMapBuilder;
@@ -71,34 +71,24 @@ public:
// The raw byte offset of the beginning of the value string table, from the
// start of the shared memory region, and its size in bytes (*not*
// characters).
size_t mValueStringsOffset;
size_t mValueStringsSize;
};
/**
- * Contains the character offset and character length of an entry in a string
- * table. This may be used for either 8-bit or 16-bit strings, and is required
- * to retrieve an entry from a string table.
- */
- struct StringEntry {
- uint32_t mOffset;
- uint32_t mLength;
- };
-
- /**
* Describes a value in the string map, as offsets into the key and value
* string tables.
*/
struct Entry {
// The offset and size of the entry's UTF-8 key in the key string table.
- StringEntry mKey;
+ StringTableEntry mKey;
// The offset and size of the entry's UTF-16 value in the value string table.
- StringEntry mValue;
+ StringTableEntry mValue;
};
NS_INLINE_DECL_REFCOUNTING(SharedStringMap)
// Note: These constructors are infallible on the premise that this class
// is used primarily in cases where it is critical to platform
// functionality.
explicit SharedStringMap(const FileDescriptor&, size_t);
@@ -169,45 +159,16 @@ public:
FileDescriptor CloneFileDescriptor() const;
size_t MapSize() const { return mMap.size(); }
protected:
~SharedStringMap() = default;
private:
- template <typename StringType>
- class StringTable
- {
- using ElemType = decltype(DeclVal<StringType>()[0]);
-
- public:
- MOZ_IMPLICIT StringTable(const RangedPtr<uint8_t>& aBuffer)
- : mBuffer(aBuffer.ReinterpretCast<ElemType>())
- {
- MOZ_ASSERT(uintptr_t(aBuffer.get()) % alignof(ElemType) == 0,
- "Got misalinged buffer");
- }
-
- StringType Get(const StringEntry& aEntry) const
- {
- StringType res;
- res.AssignLiteral(GetBare(aEntry), aEntry.mLength);
- return res;
- }
-
- const ElemType* GetBare(const StringEntry& aEntry) const
- {
- return &mBuffer[aEntry.mOffset];
- }
-
- private:
- RangedPtr<ElemType> mBuffer;
- };
-
// Type-safe getters for values in the shared memory region:
const Header& GetHeader() const
{
return mMap.get<Header>()[0];
}
RangedPtr<const Entry> Entries() const
@@ -257,62 +218,16 @@ public:
/**
* Finalizes the binary representation of the map, writes it to a shared
* memory region, and then initializes the given AutoMemMap with a reference
* to the read-only copy of it.
*/
Result<Ok, nsresult> Finalize(loader::AutoMemMap& aMap);
private:
- template <typename KeyType, typename StringType>
- class StringTableBuilder
- {
- public:
- using ElemType = typename StringType::char_type;
-
- uint32_t Add(const StringType& aKey)
- {
- auto entry = mEntries.LookupForAdd(aKey).OrInsert([&] () {
- Entry newEntry { mSize, aKey };
- mSize += aKey.Length() + 1;
-
- return newEntry;
- });
-
- return entry.mOffset;
- }
-
- void Write(const RangedPtr<uint8_t>& aBuffer)
- {
- auto buffer = aBuffer.ReinterpretCast<ElemType>();
-
- for (auto iter = mEntries.Iter(); !iter.Done(); iter.Next()) {
- auto& entry = iter.Data();
- memcpy(&buffer[entry.mOffset], entry.mValue.BeginReading(),
- sizeof(ElemType) * (entry.mValue.Length() + 1));
- }
- }
-
- uint32_t Count() const { return mEntries.Count(); }
-
- uint32_t Size() const { return mSize * sizeof(ElemType); }
-
- void Clear() { mEntries.Clear(); }
-
- private:
- struct Entry
- {
- uint32_t mOffset;
- StringType mValue;
- };
-
- nsDataHashtable<KeyType, Entry> mEntries;
- uint32_t mSize = 0;
- };
-
using Entry = SharedStringMap::Entry;
StringTableBuilder<nsCStringHashKey, nsCString> mKeyTable;
StringTableBuilder<nsStringHashKey, nsString> mValueTable;
nsDataHashtable<nsCStringHashKey, Entry> mEntries;
};
new file mode 100644
--- /dev/null
+++ b/dom/ipc/StringTable.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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 dom_ipc_StringTable_h
+#define dom_ipc_StringTable_h
+
+#include "mozilla/RangedPtr.h"
+
+/**
+ * This file contains helper classes for creating and accessing compact string
+ * tables, which can be used as the building blocks of shared memory databases.
+ * Each string table a de-duplicated set of strings which can be referenced
+ * using their character offsets within a data block and their lengths. The
+ * string tables, once created, cannot be modified, and are primarily useful in
+ * read-only shared memory or memory mapped files.
+ */
+
+namespace mozilla {
+namespace dom {
+namespace ipc {
+
+/**
+ * Contains the character offset and character length of an entry in a string
+ * table. This may be used for either 8-bit or 16-bit strings, and is required
+ * to retrieve an entry from a string table.
+ */
+struct StringTableEntry
+{
+ uint32_t mOffset;
+ uint32_t mLength;
+
+ // Ignore mLength. It must be the same for any two strings with the same
+ // offset.
+ uint32_t Hash() const { return mOffset; }
+
+ bool operator==(const StringTableEntry& aOther) const
+ {
+ return mOffset == aOther.mOffset;
+ }
+};
+
+template <typename StringType>
+class StringTable
+{
+ using ElemType = typename StringType::char_type;
+
+public:
+ MOZ_IMPLICIT StringTable(const RangedPtr<uint8_t>& aBuffer)
+ : mBuffer(aBuffer.ReinterpretCast<ElemType>())
+ {
+ MOZ_ASSERT(uintptr_t(aBuffer.get()) % alignof(ElemType) == 0,
+ "Got misalinged buffer");
+ }
+
+ StringType Get(const StringTableEntry& aEntry) const
+ {
+ StringType res;
+ res.AssignLiteral(GetBare(aEntry), aEntry.mLength);
+ return res;
+ }
+
+ const ElemType* GetBare(const StringTableEntry& aEntry) const
+ {
+ return &mBuffer[aEntry.mOffset];
+ }
+
+private:
+ RangedPtr<ElemType> mBuffer;
+};
+
+template <typename KeyType, typename StringType>
+class StringTableBuilder
+{
+public:
+ using ElemType = typename StringType::char_type;
+
+ StringTableEntry Add(const StringType& aKey)
+ {
+ const auto& entry = mEntries.LookupForAdd(aKey).OrInsert([&] () {
+ Entry newEntry { mSize, aKey };
+ mSize += aKey.Length() + 1;
+
+ return newEntry;
+ });
+
+ return { entry.mOffset, aKey.Length() };
+ }
+
+ void Write(const RangedPtr<uint8_t>& aBuffer)
+ {
+ auto buffer = aBuffer.ReinterpretCast<ElemType>();
+
+ for (auto iter = mEntries.Iter(); !iter.Done(); iter.Next()) {
+ auto& entry = iter.Data();
+ memcpy(&buffer[entry.mOffset], entry.mValue.BeginReading(),
+ sizeof(ElemType) * (entry.mValue.Length() + 1));
+ }
+ }
+
+ uint32_t Count() const { return mEntries.Count(); }
+
+ uint32_t Size() const { return mSize * sizeof(ElemType); }
+
+ void Clear() { mEntries.Clear(); }
+
+ static constexpr size_t Alignment() { return alignof(ElemType); }
+
+private:
+ struct Entry
+ {
+ uint32_t mOffset;
+ StringType mValue;
+ };
+
+ nsDataHashtable<KeyType, Entry> mEntries;
+ uint32_t mSize = 0;
+};
+
+} // namespace ipc
+} // namespace dom
+} // namespace mozilla
+
+#endif
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -10,17 +10,19 @@ with Files("**"):
XPIDL_SOURCES += [
'nsIHangReport.idl',
]
XPIDL_MODULE = 'dom'
EXPORTS.mozilla.dom.ipc += [
'IdType.h',
+ 'MemMapSnapshot.h',
'SharedStringMap.h',
+ 'StringTable.h',
'StructuredCloneData.h',
]
EXPORTS.mozilla.dom += [
'CoalescedInputData.h',
'CoalescedMouseData.h',
'CoalescedWheelData.h',
'ContentBridgeChild.h',
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -3,16 +3,18 @@
/* 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 <ctype.h>
#include <stdlib.h>
#include <string.h>
+#include "SharedPrefMap.h"
+
#include "base/basictypes.h"
#include "GeckoProfiler.h"
#include "MainThreadUtils.h"
#include "mozilla/ArenaAllocatorExtensions.h"
#include "mozilla/ArenaAllocator.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/PContent.h"
@@ -88,16 +90,18 @@
#endif
#ifdef XP_WIN
#include "windows.h"
#endif
using namespace mozilla;
+using mozilla::ipc::FileDescriptor;
+
#ifdef DEBUG
#define ENSURE_PARENT_PROCESS(func, pref) \
do { \
if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
nsPrintfCString msg( \
"ENSURE_PARENT_PROCESS: called %s on %s in a non-parent process", \
func, \
@@ -122,25 +126,16 @@ using namespace mozilla;
typedef nsTArray<nsCString> PrefSaveData;
// 1 MB should be enough for everyone.
static const uint32_t MAX_PREF_LENGTH = 1 * 1024 * 1024;
// Actually, 4kb should be enough for everyone.
static const uint32_t MAX_ADVISABLE_PREF_LENGTH = 4 * 1024;
-// Keep this in sync with PrefType in parser/src/lib.rs.
-enum class PrefType : uint8_t
-{
- None = 0, // only used when neither the default nor user value is set
- String = 1,
- Int = 2,
- Bool = 3,
-};
-
// This is used for pref names and string pref values. We encode the string
// length, then a '/', then the string chars. This encoding means there are no
// special chars that are forbidden or require escaping.
static void
SerializeAndAppendString(const char* aChars, nsCString& aStr)
{
aStr.AppendInt(uint32_t(strlen(aChars)));
aStr.Append('/');
@@ -184,16 +179,19 @@ union PrefValue {
case PrefType::Bool:
return mBoolVal == aValue.mBoolVal;
default:
MOZ_CRASH("Unhandled enum value");
}
}
+ template<typename T>
+ T Get() const;
+
void Init(PrefType aNewType, PrefValue aNewValue)
{
if (aNewType == PrefType::String) {
MOZ_ASSERT(aNewValue.mStringVal);
aNewValue.mStringVal = moz_xstrdup(aNewValue.mStringVal);
}
*this = aNewValue;
}
@@ -313,16 +311,37 @@ union PrefValue {
}
default:
MOZ_CRASH();
}
}
};
+template<>
+bool
+PrefValue::Get() const
+{
+ return mBoolVal;
+}
+
+template<>
+int32_t
+PrefValue::Get() const
+{
+ return mIntVal;
+}
+
+template<>
+nsDependentCString
+PrefValue::Get() const
+{
+ return nsDependentCString(mStringVal);
+}
+
#ifdef DEBUG
const char*
PrefTypeToString(PrefType aType)
{
switch (aType) {
case PrefType::None:
return "none";
case PrefType::String:
@@ -463,16 +482,39 @@ public:
{
mIsLocked = aValue;
mHasChangedSinceInit = true;
}
bool HasDefaultValue() const { return mHasDefaultValue; }
bool HasUserValue() const { return mHasUserValue; }
+ template<typename T>
+ void AddToMap(SharedPrefMapBuilder& aMap)
+ {
+ aMap.Add(
+ Name(),
+ { HasDefaultValue(), HasUserValue(), uint8_t(mIsSticky), IsLocked() },
+ HasDefaultValue() ? mDefaultValue.Get<T>() : T(),
+ HasUserValue() ? mUserValue.Get<T>() : T());
+ }
+
+ void AddToMap(SharedPrefMapBuilder& aMap)
+ {
+ if (IsTypeBool()) {
+ AddToMap<bool>(aMap);
+ } else if (IsTypeInt()) {
+ AddToMap<int32_t>(aMap);
+ } else if (IsTypeString()) {
+ AddToMap<nsDependentCString>(aMap);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected preference type");
+ }
+ }
+
// When a content process is created we could tell it about every pref. But
// the content process also initializes prefs from file, so we save a lot of
// IPC if we only tell it about prefs that have changed since initialization.
//
// Specifically, we send a pref if any of the following conditions are met.
//
// - If the pref has changed in any way (default value, user value, or other
// attribute, such as whether it is locked) since being initialized from
@@ -1059,16 +1101,18 @@ private:
// - CallbackNode* mNext;
// - Preferences::MatchKind mMatchKind;
// They are combined into a tagged pointer to save memory.
uintptr_t mNextAndMatchKind;
};
static PLDHashTable* gHashTable;
+static StaticRefPtr<SharedPrefMap> gSharedMap;
+
// The callback list contains all the priority callbacks followed by the
// non-priority callbacks. gLastPriorityNode records where the first part ends.
static CallbackNode* gFirstCallback = nullptr;
static CallbackNode* gLastPriorityNode = nullptr;
#ifdef DEBUG
#define ACCESS_COUNTS
#endif
@@ -3008,16 +3052,20 @@ PreferenceServiceReporter::CollectReport
}
sizes.mPrefNameArena += gPrefNameArena.SizeOfExcludingThis(mallocSizeOf);
for (CallbackNode* node = gFirstCallback; node; node = node->Next()) {
node->AddSizeOfIncludingThis(mallocSizeOf, sizes);
}
+ if (gSharedMap) {
+ sizes.mMisc += mallocSizeOf(gSharedMap);
+ }
+
MOZ_COLLECT_REPORT("explicit/preferences/hash-table",
KIND_HEAP,
UNITS_BYTES,
sizes.mHashTable,
"Memory used by libpref's hash table.");
MOZ_COLLECT_REPORT("explicit/preferences/pref-values",
KIND_HEAP,
@@ -3063,16 +3111,27 @@ PreferenceServiceReporter::CollectReport
"prefixes).");
MOZ_COLLECT_REPORT("explicit/preferences/misc",
KIND_HEAP,
UNITS_BYTES,
sizes.mMisc,
"Miscellaneous memory used by libpref.");
+ if (gSharedMap) {
+ if (XRE_IsParentProcess()) {
+ MOZ_COLLECT_REPORT("explicit/preferences/shared-memory-map",
+ KIND_NONHEAP,
+ UNITS_BYTES,
+ gSharedMap->MapSize(),
+ "The shared memory mapping used to share a "
+ "snapshot of preference values across processes.");
+ }
+ }
+
nsPrefBranch* rootBranch =
static_cast<nsPrefBranch*>(Preferences::GetRootBranch());
if (!rootBranch) {
return NS_OK;
}
size_t numStrong = 0;
size_t numWeakAlive = 0;
@@ -3486,16 +3545,46 @@ Preferences::DeserializePreferences(char
MOZ_ASSERT(p == aStr + aPrefsLen - 1);
#ifdef DEBUG
MOZ_ASSERT(!gContentProcessPrefsAreInited);
gContentProcessPrefsAreInited = true;
#endif
}
+/* static */ FileDescriptor
+Preferences::EnsureSnapshot(size_t* aSize)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!gSharedMap) {
+ SharedPrefMapBuilder builder;
+
+ for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
+ Pref* pref = static_cast<PrefEntry*>(iter.Get())->mPref;
+
+ pref->AddToMap(builder);
+ }
+
+ gSharedMap = new SharedPrefMap(std::move(builder));
+ }
+
+ *aSize = gSharedMap->MapSize();
+ return gSharedMap->CloneFileDescriptor();
+}
+
+/* static */ void
+Preferences::InitSnapshot(const FileDescriptor& aHandle, size_t aSize)
+{
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_ASSERT(!gSharedMap);
+
+ gSharedMap = new SharedPrefMap(aHandle, aSize);
+}
+
/* static */ void
Preferences::InitializeUserPrefs()
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!sPreferences->mCurrentFile, "Should only initialize prefs once");
// Prefs which are set before we initialize the profile are silently
// discarded. This is stupid, but there are various tests which depend on
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -34,18 +34,37 @@ class nsPrefBranch;
namespace mozilla {
namespace dom {
class Pref;
class PrefValue;
} // namespace dom
+namespace ipc {
+class FileDescriptor;
+} // namespace ipc
+
struct PrefsSizes;
+// Xlib.h defines Bool as a macro constant. Don't try to define this enum if
+// it's already been included.
+#ifndef Bool
+
+// Keep this in sync with PrefType in parser/src/lib.rs.
+enum class PrefType : uint8_t
+{
+ None = 0, // only used when neither the default nor user value is set
+ String = 1,
+ Int = 2,
+ Bool = 3,
+};
+
+#endif
+
#ifdef XP_UNIX
// XXX: bug 1440207 is about improving how fixed fds such as this are used.
static const int kPrefsFileDescriptor = 8;
#endif
// Keep this in sync with PrefType in parser/src/lib.rs.
enum class PrefValueKind : uint8_t
{
@@ -476,16 +495,19 @@ public:
aVariable, nsLiteralCString(aPref), aDefault, aSkipAssignment);
}
// When a content process is created these methods are used to pass changed
// prefs in bulk from the parent process, via shared memory.
static void SerializePreferences(nsCString& aStr);
static void DeserializePreferences(char* aStr, size_t aPrefsLen);
+ static mozilla::ipc::FileDescriptor EnsureSnapshot(size_t* aSize);
+ static void InitSnapshot(const mozilla::ipc::FileDescriptor&, size_t aSize);
+
// When a single pref is changed in the parent process, these methods are
// used to pass the update to content processes.
static void GetPreference(dom::Pref* aPref);
static void SetPreference(const dom::Pref& aPref);
#ifdef DEBUG
static bool ArePrefsInitedInContentProcess();
#endif
new file mode 100644
--- /dev/null
+++ b/modules/libpref/SharedPrefMap.cpp
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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 "SharedPrefMap.h"
+
+#include "mozilla/dom/ipc/MemMapSnapshot.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ipc/FileDescriptor.h"
+
+using namespace mozilla::loader;
+
+namespace mozilla {
+
+using namespace ipc;
+
+static inline size_t
+GetAlignmentOffset(size_t aOffset, size_t aAlign)
+{
+ auto mod = aOffset % aAlign;
+ return mod ? aAlign - mod : 0;
+}
+
+SharedPrefMap::SharedPrefMap(const FileDescriptor& aMapFile, size_t aMapSize)
+{
+ auto result = mMap.initWithHandle(aMapFile, aMapSize);
+ MOZ_RELEASE_ASSERT(result.isOk());
+ // We return literal nsCStrings pointing to the mapped data for preference
+ // names and string values, which means that we may still have references to
+ // the mapped data even after this instance is destroyed. That means that we
+ // need to keep the mapping alive until process shutdown, in order to be safe.
+ mMap.setPersistent();
+}
+
+SharedPrefMap::SharedPrefMap(SharedPrefMapBuilder&& aBuilder)
+{
+ auto result = aBuilder.Finalize(mMap);
+ MOZ_RELEASE_ASSERT(result.isOk());
+ mMap.setPersistent();
+}
+
+mozilla::ipc::FileDescriptor
+SharedPrefMap::CloneFileDescriptor() const
+{
+ return mMap.cloneHandle();
+}
+
+bool
+SharedPrefMap::Has(const char* aKey) const
+{
+ size_t index;
+ return Find(aKey, &index);
+}
+
+Maybe<const SharedPrefMap::Pref>
+SharedPrefMap::Get(const char* aKey) const
+{
+ Maybe<const Pref> result;
+
+ size_t index;
+ if (Find(aKey, &index)) {
+ result.emplace(Pref{ this, &Entries()[index] });
+ }
+
+ return result;
+}
+
+bool
+SharedPrefMap::Find(const char* aKey, size_t* aIndex) const
+{
+ const auto& keys = KeyTable();
+
+ return BinarySearchIf(Entries(),
+ 0,
+ EntryCount(),
+ [&](const Entry& aEntry) {
+ return strcmp(aKey, keys.GetBare(aEntry.mKey));
+ },
+ aIndex);
+}
+
+void
+SharedPrefMapBuilder::Add(const char* aKey,
+ const Flags& aFlags,
+ bool aDefaultValue,
+ bool aUserValue)
+{
+ mEntries.AppendElement(Entry{
+ aKey,
+ mKeyTable.Add(aKey),
+ { aDefaultValue, aUserValue },
+ uint8_t(PrefType::Bool),
+ aFlags.mHasDefaultValue,
+ aFlags.mHasUserValue,
+ aFlags.mIsSticky,
+ aFlags.mIsLocked,
+ });
+}
+
+void
+SharedPrefMapBuilder::Add(const char* aKey,
+ const Flags& aFlags,
+ int32_t aDefaultValue,
+ int32_t aUserValue)
+{
+ ValueIdx index;
+ if (aFlags.mHasUserValue) {
+ index = mIntValueTable.Add(aDefaultValue, aUserValue);
+ } else {
+ index = mIntValueTable.Add(aDefaultValue);
+ }
+
+ mEntries.AppendElement(Entry{
+ aKey,
+ mKeyTable.Add(aKey),
+ { index },
+ uint8_t(PrefType::Int),
+ aFlags.mHasDefaultValue,
+ aFlags.mHasUserValue,
+ aFlags.mIsSticky,
+ aFlags.mIsLocked,
+ });
+}
+
+void
+SharedPrefMapBuilder::Add(const char* aKey,
+ const Flags& aFlags,
+ const nsCString& aDefaultValue,
+ const nsCString& aUserValue)
+{
+ ValueIdx index;
+ StringTableEntry defaultVal = mValueStringTable.Add(aDefaultValue);
+ if (aFlags.mHasUserValue) {
+ StringTableEntry userVal = mValueStringTable.Add(aUserValue);
+ index = mStringValueTable.Add(defaultVal, userVal);
+ } else {
+ index = mStringValueTable.Add(defaultVal);
+ }
+
+ mEntries.AppendElement(Entry{
+ aKey,
+ mKeyTable.Add(aKey),
+ { index },
+ uint8_t(PrefType::String),
+ aFlags.mHasDefaultValue,
+ aFlags.mHasUserValue,
+ aFlags.mIsSticky,
+ aFlags.mIsLocked,
+ });
+}
+
+Result<Ok, nsresult>
+SharedPrefMapBuilder::Finalize(loader::AutoMemMap& aMap)
+{
+ using Header = SharedPrefMap::Header;
+
+ // Create an array of entry pointers for the entry array, and sort it by
+ // preference name prior to serialization, so that entries can be looked up
+ // using binary search.
+ nsTArray<Entry*> entries(mEntries.Length());
+ for (auto& entry : mEntries) {
+ entries.AppendElement(&entry);
+ }
+ entries.Sort([](const Entry* aA, const Entry* aB) {
+ return strcmp(aA->mKeyString, aB->mKeyString);
+ });
+
+ Header header = { uint32_t(entries.Length()) };
+
+ size_t offset = sizeof(header);
+ offset += GetAlignmentOffset(offset, alignof(Header));
+
+ offset += entries.Length() * sizeof(SharedPrefMap::Entry);
+
+ header.mKeyStrings.mOffset = offset;
+ header.mKeyStrings.mSize = mKeyTable.Size();
+ offset += header.mKeyStrings.mSize;
+
+ offset += GetAlignmentOffset(offset, mIntValueTable.Alignment());
+ header.mUserIntValues.mOffset = offset;
+ header.mUserIntValues.mSize = mIntValueTable.UserSize();
+ offset += header.mUserIntValues.mSize;
+
+ offset += GetAlignmentOffset(offset, mIntValueTable.Alignment());
+ header.mDefaultIntValues.mOffset = offset;
+ header.mDefaultIntValues.mSize = mIntValueTable.DefaultSize();
+ offset += header.mDefaultIntValues.mSize;
+
+ offset += GetAlignmentOffset(offset, mStringValueTable.Alignment());
+ header.mUserStringValues.mOffset = offset;
+ header.mUserStringValues.mSize = mStringValueTable.UserSize();
+ offset += header.mUserStringValues.mSize;
+
+ offset += GetAlignmentOffset(offset, mStringValueTable.Alignment());
+ header.mDefaultStringValues.mOffset = offset;
+ header.mDefaultStringValues.mSize = mStringValueTable.DefaultSize();
+ offset += header.mDefaultStringValues.mSize;
+
+ header.mValueStrings.mOffset = offset;
+ header.mValueStrings.mSize = mValueStringTable.Size();
+ offset += header.mValueStrings.mSize;
+
+ MemMapSnapshot mem;
+ MOZ_TRY(mem.Init(offset));
+
+ auto headerPtr = mem.Get<Header>();
+ headerPtr[0] = header;
+
+ auto* entryPtr = reinterpret_cast<SharedPrefMap::Entry*>(&headerPtr[1]);
+ for (auto* entry : entries) {
+ *entryPtr = {
+ entry->mKey, GetValue(*entry),
+ entry->mType, entry->mHasDefaultValue,
+ entry->mHasUserValue, entry->mIsSticky,
+ entry->mIsLocked,
+ };
+ entryPtr++;
+ }
+
+ auto ptr = mem.Get<uint8_t>();
+
+ mKeyTable.Write(
+ { &ptr[header.mKeyStrings.mOffset], header.mKeyStrings.mSize });
+
+ mValueStringTable.Write(
+ { &ptr[header.mValueStrings.mOffset], header.mValueStrings.mSize });
+
+ mIntValueTable.WriteDefaultValues(
+ { &ptr[header.mDefaultIntValues.mOffset], header.mDefaultIntValues.mSize });
+ mIntValueTable.WriteUserValues(
+ { &ptr[header.mUserIntValues.mOffset], header.mUserIntValues.mSize });
+
+ mStringValueTable.WriteDefaultValues(
+ { &ptr[header.mDefaultStringValues.mOffset],
+ header.mDefaultStringValues.mSize });
+ mStringValueTable.WriteUserValues(
+ { &ptr[header.mUserStringValues.mOffset], header.mUserStringValues.mSize });
+
+ mKeyTable.Clear();
+ mValueStringTable.Clear();
+ mIntValueTable.Clear();
+ mStringValueTable.Clear();
+ mEntries.Clear();
+
+ return mem.Finalize(aMap);
+}
+
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/modules/libpref/SharedPrefMap.h
@@ -0,0 +1,848 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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 dom_ipc_SharedPrefMap_h
+#define dom_ipc_SharedPrefMap_h
+
+#include "mozilla/AutoMemMap.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Result.h"
+#include "mozilla/dom/ipc/StringTable.h"
+#include "nsDataHashtable.h"
+
+namespace mozilla {
+
+// The approximate number of preferences expected to be in an ordinary
+// preferences database.
+//
+// This number is used to determine initial allocation sizes for data structures
+// when building the shared preference map, and should be slightly higher than
+// the expected number of preferences in an ordinary database to avoid
+// unnecessary reallocations/rehashes.
+constexpr size_t kExpectedPrefCount = 4000;
+
+class SharedPrefMapBuilder;
+
+// This class provides access to a compact, read-only copy of a preference
+// database, backed by a shared memory buffer which can be shared between
+// processes. All state data for the database is stored in the shared memory
+// region, so individual instances require no dynamic memory allocation.
+//
+// Further, all strings returned from this API are nsLiteralCStrings with
+// pointers into the shared memory region, which means that they can be copied
+// into new nsCString instances without additional allocations. For instance,
+// the following (where `pref` is a Pref object) will not cause any string
+// copies, memory allocations, or atomic refcount changes:
+//
+// nsCString prefName(pref.NameString());
+//
+// whereas if we returned a nsDependentCString or a dynamically allocated
+// nsCString, it would.
+//
+// The set of entries is stored in sorted order by preference name, so look-ups
+// are done by binary search. This means that look-ups have O(log n) complexity,
+// rather than the O(1) complexity of a dynamic hashtable. Consumers should keep
+// this in mind when planning their accesses.
+//
+// Important: The mapped memory created by this class is persistent. Once an
+// instance has been initialized, the memory that it allocates can never be
+// freed before process shutdown. Do not use it for short-lived mappings.
+class SharedPrefMap
+{
+ using FileDescriptor = mozilla::ipc::FileDescriptor;
+
+ friend class SharedPrefMapBuilder;
+
+ // Describes a block of memory within the shared memory region.
+ struct DataBlock
+ {
+ // The byte offset from the start of the shared memory region to the start
+ // of the block.
+ size_t mOffset;
+ // The size of the block, in bytes. This is typically used only for bounds
+ // checking in debug builds.
+ size_t mSize;
+ };
+
+ // Describes the contents of the shared memory region, which is laid-out as
+ // follows:
+ //
+ // - The Header struct
+ //
+ // - An array of Entry structs with mEntryCount elements, lexicographically
+ // sorted by preference name.
+ //
+ // - A set of data blocks, with offsets and sizes described by the DataBlock
+ // entries in the header, described below.
+ //
+ // Each entry stores its name string and values as indices into these blocks,
+ // as documented in the Entry struct, but with some important optimizations:
+ //
+ // - Boolean values are always stored inline. Both the default and user
+ // values can be retrieved directly from the entry. Other types have only
+ // one value index, and their values appear at the same indices in the
+ // default and user value arrays.
+ //
+ // Aside from reducing our memory footprint, this space-efficiency means
+ // that we can fit more entries in the CPU cache at once, and reduces the
+ // number of likely cache misses during lookups.
+ //
+ // - Key strings are stored in a separate string table from value strings. As
+ // above, this makes it more likely that the strings we need will be
+ // available in the CPU cache during lookups by not interleaving them with
+ // extraneous data.
+ //
+ // - Default and user values are stored in separate arrays. Entries with user
+ // values always appear before entries with default values in the value
+ // arrays, and entries without user values do not have entries in the user
+ // array at all. Since the same index is used for both arrays, this means
+ // that entries with a default value but no user value do not allocate any
+ // space to store their user value.
+ //
+ // - For preferences with no user value, the entries in the default value are
+ // de-duplicated. All preferences with the same default value (and no user
+ // value) point to the same index in the default value array.
+ //
+ //
+ // For example, a preference database containing:
+ //
+ // +---------+-------------------------------+-------------------------------+
+ // | Name | Default Value | User Value | |
+ // +---------+---------------+---------------+-------------------------------+
+ // | string1 | "meh" | "hem" | |
+ // | string2 | | "b" | |
+ // | string3 | "a" | | |
+ // | string4 | "foo" | | |
+ // | string5 | "foo" | | |
+ // | string6 | "meh" | | |
+ // +---------+---------------+---------------+-------------------------------+
+ // | bool1 | false | true | |
+ // | bool2 | | false | |
+ // | bool3 | true | | |
+ // +---------+---------------+---------------+-------------------------------+
+ // | int1 | 18 | 16 | |
+ // | int2 | | 24 | |
+ // | int3 | 42 | | |
+ // | int4 | 12 | | |
+ // | int5 | 12 | | |
+ // | int6 | 18 | | |
+ // +---------+---------------+---------------+-------------------------------+
+ //
+ // Results in a database that looks like:
+ //
+ // +-------------------------------------------------------------------------+
+ // | Header: |
+ // +-------------------------------------------------------------------------+
+ // | mEntryCount = 15 |
+ // | ... |
+ // +-------------------------------------------------------------------------+
+ //
+ // +-------------------------------------------------------------------------+
+ // | Key strings: |
+ // +--------+----------------------------------------------------------------+
+ // | Offset | Value |
+ // +--------+----------------------------------------------------------------+
+ // | 0 | string1\0 |
+ // | 8 | string2\0 |
+ // | 16 | string3\0 |
+ // | 24 | string4\0 |
+ // | 32 | string5\0 |
+ // | 40 | string6\0 |
+ // | 48 | bool1\0 |
+ // | 54 | bool2\0 |
+ // | 60 | bool3\0 |
+ // | 66 | int1\0 |
+ // | 71 | int2\0 |
+ // | 76 | int3\0 |
+ // | 81 | int4\0 |
+ // | 86 | int6\0 |
+ // | 91 | int6\0 |
+ // +--------+----------------------------------------------------------------+
+ //
+ // +-------------------------------------------------------------------------+
+ // | Entries: |
+ // +---------------------+------+------------+------------+------------------+
+ // | Key[1] | Type | HasDefault | HasUser | Value |
+ // +---------------------+------+------------+------------+------------------+
+ // | K["bool1", 48, 5] | 3 | true | true | { false, true } |
+ // | K["bool2", 54, 5] | 3 | false | true | { 0, false } |
+ // | K["bool3", 60, 5] | 3 | true | false | { true, 0 } |
+ // | K["int1", 66, 4] | 2 | true | true | 0 |
+ // | K["int2", 71, 4] | 2 | false | true | 1 |
+ // | K["int3", 76, 4] | 2 | true | false | 2 |
+ // | K["int4", 81, 4] | 2 | true | false | 3 |
+ // | K["int5", 86, 4] | 2 | true | false | 3 |
+ // | K["int6", 91, 4] | 2 | true | false | 4 |
+ // | K["string1", 0, 6] | 1 | true | true | 0 |
+ // | K["string2", 8, 6] | 1 | false | true | 1 |
+ // | K["string3", 16, 6] | 1 | true | false | 2 |
+ // | K["string4", 24, 6] | 1 | true | false | 3 |
+ // | K["string5", 32, 6] | 1 | true | false | 3 |
+ // | K["string6", 40, 6] | 1 | true | false | 4 |
+ // +---------------------+------+------------+------------+------------------+
+ // | [1]: Encoded as an offset into the key table and a length. Specified |
+ // | as K[string, offset, length] for clarity. |
+ // +-------------------------------------------------------------------------+
+ //
+ // +------------------------------------+------------------------------------+
+ // | User integer values | Default integer values |
+ // +-------+----------------------------+-------+----------------------------+
+ // | Index | Contents | Index | Contents |
+ // +-------+----------------------------+-------+----------------------------+
+ // | 0 | 16 | 0 | 18 |
+ // | 1 | 24 | 1 | |
+ // | | | 2 | 42 |
+ // | | | 3 | 12 |
+ // | | | 4 | 18 |
+ // +-------+----------------------------+-------+----------------------------+
+ // | * Note: Tables are laid out sequentially in memory, but displayed |
+ // | here side-by-side for clarity. |
+ // +-------------------------------------------------------------------------+
+ //
+ // +------------------------------------+------------------------------------+
+ // | User string values | Default string values |
+ // +-------+----------------------------+-------+----------------------------+
+ // | Index | Contents[1] | Index | Contents[1] |
+ // +-------+----------------------------+-------+----------------------------+
+ // | 0 | V["hem", 0, 3] | 0 | V["meh", 4, 3] |
+ // | 1 | V["b", 8, 1] | 1 | |
+ // | | | 2 | V["a", 10, 1] |
+ // | | | 3 | V["foo", 12, 3] |
+ // | | | 4 | V["meh", 4, 3] |
+ // |-------+----------------------------+-------+----------------------------+
+ // | [1]: Encoded as an offset into the value table and a length. Specified |
+ // | as V[string, offset, length] for clarity. |
+ // +-------------------------------------------------------------------------+
+ // | * Note: Tables are laid out sequentially in memory, but displayed |
+ // | here side-by-side for clarity. |
+ // +-------------------------------------------------------------------------+
+ //
+ // +-------------------------------------------------------------------------+
+ // | Value strings: |
+ // +--------+----------------------------------------------------------------+
+ // | Offset | Value |
+ // +--------+----------------------------------------------------------------+
+ // | 0 | hem\0 |
+ // | 4 | meh\0 |
+ // | 8 | b\0 |
+ // | 10 | a\0 |
+ // | 12 | foo\0 |
+ // +--------+----------------------------------------------------------------+
+ struct Header
+ {
+ // The number of entries in this map.
+ uint32_t mEntryCount;
+
+ // The StringTable data block for preference name strings, which act as keys
+ // in the map.
+ DataBlock mKeyStrings;
+
+ // The int32_t arrays of user and default int preference values. Entries in
+ // the map store their values as indices into these arrays.
+ DataBlock mUserIntValues;
+ DataBlock mDefaultIntValues;
+
+ // The StringTableEntry arrays of user and default string preference values.
+ //
+ // Strings are stored as StringTableEntry structs with character offsets
+ // into the mValueStrings string table and their corresponding lenghts.
+ //
+ // Entries in the map, likewise, store their string values as indices into
+ // these arrays.
+ DataBlock mUserStringValues;
+ DataBlock mDefaultStringValues;
+
+ // The StringTable data block for string preference values, referenced by
+ // the above two data blocks.
+ DataBlock mValueStrings;
+ };
+
+ using StringTableEntry = mozilla::dom::ipc::StringTableEntry;
+
+ // Represents a preference value, as either a pair of boolean values, or an
+ // index into one of the above value arrays.
+ union Value {
+ Value(bool aDefaultValue, bool aUserValue)
+ : mDefaultBool(aDefaultValue)
+ , mUserBool(aUserValue)
+ {
+ }
+
+ MOZ_IMPLICIT Value(uint16_t aIndex)
+ : mIndex(aIndex)
+ {
+ }
+
+ // The index of this entry in the value arrays.
+ //
+ // User and default preference values have the same indices in their
+ // respective arrays. However, entries without a user value are not
+ // guaranteed to have space allocated for them in the user value array, and
+ // likewise for preferences without default values in the default value
+ // array. This means that callers must only access value entries for entries
+ // which claim to have a value of that type.
+ uint16_t mIndex;
+ struct
+ {
+ bool mDefaultBool;
+ bool mUserBool;
+ };
+ };
+
+ // Represents a preference entry in the map, containing its name, type info,
+ // flags, and a reference to its value.
+ struct Entry
+ {
+ // A pointer to the preference name in the KeyTable string table.
+ StringTableEntry mKey;
+
+ // The preference's value, either as a pair of booleans, or an index into
+ // the value arrays. Please see the documentation for the Value struct
+ // above.
+ Value mValue;
+
+ // The preference's type, as a PrefType enum value. This must *never* be
+ // PrefType::None for values in a shared array.
+ uint8_t mType : 2;
+ // True if the preference has a default value. Callers must not attempt to
+ // access the entry's default value if this is false.
+ uint8_t mHasDefaultValue : 1;
+ // True if the preference has a user value. Callers must not attempt to
+ // access the entry's user value if this is false.
+ uint8_t mHasUserValue : 1;
+ // True if the preference is sticky, as defined by the preference service.
+ uint8_t mIsSticky : 1;
+ // True if the preference is locked, as defined by the preference service.
+ uint8_t mIsLocked : 1;
+ };
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(SharedPrefMap)
+
+ // A temporary wrapper class for accessing entries in the array. Instances of
+ // this class are valid as long as SharedPrefMap instance is alive, but
+ // generally should not be stored long term, or allocated on the heap.
+ //
+ // The class is implemented as two pointers, one to the SharedPrefMap
+ // instance, and one to the Entry that corresponds to the preference, and is
+ // meant to be cheaply returned by value from preference lookups and
+ // iterators. All property accessors lazily fetch the appropriate values from
+ // the shared memory region.
+ class MOZ_STACK_CLASS Pref final
+ {
+ public:
+ const char* Name() const { return mMap->KeyTable().GetBare(mEntry->mKey); }
+
+ nsCString NameString() const { return mMap->KeyTable().Get(mEntry->mKey); }
+
+ PrefType Type() const
+ {
+ MOZ_ASSERT(PrefType(mEntry->mType) != PrefType::None);
+ return PrefType(mEntry->mType);
+ }
+
+ bool HasDefaultValue() const { return mEntry->mHasDefaultValue; }
+ bool HasUserValue() const { return mEntry->mHasUserValue; }
+ bool IsLocked() const { return mEntry->mIsLocked; }
+ bool IsSticky() const { return mEntry->mIsSticky; }
+
+ bool GetBoolValue(PrefValueKind aKind = PrefValueKind::User) const
+ {
+ MOZ_ASSERT(Type() == PrefType::Bool);
+ MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
+ : HasUserValue());
+
+ return aKind == PrefValueKind::Default ? mEntry->mValue.mDefaultBool
+ : mEntry->mValue.mUserBool;
+ }
+
+ int32_t GetIntValue(PrefValueKind aKind = PrefValueKind::User) const
+ {
+ MOZ_ASSERT(Type() == PrefType::Int);
+ MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
+ : HasUserValue());
+
+ return aKind == PrefValueKind::Default
+ ? mMap->DefaultIntValues()[mEntry->mValue.mIndex]
+ : mMap->UserIntValues()[mEntry->mValue.mIndex];
+ }
+
+ nsCString GetStringValue(PrefValueKind aKind = PrefValueKind::User) const
+ {
+ MOZ_ASSERT(Type() == PrefType::String);
+ MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
+ : HasUserValue());
+
+ auto& stringEntry = aKind == PrefValueKind::Default
+ ? mMap->DefaultStringValues()[mEntry->mValue.mIndex]
+ : mMap->UserStringValues()[mEntry->mValue.mIndex];
+
+ return mMap->ValueTable().Get(stringEntry);
+ }
+
+ const char* GetBareStringValue(
+ PrefValueKind aKind = PrefValueKind::User) const
+ {
+ 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(); }
+
+ Pref(const Pref& aPref) = default;
+
+ protected:
+ friend class SharedPrefMap;
+
+ Pref(const SharedPrefMap* aPrefMap, const Entry* aEntry)
+ : mMap(aPrefMap)
+ , mEntry(aEntry)
+ {
+ }
+
+ private:
+ const SharedPrefMap* const mMap;
+ const Entry* mEntry;
+ };
+
+ // Note: These constructors are infallible, because the preference database is
+ // critical to platform functionality, and we cannot operate without it.
+ SharedPrefMap(const FileDescriptor&, size_t);
+ explicit SharedPrefMap(SharedPrefMapBuilder&&);
+
+ // Searches for the given preference in the map, and returns true if it
+ // exists.
+ bool Has(const char* aKey) const;
+
+ bool Has(const nsCString& aKey) const { return Has(aKey.get()); }
+
+ // Searches for the given preference in the map, and if it exists, returns
+ // a Some<Pref> containing its details.
+ Maybe<const Pref> Get(const char* aKey) const;
+
+ Maybe<const Pref> Get(const nsCString& aKey) const { return Get(aKey.get()); }
+
+private:
+ // Searches for an entry for the given key. If found, returns true, and
+ // places its index in the entry array in aIndex.
+ bool Find(const char* aKey, size_t* aIndex) const;
+
+public:
+ // Returns the number of entries in the map.
+ uint32_t Count() const { return EntryCount(); }
+
+ // Returns the string entry at the given index. Keys are guaranteed to be
+ // sorted lexicographically.
+ //
+ // The given index *must* be less than the value returned by Count().
+ //
+ // The returned value is a literal string which references the mapped memory
+ // region.
+ nsCString GetKeyAt(uint32_t aIndex) const
+ {
+ MOZ_ASSERT(aIndex < Count());
+ return KeyTable().Get(Entries()[aIndex].mKey);
+ }
+
+ // 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 { this, (Entries() + aIndex).get() };
+ }
+
+ // 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.
+ size_t MapSize() const { return mMap.size(); }
+
+protected:
+ ~SharedPrefMap() = default;
+
+private:
+ template<typename T>
+ using StringTable = mozilla::dom::ipc::StringTable<T>;
+
+ // Type-safe getters for values in the shared memory region:
+ const Header& GetHeader() const { return mMap.get<Header>()[0]; }
+
+ RangedPtr<const Entry> Entries() const
+ {
+ return { reinterpret_cast<const Entry*>(&GetHeader() + 1), EntryCount() };
+ }
+
+ uint32_t EntryCount() const { return GetHeader().mEntryCount; }
+
+ template<typename T>
+ RangedPtr<const T> GetBlock(const DataBlock& aBlock) const
+ {
+ return RangedPtr<uint8_t>(&mMap.get<uint8_t>()[aBlock.mOffset],
+ aBlock.mSize)
+ .ReinterpretCast<const T>();
+ }
+
+ RangedPtr<const int32_t> DefaultIntValues() const
+ {
+ return GetBlock<int32_t>(GetHeader().mDefaultIntValues);
+ }
+ RangedPtr<const int32_t> UserIntValues() const
+ {
+ return GetBlock<int32_t>(GetHeader().mUserIntValues);
+ }
+
+ RangedPtr<const StringTableEntry> DefaultStringValues() const
+ {
+ return GetBlock<StringTableEntry>(GetHeader().mDefaultStringValues);
+ }
+ RangedPtr<const StringTableEntry> UserStringValues() const
+ {
+ return GetBlock<StringTableEntry>(GetHeader().mUserStringValues);
+ }
+
+ StringTable<nsCString> KeyTable() const
+ {
+ auto& block = GetHeader().mKeyStrings;
+ return { { &mMap.get<uint8_t>()[block.mOffset], block.mSize } };
+ }
+
+ StringTable<nsCString> ValueTable() const
+ {
+ auto& block = GetHeader().mValueStrings;
+ return { { &mMap.get<uint8_t>()[block.mOffset], block.mSize } };
+ }
+
+ loader::AutoMemMap mMap;
+};
+
+// A helper class which builds the contiguous look-up table used by
+// SharedPrefMap. Each preference in the final map is added to the builder,
+// before it is finalized and transformed into a read-only snapshot.
+class MOZ_RAII SharedPrefMapBuilder
+{
+public:
+ SharedPrefMapBuilder() = default;
+
+ // The set of flags for the preference, as documented in SharedPrefMap::Entry.
+ struct Flags
+ {
+ uint8_t mHasDefaultValue : 1;
+ uint8_t mHasUserValue : 1;
+ uint8_t mIsSticky : 1;
+ uint8_t mIsLocked : 1;
+ };
+
+ void Add(const char* aKey,
+ const Flags& aFlags,
+ bool aDefaultValue,
+ bool aUserValue);
+
+ void Add(const char* aKey,
+ const Flags& aFlags,
+ int32_t aDefaultValue,
+ int32_t aUserValue);
+
+ void Add(const char* aKey,
+ const Flags& aFlags,
+ const nsCString& aDefaultValue,
+ const nsCString& aUserValue);
+
+ // Finalizes the binary representation of the map, writes it to a shared
+ // memory region, and then initializes the given AutoMemMap with a reference
+ // to the read-only copy of it.
+ //
+ // This should generally not be used directly by callers. The
+ // SharedPrefMapBuilder instance should instead be passed to the SharedPrefMap
+ // constructor as a move reference.
+ Result<Ok, nsresult> Finalize(loader::AutoMemMap& aMap);
+
+private:
+ using StringTableEntry = mozilla::dom::ipc::StringTableEntry;
+ template<typename T, typename U>
+ using StringTableBuilder = mozilla::dom::ipc::StringTableBuilder<T, U>;
+
+ // An opaque descriptor of the index of a preference entry in a value array,
+ // which can be converted numeric index after the ValueTableBuilder is
+ // finalized.
+ struct ValueIdx
+ {
+ // The relative index of the entry, based on its class. Entries for
+ // preferences with user values appear at the value arrays. Entries with
+ // only default values begin after the last entry with a user value.
+ uint16_t mIndex;
+ bool mHasUserValue;
+ };
+
+ // A helper class for building default and user value arrays for preferences.
+ //
+ // As described in the SharedPrefMap class, this helper optimizes the way that
+ // it builds its value arrays, in that:
+ //
+ // - It stores value entries for all preferences with user values before
+ // entries for preferences with only default values, and allocates no
+ // entries for preferences with only default values in the user value array.
+ // Since most preferences have only default values, this dramatically
+ // reduces the space required for value storage.
+ //
+ // - For preferences with only default values, it de-duplicates value entries,
+ // and returns the same indices for all preferences with the same value.
+ //
+ // One important complication of this approach is that it means we cannot know
+ // the final index of any entry with only a default value until all entries
+ // have been added to the builder, since it depends on the final number of
+ // user entries in the output.
+ //
+ // To deal with this, when entries are added, we return an opaque ValueIndex
+ // struct, from which we can calculate the final index after the map has been
+ // finalized.
+ template<typename HashKey, typename ValueType_>
+ class ValueTableBuilder
+ {
+ public:
+ using ValueType = ValueType_;
+
+ // Adds an entry for a preference with only a default value to the array,
+ // and returns an opaque descriptor for its index.
+ ValueIdx Add(const ValueType& aDefaultValue)
+ {
+ auto index = uint16_t(mDefaultEntries.Count());
+
+ auto entry = mDefaultEntries.LookupForAdd(aDefaultValue).OrInsert([&]() {
+ return Entry{ index, false, aDefaultValue };
+ });
+
+ return { entry.mIndex, false };
+ }
+
+ // Adds an entry for a preference with a user value to the array. Regardless
+ // of whether the preference has a default value, space must be allocated
+ // for it. For preferences with no default value, the actual value which
+ // appears in the array at its value index is ignored.
+ ValueIdx Add(const ValueType& aDefaultValue, const ValueType& aUserValue)
+ {
+ auto index = uint16_t(mUserEntries.Length());
+
+ mUserEntries.AppendElement(
+ Entry{ index, true, aDefaultValue, aUserValue });
+
+ return { index, true };
+ }
+
+ // Returns the final index for an entry based on its opaque index
+ // descriptor. This must only be called after the caller has finished adding
+ // entries to the builder.
+ uint16_t GetIndex(const ValueIdx& aIndex) const
+ {
+ uint16_t base = aIndex.mHasUserValue ? 0 : UserCount();
+ return base + aIndex.mIndex;
+ }
+
+ // Writes out the array of default values at the block beginning at the
+ // given pointer. The block must be at least as large as the value returned
+ // by DefaultSize().
+ void WriteDefaultValues(const RangedPtr<uint8_t>& aBuffer) const
+ {
+ auto buffer = aBuffer.ReinterpretCast<ValueType>();
+
+ for (const auto& entry : mUserEntries) {
+ buffer[entry.mIndex] = entry.mDefaultValue;
+ }
+
+ size_t defaultsOffset = UserCount();
+ for (auto iter = mDefaultEntries.ConstIter(); !iter.Done(); iter.Next()) {
+ const auto& entry = iter.Data();
+ buffer[defaultsOffset + entry.mIndex] = entry.mDefaultValue;
+ }
+ }
+
+ // Writes out the array of user values at the block beginning at the
+ // given pointer. The block must be at least as large as the value returned
+ // by UserSize().
+ void WriteUserValues(const RangedPtr<uint8_t>& aBuffer) const
+ {
+ auto buffer = aBuffer.ReinterpretCast<ValueType>();
+
+ for (const auto& entry : mUserEntries) {
+ buffer[entry.mIndex] = entry.mUserValue;
+ }
+ }
+
+ // These return the number of entries in the default and user value arrays,
+ // respectively.
+ uint32_t DefaultCount() const
+ {
+ return UserCount() + mDefaultEntries.Count();
+ }
+ uint32_t UserCount() const { return mUserEntries.Length(); }
+
+ // These return the byte sizes of the default and user value arrays,
+ // respectively.
+ uint32_t DefaultSize() const { return DefaultCount() * sizeof(ValueType); }
+ uint32_t UserSize() const { return UserCount() * sizeof(ValueType); }
+
+ void Clear()
+ {
+ mUserEntries.Clear();
+ mDefaultEntries.Clear();
+ }
+
+ static constexpr size_t Alignment() { return alignof(ValueType); }
+
+ private:
+ struct Entry
+ {
+ uint16_t mIndex;
+ bool mHasUserValue;
+ ValueType mDefaultValue;
+ ValueType mUserValue{};
+ };
+
+ AutoTArray<Entry, 256> mUserEntries;
+
+ nsDataHashtable<HashKey, Entry> mDefaultEntries;
+ };
+
+ // A special-purpose string table builder for keys which are already
+ // guaranteed to be unique. Duplicate values will not be detected or
+ // de-duplicated.
+ template<typename CharType>
+ class UniqueStringTableBuilder
+ {
+ public:
+ using ElemType = CharType;
+
+ explicit UniqueStringTableBuilder(size_t aCapacity)
+ : mEntries(aCapacity)
+ {
+ }
+
+ StringTableEntry Add(const CharType* aKey)
+ {
+ auto entry = mEntries.AppendElement(Entry{ mSize, uint32_t(strlen(aKey)), aKey });
+
+ mSize += entry->mLength + 1;
+
+ return { entry->mOffset, entry->mLength };
+ }
+
+ void Write(const RangedPtr<uint8_t>& aBuffer)
+ {
+ auto buffer = aBuffer.ReinterpretCast<ElemType>();
+
+ for (auto& entry : mEntries) {
+ memcpy(&buffer[entry.mOffset],
+ entry.mValue,
+ sizeof(ElemType) * (entry.mLength + 1));
+ }
+ }
+
+ uint32_t Count() const { return mEntries.Length(); }
+
+ uint32_t Size() const { return mSize * sizeof(ElemType); }
+
+ void Clear() { mEntries.Clear(); }
+
+ static constexpr size_t Alignment() { return alignof(ElemType); }
+
+ private:
+ struct Entry
+ {
+ uint32_t mOffset;
+ uint32_t mLength;
+ const CharType* mValue;
+ };
+
+ nsTArray<Entry> mEntries;
+ uint32_t mSize = 0;
+ };
+
+ // A preference value entry, roughly corresponding to the
+ // SharedPrefMap::Value struct, but with a temporary place-holder value rather
+ // than a final value index.
+ union Value {
+ Value(bool aDefaultValue, bool aUserValue)
+ : mDefaultBool(aDefaultValue)
+ , mUserBool(aUserValue)
+ {
+ }
+
+ MOZ_IMPLICIT Value(const ValueIdx& aIndex)
+ : mIndex(aIndex)
+ {
+ }
+
+ // For Bool preferences, their default and user bool values.
+ struct
+ {
+ bool mDefaultBool;
+ bool mUserBool;
+ };
+ // For Int and String preferences, an opaque descriptor for their entries in
+ // their value arrays. This must be passed to the appropriate
+ // ValueTableBuilder to obtain the final index when the entry is serialized.
+ ValueIdx mIndex;
+ };
+
+ // A preference entry, to be converted to a SharedPrefMap::Entry struct during
+ // serialization.
+ struct Entry
+ {
+ // The entry's preference name, as passed to Add(). The caller is
+ // responsible for keeping this pointer alive until the builder is
+ // finalized.
+ const char* mKeyString;
+ // The entry in mKeyTable corresponding to mKeyString.
+ StringTableEntry mKey;
+ Value mValue;
+
+ uint8_t mType : 2;
+ uint8_t mHasDefaultValue : 1;
+ uint8_t mHasUserValue : 1;
+ uint8_t mIsSticky : 1;
+ uint8_t mIsLocked : 1;
+ };
+
+ // Converts a builder Value struct to a SharedPrefMap::Value struct for
+ // serialization. This must not be called before callers have finished adding
+ // entries to the value array builders.
+ SharedPrefMap::Value GetValue(const Entry& aEntry) const
+ {
+ switch (PrefType(aEntry.mType)) {
+ case PrefType::Bool:
+ return { aEntry.mValue.mDefaultBool, aEntry.mValue.mUserBool };
+ case PrefType::Int:
+ return { mIntValueTable.GetIndex(aEntry.mValue.mIndex) };
+ case PrefType::String:
+ return { mStringValueTable.GetIndex(aEntry.mValue.mIndex) };
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid pref type");
+ return { false, false };
+ }
+ }
+
+ UniqueStringTableBuilder<char> mKeyTable{ kExpectedPrefCount };
+ StringTableBuilder<nsCStringHashKey, nsCString> mValueStringTable;
+
+ ValueTableBuilder<nsUint32HashKey, uint32_t> mIntValueTable;
+ ValueTableBuilder<nsGenericHashKey<StringTableEntry>, StringTableEntry>
+ mStringValueTable;
+
+ nsTArray<Entry> mEntries{ kExpectedPrefCount };
+};
+
+} // mozilla
+
+#endif // dom_ipc_SharedPrefMap_h
--- a/modules/libpref/moz.build
+++ b/modules/libpref/moz.build
@@ -27,16 +27,17 @@ XPIDL_MODULE = 'pref'
EXPORTS.mozilla += [
'init/StaticPrefList.h',
'Preferences.h',
'StaticPrefs.h',
]
UNIFIED_SOURCES += [
'Preferences.cpp',
+ 'SharedPrefMap.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
DEFINES['OS_ARCH'] = CONFIG['OS_ARCH']
DEFINES['MOZ_WIDGET_TOOLKIT'] = CONFIG['MOZ_WIDGET_TOOLKIT']