Source code

Revision control

Copy as Markdown

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/AppShutdown.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/dom/Promise.h"
#include "mozilla/dom/RemoteType.h"
#include "mozilla/glean/GleanMetrics.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/StaticPtr.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Try.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 "nsContentUtils.h"
#include "nsCRT.h"
#include "nsTHashMap.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 "nsProxyRelease.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 "prdtoa.h"
#include "prlink.h"
#include "xpcpublic.h"
#include "js/RootingAPI.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
#if defined(MOZ_WIDGET_GTK)
# include "mozilla/WidgetUtilsGtk.h"
#endif // defined(MOZ_WIDGET_GTK)
#ifdef MOZ_WIDGET_COCOA
# include "ChannelPrefsUtil.h"
#endif
using namespace mozilla;
using dom::Promise;
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
// Forward declarations.
namespace mozilla::StaticPrefs {
static void InitAll();
static void StartObservingAlwaysPrefs();
static void InitOncePrefs();
static void InitStaticPrefsFromShared();
static void RegisterOncePrefs(SharedPrefMapBuilder& aBuilder);
static void ShutdownAlwaysPrefs();
} // namespace mozilla::StaticPrefs
//===========================================================================
// 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(uint64_t(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();
}
}
void ToString(PrefType aType, nsCString& aStr) {
switch (aType) {
case PrefType::Bool:
aStr.Append(mBoolVal ? "true" : "false");
break;
case PrefType::Int:
aStr.AppendInt(mIntVal);
break;
case PrefType::String: {
aStr.Append(nsDependentCString(mStringVal));
break;
}
case PrefType::None:
default:;
}
}
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('"');
}
// Mimic the behaviour of nsTStringRepr::ToFloat before bug 840706 to preserve
// error case handling for parsing pref strings. Many callers do not check error
// codes, so the returned values may be used even if an error is set.
//
// This method should never return NaN, but may return +-inf if the provided
// number is too large to fit in a float.
static float ParsePrefFloat(const nsCString& aString, nsresult* aError) {
if (aString.IsEmpty()) {
*aError = NS_ERROR_ILLEGAL_VALUE;
return 0.f;
}
// PR_strtod does a locale-independent conversion.
char* stopped = nullptr;
float result = PR_strtod(aString.get(), &stopped);
// Defensively avoid potential breakage caused by returning NaN into
// unsuspecting code. AFAIK this should never happen as PR_strtod cannot
// return NaN as currently configured.
if (std::isnan(result)) {
MOZ_ASSERT_UNREACHABLE("PR_strtod shouldn't return NaN");
*aError = NS_ERROR_ILLEGAL_VALUE;
return 0.f;
}
*aError = (stopped == aString.EndReading()) ? NS_OK : NS_ERROR_ILLEGAL_VALUE;
return result;
}
struct PreferenceMarker {
static constexpr Span<const char> MarkerTypeName() {
return MakeStringSpan("Preference");
}
static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
const ProfilerString8View& aPrefName,
const Maybe<PrefValueKind>& aPrefKind,
PrefType aPrefType,
const ProfilerString8View& aPrefValue) {
aWriter.StringProperty("prefName", aPrefName);
aWriter.StringProperty("prefKind", PrefValueKindToString(aPrefKind));
aWriter.StringProperty("prefType", PrefTypeToString(aPrefType));
aWriter.StringProperty("prefValue", aPrefValue);
}
static MarkerSchema MarkerTypeDisplay() {
using MS = MarkerSchema;
MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
schema.AddKeyLabelFormatSearchable("prefName", "Name", MS::Format::String,
MS::Searchable::Searchable);
schema.AddKeyLabelFormat("prefKind", "Kind", MS::Format::String);
schema.AddKeyLabelFormat("prefType", "Type", MS::Format::String);
schema.AddKeyLabelFormat("prefValue", "Value", MS::Format::String);
schema.SetTableLabel(
"{marker.name} — {marker.data.prefName}: {marker.data.prefValue} "
"({marker.data.prefType})");
return schema;
}
private:
static Span<const char> PrefValueKindToString(
const Maybe<PrefValueKind>& aKind) {
if (aKind) {
return *aKind == PrefValueKind::Default ? MakeStringSpan("Default")
: MakeStringSpan("User");
}
return "Shared";
}
static Span<const char> PrefTypeToString(PrefType type) {
switch (type) {
case PrefType::None:
return "None";
case PrefType::Int:
return "Int";
case PrefType::Bool:
return "Bool";
case PrefType::String:
return "String";
default:
MOZ_ASSERT_UNREACHABLE("Unknown preference type.");
return "Unknown";
}
}
};
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.
// Never access sPrefNameArena directly, always use PrefNameArena()
// because it must only be accessed on the Main Thread
typedef ArenaAllocator<4096, 1> NameArena;
static NameArena* sPrefNameArena;
static inline NameArena& PrefNameArena() {
MOZ_ASSERT(NS_IsMainThread());
if (!sPrefNameArena) {
sPrefNameArena = new NameArena();
}
return *sPrefNameArena;
}
class PrefWrapper;
// Three forward declarations for immediately below
class Pref;
static bool IsPreferenceSanitized(const Pref* const aPref);
static bool ShouldSanitizePreference(const Pref* const aPref);
// Note that this never changes in the parent process, and is only read in
// content processes.
static bool gContentProcessPrefsAreInited = false;
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),
mIsSanitized(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 IsSanitized() const { return mIsSanitized; }
bool HasDefaultValue() const { return mHasDefaultValue; }
bool HasUserValue() const { return mHasUserValue; }
template <typename T>
void AddToMap(SharedPrefMapBuilder& aMap) {
// Sanitized preferences should never be added to the shared pref map
MOZ_ASSERT(!ShouldSanitizePreference(this));
aMap.Add(NameString(),
{HasDefaultValue(), HasUserValue(), IsSticky(), IsLocked(),
/* isSanitized */ false, 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.
#define CHECK_SANITIZATION() \
if (IsPreferenceSanitized(this)) { \
glean::security::pref_usage_content_process.Record( \
Some(glean::security::PrefUsageContentProcessExtra{Some(Name())})); \
if (sCrashOnBlocklistedPref) { \
MOZ_CRASH_UNSAFE_PRINTF( \
"Should not access the preference '%s' in the Content Processes", \
Name()); \
} \
}
bool GetBoolValue(PrefValueKind aKind = PrefValueKind::User) const {
MOZ_ASSERT(IsTypeBool());
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
: HasUserValue());
CHECK_SANITIZATION();
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());
CHECK_SANITIZATION();
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());
CHECK_SANITIZATION();
return aKind == PrefValueKind::Default ? mDefaultValue.mStringVal
: mUserValue.mStringVal;
}
#undef CHECK_SANITIZATION
nsDependentCString GetStringValue(
PrefValueKind aKind = PrefValueKind::User) const {
return nsDependentCString(GetBareStringValue(aKind));
}
void ToDomPref(dom::Pref* aDomPref, bool aIsDestinationWebContentProcess) {
MOZ_ASSERT(XRE_IsParentProcess());
aDomPref->name() = mName;
aDomPref->isLocked() = mIsLocked;
aDomPref->isSanitized() =
aIsDestinationWebContentProcess && ShouldSanitizePreference(this);
if (mHasDefaultValue) {
aDomPref->defaultValue() = Some(dom::PrefValue());
mDefaultValue.ToDomPrefValue(Type(), &aDomPref->defaultValue().ref());
} else {
aDomPref->defaultValue() = Nothing();
}
if (mHasUserValue &&
!(aDomPref->isSanitized() && sOmitBlocklistedPrefValues)) {
aDomPref->userValue() = Some(dom::PrefValue());
mUserValue.ToDomPrefValue(Type(), &aDomPref->userValue().ref());
} else {
aDomPref->userValue() = Nothing();
}
MOZ_ASSERT(aDomPref->defaultValue().isNothing() ||
aDomPref->userValue().isNothing() ||
(mIsSanitized && sOmitBlocklistedPrefValues) ||
(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();
mIsSanitized = aDomPref.isSanitized();
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> <sanitized> ':' <name> ':' <value>? ':'
// <value>? '\n'
// <type> = 'B' | 'I' | 'S'
// <locked> = 'L' | '-'
// <sanitized> = 'S' | '-'
// <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 sanitized, unlocked boolean prefs:
// - "B-S:8/my.bool1:F:T\n"
// - "B-S:8/my.bool2:F:\n"
// - "B-S: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, bool aSanitizeUserValue) {
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(aSanitizeUserValue ? 'S' : '-');
aStr.Append(':');
SerializeAndAppendString(mName, aStr);
aStr.Append(':');
if (mHasDefaultValue) {
mDefaultValue.SerializeAndAppend(Type(), aStr);
}
aStr.Append(':');
if (mHasUserValue && !(aSanitizeUserValue && sOmitBlocklistedPrefValues)) {
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
// Sanitize?
bool isSanitized;
if (*p == 'S') {
isSanitized = true;
} else if (*p == '-') {
isSanitized = false;
} else {
NS_ERROR("bad pref sanitized status");
isSanitized = false;
}
p++; // move past the isSanitized 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, isSanitized, 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);
}
}
}
void RelocateName(NameArena* aArena) {
mName.Rebind(ArenaStrdup(mName.get(), *aArena), mName.Length());
}
private:
nsDependentCString mName; // allocated in sPrefNameArena
uint32_t mType : 2;
uint32_t mIsSticky : 1;
uint32_t mIsLocked : 1;
uint32_t mIsSanitized : 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, IsSanitized)
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)};
case PrefType::None:
// This check will be performed in the above functions; but for NoneType
// we need to do it explicitly, then fall-through.
if (IsPreferenceSanitized(Name())) {
glean::security::pref_usage_content_process.Record(Some(
glean::security::PrefUsageContentProcessExtra{Some(Name())}));
if (sCrashOnBlocklistedPref) {
MOZ_CRASH_UNSAFE_PRINTF(
"Should not access the preference '%s' in the Content "
"Processes",
Name());
}
}
[[fallthrough]];
default: