зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1471025: Part 2 - Add a helper class creating and accessing shared preference map snapshots. r=njn,erahm
This is based on the SharedStringMap that's currently used for shared memory string bundles. When the parent process is ready to launch its first content process, it creates a snapshot of the current state of the preference database, maps that as read-only, and shares it with each content process. Look-ups in the snapshotted map are done entirely using data in the shared memory region. It doesn't require any additional per-process state data. MozReview-Commit-ID: BdTUhak7dmS --HG-- extra : intermediate-source : 434106f1b75e3ba900912f261bd22a1b7f5c931d extra : absorb_source : 647ad37590448ad3c1eb8eb512bf671f262fa96e extra : source : 68bb03c63b3cee1d47cbddfd3abf919f5783c04b extra : histedit_source : 2228a9f8395929f5072a3c5ebda6ae3221e4a62d
This commit is contained in:
Родитель
9b5cf7e9da
Коммит
82bc4d713f
|
@ -7,7 +7,7 @@
|
|||
#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"
|
||||
|
|
|
@ -90,8 +90,7 @@ SharedStringMap::Find(const nsCString& aKey, size_t* 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>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
#include "mozilla/AutoMemMap.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "mozilla/TypeTraits.h"
|
||||
#include "mozilla/dom/ipc/StringTable.h"
|
||||
#include "nsDataHashtable.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -75,25 +75,15 @@ public:
|
|||
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)
|
||||
|
@ -174,35 +164,6 @@ 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
|
||||
|
@ -262,52 +223,6 @@ public:
|
|||
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;
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
/* -*- 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"
|
||||
#include "nsDataHashtable.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
|
|
@ -15,9 +15,11 @@ XPIDL_MODULE = 'dom'
|
|||
|
||||
EXPORTS.mozilla.dom.ipc += [
|
||||
'IdType.h',
|
||||
'MemMapSnapshot.h',
|
||||
'SharedMap.h',
|
||||
'SharedMapChangeEvent.h',
|
||||
'SharedStringMap.h',
|
||||
'StringTable.h',
|
||||
'StructuredCloneData.h',
|
||||
]
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "SharedPrefMap.h"
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "MainThreadUtils.h"
|
||||
|
@ -93,6 +95,8 @@
|
|||
|
||||
using namespace mozilla;
|
||||
|
||||
using mozilla::ipc::FileDescriptor;
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
#define ENSURE_PARENT_PROCESS(func, pref) \
|
||||
|
@ -127,15 +131,6 @@ 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.
|
||||
|
@ -189,6 +184,9 @@ union PrefValue {
|
|||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T Get() const;
|
||||
|
||||
void Init(PrefType aNewType, PrefValue aNewValue)
|
||||
{
|
||||
if (aNewType == PrefType::String) {
|
||||
|
@ -318,6 +316,27 @@ union PrefValue {
|
|||
}
|
||||
};
|
||||
|
||||
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)
|
||||
|
@ -468,6 +487,29 @@ public:
|
|||
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.
|
||||
|
@ -1064,6 +1106,8 @@ private:
|
|||
|
||||
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;
|
||||
|
@ -3018,6 +3062,10 @@ PreferenceServiceReporter::CollectReports(
|
|||
node->AddSizeOfIncludingThis(mallocSizeOf, sizes);
|
||||
}
|
||||
|
||||
if (gSharedMap) {
|
||||
sizes.mMisc += mallocSizeOf(gSharedMap);
|
||||
}
|
||||
|
||||
MOZ_COLLECT_REPORT("explicit/preferences/hash-table",
|
||||
KIND_HEAP,
|
||||
UNITS_BYTES,
|
||||
|
@ -3073,6 +3121,17 @@ PreferenceServiceReporter::CollectReports(
|
|||
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) {
|
||||
|
@ -3445,6 +3504,8 @@ Preferences::~Preferences()
|
|||
delete gAccessCounts;
|
||||
#endif
|
||||
|
||||
gSharedMap = nullptr;
|
||||
|
||||
gPrefNameArena.Clear();
|
||||
}
|
||||
|
||||
|
@ -3496,6 +3557,36 @@ Preferences::DeserializePreferences(char* aStr, size_t aPrefsLen)
|
|||
#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()
|
||||
{
|
||||
|
|
|
@ -39,8 +39,27 @@ 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;
|
||||
|
@ -481,6 +500,9 @@ public:
|
|||
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);
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,854 @@
|
|||
/* -*- 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];
|
||||
}
|
||||
|
||||
private:
|
||||
const StringTableEntry& GetStringEntry(PrefValueKind aKind) const
|
||||
{
|
||||
MOZ_ASSERT(Type() == PrefType::String);
|
||||
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
|
||||
: HasUserValue());
|
||||
|
||||
return aKind == PrefValueKind::Default
|
||||
? mMap->DefaultStringValues()[mEntry->mValue.mIndex]
|
||||
: mMap->UserStringValues()[mEntry->mValue.mIndex];
|
||||
}
|
||||
|
||||
public:
|
||||
nsCString GetStringValue(PrefValueKind aKind = PrefValueKind::User) const
|
||||
{
|
||||
return mMap->ValueTable().Get(GetStringEntry(aKind));
|
||||
}
|
||||
|
||||
const char* GetBareStringValue(
|
||||
PrefValueKind aKind = PrefValueKind::User) const
|
||||
{
|
||||
return mMap->ValueTable().GetBare(GetStringEntry(aKind));
|
||||
}
|
||||
|
||||
// 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
|
|
@ -32,6 +32,7 @@ EXPORTS.mozilla += [
|
|||
|
||||
UNIFIED_SOURCES += [
|
||||
'Preferences.cpp',
|
||||
'SharedPrefMap.cpp',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
|
Загрузка…
Ссылка в новой задаче