Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Documentation for libpref is in modules/libpref/docs/index.rst.
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "SharedPrefMap.h"
#include "base/basictypes.h"
#include "MainThreadUtils.h"
#include "mozilla/ArenaAllocatorExtensions.h"
#include "mozilla/ArenaAllocator.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/Components.h"
#include "mozilla/dom/PContent.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/HashTable.h"
#include "mozilla/Logging.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Omnijar.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPrefsAll.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/URLPreloader.h"
#include "mozilla/Variant.h"
#include "mozilla/Vector.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsCategoryManagerUtils.h"
#include "nsClassHashtable.h"
#include "nsCOMArray.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsCRT.h"
#include "nsDataHashtable.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIConsoleService.h"
#include "nsIFile.h"
#include "nsIMemoryReporter.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIOutputStream.h"
#include "nsIPrefBranch.h"
#include "nsIPrefLocalizedString.h"
#include "nsIRelativeFilePref.h"
#include "nsISafeOutputStream.h"
#include "nsISimpleEnumerator.h"
#include "nsIStringBundle.h"
#include "nsISupportsImpl.h"
#include "nsISupportsPrimitives.h"
#include "nsIZipReader.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsQuickSort.h"
#include "nsReadableUtils.h"
#include "nsRefPtrHashtable.h"
#include "nsRelativeFilePref.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "nsUTF8Utils.h"
#include "nsWeakReference.h"
#include "nsXPCOMCID.h"
#include "nsXPCOM.h"
#include "nsXULAppAPI.h"
#include "nsZipArchive.h"
#include "plbase64.h"
#include "PLDHashTable.h"
#include "plstr.h"
#include "prlink.h"
#include "xpcpublic.h"
#ifdef MOZ_BACKGROUNDTASKS
# include "mozilla/BackgroundTasks.h"
#endif
#ifdef DEBUG
# include <map>
#endif
#ifdef MOZ_MEMORY
# include "mozmemory.h"
#endif
#ifdef XP_WIN
# include "windows.h"
#endif
using namespace mozilla;
using 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, pref); \
NS_ERROR(msg.get()); \
return NS_ERROR_NOT_AVAILABLE; \
} \
} while (0)
#else // DEBUG
# define ENSURE_PARENT_PROCESS(func, pref) \
if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
return NS_ERROR_NOT_AVAILABLE; \
}
#endif // DEBUG
//===========================================================================
// Low-level types and operations
//===========================================================================
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;
// 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 nsCString& aChars, nsCString& aStr) {
aStr.AppendInt(aChars.Length());
aStr.Append('/');
aStr.Append(aChars);
}
static char* DeserializeString(char* aChars, nsCString& aStr) {
char* p = aChars;
uint32_t length = strtol(p, &p, 10);
MOZ_ASSERT(p[0] == '/');
p++; // move past the '/'
aStr.Assign(p, length);
p += length; // move past the string itself
return p;
}
// Keep this in sync with PrefValue in parser/src/lib.rs.
union PrefValue {
// PrefValues within Pref objects own their chars. PrefValues passed around
// as arguments don't own their chars.
const char* mStringVal;
int32_t mIntVal;
bool mBoolVal;
PrefValue() = default;
explicit PrefValue(bool aVal) : mBoolVal(aVal) {}
explicit PrefValue(int32_t aVal) : mIntVal(aVal) {}
explicit PrefValue(const char* aVal) : mStringVal(aVal) {}
bool Equals(PrefType aType, PrefValue aValue) {
switch (aType) {
case PrefType::String: {
if (mStringVal && aValue.mStringVal) {
return strcmp(mStringVal, aValue.mStringVal) == 0;
}
if (!mStringVal && !aValue.mStringVal) {
return true;
}
return false;
}
case PrefType::Int:
return mIntVal == aValue.mIntVal;
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;
}
void Clear(PrefType aType) {
if (aType == PrefType::String) {
free(const_cast<char*>(mStringVal));
}
// Zero the entire value (regardless of type) via mStringVal.
mStringVal = nullptr;
}
void Replace(bool aHasValue, PrefType aOldType, PrefType aNewType,
PrefValue aNewValue) {
if (aHasValue) {
Clear(aOldType);
}
Init(aNewType, aNewValue);
}
void ToDomPrefValue(PrefType aType, dom::PrefValue* aDomValue) {
switch (aType) {
case PrefType::String:
*aDomValue = nsDependentCString(mStringVal);
return;
case PrefType::Int:
*aDomValue = mIntVal;
return;
case PrefType::Bool:
*aDomValue = mBoolVal;
return;
default:
MOZ_CRASH();
}
}
PrefType FromDomPrefValue(const dom::PrefValue& aDomValue) {
switch (aDomValue.type()) {
case dom::PrefValue::TnsCString:
mStringVal = aDomValue.get_nsCString().get();
return PrefType::String;
case dom::PrefValue::Tint32_t:
mIntVal = aDomValue.get_int32_t();
return PrefType::Int;
case dom::PrefValue::Tbool:
mBoolVal = aDomValue.get_bool();
return PrefType::Bool;
default:
MOZ_CRASH();
}
}
void SerializeAndAppend(PrefType aType, nsCString& aStr) {
switch (aType) {
case PrefType::Bool:
aStr.Append(mBoolVal ? 'T' : 'F');
break;
case PrefType::Int:
aStr.AppendInt(mIntVal);
break;
case PrefType::String: {
SerializeAndAppendString(nsDependentCString(mStringVal), aStr);
break;
}
case PrefType::None:
default:
MOZ_CRASH();
}
}
static char* Deserialize(PrefType aType, char* aStr,
Maybe<dom::PrefValue>* aDomValue) {
char* p = aStr;
switch (aType) {
case PrefType::Bool:
if (*p == 'T') {
*aDomValue = Some(true);
} else if (*p == 'F') {
*aDomValue = Some(false);
} else {
*aDomValue = Some(false);
NS_ERROR("bad bool pref value");
}
p++;
return p;
case PrefType::Int: {
*aDomValue = Some(int32_t(strtol(p, &p, 10)));
return p;
}
case PrefType::String: {
nsCString str;
p = DeserializeString(p, str);
*aDomValue = Some(str);
return p;
}
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:
return "string";
case PrefType::Int:
return "int";
case PrefType::Bool:
return "bool";
default:
MOZ_CRASH("Unhandled enum value");
}
}
#endif
// Assign to aResult a quoted, escaped copy of aOriginal.
static void StrEscape(const char* aOriginal, nsCString& aResult) {
if (aOriginal == nullptr) {
aResult.AssignLiteral("\"\"");
return;
}
// JavaScript does not allow quotes, slashes, or line terminators inside
// strings so we must escape them. ECMAScript defines four line terminators,
// but we're only worrying about \r and \n here. We currently feed our pref
// script to the JS interpreter as Latin-1 so we won't encounter \u2028
// (line separator) or \u2029 (paragraph separator).
//
// WARNING: There are hints that we may be moving to storing prefs as utf8.
// If we ever feed them to the JS compiler as UTF8 then we'll have to worry
// about the multibyte sequences that would be interpreted as \u2028 and
// \u2029.
const char* p;
aResult.Assign('"');
// Paranoid worst case all slashes will free quickly.
for (p = aOriginal; *p; ++p) {
switch (*p) {
case '\n':
aResult.AppendLiteral("\\n");
break;
case '\r':
aResult.AppendLiteral("\\r");
break;
case '\\':
aResult.AppendLiteral("\\\\");
break;
case '\"':
aResult.AppendLiteral("\\\"");
break;
default:
aResult.Append(*p);
break;
}
}
aResult.Append('"');
}
namespace mozilla {
struct PrefsSizes {
PrefsSizes()
: mHashTable(0),
mPrefValues(0),
mStringValues(0),
mRootBranches(0),
mPrefNameArena(0),
mCallbacksObjects(0),
mCallbacksDomains(0),
mMisc(0) {}
size_t mHashTable;
size_t mPrefValues;
size_t mStringValues;
size_t mRootBranches;
size_t mPrefNameArena;
size_t mCallbacksObjects;
size_t mCallbacksDomains;
size_t mMisc;
};
} // namespace mozilla
static StaticRefPtr<SharedPrefMap> gSharedMap;
// Arena for Pref names. Inside a function so we can assert it's only accessed
// on the main thread.
static inline ArenaAllocator<4096, 1>& PrefNameArena() {
MOZ_ASSERT(NS_IsMainThread());
static ArenaAllocator<4096, 1> sPrefNameArena;
return sPrefNameArena;
}
class PrefWrapper;
class Pref {
public:
explicit Pref(const nsACString& aName)
: mName(ArenaStrdup(aName, PrefNameArena()), aName.Length()),
mType(static_cast<uint32_t>(PrefType::None)),
mIsSticky(false),
mIsLocked(false),
mHasDefaultValue(false),
mHasUserValue(false),
mIsSkippedByIteration(false),
mDefaultValue(),
mUserValue() {}
~Pref() {
// There's no need to free mName because it's allocated in memory owned by
// sPrefNameArena.
mDefaultValue.Clear(Type());
mUserValue.Clear(Type());
}
const char* Name() const { return mName.get(); }
const nsDependentCString& NameString() const { return mName; }
// Types.
PrefType Type() const { return static_cast<PrefType>(mType); }
void SetType(PrefType aType) { mType = static_cast<uint32_t>(aType); }
bool IsType(PrefType aType) const { return Type() == aType; }
bool IsTypeNone() const { return IsType(PrefType::None); }
bool IsTypeString() const { return IsType(PrefType::String); }
bool IsTypeInt() const { return IsType(PrefType::Int); }
bool IsTypeBool() const { return IsType(PrefType::Bool); }
// Other properties.
bool IsLocked() const { return mIsLocked; }
void SetIsLocked(bool aValue) { mIsLocked = aValue; }
bool IsSkippedByIteration() const { return mIsSkippedByIteration; }
void SetIsSkippedByIteration(bool aValue) { mIsSkippedByIteration = aValue; }
bool IsSticky() const { return mIsSticky; }
bool HasDefaultValue() const { return mHasDefaultValue; }
bool HasUserValue() const { return mHasUserValue; }
template <typename T>
void AddToMap(SharedPrefMapBuilder& aMap) {
aMap.Add(NameString(),
{HasDefaultValue(), HasUserValue(), IsSticky(), IsLocked(),
IsSkippedByIteration()},
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");
}
}
// Other operations.
bool GetBoolValue(PrefValueKind aKind = PrefValueKind::User) const {
MOZ_ASSERT(IsTypeBool());
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
: HasUserValue());
return aKind == PrefValueKind::Default ? mDefaultValue.mBoolVal
: mUserValue.mBoolVal;
}
int32_t GetIntValue(PrefValueKind aKind = PrefValueKind::User) const {
MOZ_ASSERT(IsTypeInt());
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
: HasUserValue());
return aKind == PrefValueKind::Default ? mDefaultValue.mIntVal
: mUserValue.mIntVal;
}
const char* GetBareStringValue(
PrefValueKind aKind = PrefValueKind::User) const {
MOZ_ASSERT(IsTypeString());
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
: HasUserValue());
return aKind == PrefValueKind::Default ? mDefaultValue.mStringVal
: mUserValue.mStringVal;
}
nsDependentCString GetStringValue(
PrefValueKind aKind = PrefValueKind::User) const {
return nsDependentCString(GetBareStringValue(aKind));
}
void ToDomPref(dom::Pref* aDomPref) {
MOZ_ASSERT(XRE_IsParentProcess());
aDomPref->name() = mName;
aDomPref->isLocked() = mIsLocked;
if (mHasDefaultValue) {
aDomPref->defaultValue() = Some(dom::PrefValue());
mDefaultValue.ToDomPrefValue(Type(), &aDomPref->defaultValue().ref());
} else {
aDomPref->defaultValue() = Nothing();
}
if (mHasUserValue) {
aDomPref->userValue() = Some(dom::PrefValue());
mUserValue.ToDomPrefValue(Type(), &aDomPref->userValue().ref());
} else {
aDomPref->userValue() = Nothing();
}
MOZ_ASSERT(aDomPref->defaultValue().isNothing() ||
aDomPref->userValue().isNothing() ||
(aDomPref->defaultValue().ref().type() ==
aDomPref->userValue().ref().type()));
}
void FromDomPref(const dom::Pref& aDomPref, bool* aValueChanged) {
MOZ_ASSERT(!XRE_IsParentProcess());
MOZ_ASSERT(mName == aDomPref.name());
mIsLocked = aDomPref.isLocked();
const Maybe<dom::PrefValue>& defaultValue = aDomPref.defaultValue();
bool defaultValueChanged = false;
if (defaultValue.isSome()) {
PrefValue value;
PrefType type = value.FromDomPrefValue(defaultValue.ref());
if (!ValueMatches(PrefValueKind::Default, type, value)) {
// Type() is PrefType::None if it's a newly added pref. This is ok.
mDefaultValue.Replace(mHasDefaultValue, Type(), type, value);
SetType(type);
mHasDefaultValue = true;
defaultValueChanged = true;
}
}
// Note: we never clear a default value.
const Maybe<dom::PrefValue>& userValue = aDomPref.userValue();
bool userValueChanged = false;
if (userValue.isSome()) {
PrefValue value;
PrefType type = value.FromDomPrefValue(userValue.ref());
if (!ValueMatches(PrefValueKind::User, type, value)) {
// Type() is PrefType::None if it's a newly added pref. This is ok.
mUserValue.Replace(mHasUserValue, Type(), type, value);
SetType(type);
mHasUserValue = true;
userValueChanged = true;
}
} else if (mHasUserValue) {
ClearUserValue();
userValueChanged = true;
}
if (userValueChanged || (defaultValueChanged && !mHasUserValue)) {
*aValueChanged = true;
}
}
void FromWrapper(PrefWrapper& aWrapper);
bool HasAdvisablySizedValues() {
MOZ_ASSERT(XRE_IsParentProcess());
if (!IsTypeString()) {
return true;
}
if (mHasDefaultValue &&
strlen(mDefaultValue.mStringVal) > MAX_ADVISABLE_PREF_LENGTH) {
return false;
}
if (mHasUserValue &&
strlen(mUserValue.mStringVal) > MAX_ADVISABLE_PREF_LENGTH) {
return false;
}
return true;
}
private:
bool ValueMatches(PrefValueKind aKind, PrefType aType, PrefValue aValue) {
return IsType(aType) &&
(aKind == PrefValueKind::Default
? mHasDefaultValue && mDefaultValue.Equals(aType, aValue)
: mHasUserValue && mUserValue.Equals(aType, aValue));
}
public:
void ClearUserValue() {
mUserValue.Clear(Type());
mHasUserValue = false;
}
nsresult SetDefaultValue(PrefType aType, PrefValue aValue, bool aIsSticky,
bool aIsLocked, bool* aValueChanged) {
// Types must always match when setting the default value.
if (!IsType(aType)) {
return NS_ERROR_UNEXPECTED;
}
// Should we set the default value? Only if the pref is not locked, and
// doing so would change the default value.
if (!IsLocked()) {
if (aIsLocked) {
SetIsLocked(true);
}
if (!ValueMatches(PrefValueKind::Default, aType, aValue)) {
mDefaultValue.Replace(mHasDefaultValue, Type(), aType, aValue);
mHasDefaultValue = true;
if (aIsSticky) {
mIsSticky = true;
}
if (!mHasUserValue) {
*aValueChanged = true;
}
// What if we change the default to be the same as the user value?
// Should we clear the user value? Currently we don't.
}
}
return NS_OK;
}
nsresult SetUserValue(PrefType aType, PrefValue aValue, bool aFromInit,
bool* aValueChanged) {
// If we have a default value, types must match when setting the user
// value.
if (mHasDefaultValue && !IsType(aType)) {
return NS_ERROR_UNEXPECTED;
}
// Should we clear the user value, if present? Only if the new user value
// matches the default value, and the pref isn't sticky, and we aren't
// force-setting it during initialization.
if (ValueMatches(PrefValueKind::Default, aType, aValue) && !mIsSticky &&
!aFromInit) {
if (mHasUserValue) {
ClearUserValue();
if (!IsLocked()) {
*aValueChanged = true;
}
}
// Otherwise, should we set the user value? Only if doing so would
// change the user value.
} else if (!ValueMatches(PrefValueKind::User, aType, aValue)) {
mUserValue.Replace(mHasUserValue, Type(), aType, aValue);
SetType(aType); // needed because we may have changed the type
mHasUserValue = true;
if (!IsLocked()) {
*aValueChanged = true;
}
}
return NS_OK;
}
// Prefs are serialized in a manner that mirrors dom::Pref. The two should be
// kept in sync. E.g. if something is added to one it should also be added to
// the other. (It would be nice to be able to use the code generated from
// IPDL for serializing dom::Pref here instead of writing by hand this
// serialization/deserialization. Unfortunately, that generated code is
// difficult to use directly, outside of the IPDL IPC code.)
//
// The grammar for the serialized prefs has the following form.
//
// <pref> = <type> <locked> ':' <name> ':' <value>? ':' <value>? '\n'
// <type> = 'B' | 'I' | 'S'
// <locked> = 'L' | '-'
// <name> = <string-value>
// <value> = <bool-value> | <int-value> | <string-value>
// <bool-value> = 'T' | 'F'
// <int-value> = an integer literal accepted by strtol()
// <string-value> = <int-value> '/' <chars>
// <chars> = any char sequence of length dictated by the preceding
// <int-value>.
//
// No whitespace is tolerated between tokens. <type> must match the types of
// the values.
//
// The serialization is text-based, rather than binary, for the following
// reasons.
//
// - The size difference wouldn't be much different between text-based and
// binary. Most of the space is for strings (pref names and string pref
// values), which would be the same in both styles. And other differences
// would be minimal, e.g. small integers are shorter in text but long
// integers are longer in text.
//
// - Likewise, speed differences should be negligible.
//
// - It's much easier to debug a text-based serialization. E.g. you can
// print it and inspect it easily in a debugger.
//
// Examples of unlocked boolean prefs:
// - "B-:8/my.bool1:F:T\n"
// - "B-:8/my.bool2:F:\n"
// - "B-:8/my.bool3::T\n"
//
// Examples of locked integer prefs:
// - "IL:7/my.int1:0:1\n"
// - "IL:7/my.int2:123:\n"
// - "IL:7/my.int3::-99\n"
//
// Examples of unlocked string prefs:
// - "S-:10/my.string1:3/abc:4/wxyz\n"
// - "S-:10/my.string2:5/1.234:\n"
// - "S-:10/my.string3::7/string!\n"
void SerializeAndAppend(nsCString& aStr) {
switch (Type()) {
case PrefType::Bool:
aStr.Append('B');
break;
case PrefType::Int:
aStr.Append('I');
break;
case PrefType::String: {
aStr.Append('S');
break;
}
case PrefType::None:
default:
MOZ_CRASH();
}
aStr.Append(mIsLocked ? 'L' : '-');
aStr.Append(':');
SerializeAndAppendString(mName, aStr);
aStr.Append(':');
if (mHasDefaultValue) {
mDefaultValue.SerializeAndAppend(Type(), aStr);
}
aStr.Append(':');
if (mHasUserValue) {
mUserValue.SerializeAndAppend(Type(), aStr);
}
aStr.Append('\n');
}
static char* Deserialize(char* aStr, dom::Pref* aDomPref) {
char* p = aStr;
// The type.
PrefType type;
if (*p == 'B') {
type = PrefType::Bool;
} else if (*p == 'I') {
type = PrefType::Int;
} else if (*p == 'S') {
type = PrefType::String;
} else {
NS_ERROR("bad pref type");
type = PrefType::None;
}
p++; // move past the type char
// Locked?
bool isLocked;
if (*p == 'L') {
isLocked = true;
} else if (*p == '-') {
isLocked = false;
} else {
NS_ERROR("bad pref locked status");
isLocked = false;
}
p++; // move past the isLocked char
MOZ_ASSERT(*p == ':');
p++; // move past the ':'
// The pref name.
nsCString name;
p = DeserializeString(p, name);
MOZ_ASSERT(*p == ':');
p++; // move past the ':' preceding the default value
Maybe<dom::PrefValue> maybeDefaultValue;
if (*p != ':') {
dom::PrefValue defaultValue;
p = PrefValue::Deserialize(type, p, &maybeDefaultValue);
}
MOZ_ASSERT(*p == ':');
p++; // move past the ':' between the default and user values
Maybe<dom::PrefValue> maybeUserValue;
if (*p != '\n') {
dom::PrefValue userValue;
p = PrefValue::Deserialize(type, p, &maybeUserValue);
}
MOZ_ASSERT(*p == '\n');
p++; // move past the '\n' following the user value
*aDomPref = dom::Pref(name, isLocked, maybeDefaultValue, maybeUserValue);
return p;
}
void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, PrefsSizes& aSizes) {
// Note: mName is allocated in sPrefNameArena, measured elsewhere.
aSizes.mPrefValues += aMallocSizeOf(this);
if (IsTypeString()) {
if (mHasDefaultValue) {
aSizes.mStringValues += aMallocSizeOf(mDefaultValue.mStringVal);
}
if (mHasUserValue) {
aSizes.mStringValues += aMallocSizeOf(mUserValue.mStringVal);
}
}
}
private:
const nsDependentCString mName; // allocated in sPrefNameArena
uint32_t mType : 2;
uint32_t mIsSticky : 1;
uint32_t mIsLocked : 1;
uint32_t mHasDefaultValue : 1;
uint32_t mHasUserValue : 1;
uint32_t mIsSkippedByIteration : 1;
PrefValue mDefaultValue;
PrefValue mUserValue;
};
struct PrefHasher {
using Key = UniquePtr<Pref>;
using Lookup = const char*;
static HashNumber hash(const Lookup aLookup) { return HashString(aLookup); }
static bool match(const Key& aKey, const Lookup aLookup) {
if (!aLookup || !aKey->Name()) {
return false;
}
return strcmp(aLookup, aKey->Name()) == 0;
}
};
using PrefWrapperBase = Variant<Pref*, SharedPrefMap::Pref>;
class MOZ_STACK_CLASS PrefWrapper : public PrefWrapperBase {
using SharedPref = const SharedPrefMap::Pref;
public:
MOZ_IMPLICIT PrefWrapper(Pref* aPref) : PrefWrapperBase(AsVariant(aPref)) {}
MOZ_IMPLICIT PrefWrapper(const SharedPrefMap::Pref& aPref)
: PrefWrapperBase(AsVariant(aPref)) {}
// Types.
bool IsType(PrefType aType) const { return Type() == aType; }
bool IsTypeNone() const { return IsType(PrefType::None); }
bool IsTypeString() const { return IsType(PrefType::String); }
bool IsTypeInt() const { return IsType(PrefType::Int); }
bool IsTypeBool() const { return IsType(PrefType::Bool); }
#define FORWARD(retType, method) \
retType method() const { \
struct Matcher { \
retType operator()(const Pref* aPref) { return aPref->method(); } \
retType operator()(SharedPref& aPref) { return aPref.method(); } \
}; \
return match(Matcher()); \
}
FORWARD(bool, IsLocked)
FORWARD(bool, IsSticky)
FORWARD(bool, HasDefaultValue)
FORWARD(bool, HasUserValue)
FORWARD(const char*, Name)
FORWARD(nsCString, NameString)
FORWARD(PrefType, Type)
#undef FORWARD
#define FORWARD(retType, method) \
retType method(PrefValueKind aKind = PrefValueKind::User) const { \
struct Matcher { \
PrefValueKind mKind; \
\
retType operator()(const Pref* aPref) { return aPref->method(mKind); } \
retType operator()(SharedPref& aPref) { return aPref.method(mKind); } \
}; \
return match(Matcher{aKind}); \
}
FORWARD(bool, GetBoolValue)
FORWARD(int32_t, GetIntValue)
FORWARD(nsCString, GetStringValue)
FORWARD(const char*, GetBareStringValue)
#undef FORWARD
PrefValue GetValue(PrefValueKind aKind = PrefValueKind::User) const {
switch (Type()) {
case PrefType::Bool:
return PrefValue{GetBoolValue(aKind)};
case PrefType::Int:
return PrefValue{GetIntValue(aKind)};
case PrefType::String:
return PrefValue{GetBareStringValue(aKind)};
default:
MOZ_ASSERT_UNREACHABLE("Unexpected pref type");
return PrefValue{};
}
}
Result<PrefValueKind, nsresult> WantValueKind(PrefType aType,
PrefValueKind aKind) const {
if (Type() != aType) {
return Err(NS_ERROR_UNEXPECTED);
}
if (aKind == PrefValueKind::Default || IsLocked() || !HasUserValue()) {
if (!HasDefaultValue()) {
return Err(NS_ERROR_UNEXPECTED);
}
return PrefValueKind::Default;
}
return PrefValueKind::User;
}
nsresult GetValue(PrefValueKind aKind, bool* aResult) const {
PrefValueKind kind;
MOZ_TRY_VAR(kind, WantValueKind(PrefType::Bool, aKind));
*aResult = GetBoolValue(kind);
return NS_OK;
}
nsresult GetValue(PrefValueKind aKind, int32_t* aResult) const {
PrefValueKind kind;
MOZ_TRY_VAR(kind, WantValueKind(PrefType::Int, aKind));
*aResult = GetIntValue(kind);
return NS_OK;
}
nsresult GetValue(PrefValueKind aKind, uint32_t* aResult) const {
return GetValue(aKind, reinterpret_cast<int32_t*>(aResult));
}
nsresult GetValue(PrefValueKind aKind, float* aResult) const {
nsAutoCString result;
nsresult rv = GetValue(aKind, result);
if (NS_SUCCEEDED(rv)) {
// ToFloat() does a locale-independent conversion.
*aResult = result.ToFloat(&rv);
}
return rv;
}
nsresult GetValue(PrefValueKind aKind, nsACString& aResult) const {
PrefValueKind kind;
MOZ_TRY_VAR(kind, WantValueKind(PrefType::String, aKind));
aResult = GetStringValue(kind);
return NS_OK;
}
// Returns false if this pref doesn't have a user value worth saving.
bool UserValueToStringForSaving(nsCString& aStr) {
// Should we save the user value, if present? Only if it does not match the
// default value, or it is sticky.
if (HasUserValue() &&
(!ValueMatches(PrefValueKind::Default, Type(), GetValue()) ||
IsSticky())) {
if (IsTypeString()) {
StrEscape(GetStringValue().get(), aStr);
} else if (IsTypeInt()) {
aStr.AppendInt(GetIntValue());
} else if (IsTypeBool()) {
aStr = GetBoolValue() ? "true" : "false";
}
return true;
}
// Do not save default prefs that haven't changed.
return false;
}
bool Matches(PrefType aType, PrefValueKind aKind, PrefValue& aValue,
bool aIsSticky, bool aIsLocked) const {
return (ValueMatches(aKind, aType, aValue) && aIsSticky == IsSticky() &&
aIsLocked == IsLocked());
}
bool ValueMatches(PrefValueKind aKind, PrefType aType,
const PrefValue& aValue) const {
if (!IsType(aType)) {
return false;
}
if (!(aKind == PrefValueKind::Default ? HasDefaultValue()
: HasUserValue())) {
return false;
}
switch (aType) {
case PrefType::Bool:
return GetBoolValue(aKind) == aValue.mBoolVal;
case PrefType::Int:
return GetIntValue(aKind) == aValue.mIntVal;
case PrefType::String:
return strcmp(GetBareStringValue(aKind), aValue.mStringVal) == 0;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected preference type");
return false;
}
}
};
void Pref::FromWrapper(PrefWrapper& aWrapper) {
MOZ_ASSERT(aWrapper.is<SharedPrefMap::Pref>());
auto pref = aWrapper.as<SharedPrefMap::Pref>();
MOZ_ASSERT(IsTypeNone());
MOZ_ASSERT(mName == pref.NameString());
mType = uint32_t(pref.Type());
mIsLocked = pref.IsLocked();
mIsSticky = pref.IsSticky();
mHasDefaultValue = pref.HasDefaultValue();
mHasUserValue = pref.HasUserValue();
if (mHasDefaultValue) {
mDefaultValue.Init(Type(), aWrapper.GetValue(PrefValueKind::Default));
}
if (mHasUserValue) {
mUserValue.Init(Type(), aWrapper.GetValue(PrefValueKind::User));
}
}
class CallbackNode {
public:
CallbackNode(const nsACString& aDomain, PrefChangedFunc aFunc, void* aData,
Preferences::MatchKind aMatchKind)
: mDomain(AsVariant(nsCString(aDomain))),
mFunc(aFunc),
mData(aData),
mNextAndMatchKind(aMatchKind) {}
CallbackNode(const char** aDomains, PrefChangedFunc aFunc, void* aData,
Preferences::MatchKind aMatchKind)
: mDomain(AsVariant(aDomains)),
mFunc(aFunc),
mData(aData),
mNextAndMatchKind(aMatchKind) {}
// mDomain is a UniquePtr<>, so any uses of Domain() should only be temporary
// borrows.
const Variant<nsCString, const char**>& Domain() const { return mDomain; }
PrefChangedFunc Func() const { return mFunc; }
void ClearFunc() { mFunc = nullptr; }
void* Data() const { return mData; }
Preferences::MatchKind MatchKind() const {
return static_cast<Preferences::MatchKind>(mNextAndMatchKind &
kMatchKindMask);
}
bool DomainIs(const nsACString& aDomain) const {
return mDomain.is<nsCString>() && mDomain.as<nsCString>() == aDomain;
}
bool DomainIs(const char** aPrefs) const {
return mDomain == AsVariant(aPrefs);
}
bool Matches(const nsACString& aPrefName) const {
auto match = [&](const nsACString& aStr) {
return MatchKind() == Preferences::ExactMatch
? aPrefName == aStr
: StringBeginsWith(aPrefName, aStr);
};
if (mDomain.is<nsCString>()) {
return match(mDomain.as<nsCString>());
}
for (const char** ptr = mDomain.as<const char**>(); *ptr; ptr++) {
if (match(nsDependentCString(*ptr))) {
return true;
}
}
return false;
}
CallbackNode* Next() const {
return reinterpret_cast<CallbackNode*>(mNextAndMatchKind & kNextMask);
}
void SetNext(CallbackNode* aNext) {
uintptr_t matchKind = mNextAndMatchKind & kMatchKindMask;
mNextAndMatchKind = reinterpret_cast<uintptr_t>(aNext);
MOZ_ASSERT((mNextAndMatchKind & kMatchKindMask) == 0);
mNextAndMatchKind |= matchKind;
}
void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, PrefsSizes& aSizes) {
aSizes.mCallbacksObjects += aMallocSizeOf(this);
if (mDomain.is<nsCString>()) {
aSizes.mCallbacksDomains +=
mDomain.as<nsCString>().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
}
}
private:
static const uintptr_t kMatchKindMask = uintptr_t(0x1);
static const uintptr_t kNextMask = ~kMatchKindMask;
Variant<nsCString, const char**> mDomain;
// If someone attempts to remove the node from the callback list while
// NotifyCallbacks() is running, |func| is set to nullptr. Such nodes will
// be removed at the end of NotifyCallbacks().
PrefChangedFunc mFunc;
void* mData;
// Conceptually this is two fields:
// - CallbackNode* mNext;
// - Preferences::MatchKind mMatchKind;
// They are combined into a tagged pointer to save memory.
uintptr_t mNextAndMatchKind;
};
using PrefsHashTable = HashSet<UniquePtr<Pref>, PrefHasher>;
// The main prefs hash table. Inside a function so we can assert it's only
// accessed on the main thread. (That assertion can be avoided but only do so
// with great care!)
static inline PrefsHashTable*& HashTable(bool aOffMainThread = false) {
MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
static PrefsHashTable* sHashTable = nullptr;
return sHashTable;
}
#ifdef DEBUG
// This defines the type used to store our `once` mirrors checker. We can't use
// HashMap for now due to alignment restrictions when dealing with
// std::function<void()> (see bug 1557617).
typedef std::function<void()> AntiFootgunCallback;
struct CompareStr {
bool operator()(char const* a, char const* b) const {
return std::strcmp(a, b) < 0;
}
};
typedef std::map<const char*, AntiFootgunCallback, CompareStr> AntiFootgunMap;
static AntiFootgunMap* gOnceStaticPrefsAntiFootgun;
#endif
// 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
#ifdef ACCESS_COUNTS
using AccessCountsHashTable = nsDataHashtable<nsCStringHashKey, uint32_t>;
static AccessCountsHashTable* gAccessCounts = nullptr;
static void AddAccessCount(const nsACString& aPrefName) {
// FIXME: Servo reads preferences from background threads in unsafe ways (bug
// 1474789), and triggers assertions here if we try to add usage count entries
// from background threads.
if (NS_IsMainThread()) {
uint32_t& count = gAccessCounts->LookupOrInsert(aPrefName);
count++;
}
}
static void AddAccessCount(const char* aPrefName) {
AddAccessCount(nsDependentCString(aPrefName));
}
#else
static void MOZ_MAYBE_UNUSED AddAccessCount(const nsACString& aPrefName) {}
static void AddAccessCount(const char* aPrefName) {}
#endif
// These are only used during the call to NotifyCallbacks().
static bool gCallbacksInProgress = false;
static bool gShouldCleanupDeadNodes = false;
class PrefsHashIter {
using Iterator = decltype(HashTable()->modIter());
using ElemType = Pref*;
Iterator mIter;
public:
explicit PrefsHashIter(PrefsHashTable* aTable) : mIter(aTable->modIter()) {}
class Elem {
friend class PrefsHashIter;
PrefsHashIter& mParent;
bool mDone;
Elem(PrefsHashIter& aIter, bool aDone) : mParent(aIter), mDone(aDone) {}
Iterator& Iter() { return mParent.mIter; }
public:
Elem& operator*() { return *this; }
ElemType get() {
if (mDone) {
return nullptr;
}
return Iter().get().get();
}
ElemType get() const { return const_cast<Elem*>(this)->get(); }
ElemType operator->() { return get(); }
ElemType operator->() const { return get(); }
operator ElemType() { return get(); }
void Remove() { Iter().remove(); }
Elem& operator++() {
MOZ_ASSERT(!mDone);
Iter().next();
mDone = Iter().done();
return *this;
}
bool operator!=(Elem& other) {
return mDone != other.mDone || this->get() != other.get();
}
};
Elem begin() { return Elem(*this, mIter.done()); }
Elem end() { return Elem(*this, true); }
};
class PrefsIter {
using Iterator = decltype(HashTable()->iter());
using ElemType = PrefWrapper;
using HashElem = PrefsHashIter::Elem;
using SharedElem = SharedPrefMap::Pref;
using ElemTypeVariant = Variant<HashElem, SharedElem>;
SharedPrefMap* mSharedMap;
PrefsHashTable* mHashTable;
PrefsHashIter mIter;
ElemTypeVariant mPos;
ElemTypeVariant mEnd;
Maybe<PrefWrapper> mEntry;
public:
PrefsIter(PrefsHashTable* aHashTable, SharedPrefMap* aSharedMap)
: mSharedMap(aSharedMap),
mHashTable(aHashTable),
mIter(aHashTable),
mPos(AsVariant(mIter.begin())),
mEnd(AsVariant(mIter.end())) {
if (Done()) {
NextIterator();
}
}
private:
#define MATCH(type, ...) \
do { \
struct Matcher { \
PrefsIter& mIter; \
type operator()(HashElem& pos) { \
HashElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<HashElem>(); \
__VA_ARGS__; \
} \
type operator()(SharedElem& pos) { \
SharedElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<SharedElem>(); \
__VA_ARGS__; \
} \
}; \
return mPos.match(Matcher{*this}); \
} while (0);
bool Done() { MATCH(bool, return pos == end); }
PrefWrapper MakeEntry() { MATCH(PrefWrapper, return PrefWrapper(pos)); }
void NextEntry() {
mEntry.reset();
MATCH(void, ++pos);
}
#undef MATCH
bool Next() {
NextEntry();
return !Done() || NextIterator();
}
bool NextIterator() {
if (mPos.is<HashElem>() && mSharedMap) {
mPos = AsVariant(mSharedMap->begin());
mEnd = AsVariant(mSharedMap->end());
return !Done();
}
return false;
}
bool IteratingBase() { return mPos.is<SharedElem>(); }
PrefWrapper& Entry() {
MOZ_ASSERT(!Done());
if (!mEntry.isSome()) {
mEntry.emplace(MakeEntry());
}
return mEntry.ref();
}
public:
class Elem {
friend class PrefsIter;
PrefsIter& mParent;
bool mDone;
Elem(PrefsIter& aIter, bool aDone) : mParent(aIter), mDone(aDone) {
SkipDuplicates();
}
void Next() { mDone = !mParent.Next(); }
void SkipDuplicates() {
while (!mDone &&
(mParent.IteratingBase() ? mParent.mHashTable->has(ref().Name())
: ref().IsTypeNone())) {
Next();
}
}
public:
Elem& operator*() { return *this; }
ElemType& ref() { return mParent.Entry(); }
const ElemType& ref() const { return const_cast<Elem*>(this)->ref(); }
ElemType* operator->() { return &ref(); }
const ElemType* operator->() const { return &ref(); }
operator ElemType() { return ref(); }
Elem& operator++() {
MOZ_ASSERT(!mDone);
Next();
SkipDuplicates();
return *this;
}
bool operator!=(Elem& other) {
if (mDone != other.mDone) {
return true;
}
if (mDone) {
return false;
}
return &this->ref() != &other.ref();
}
};
Elem begin() { return {*this, Done()}; }
Elem end() { return {*this, true}; }
};
static Pref* pref_HashTableLookup(const char* aPrefName);
static void NotifyCallbacks(const nsCString& aPrefName,
const PrefWrapper* aPref = nullptr);
static void NotifyCallbacks(const nsCString& aPrefName,
const PrefWrapper& aPref) {
NotifyCallbacks(aPrefName, &aPref);
}
// The approximate number of preferences in the dynamic hashtable for the parent
// and content processes, respectively. These numbers are used to determine the
// initial size of the dynamic preference hashtables, and should be chosen to
// avoid rehashing during normal usage. The actual number of preferences will,
// or course, change over time, but these numbers only need to be within a
// binary order of magnitude of the actual values to remain effective.
//
// The number for the parent process should reflect the total number of
// preferences in the database, since the parent process needs to initially
// build a dynamic hashtable of the entire preference database. The number for
// the child process should reflect the number of preferences which are likely
// to change after the startup of the first content process, since content
// processes only store changed preferences on top of a snapshot of the database
// created at startup.
//
// Note: The capacity of a hashtable doubles when its length reaches an exact
// power of two. A table with an initial length of 64 is twice as large as one
// with an initial length of 63. This is important in content processes, where
// lookup speed is less critical and we pay the price of the additional overhead
// for each content process. So the initial content length should generally be
// *under* the next power-of-two larger than its expected length.
constexpr size_t kHashTableInitialLengthParent = 3000;
constexpr size_t kHashTableInitialLengthContent = 64;
static PrefSaveData pref_savePrefs() {
MOZ_ASSERT(NS_IsMainThread());
PrefSaveData savedPrefs(HashTable()->count());
for (auto& pref : PrefsIter(HashTable(), gSharedMap)) {
nsAutoCString prefValueStr;
if (!pref->UserValueToStringForSaving(prefValueStr)) {
continue;
}
nsAutoCString prefNameStr;
StrEscape(pref->Name(), prefNameStr);
nsPrintfCString str("user_pref(%s, %s);", prefNameStr.get(),
prefValueStr.get());
savedPrefs.AppendElement(str);
}
return savedPrefs;
}
#ifdef DEBUG
// Note that this never changes in the parent process, and is only read in
// content processes.
static bool gContentProcessPrefsAreInited = false;
#endif // DEBUG
static Pref* pref_HashTableLookup(const char* aPrefName) {
MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
MOZ_ASSERT_IF(!XRE_IsParentProcess(), gContentProcessPrefsAreInited);
// We use readonlyThreadsafeLookup() because we often have concurrent lookups
// from multiple Stylo threads. This is safe because those threads cannot
// modify sHashTable, and the main thread is blocked while Stylo threads are
// doing these lookups.
auto p = HashTable()->readonlyThreadsafeLookup(aPrefName);
return p ? p->get() : nullptr;
}
// While notifying preference callbacks, this holds the wrapper for the
// preference being notified, in order to optimize lookups.
//
// Note: Callbacks and lookups only happen on the main thread, so this is safe
// to use without locking.
static const PrefWrapper* gCallbackPref;
Maybe<PrefWrapper> pref_SharedLookup(const char* aPrefName) {
MOZ_DIAGNOSTIC_ASSERT(gSharedMap, "gSharedMap must be initialized");
if (Maybe<SharedPrefMap::Pref> pref = gSharedMap->Get(aPrefName)) {
return Some(*pref);
}
return Nothing();
}
Maybe<PrefWrapper> pref_Lookup(const char* aPrefName,
bool aIncludeTypeNone = false) {
MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
AddAccessCount(aPrefName);
if (gCallbackPref && strcmp(aPrefName, gCallbackPref->Name()) == 0) {
return Some(*gCallbackPref);
}
if (Pref* pref = pref_HashTableLookup(aPrefName)) {
if (aIncludeTypeNone || !pref->IsTypeNone()) {
return Some(pref);
}
} else if (gSharedMap) {
return pref_SharedLookup(aPrefName);
}
return Nothing();
}
static Result<Pref*, nsresult> pref_LookupForModify(
const nsCString& aPrefName,
const std::function<bool(const PrefWrapper&)>& aCheckFn) {
Maybe<PrefWrapper> wrapper =
pref_Lookup(aPrefName.get(), /* includeTypeNone */ true);
if (wrapper.isNothing()) {
return Err(NS_ERROR_INVALID_ARG);
}
if (!aCheckFn(*wrapper)) {
return nullptr;
}
if (wrapper->is<Pref*>()) {
return wrapper->as<Pref*>();
}
Pref* pref = new Pref(aPrefName);
if (!HashTable()->putNew(aPrefName.get(), pref)) {
delete pref;
return Err(NS_ERROR_OUT_OF_MEMORY);
}
pref->FromWrapper(*wrapper);
return pref;
}
static nsresult pref_SetPref(const nsCString& aPrefName, PrefType aType,
PrefValueKind aKind, PrefValue aValue,
bool aIsSticky, bool aIsLocked, bool aFromInit) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
if (!HashTable()) {
return NS_ERROR_OUT_OF_MEMORY;
}
Pref* pref = nullptr;
if (gSharedMap) {
auto result =
pref_LookupForModify(aPrefName, [&](const PrefWrapper& aWrapper) {
return !aWrapper.Matches(aType, aKind, aValue, aIsSticky, aIsLocked);
});
if (result.isOk() && !(pref = result.unwrap())) {
// No changes required.
return NS_OK;
}
}
if (!pref) {
auto p = HashTable()->lookupForAdd(aPrefName.get());
if (!p) {
pref = new Pref(aPrefName);
pref->SetType(aType);
if (!HashTable()->add(p, pref)) {
delete pref;
return NS_ERROR_OUT_OF_MEMORY;
}
} else {
pref = p->get();
}
}
bool valueChanged = false;
nsresult rv;
if (aKind == PrefValueKind::Default) {
rv = pref->SetDefaultValue(aType, aValue, aIsSticky, aIsLocked,
&valueChanged);
} else {
MOZ_ASSERT(!aIsLocked); // `locked` is disallowed in user pref files
rv = pref->SetUserValue(aType, aValue, aFromInit, &valueChanged);
}
if (NS_FAILED(rv)) {
NS_WARNING(
nsPrintfCString("Rejected attempt to change type of pref %s's %s value "
"from %s to %s",
aPrefName.get(),
(aKind == PrefValueKind::Default) ? "default" : "user",
PrefTypeToString(pref->Type()), PrefTypeToString(aType))
.get());
return rv;
}
if (valueChanged) {
if (aKind == PrefValueKind::User) {
Preferences::HandleDirty();
}
NotifyCallbacks(aPrefName, PrefWrapper(pref));
}
return NS_OK;
}
// Removes |node| from callback list. Returns the node after the deleted one.
static CallbackNode* pref_RemoveCallbackNode(CallbackNode* aNode,
CallbackNode* aPrevNode) {
MOZ_ASSERT(!aPrevNode || aPrevNode->Next() == aNode);
MOZ_ASSERT(aPrevNode || gFirstCallback == aNode);
MOZ_ASSERT(!gCallbacksInProgress);
CallbackNode* next_node = aNode->Next();
if (aPrevNode) {
aPrevNode->SetNext(next_node);
} else {
gFirstCallback = next_node;
}
if (gLastPriorityNode == aNode) {
gLastPriorityNode = aPrevNode;
}
delete aNode;
return next_node;
}
static void NotifyCallbacks(const nsCString& aPrefName,
const PrefWrapper* aPref) {
bool reentered = gCallbacksInProgress;
gCallbackPref = aPref;
auto cleanup = MakeScopeExit([]() { gCallbackPref = nullptr; });
// Nodes must not be deleted while gCallbacksInProgress is true.
// Nodes that need to be deleted are marked for deletion by nulling
// out the |func| pointer. We release them at the end of this function
// if we haven't reentered.
gCallbacksInProgress = true;
for (CallbackNode* node = gFirstCallback; node; node = node->Next()) {
if (node->Func()) {
if (node->Matches(aPrefName)) {
(node->Func())(aPrefName.get(), node->Data());
}
}
}
gCallbacksInProgress = reentered;
if (gShouldCleanupDeadNodes && !gCallbacksInProgress) {
CallbackNode* prev_node = nullptr;
CallbackNode* node = gFirstCallback;
while (node) {
if (!node->Func()) {
node = pref_RemoveCallbackNode(node, prev_node);
} else {
prev_node = node;
node = node->Next();
}
}
gShouldCleanupDeadNodes = false;
}
#ifdef DEBUG
if (XRE_IsParentProcess() &&
!StaticPrefs::preferences_force_disable_check_once_policy() &&
(StaticPrefs::preferences_check_once_policy() || xpc::IsInAutomation())) {
// Check that we aren't modifying a `once`-mirrored pref using that pref
// name. We have about 100 `once`-mirrored prefs. std::map performs a
// search in O(log n), so this is fast enough.
MOZ_ASSERT(gOnceStaticPrefsAntiFootgun);
auto search = gOnceStaticPrefsAntiFootgun->find(aPrefName.get());
if (search != gOnceStaticPrefsAntiFootgun->