summaryrefslogtreecommitdiffstats
path: root/modules/libpref/Preferences.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /modules/libpref/Preferences.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--modules/libpref/Preferences.cpp6275
1 files changed, 6275 insertions, 0 deletions
diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp
new file mode 100644
index 0000000000..c04744a8b9
--- /dev/null
+++ b/modules/libpref/Preferences.cpp
@@ -0,0 +1,6275 @@
+/* -*- 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/RemoteType.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/Telemetry.h"
+#include "mozilla/TelemetryEventEnums.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 "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 "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)
+
+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
+
+// 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
+//===========================================================================
+
+Atomic<bool, mozilla::Relaxed> sPrefTelemetryEventEnabled(false);
+
+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)) { \
+ if (!sPrefTelemetryEventEnabled.exchange(true)) { \
+ sPrefTelemetryEventEnabled = true; \
+ Telemetry::SetEventRecordingEnabled("security"_ns, true); \
+ } \
+ Telemetry::RecordEvent( \
+ Telemetry::EventID::Security_Prefusage_Contentprocess, \
+ mozilla::Some(Name()), mozilla::Nothing()); \
+ 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())) {
+ if (!sPrefTelemetryEventEnabled.exchange(true)) {
+ sPrefTelemetryEventEnabled = true;
+ Telemetry::SetEventRecordingEnabled("security"_ns, true);
+ }
+
+ Telemetry::RecordEvent(
+ Telemetry::EventID::Security_Prefusage_Contentprocess,
+ mozilla::Some(Name()), mozilla::Nothing());
+
+ if (sCrashOnBlocklistedPref) {
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "Should not access the preference '%s' in the Content "
+ "Processes",
+ Name());
+ }
+ }
+ [[fallthrough]];
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected pref type");
+ return PrefValue{};
+ }
+ }
+
+ Result<PrefValueKind, nsresult> WantValueKind(PrefType aType,
+ PrefValueKind aKind) const {
+ // WantValueKind may short-circuit GetValue functions and cause them to
+ // return early, before this check occurs in GetFooValue()
+ if (this->is<Pref*>() && IsPreferenceSanitized(this->as<Pref*>())) {
+ if (!sPrefTelemetryEventEnabled.exchange(true)) {
+ sPrefTelemetryEventEnabled = true;
+ Telemetry::SetEventRecordingEnabled("security"_ns, true);
+ }
+
+ Telemetry::RecordEvent(
+ Telemetry::EventID::Security_Prefusage_Contentprocess,
+ mozilla::Some(Name()), mozilla::Nothing());
+
+ if (sCrashOnBlocklistedPref) {
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "Should not access the preference '%s' in the Content Processes",
+ Name());
+ }
+ } else if (!this->is<Pref*>()) {
+ // While we could use Name() above, and avoid the Variant checks, it
+ // would less efficient than needed and we can instead do a debug-only
+ // assert here to limit the inefficientcy
+ MOZ_ASSERT(!IsPreferenceSanitized(Name()),
+ "We should never have a sanitized SharedPrefMap::Pref.");
+ }
+
+ 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)) {
+ // ParsePrefFloat() does a locale-independent conversion.
+ // FIXME: Other `GetValue` overloads don't clobber `aResult` on error.
+ *aResult = ParsePrefFloat(result, &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;
+ }
+
+ nsresult GetValue(PrefValueKind aKind, nsACString* aResult) const {
+ return GetValue(aKind, *aResult);
+ }
+
+ // 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();
+ mIsSanitized = pref.IsSanitized();
+ 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 StaticAutoPtr<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 = nsTHashMap<nsCStringHashKey, uint32_t>;
+static StaticAutoPtr<AccessCountsHashTable> gAccessCounts;
+
+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()) {
+ JS::AutoSuppressGCAnalysis nogc; // Hash functions will not GC.
+ 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;
+}
+
+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() || pref->IsSanitized()) {
+ 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 (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) {
+ printf(
+ "pref_SetPref: Attempt to write pref %s after XPCOMShutdownThreads "
+ "started.\n",
+ aPrefName.get());
+ if (nsContentUtils::IsInitialized()) {
+ xpc_DumpJSStack(true, true, false);
+ }
+ MOZ_ASSERT(false, "Late preference writes should be avoided.");
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ 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 (!aFromInit && profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString value;
+ aValue.ToString(aType, value);
+ profiler_add_marker(
+ "Preference Write", baseprofiler::category::OTHER_PreferenceRead, {},
+ PreferenceMarker{}, aPrefName, Some(aKind), aType, value);
+ }
+
+ 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->end()) {
+ // Run the callback.
+ (search->second)();
+ }
+ }
+#endif
+}
+
+//===========================================================================
+// Prefs parsing
+//===========================================================================
+
+extern "C" {
+
+// Keep this in sync with PrefFn in parser/src/lib.rs.
+typedef void (*PrefsParserPrefFn)(const char* aPrefName, PrefType aType,
+ PrefValueKind aKind, PrefValue aValue,
+ bool aIsSticky, bool aIsLocked);
+
+// Keep this in sync with ErrorFn in parser/src/lib.rs.
+//
+// `aMsg` is just a borrow of the string, and must be copied if it is used
+// outside the lifetime of the prefs_parser_parse() call.
+typedef void (*PrefsParserErrorFn)(const char* aMsg);
+
+// Keep this in sync with prefs_parser_parse() in parser/src/lib.rs.
+bool prefs_parser_parse(const char* aPath, PrefValueKind aKind,
+ const char* aBuf, size_t aLen,
+ PrefsParserPrefFn aPrefFn, PrefsParserErrorFn aErrorFn);
+}
+
+class Parser {
+ public:
+ Parser() = default;
+ ~Parser() = default;
+
+ bool Parse(PrefValueKind aKind, const char* aPath, const nsCString& aBuf) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return prefs_parser_parse(aPath, aKind, aBuf.get(), aBuf.Length(),
+ HandlePref, HandleError);
+ }
+
+ private:
+ static void HandlePref(const char* aPrefName, PrefType aType,
+ PrefValueKind aKind, PrefValue aValue, bool aIsSticky,
+ bool aIsLocked) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ pref_SetPref(nsDependentCString(aPrefName), aType, aKind, aValue, aIsSticky,
+ aIsLocked,
+ /* fromInit */ true);
+ }
+
+ static void HandleError(const char* aMsg) {
+ nsresult rv;
+ nsCOMPtr<nsIConsoleService> console =
+ do_GetService("@mozilla.org/consoleservice;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ console->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg).get());
+ }
+#ifdef DEBUG
+ NS_ERROR(aMsg);
+#else
+ printf_stderr("%s\n", aMsg);
+#endif
+ }
+};
+
+// The following code is test code for the gtest.
+
+static void TestParseErrorHandlePref(const char* aPrefName, PrefType aType,
+ PrefValueKind aKind, PrefValue aValue,
+ bool aIsSticky, bool aIsLocked) {}
+
+static nsCString gTestParseErrorMsgs;
+
+static void TestParseErrorHandleError(const char* aMsg) {
+ gTestParseErrorMsgs.Append(aMsg);
+ gTestParseErrorMsgs.Append('\n');
+}
+
+// Keep this in sync with the declaration in test/gtest/Parser.cpp.
+void TestParseError(PrefValueKind aKind, const char* aText,
+ nsCString& aErrorMsg) {
+ prefs_parser_parse("test", aKind, aText, strlen(aText),
+ TestParseErrorHandlePref, TestParseErrorHandleError);
+
+ // Copy the error messages into the outparam, then clear them from
+ // gTestParseErrorMsgs.
+ aErrorMsg.Assign(gTestParseErrorMsgs);
+ gTestParseErrorMsgs.Truncate();
+}
+
+//===========================================================================
+// nsPrefBranch et al.
+//===========================================================================
+
+namespace mozilla {
+class PreferenceServiceReporter;
+} // namespace mozilla
+
+class PrefCallback : public PLDHashEntryHdr {
+ friend class mozilla::PreferenceServiceReporter;
+
+ public:
+ typedef PrefCallback* KeyType;
+ typedef const PrefCallback* KeyTypePointer;
+
+ static const PrefCallback* KeyToPointer(PrefCallback* aKey) { return aKey; }
+
+ static PLDHashNumber HashKey(const PrefCallback* aKey) {
+ uint32_t hash = HashString(aKey->mDomain);
+ return AddToHash(hash, aKey->mCanonical);
+ }
+
+ public:
+ // Create a PrefCallback with a strong reference to its observer.
+ PrefCallback(const nsACString& aDomain, nsIObserver* aObserver,
+ nsPrefBranch* aBranch)
+ : mDomain(aDomain),
+ mBranch(aBranch),
+ mWeakRef(nullptr),
+ mStrongRef(aObserver) {
+ MOZ_COUNT_CTOR(PrefCallback);
+ nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver);
+ mCanonical = canonical;
+ }
+
+ // Create a PrefCallback with a weak reference to its observer.
+ PrefCallback(const nsACString& aDomain, nsISupportsWeakReference* aObserver,
+ nsPrefBranch* aBranch)
+ : mDomain(aDomain),
+ mBranch(aBranch),
+ mWeakRef(do_GetWeakReference(aObserver)),
+ mStrongRef(nullptr) {
+ MOZ_COUNT_CTOR(PrefCallback);
+ nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver);
+ mCanonical = canonical;
+ }
+
+ // This is explicitly not a copy constructor.
+ explicit PrefCallback(const PrefCallback*& aCopy)
+ : mDomain(aCopy->mDomain),
+ mBranch(aCopy->mBranch),
+ mWeakRef(aCopy->mWeakRef),
+ mStrongRef(aCopy->mStrongRef),
+ mCanonical(aCopy->mCanonical) {
+ MOZ_COUNT_CTOR(PrefCallback);
+ }
+
+ PrefCallback(const PrefCallback&) = delete;
+ PrefCallback(PrefCallback&&) = default;
+
+ MOZ_COUNTED_DTOR(PrefCallback)
+
+ bool KeyEquals(const PrefCallback* aKey) const {
+ // We want to be able to look up a weakly-referencing PrefCallback after
+ // its observer has died so we can remove it from the table. Once the
+ // callback's observer dies, its canonical pointer is stale -- in
+ // particular, we may have allocated a new observer in the same spot in
+ // memory! So we can't just compare canonical pointers to determine whether
+ // aKey refers to the same observer as this.
+ //
+ // Our workaround is based on the way we use this hashtable: When we ask
+ // the hashtable to remove a PrefCallback whose weak reference has expired,
+ // we use as the key for removal the same object as was inserted into the
+ // hashtable. Thus we can say that if one of the keys' weak references has
+ // expired, the two keys are equal iff they're the same object.
+
+ if (IsExpired() || aKey->IsExpired()) {
+ return this == aKey;
+ }
+
+ if (mCanonical != aKey->mCanonical) {
+ return false;
+ }
+
+ return mDomain.Equals(aKey->mDomain);
+ }
+
+ PrefCallback* GetKey() const { return const_cast<PrefCallback*>(this); }
+
+ // Get a reference to the callback's observer, or null if the observer was
+ // weakly referenced and has been destroyed.
+ already_AddRefed<nsIObserver> GetObserver() const {
+ if (!IsWeak()) {
+ nsCOMPtr<nsIObserver> copy = mStrongRef;
+ return copy.forget();
+ }
+
+ nsCOMPtr<nsIObserver> observer = do_QueryReferent(mWeakRef);
+ return observer.forget();
+ }
+
+ const nsCString& GetDomain() const { return mDomain; }
+
+ nsPrefBranch* GetPrefBranch() const { return mBranch; }
+
+ // Has this callback's weak reference died?
+ bool IsExpired() const {
+ if (!IsWeak()) return false;
+
+ nsCOMPtr<nsIObserver> observer(do_QueryReferent(mWeakRef));
+ return !observer;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ n += mDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+ // All the other fields are non-owning pointers, so we don't measure them.
+
+ return n;
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ nsCString mDomain;
+ nsPrefBranch* mBranch;
+
+ // Exactly one of mWeakRef and mStrongRef should be non-null.
+ nsWeakPtr mWeakRef;
+ nsCOMPtr<nsIObserver> mStrongRef;
+
+ // We need a canonical nsISupports pointer, per bug 578392.
+ nsISupports* mCanonical;
+
+ bool IsWeak() const { return !!mWeakRef; }
+};
+
+class nsPrefBranch final : public nsIPrefBranch,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ friend class mozilla::PreferenceServiceReporter;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPREFBRANCH
+ NS_DECL_NSIOBSERVER
+
+ nsPrefBranch(const char* aPrefRoot, PrefValueKind aKind);
+ nsPrefBranch() = delete;
+
+ static void NotifyObserver(const char* aNewpref, void* aData);
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ using PrefName = nsCString;
+
+ virtual ~nsPrefBranch();
+
+ int32_t GetRootLength() const { return mPrefRoot.Length(); }
+
+ nsresult GetDefaultFromPropertiesFile(const char* aPrefName,
+ nsAString& aReturn);
+
+ // As SetCharPref, but without any check on the length of |aValue|.
+ nsresult SetCharPrefNoLengthCheck(const char* aPrefName,
+ const nsACString& aValue);
+
+ // Reject strings that are more than 1Mb, warn if strings are more than 16kb.
+ nsresult CheckSanityOfStringLength(const char* aPrefName,
+ const nsAString& aValue);
+ nsresult CheckSanityOfStringLength(const char* aPrefName,
+ const nsACString& aValue);
+ nsresult CheckSanityOfStringLength(const char* aPrefName,
+ const uint32_t aLength);
+
+ void RemoveExpiredCallback(PrefCallback* aCallback);
+
+ PrefName GetPrefName(const char* aPrefName) const {
+ return GetPrefName(nsDependentCString(aPrefName));
+ }
+
+ PrefName GetPrefName(const nsACString& aPrefName) const;
+
+ void FreeObserverList(void);
+
+ const nsCString mPrefRoot;
+ PrefValueKind mKind;
+
+ bool mFreeingObserverList;
+ nsClassHashtable<PrefCallback, PrefCallback> mObservers;
+};
+
+class nsPrefLocalizedString final : public nsIPrefLocalizedString {
+ public:
+ nsPrefLocalizedString();
+
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSISUPPORTSPRIMITIVE(mUnicodeString->)
+ NS_FORWARD_NSISUPPORTSSTRING(mUnicodeString->)
+
+ nsresult Init();
+
+ private:
+ virtual ~nsPrefLocalizedString();
+
+ nsCOMPtr<nsISupportsString> mUnicodeString;
+};
+
+//----------------------------------------------------------------------------
+// nsPrefBranch
+//----------------------------------------------------------------------------
+
+nsPrefBranch::nsPrefBranch(const char* aPrefRoot, PrefValueKind aKind)
+ : mPrefRoot(aPrefRoot),
+ mKind(aKind),
+ mFreeingObserverList(false),
+ mObservers() {
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ ++mRefCnt; // must be > 0 when we call this, or we'll get deleted!
+
+ // Add weakly so we don't have to clean up at shutdown.
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ --mRefCnt;
+ }
+}
+
+nsPrefBranch::~nsPrefBranch() { FreeObserverList(); }
+
+NS_IMPL_ISUPPORTS(nsPrefBranch, nsIPrefBranch, nsIObserver,
+ nsISupportsWeakReference)
+
+NS_IMETHODIMP
+nsPrefBranch::GetRoot(nsACString& aRoot) {
+ aRoot = mPrefRoot;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetPrefType(const char* aPrefName, int32_t* aRetVal) {
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& prefName = GetPrefName(aPrefName);
+ *aRetVal = Preferences::GetType(prefName.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetBoolPrefWithDefault(const char* aPrefName, bool aDefaultValue,
+ uint8_t aArgc, bool* aRetVal) {
+ nsresult rv = GetBoolPref(aPrefName, aRetVal);
+ if (NS_FAILED(rv) && aArgc == 1) {
+ *aRetVal = aDefaultValue;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetBoolPref(const char* aPrefName, bool* aRetVal) {
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ return Preferences::GetBool(pref.get(), aRetVal, mKind);
+}
+
+NS_IMETHODIMP
+nsPrefBranch::SetBoolPref(const char* aPrefName, bool aValue) {
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ return Preferences::SetBool(pref.get(), aValue, mKind);
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetFloatPrefWithDefault(const char* aPrefName,
+ float aDefaultValue, uint8_t aArgc,
+ float* aRetVal) {
+ nsresult rv = GetFloatPref(aPrefName, aRetVal);
+
+ if (NS_FAILED(rv) && aArgc == 1) {
+ *aRetVal = aDefaultValue;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetFloatPref(const char* aPrefName, float* aRetVal) {
+ NS_ENSURE_ARG(aPrefName);
+
+ nsAutoCString stringVal;
+ nsresult rv = GetCharPref(aPrefName, stringVal);
+ if (NS_SUCCEEDED(rv)) {
+ // ParsePrefFloat() does a locale-independent conversion.
+ *aRetVal = ParsePrefFloat(stringVal, &rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetCharPrefWithDefault(const char* aPrefName,
+ const nsACString& aDefaultValue,
+ uint8_t aArgc, nsACString& aRetVal) {
+ nsresult rv = GetCharPref(aPrefName, aRetVal);
+
+ if (NS_FAILED(rv) && aArgc == 1) {
+ aRetVal = aDefaultValue;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetCharPref(const char* aPrefName, nsACString& aRetVal) {
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ return Preferences::GetCString(pref.get(), aRetVal, mKind);
+}
+
+NS_IMETHODIMP
+nsPrefBranch::SetCharPref(const char* aPrefName, const nsACString& aValue) {
+ nsresult rv = CheckSanityOfStringLength(aPrefName, aValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return SetCharPrefNoLengthCheck(aPrefName, aValue);
+}
+
+nsresult nsPrefBranch::SetCharPrefNoLengthCheck(const char* aPrefName,
+ const nsACString& aValue) {
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ return Preferences::SetCString(pref.get(), aValue, mKind);
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetStringPref(const char* aPrefName,
+ const nsACString& aDefaultValue, uint8_t aArgc,
+ nsACString& aRetVal) {
+ nsCString utf8String;
+ nsresult rv = GetCharPref(aPrefName, utf8String);
+ if (NS_SUCCEEDED(rv)) {
+ aRetVal = utf8String;
+ return rv;
+ }
+
+ if (aArgc == 1) {
+ aRetVal = aDefaultValue;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::SetStringPref(const char* aPrefName, const nsACString& aValue) {
+ nsresult rv = CheckSanityOfStringLength(aPrefName, aValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return SetCharPrefNoLengthCheck(aPrefName, aValue);
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetIntPrefWithDefault(const char* aPrefName,
+ int32_t aDefaultValue, uint8_t aArgc,
+ int32_t* aRetVal) {
+ nsresult rv = GetIntPref(aPrefName, aRetVal);
+
+ if (NS_FAILED(rv) && aArgc == 1) {
+ *aRetVal = aDefaultValue;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetIntPref(const char* aPrefName, int32_t* aRetVal) {
+ NS_ENSURE_ARG(aPrefName);
+ const PrefName& pref = GetPrefName(aPrefName);
+ return Preferences::GetInt(pref.get(), aRetVal, mKind);
+}
+
+NS_IMETHODIMP
+nsPrefBranch::SetIntPref(const char* aPrefName, int32_t aValue) {
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ return Preferences::SetInt(pref.get(), aValue, mKind);
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetComplexValue(const char* aPrefName, const nsIID& aType,
+ void** aRetVal) {
+ NS_ENSURE_ARG(aPrefName);
+
+ nsresult rv;
+ nsAutoCString utf8String;
+
+ // We have to do this one first because it's different to all the rest.
+ if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) {
+ nsCOMPtr<nsIPrefLocalizedString> theString(
+ do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ bool bNeedDefault = false;
+
+ if (mKind == PrefValueKind::Default) {
+ bNeedDefault = true;
+ } else {
+ // if there is no user (or locked) value
+ if (!Preferences::HasUserValue(pref.get()) &&
+ !Preferences::IsLocked(pref.get())) {
+ bNeedDefault = true;
+ }
+ }
+
+ // if we need to fetch the default value, do that instead, otherwise use the
+ // value we pulled in at the top of this function
+ if (bNeedDefault) {
+ nsAutoString utf16String;
+ rv = GetDefaultFromPropertiesFile(pref.get(), utf16String);
+ if (NS_SUCCEEDED(rv)) {
+ theString->SetData(utf16String);
+ }
+ } else {
+ rv = GetCharPref(aPrefName, utf8String);
+ if (NS_SUCCEEDED(rv)) {
+ theString->SetData(NS_ConvertUTF8toUTF16(utf8String));
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ theString.forget(reinterpret_cast<nsIPrefLocalizedString**>(aRetVal));
+ }
+
+ return rv;
+ }
+
+ // if we can't get the pref, there's no point in being here
+ rv = GetCharPref(aPrefName, utf8String);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aType.Equals(NS_GET_IID(nsIFile))) {
+ ENSURE_PARENT_PROCESS("GetComplexValue(nsIFile)", aPrefName);
+
+ nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = file->SetPersistentDescriptor(utf8String);
+ if (NS_SUCCEEDED(rv)) {
+ file.forget(reinterpret_cast<nsIFile**>(aRetVal));
+ return NS_OK;
+ }
+ }
+ return rv;
+ }
+
+ if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) {
+ ENSURE_PARENT_PROCESS("GetComplexValue(nsIRelativeFilePref)", aPrefName);
+
+ nsACString::const_iterator keyBegin, strEnd;
+ utf8String.BeginReading(keyBegin);
+ utf8String.EndReading(strEnd);
+
+ // The pref has the format: [fromKey]a/b/c
+ if (*keyBegin++ != '[') {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsACString::const_iterator keyEnd(keyBegin);
+ if (!FindCharInReadable(']', keyEnd, strEnd)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString key(Substring(keyBegin, keyEnd));
+
+ nsCOMPtr<nsIFile> fromFile;
+ nsCOMPtr<nsIProperties> directoryService(
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = directoryService->Get(key.get(), NS_GET_IID(nsIFile),
+ getter_AddRefs(fromFile));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> theFile;
+ rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(theFile));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = theFile->SetRelativeDescriptor(fromFile, Substring(++keyEnd, strEnd));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIRelativeFilePref> relativePref = new nsRelativeFilePref();
+ Unused << relativePref->SetFile(theFile);
+ Unused << relativePref->SetRelativeToKey(key);
+
+ relativePref.forget(reinterpret_cast<nsIRelativeFilePref**>(aRetVal));
+ return NS_OK;
+ }
+
+ NS_WARNING("nsPrefBranch::GetComplexValue - Unsupported interface type");
+ return NS_NOINTERFACE;
+}
+
+nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName,
+ const nsAString& aValue) {
+ return CheckSanityOfStringLength(aPrefName, aValue.Length());
+}
+
+nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName,
+ const nsACString& aValue) {
+ return CheckSanityOfStringLength(aPrefName, aValue.Length());
+}
+
+nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName,
+ const uint32_t aLength) {
+ if (aLength > MAX_PREF_LENGTH) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (aLength <= MAX_ADVISABLE_PREF_LENGTH) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIConsoleService> console =
+ do_GetService("@mozilla.org/consoleservice;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString message(nsPrintfCString(
+ "Warning: attempting to write %d bytes to preference %s. This is bad "
+ "for general performance and memory usage. Such an amount of data "
+ "should rather be written to an external file.",
+ aLength, GetPrefName(aPrefName).get()));
+
+ rv = console->LogStringMessage(NS_ConvertUTF8toUTF16(message).get());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::SetComplexValue(const char* aPrefName, const nsIID& aType,
+ nsISupports* aValue) {
+ ENSURE_PARENT_PROCESS("SetComplexValue", aPrefName);
+ NS_ENSURE_ARG(aPrefName);
+
+ nsresult rv = NS_NOINTERFACE;
+
+ if (aType.Equals(NS_GET_IID(nsIFile))) {
+ nsCOMPtr<nsIFile> file = do_QueryInterface(aValue);
+ if (!file) {
+ return NS_NOINTERFACE;
+ }
+
+ nsAutoCString descriptorString;
+ rv = file->GetPersistentDescriptor(descriptorString);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetCharPrefNoLengthCheck(aPrefName, descriptorString);
+ }
+ return rv;
+ }
+
+ if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) {
+ nsCOMPtr<nsIRelativeFilePref> relFilePref = do_QueryInterface(aValue);
+ if (!relFilePref) {
+ return NS_NOINTERFACE;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ relFilePref->GetFile(getter_AddRefs(file));
+ if (!file) {
+ return NS_NOINTERFACE;
+ }
+
+ nsAutoCString relativeToKey;
+ (void)relFilePref->GetRelativeToKey(relativeToKey);
+
+ nsCOMPtr<nsIFile> relativeToFile;
+ nsCOMPtr<nsIProperties> directoryService(
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = directoryService->Get(relativeToKey.get(), NS_GET_IID(nsIFile),
+ getter_AddRefs(relativeToFile));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString relDescriptor;
+ rv = file->GetRelativeDescriptor(relativeToFile, relDescriptor);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString descriptorString;
+ descriptorString.Append('[');
+ descriptorString.Append(relativeToKey);
+ descriptorString.Append(']');
+ descriptorString.Append(relDescriptor);
+ return SetCharPrefNoLengthCheck(aPrefName, descriptorString);
+ }
+
+ if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) {
+ nsCOMPtr<nsISupportsString> theString = do_QueryInterface(aValue);
+
+ if (theString) {
+ nsString wideString;
+
+ rv = theString->GetData(wideString);
+ if (NS_SUCCEEDED(rv)) {
+ // Check sanity of string length before any lengthy conversion
+ rv = CheckSanityOfStringLength(aPrefName, wideString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = SetCharPrefNoLengthCheck(aPrefName,
+ NS_ConvertUTF16toUTF8(wideString));
+ }
+ }
+ return rv;
+ }
+
+ NS_WARNING("nsPrefBranch::SetComplexValue - Unsupported interface type");
+ return NS_NOINTERFACE;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::ClearUserPref(const char* aPrefName) {
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ return Preferences::ClearUser(pref.get());
+}
+
+NS_IMETHODIMP
+nsPrefBranch::PrefHasUserValue(const char* aPrefName, bool* aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ *aRetVal = Preferences::HasUserValue(pref.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::PrefHasDefaultValue(const char* aPrefName, bool* aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ *aRetVal = Preferences::HasDefaultValue(pref.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::LockPref(const char* aPrefName) {
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ return Preferences::Lock(pref.get());
+}
+
+NS_IMETHODIMP
+nsPrefBranch::PrefIsLocked(const char* aPrefName, bool* aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ *aRetVal = Preferences::IsLocked(pref.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::PrefIsSanitized(const char* aPrefName, bool* aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ *aRetVal = Preferences::IsSanitized(pref.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::UnlockPref(const char* aPrefName) {
+ NS_ENSURE_ARG(aPrefName);
+
+ const PrefName& pref = GetPrefName(aPrefName);
+ return Preferences::Unlock(pref.get());
+}
+
+NS_IMETHODIMP
+nsPrefBranch::ResetBranch(const char* aStartingAt) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::DeleteBranch(const char* aStartingAt) {
+ ENSURE_PARENT_PROCESS("DeleteBranch", aStartingAt);
+ NS_ENSURE_ARG(aStartingAt);
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!HashTable()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ const PrefName& pref = GetPrefName(aStartingAt);
+ nsAutoCString branchName(pref.get());
+
+ // Add a trailing '.' if it doesn't already have one.
+ if (branchName.Length() > 1 && !StringEndsWith(branchName, "."_ns)) {
+ branchName += '.';
+ }
+
+ const nsACString& branchNameNoDot =
+ Substring(branchName, 0, branchName.Length() - 1);
+
+ for (auto iter = HashTable()->modIter(); !iter.done(); iter.next()) {
+ // The first disjunct matches branches: e.g. a branch name "foo.bar."
+ // matches a name "foo.bar.baz" (but it won't match "foo.barrel.baz").
+ // The second disjunct matches leaf nodes: e.g. a branch name "foo.bar."
+ // matches a name "foo.bar" (by ignoring the trailing '.').
+ nsDependentCString name(iter.get()->Name());
+ if (StringBeginsWith(name, branchName) || name.Equals(branchNameNoDot)) {
+ iter.remove();
+ // The saved callback pref may be invalid now.
+ gCallbackPref = nullptr;
+ }
+ }
+
+ Preferences::HandleDirty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::GetChildList(const char* aStartingAt,
+ nsTArray<nsCString>& aChildArray) {
+ NS_ENSURE_ARG(aStartingAt);
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // This will contain a list of all the pref name strings. Allocated on the
+ // stack for speed.
+ AutoTArray<nsCString, 32> prefArray;
+
+ const PrefName& parent = GetPrefName(aStartingAt);
+ size_t parentLen = parent.Length();
+ for (auto& pref : PrefsIter(HashTable(), gSharedMap)) {
+ if (strncmp(pref->Name(), parent.get(), parentLen) == 0) {
+ prefArray.AppendElement(pref->NameString());
+ }
+ }
+
+ // Now that we've built up the list, run the callback on all the matching
+ // elements.
+ aChildArray.SetCapacity(prefArray.Length());
+ for (auto& element : prefArray) {
+ // we need to lop off mPrefRoot in case the user is planning to pass this
+ // back to us because if they do we are going to add mPrefRoot again.
+ aChildArray.AppendElement(Substring(element, mPrefRoot.Length()));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::AddObserverImpl(const nsACString& aDomain, nsIObserver* aObserver,
+ bool aHoldWeak) {
+ UniquePtr<PrefCallback> pCallback;
+
+ NS_ENSURE_ARG(aObserver);
+
+ const nsCString& prefName = GetPrefName(aDomain);
+
+ // Hold a weak reference to the observer if so requested.
+ if (aHoldWeak) {
+ nsCOMPtr<nsISupportsWeakReference> weakRefFactory =
+ do_QueryInterface(aObserver);
+ if (!weakRefFactory) {
+ // The caller didn't give us a object that supports weak reference...
+ // tell them.
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Construct a PrefCallback with a weak reference to the observer.
+ pCallback = MakeUnique<PrefCallback>(prefName, weakRefFactory, this);
+
+ } else {
+ // Construct a PrefCallback with a strong reference to the observer.
+ pCallback = MakeUnique<PrefCallback>(prefName, aObserver, this);
+ }
+
+ mObservers.WithEntryHandle(pCallback.get(), [&](auto&& p) {
+ if (p) {
+ NS_WARNING("Ignoring duplicate observer.");
+ } else {
+ // We must pass a fully qualified preference name to the callback
+ // aDomain == nullptr is the only possible failure, and we trapped it with
+ // NS_ENSURE_ARG above.
+ Preferences::RegisterCallback(NotifyObserver, prefName, pCallback.get(),
+ Preferences::PrefixMatch,
+ /* isPriority */ false);
+
+ p.Insert(std::move(pCallback));
+ }
+ });
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::RemoveObserverImpl(const nsACString& aDomain,
+ nsIObserver* aObserver) {
+ NS_ENSURE_ARG(aObserver);
+
+ nsresult rv = NS_OK;
+
+ // If we're in the middle of a call to FreeObserverList, don't process this
+ // RemoveObserver call -- the observer in question will be removed soon, if
+ // it hasn't been already.
+ //
+ // It's important that we don't touch mObservers in any way -- even a Get()
+ // which returns null might cause the hashtable to resize itself, which will
+ // break the iteration in FreeObserverList.
+ if (mFreeingObserverList) {
+ return NS_OK;
+ }
+
+ // Remove the relevant PrefCallback from mObservers and get an owning pointer
+ // to it. Unregister the callback first, and then let the owning pointer go
+ // out of scope and destroy the callback.
+ const nsCString& prefName = GetPrefName(aDomain);
+ PrefCallback key(prefName, aObserver, this);
+ mozilla::UniquePtr<PrefCallback> pCallback;
+ mObservers.Remove(&key, &pCallback);
+ if (pCallback) {
+ rv = Preferences::UnregisterCallback(
+ NotifyObserver, prefName, pCallback.get(), Preferences::PrefixMatch);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrefBranch::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // Watch for xpcom shutdown and free our observers to eliminate any cyclic
+ // references.
+ if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ FreeObserverList();
+ }
+ return NS_OK;
+}
+
+/* static */
+void nsPrefBranch::NotifyObserver(const char* aNewPref, void* aData) {
+ PrefCallback* pCallback = (PrefCallback*)aData;
+
+ nsCOMPtr<nsIObserver> observer = pCallback->GetObserver();
+ if (!observer) {
+ // The observer has expired. Let's remove this callback.
+ pCallback->GetPrefBranch()->RemoveExpiredCallback(pCallback);
+ return;
+ }
+
+ // Remove any root this string may contain so as to not confuse the observer
+ // by passing them something other than what they passed us as a topic.
+ uint32_t len = pCallback->GetPrefBranch()->GetRootLength();
+ nsDependentCString suffix(aNewPref + len);
+
+ observer->Observe(static_cast<nsIPrefBranch*>(pCallback->GetPrefBranch()),
+ NS_PREFBRANCH_PREFCHANGE_TOPIC_ID,
+ NS_ConvertASCIItoUTF16(suffix).get());
+}
+
+size_t nsPrefBranch::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ n += mPrefRoot.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+ n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& entry : mObservers) {
+ const PrefCallback* data = entry.GetWeak();
+ n += data->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+void nsPrefBranch::FreeObserverList() {
+ // We need to prevent anyone from modifying mObservers while we're iterating
+ // over it. In particular, some clients will call RemoveObserver() when
+ // they're removed and destructed via the iterator; we set
+ // mFreeingObserverList to keep those calls from touching mObservers.
+ mFreeingObserverList = true;
+ for (auto iter = mObservers.Iter(); !iter.Done(); iter.Next()) {
+ auto callback = iter.UserData();
+ Preferences::UnregisterCallback(nsPrefBranch::NotifyObserver,
+ callback->GetDomain(), callback,
+ Preferences::PrefixMatch);
+ iter.Remove();
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ mFreeingObserverList = false;
+}
+
+void nsPrefBranch::RemoveExpiredCallback(PrefCallback* aCallback) {
+ MOZ_ASSERT(aCallback->IsExpired());
+ mObservers.Remove(aCallback);
+}
+
+nsresult nsPrefBranch::GetDefaultFromPropertiesFile(const char* aPrefName,
+ nsAString& aReturn) {
+ // The default value contains a URL to a .properties file.
+
+ nsAutoCString propertyFileURL;
+ nsresult rv = Preferences::GetCString(aPrefName, propertyFileURL,
+ PrefValueKind::Default);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ components::StringBundle::Service();
+ if (!bundleService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(propertyFileURL.get(),
+ getter_AddRefs(bundle));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return bundle->GetStringFromName(aPrefName, aReturn);
+}
+
+nsPrefBranch::PrefName nsPrefBranch::GetPrefName(
+ const nsACString& aPrefName) const {
+ if (mPrefRoot.IsEmpty()) {
+ return PrefName(PromiseFlatCString(aPrefName));
+ }
+
+ return PrefName(mPrefRoot + aPrefName);
+}
+
+//----------------------------------------------------------------------------
+// nsPrefLocalizedString
+//----------------------------------------------------------------------------
+
+nsPrefLocalizedString::nsPrefLocalizedString() = default;
+
+nsPrefLocalizedString::~nsPrefLocalizedString() = default;
+
+NS_IMPL_ISUPPORTS(nsPrefLocalizedString, nsIPrefLocalizedString,
+ nsISupportsString)
+
+nsresult nsPrefLocalizedString::Init() {
+ nsresult rv;
+ mUnicodeString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+// nsRelativeFilePref
+//----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsRelativeFilePref, nsIRelativeFilePref)
+
+nsRelativeFilePref::nsRelativeFilePref() = default;
+
+nsRelativeFilePref::~nsRelativeFilePref() = default;
+
+NS_IMETHODIMP
+nsRelativeFilePref::GetFile(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = mFile;
+ NS_IF_ADDREF(*aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRelativeFilePref::SetFile(nsIFile* aFile) {
+ mFile = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRelativeFilePref::GetRelativeToKey(nsACString& aRelativeToKey) {
+ aRelativeToKey.Assign(mRelativeToKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRelativeFilePref::SetRelativeToKey(const nsACString& aRelativeToKey) {
+ mRelativeToKey.Assign(aRelativeToKey);
+ return NS_OK;
+}
+
+//===========================================================================
+// class Preferences and related things
+//===========================================================================
+
+namespace mozilla {
+
+#define INITIAL_PREF_FILES 10
+
+void Preferences::HandleDirty() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!HashTable() || !sPreferences) {
+ return;
+ }
+
+ if (sPreferences->mProfileShutdown) {
+ NS_WARNING("Setting user pref after profile shutdown.");
+ return;
+ }
+
+ if (!sPreferences->mDirty) {
+ sPreferences->mDirty = true;
+
+ if (sPreferences->mCurrentFile && sPreferences->AllowOffMainThreadSave() &&
+ !sPreferences->mSavePending) {
+ sPreferences->mSavePending = true;
+ static const int PREF_DELAY_MS = 500;
+ NS_DelayedDispatchToCurrentThread(
+ NewRunnableMethod("Preferences::SavePrefFileAsynchronous",
+ sPreferences.get(),
+ &Preferences::SavePrefFileAsynchronous),
+ PREF_DELAY_MS);
+ }
+ }
+}
+
+static nsresult openPrefFile(nsIFile* aFile, PrefValueKind aKind);
+
+static nsresult parsePrefData(const nsCString& aData, PrefValueKind aKind);
+
+// clang-format off
+static const char kPrefFileHeader[] =
+ "// Mozilla User Preferences"
+ NS_LINEBREAK
+ NS_LINEBREAK
+ "// DO NOT EDIT THIS FILE."
+ NS_LINEBREAK
+ "//"
+ NS_LINEBREAK
+ "// If you make changes to this file while the application is running,"
+ NS_LINEBREAK
+ "// the changes will be overwritten when the application exits."
+ NS_LINEBREAK
+ "//"
+ NS_LINEBREAK
+ "// To change a preference value, you can either:"
+ NS_LINEBREAK
+ "// - modify it via the UI (e.g. via about:config in the browser); or"
+ NS_LINEBREAK
+ "// - set it within a user.js file in your profile."
+ NS_LINEBREAK
+ NS_LINEBREAK;
+// clang-format on
+
+// Note: if sShutdown is true, sPreferences will be nullptr.
+StaticRefPtr<Preferences> Preferences::sPreferences;
+bool Preferences::sShutdown = false;
+
+// This globally enables or disables OMT pref writing, both sync and async.
+static int32_t sAllowOMTPrefWrite = -1;
+
+// Write the preference data to a file.
+class PreferencesWriter final {
+ public:
+ PreferencesWriter() = default;
+
+ static nsresult Write(nsIFile* aFile, PrefSaveData& aPrefs) {
+ nsCOMPtr<nsIOutputStream> outStreamSink;
+ nsCOMPtr<nsIOutputStream> outStream;
+ uint32_t writeAmount;
+ nsresult rv;
+
+ // Execute a "safe" save by saving through a tempfile.
+ rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink), aFile,
+ -1, 0600);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream),
+ outStreamSink.forget(), 4096);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ struct CharComparator {
+ bool LessThan(const nsCString& aA, const nsCString& aB) const {
+ return aA < aB;
+ }
+
+ bool Equals(const nsCString& aA, const nsCString& aB) const {
+ return aA == aB;
+ }
+ };
+
+ // Sort the preferences to make a readable file on disk.
+ aPrefs.Sort(CharComparator());
+
+ // Write out the file header.
+ outStream->Write(kPrefFileHeader, sizeof(kPrefFileHeader) - 1,
+ &writeAmount);
+
+ for (nsCString& pref : aPrefs) {
+ outStream->Write(pref.get(), pref.Length(), &writeAmount);
+ outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount);
+ }
+
+ // Tell the safe output stream to overwrite the real prefs file.
+ // (It'll abort if there were any errors during writing.)
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream);
+ MOZ_ASSERT(safeStream, "expected a safe output stream!");
+ if (safeStream) {
+ rv = safeStream->Finish();
+ }
+
+#ifdef DEBUG
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to save prefs file! possible data loss");
+ }
+#endif
+
+ return rv;
+ }
+
+ static void Flush() {
+ MOZ_DIAGNOSTIC_ASSERT(sPendingWriteCount >= 0);
+ // SpinEventLoopUntil is unfortunate, but ultimately it's the best thing
+ // we can do here given the constraint that we need to ensure that
+ // the preferences on disk match what we have in memory. We could
+ // easily perform the write here ourselves by doing exactly what
+ // happens in PWRunnable::Run. This would be the right thing to do
+ // if we're stuck here because other unrelated runnables are taking
+ // a long time, and the wrong thing to do if PreferencesWriter::Write
+ // is what takes a long time, as we would be trading a SpinEventLoopUntil
+ // for a synchronous disk write, wherein we could not even spin the
+ // event loop. Given that PWRunnable generally runs on a thread pool,
+ // if we're stuck here, it's likely because of PreferencesWriter::Write
+ // and not some other runnable. Thus, spin away.
+ mozilla::SpinEventLoopUntil("PreferencesWriter::Flush"_ns,
+ []() { return sPendingWriteCount <= 0; });
+ }
+
+ // This is the data that all of the runnables (see below) will attempt
+ // to write. It will always have the most up to date version, or be
+ // null, if the up to date information has already been written out.
+ static Atomic<PrefSaveData*> sPendingWriteData;
+
+ // This is the number of writes via PWRunnables which have been dispatched
+ // but not yet completed. This is intended to be used by Flush to ensure
+ // that there are no outstanding writes left incomplete, and thus our prefs
+ // on disk are in sync with what we have in memory.
+ static Atomic<int> sPendingWriteCount;
+
+ // See PWRunnable::Run for details on why we need this lock.
+ static StaticMutex sWritingToFile MOZ_UNANNOTATED;
+};
+
+Atomic<PrefSaveData*> PreferencesWriter::sPendingWriteData(nullptr);
+Atomic<int> PreferencesWriter::sPendingWriteCount(0);
+StaticMutex PreferencesWriter::sWritingToFile;
+
+class PWRunnable : public Runnable {
+ public:
+ explicit PWRunnable(nsIFile* aFile) : Runnable("PWRunnable"), mFile(aFile) {}
+
+ NS_IMETHOD Run() override {
+ // Preference writes are handled a bit strangely, in that a "newer"
+ // write is generally regarded as always better. For this reason,
+ // sPendingWriteData can be overwritten multiple times before anyone
+ // gets around to actually using it, minimizing writes. However,
+ // once we've acquired sPendingWriteData we've reached a
+ // "point of no return" and have to complete the write.
+ //
+ // Unfortunately, this design allows the following behaviour:
+ //
+ // 1. write1 is queued up
+ // 2. thread1 acquires write1
+ // 3. write2 is queued up
+ // 4. thread2 acquires write2
+ // 5. thread1 and thread2 concurrently clobber each other
+ //
+ // To avoid this, we use this lock to ensure that only one thread
+ // at a time is trying to acquire the write, and when it does,
+ // all other threads are prevented from acquiring writes until it
+ // completes the write. New writes are still allowed to be queued
+ // up in this time.
+ //
+ // Although it's atomic, the acquire needs to be guarded by the mutex
+ // to avoid reordering of writes -- we don't want an older write to
+ // run after a newer one. To avoid this causing too much waiting, we check
+ // if sPendingWriteData is already null before acquiring the mutex. If it
+ // is, then there's definitely no work to be done (or someone is in the
+ // middle of doing it for us).
+ //
+ // Note that every time a new write is queued up, a new write task is
+ // is also queued up, so there will always be a task that can see the newest
+ // write.
+ //
+ // Ideally this lock wouldn't be necessary, and the PreferencesWriter
+ // would be used more carefully, but it's hard to untangle all that.
+ nsresult rv = NS_OK;
+ if (PreferencesWriter::sPendingWriteData) {
+ StaticMutexAutoLock lock(PreferencesWriter::sWritingToFile);
+ // If we get a nullptr on the exchange, it means that somebody
+ // else has already processed the request, and we can just return.
+ UniquePtr<PrefSaveData> prefs(
+ PreferencesWriter::sPendingWriteData.exchange(nullptr));
+ if (prefs) {
+ rv = PreferencesWriter::Write(mFile, *prefs);
+ // Make a copy of these so we can have them in runnable lambda.
+ // nsIFile is only there so that we would never release the
+ // ref counted pointer off main thread.
+ nsresult rvCopy = rv;
+ nsCOMPtr<nsIFile> fileCopy(mFile);
+ SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction("Preferences::WriterRunnable",
+ [fileCopy, rvCopy] {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (NS_FAILED(rvCopy)) {
+ Preferences::HandleDirty();
+ }
+ }));
+ }
+ }
+ // We've completed the write to the best of our abilities, whether
+ // we had prefs to write or another runnable got to them first. If
+ // PreferencesWriter::Write failed, this is still correct as the
+ // write is no longer outstanding, and the above HandleDirty call
+ // will just start the cycle again.
+ PreferencesWriter::sPendingWriteCount--;
+ return rv;
+ }
+
+ protected:
+ nsCOMPtr<nsIFile> mFile;
+};
+
+// Although this is a member of Preferences, it measures sPreferences and
+// several other global structures.
+/* static */
+void Preferences::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ PrefsSizes& aSizes) {
+ if (!sPreferences) {
+ return;
+ }
+
+ aSizes.mMisc += aMallocSizeOf(sPreferences.get());
+
+ aSizes.mRootBranches +=
+ static_cast<nsPrefBranch*>(sPreferences->mRootBranch.get())
+ ->SizeOfIncludingThis(aMallocSizeOf) +
+ static_cast<nsPrefBranch*>(sPreferences->mDefaultRootBranch.get())
+ ->SizeOfIncludingThis(aMallocSizeOf);
+}
+
+class PreferenceServiceReporter final : public nsIMemoryReporter {
+ ~PreferenceServiceReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ protected:
+ static const uint32_t kSuspectReferentCount = 1000;
+};
+
+NS_IMPL_ISUPPORTS(PreferenceServiceReporter, nsIMemoryReporter)
+
+MOZ_DEFINE_MALLOC_SIZE_OF(PreferenceServiceMallocSizeOf)
+
+NS_IMETHODIMP
+PreferenceServiceReporter::CollectReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MallocSizeOf mallocSizeOf = PreferenceServiceMallocSizeOf;
+ PrefsSizes sizes;
+
+ Preferences::AddSizeOfIncludingThis(mallocSizeOf, sizes);
+
+ if (HashTable()) {
+ sizes.mHashTable += HashTable()->shallowSizeOfIncludingThis(mallocSizeOf);
+ for (auto iter = HashTable()->iter(); !iter.done(); iter.next()) {
+ iter.get()->AddSizeOfIncludingThis(mallocSizeOf, sizes);
+ }
+ }
+
+ sizes.mPrefNameArena += PrefNameArena().SizeOfExcludingThis(mallocSizeOf);
+
+ for (CallbackNode* node = gFirstCallback; node; node = node->Next()) {
+ node->AddSizeOfIncludingThis(mallocSizeOf, sizes);
+ }
+
+ if (gSharedMap) {
+ sizes.mMisc += mallocSizeOf(gSharedMap);
+ }
+
+#ifdef ACCESS_COUNTS
+ if (gAccessCounts) {
+ sizes.mMisc += gAccessCounts->ShallowSizeOfIncludingThis(mallocSizeOf);
+ }
+#endif
+
+ MOZ_COLLECT_REPORT("explicit/preferences/hash-table", KIND_HEAP, UNITS_BYTES,
+ sizes.mHashTable, "Memory used by libpref's hash table.");
+
+ MOZ_COLLECT_REPORT("explicit/preferences/pref-values", KIND_HEAP, UNITS_BYTES,
+ sizes.mPrefValues,
+ "Memory used by PrefValues hanging off the hash table.");
+
+ MOZ_COLLECT_REPORT("explicit/preferences/string-values", KIND_HEAP,
+ UNITS_BYTES, sizes.mStringValues,
+ "Memory used by libpref's string pref values.");
+
+ MOZ_COLLECT_REPORT("explicit/preferences/root-branches", KIND_HEAP,
+ UNITS_BYTES, sizes.mRootBranches,
+ "Memory used by libpref's root branches.");
+
+ MOZ_COLLECT_REPORT("explicit/preferences/pref-name-arena", KIND_HEAP,
+ UNITS_BYTES, sizes.mPrefNameArena,
+ "Memory used by libpref's arena for pref names.");
+
+ MOZ_COLLECT_REPORT("explicit/preferences/callbacks/objects", KIND_HEAP,
+ UNITS_BYTES, sizes.mCallbacksObjects,
+ "Memory used by pref callback objects.");
+
+ MOZ_COLLECT_REPORT("explicit/preferences/callbacks/domains", KIND_HEAP,
+ UNITS_BYTES, sizes.mCallbacksDomains,
+ "Memory used by pref callback domains (pref names and "
+ "prefixes).");
+
+ MOZ_COLLECT_REPORT("explicit/preferences/misc", KIND_HEAP, UNITS_BYTES,
+ sizes.mMisc, "Miscellaneous memory used by libpref.");
+
+ if (gSharedMap) {
+ if (XRE_IsParentProcess()) {
+ MOZ_COLLECT_REPORT("explicit/preferences/shared-memory-map", KIND_NONHEAP,
+ UNITS_BYTES, gSharedMap->MapSize(),
+ "The shared memory mapping used to share a "
+ "snapshot of preference values across processes.");
+ }
+ }
+
+ nsPrefBranch* rootBranch =
+ static_cast<nsPrefBranch*>(Preferences::GetRootBranch());
+ if (!rootBranch) {
+ return NS_OK;
+ }
+
+ size_t numStrong = 0;
+ size_t numWeakAlive = 0;
+ size_t numWeakDead = 0;
+ nsTArray<nsCString> suspectPreferences;
+ // Count of the number of referents for each preference.
+ nsTHashMap<nsCStringHashKey, uint32_t> prefCounter;
+
+ for (const auto& entry : rootBranch->mObservers) {
+ auto* callback = entry.GetWeak();
+
+ if (callback->IsWeak()) {
+ nsCOMPtr<nsIObserver> callbackRef = do_QueryReferent(callback->mWeakRef);
+ if (callbackRef) {
+ numWeakAlive++;
+ } else {
+ numWeakDead++;
+ }
+ } else {
+ numStrong++;
+ }
+
+ const uint32_t currentCount = prefCounter.Get(callback->GetDomain()) + 1;
+ prefCounter.InsertOrUpdate(callback->GetDomain(), currentCount);
+
+ // Keep track of preferences that have a suspiciously large number of
+ // referents (a symptom of a leak).
+ if (currentCount == kSuspectReferentCount) {
+ suspectPreferences.AppendElement(callback->GetDomain());
+ }
+ }
+
+ for (uint32_t i = 0; i < suspectPreferences.Length(); i++) {
+ nsCString& suspect = suspectPreferences[i];
+ const uint32_t totalReferentCount = prefCounter.Get(suspect);
+
+ nsPrintfCString suspectPath(
+ "preference-service-suspect/"
+ "referent(pref=%s)",
+ suspect.get());
+
+ aHandleReport->Callback(
+ /* process = */ ""_ns, suspectPath, KIND_OTHER, UNITS_COUNT,
+ totalReferentCount,
+ "A preference with a suspiciously large number "
+ "referents (symptom of a leak)."_ns,
+ aData);
+ }
+
+ MOZ_COLLECT_REPORT(
+ "preference-service/referent/strong", KIND_OTHER, UNITS_COUNT, numStrong,
+ "The number of strong referents held by the preference service.");
+
+ MOZ_COLLECT_REPORT(
+ "preference-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT,
+ numWeakAlive,
+ "The number of weak referents held by the preference service that are "
+ "still alive.");
+
+ MOZ_COLLECT_REPORT(
+ "preference-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT,
+ numWeakDead,
+ "The number of weak referents held by the preference service that are "
+ "dead.");
+
+ return NS_OK;
+}
+
+namespace {
+
+class AddPreferencesMemoryReporterRunnable : public Runnable {
+ public:
+ AddPreferencesMemoryReporterRunnable()
+ : Runnable("AddPreferencesMemoryReporterRunnable") {}
+
+ NS_IMETHOD Run() override {
+ return RegisterStrongMemoryReporter(new PreferenceServiceReporter());
+ }
+};
+
+} // namespace
+
+// A list of changed prefs sent from the parent via shared memory.
+static StaticAutoPtr<nsTArray<dom::Pref>> gChangedDomPrefs;
+
+static const char kTelemetryPref[] = "toolkit.telemetry.enabled";
+static const char kChannelPref[] = "app.update.channel";
+
+#ifdef MOZ_WIDGET_ANDROID
+
+static Maybe<bool> TelemetryPrefValue() {
+ // Leave it unchanged if it's already set.
+ // XXX: how could it already be set?
+ if (Preferences::GetType(kTelemetryPref) != nsIPrefBranch::PREF_INVALID) {
+ return Nothing();
+ }
+
+ // Determine the correct default for toolkit.telemetry.enabled. If this
+ // build has MOZ_TELEMETRY_ON_BY_DEFAULT *or* we're on the beta channel,
+ // telemetry is on by default, otherwise not. This is necessary so that
+ // beta users who are testing final release builds don't flipflop defaults.
+# ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
+ return Some(true);
+# else
+ nsAutoCString channelPrefValue;
+ Unused << Preferences::GetCString(kChannelPref, channelPrefValue,
+ PrefValueKind::Default);
+ return Some(channelPrefValue.EqualsLiteral("beta"));
+# endif
+}
+
+/* static */
+void Preferences::SetupTelemetryPref() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ Maybe<bool> telemetryPrefValue = TelemetryPrefValue();
+ if (telemetryPrefValue.isSome()) {
+ Preferences::SetBool(kTelemetryPref, *telemetryPrefValue,
+ PrefValueKind::Default);
+ }
+}
+
+#else // !MOZ_WIDGET_ANDROID
+
+static bool TelemetryPrefValue() {
+ // For platforms with Unified Telemetry (here meaning not-Android),
+ // toolkit.telemetry.enabled determines whether we send "extended" data.
+ // We only want extended data from pre-release channels due to size.
+
+ constexpr auto channel = MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL) ""_ns;
+
+ // Easy cases: Nightly, Aurora, Beta.
+ if (channel.EqualsLiteral("nightly") || channel.EqualsLiteral("aurora") ||
+ channel.EqualsLiteral("beta")) {
+ return true;
+ }
+
+# ifndef MOZILLA_OFFICIAL
+ // Local developer builds: non-official builds on the "default" channel.
+ if (channel.EqualsLiteral("default")) {
+ return true;
+ }
+# endif
+
+ // Release Candidate builds: builds that think they are release builds, but
+ // are shipped to beta users.
+ if (channel.EqualsLiteral("release")) {
+ nsAutoCString channelPrefValue;
+ Unused << Preferences::GetCString(kChannelPref, channelPrefValue,
+ PrefValueKind::Default);
+ if (channelPrefValue.EqualsLiteral("beta")) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* static */
+void Preferences::SetupTelemetryPref() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ Preferences::SetBool(kTelemetryPref, TelemetryPrefValue(),
+ PrefValueKind::Default);
+ Preferences::Lock(kTelemetryPref);
+}
+
+static void CheckTelemetryPref() {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ // Make sure the children got passed the right telemetry pref details.
+ DebugOnly<bool> value;
+ MOZ_ASSERT(NS_SUCCEEDED(Preferences::GetBool(kTelemetryPref, &value)) &&
+ value == TelemetryPrefValue());
+ MOZ_ASSERT(Preferences::IsLocked(kTelemetryPref));
+}
+
+#endif // MOZ_WIDGET_ANDROID
+
+/* static */
+already_AddRefed<Preferences> Preferences::GetInstanceForService() {
+ if (sPreferences) {
+ return do_AddRef(sPreferences);
+ }
+
+ if (sShutdown) {
+ return nullptr;
+ }
+
+ sPreferences = new Preferences();
+
+ MOZ_ASSERT(!HashTable());
+ HashTable() = new PrefsHashTable(XRE_IsParentProcess()
+ ? kHashTableInitialLengthParent
+ : kHashTableInitialLengthContent);
+
+#ifdef DEBUG
+ gOnceStaticPrefsAntiFootgun = new AntiFootgunMap();
+#endif
+
+#ifdef ACCESS_COUNTS
+ MOZ_ASSERT(!gAccessCounts);
+ gAccessCounts = new AccessCountsHashTable();
+#endif
+
+ nsresult rv = InitInitialObjects(/* isStartup */ true);
+ if (NS_FAILED(rv)) {
+ sPreferences = nullptr;
+ return nullptr;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ MOZ_ASSERT(gChangedDomPrefs);
+ for (unsigned int i = 0; i < gChangedDomPrefs->Length(); i++) {
+ Preferences::SetPreference(gChangedDomPrefs->ElementAt(i));
+ }
+ gChangedDomPrefs = nullptr;
+
+#ifndef MOZ_WIDGET_ANDROID
+ CheckTelemetryPref();
+#endif
+
+ } else {
+ // Check if there is a deployment configuration file. If so, set up the
+ // pref config machinery, which will actually read the file.
+ nsAutoCString lockFileName;
+ nsresult rv = Preferences::GetCString("general.config.filename",
+ lockFileName, PrefValueKind::User);
+ if (NS_SUCCEEDED(rv)) {
+ NS_CreateServicesFromCategory(
+ "pref-config-startup",
+ static_cast<nsISupports*>(static_cast<void*>(sPreferences)),
+ "pref-config-startup");
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (!observerService) {
+ sPreferences = nullptr;
+ return nullptr;
+ }
+
+ observerService->AddObserver(sPreferences,
+ "profile-before-change-telemetry", true);
+ rv = observerService->AddObserver(sPreferences, "profile-before-change",
+ true);
+
+ observerService->AddObserver(sPreferences, "suspend_process_notification",
+ true);
+
+ if (NS_FAILED(rv)) {
+ sPreferences = nullptr;
+ return nullptr;
+ }
+ }
+
+ const char* defaultPrefs = getenv("MOZ_DEFAULT_PREFS");
+ if (defaultPrefs) {
+ parsePrefData(nsCString(defaultPrefs), PrefValueKind::Default);
+ }
+
+ // Preferences::GetInstanceForService() can be called from GetService(), and
+ // RegisterStrongMemoryReporter calls GetService(nsIMemoryReporter). To
+ // avoid a potential recursive GetService() call, we can't register the
+ // memory reporter here; instead, do it off a runnable.
+ RefPtr<AddPreferencesMemoryReporterRunnable> runnable =
+ new AddPreferencesMemoryReporterRunnable();
+ NS_DispatchToMainThread(runnable);
+
+ return do_AddRef(sPreferences);
+}
+
+/* static */
+bool Preferences::IsServiceAvailable() { return !!sPreferences; }
+
+/* static */
+bool Preferences::InitStaticMembers() {
+ MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
+
+ if (MOZ_LIKELY(sPreferences)) {
+ return true;
+ }
+
+ if (!sShutdown) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIPrefService> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ }
+
+ return sPreferences != nullptr;
+}
+
+/* static */
+void Preferences::Shutdown() {
+ if (!sShutdown) {
+ sShutdown = true; // Don't create the singleton instance after here.
+ sPreferences = nullptr;
+ StaticPrefs::ShutdownAlwaysPrefs();
+ }
+}
+
+Preferences::Preferences()
+ : mRootBranch(new nsPrefBranch("", PrefValueKind::User)),
+ mDefaultRootBranch(new nsPrefBranch("", PrefValueKind::Default)) {}
+
+Preferences::~Preferences() {
+ MOZ_ASSERT(!sPreferences);
+
+ MOZ_ASSERT(!gCallbacksInProgress);
+
+ CallbackNode* node = gFirstCallback;
+ while (node) {
+ CallbackNode* next_node = node->Next();
+ delete node;
+ node = next_node;
+ }
+ gLastPriorityNode = gFirstCallback = nullptr;
+
+ delete HashTable();
+ HashTable() = nullptr;
+
+#ifdef DEBUG
+ gOnceStaticPrefsAntiFootgun = nullptr;
+#endif
+
+#ifdef ACCESS_COUNTS
+ gAccessCounts = nullptr;
+#endif
+
+ gSharedMap = nullptr;
+
+ PrefNameArena().Clear();
+}
+
+NS_IMPL_ISUPPORTS(Preferences, nsIPrefService, nsIObserver, nsIPrefBranch,
+ nsISupportsWeakReference)
+
+/* static */
+void Preferences::SerializePreferences(nsCString& aStr,
+ bool aIsDestinationWebContentProcess) {
+ MOZ_RELEASE_ASSERT(InitStaticMembers());
+
+ aStr.Truncate();
+
+ for (auto iter = HashTable()->iter(); !iter.done(); iter.next()) {
+ Pref* pref = iter.get().get();
+ if (!pref->IsTypeNone() && pref->HasAdvisablySizedValues()) {
+ pref->SerializeAndAppend(aStr, aIsDestinationWebContentProcess &&
+ ShouldSanitizePreference(pref));
+ }
+ }
+
+ aStr.Append('\0');
+}
+
+/* static */
+void Preferences::DeserializePreferences(char* aStr, size_t aPrefsLen) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ MOZ_ASSERT(!gChangedDomPrefs);
+ gChangedDomPrefs = new nsTArray<dom::Pref>();
+
+ char* p = aStr;
+ while (*p != '\0') {
+ dom::Pref pref;
+ p = Pref::Deserialize(p, &pref);
+ gChangedDomPrefs->AppendElement(pref);
+ }
+
+ // We finished parsing on a '\0'. That should be the last char in the shared
+ // memory. (aPrefsLen includes the '\0'.)
+ MOZ_ASSERT(p == aStr + aPrefsLen - 1);
+
+ MOZ_ASSERT(!gContentProcessPrefsAreInited);
+ gContentProcessPrefsAreInited = true;
+}
+
+/* static */
+FileDescriptor Preferences::EnsureSnapshot(size_t* aSize) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gSharedMap) {
+ SharedPrefMapBuilder builder;
+
+ nsTArray<Pref*> toRepopulate;
+ NameArena* newPrefNameArena = new NameArena();
+ for (auto iter = HashTable()->modIter(); !iter.done(); iter.next()) {
+ if (!ShouldSanitizePreference(iter.get().get())) {
+ iter.get()->AddToMap(builder);
+ } else {
+ Pref* pref = iter.getMutable().release();
+ pref->RelocateName(newPrefNameArena);
+ toRepopulate.AppendElement(pref);
+ }
+ }
+
+ // Store the current value of `once`-mirrored prefs. After this point they
+ // will be immutable.
+ StaticPrefs::RegisterOncePrefs(builder);
+
+ gSharedMap = new SharedPrefMap(std::move(builder));
+
+ // Once we've built a snapshot of the database, there's no need to continue
+ // storing dynamic copies of the preferences it contains. Once we reset the
+ // hashtable, preference lookups will fall back to the snapshot for any
+ // preferences not in the dynamic hashtable.
+ //
+ // And since the majority of the database is now contained in the snapshot,
+ // we can initialize the hashtable with the expected number of per-session
+ // changed preferences, rather than the expected total number of
+ // preferences.
+ HashTable()->clearAndCompact();
+ Unused << HashTable()->reserve(kHashTableInitialLengthContent);
+
+ delete sPrefNameArena;
+ sPrefNameArena = newPrefNameArena;
+ gCallbackPref = nullptr;
+
+ for (uint32_t i = 0; i < toRepopulate.Length(); i++) {
+ auto pref = toRepopulate[i];
+ auto p = HashTable()->lookupForAdd(pref->Name());
+ MOZ_ASSERT(!p.found());
+ Unused << HashTable()->add(p, pref);
+ }
+ }
+
+ *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);
+
+ StaticPrefs::InitStaticPrefsFromShared();
+}
+
+/* static */
+void Preferences::InitializeUserPrefs() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!sPreferences->mCurrentFile, "Should only initialize prefs once");
+
+ // Prefs which are set before we initialize the profile are silently
+ // discarded. This is stupid, but there are various tests which depend on
+ // this behavior.
+ sPreferences->ResetUserPrefs();
+
+ nsCOMPtr<nsIFile> prefsFile = sPreferences->ReadSavedPrefs();
+ sPreferences->ReadUserOverridePrefs();
+
+ sPreferences->mDirty = false;
+
+ // Don't set mCurrentFile until we're done so that dirty flags work properly.
+ sPreferences->mCurrentFile = std::move(prefsFile);
+}
+
+/* static */
+void Preferences::FinishInitializingUserPrefs() {
+ sPreferences->NotifyServiceObservers(NS_PREFSERVICE_READ_TOPIC_ID);
+}
+
+NS_IMETHODIMP
+Preferences::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* someData) {
+ if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = NS_OK;
+
+ if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
+ // Normally prefs aren't written after this point, and so we kick off
+ // an asynchronous pref save so that I/O can be done in parallel with
+ // other shutdown.
+ if (AllowOffMainThreadSave()) {
+ SavePrefFile(nullptr);
+ }
+
+ } else if (!nsCRT::strcmp(aTopic, "profile-before-change-telemetry")) {
+ // It's possible that a profile-before-change observer after ours
+ // set a pref. A blocking save here re-saves if necessary and also waits
+ // for any pending saves to complete.
+ SavePrefFileBlocking();
+ MOZ_ASSERT(!mDirty, "Preferences should not be dirty");
+ mProfileShutdown = true;
+
+ } else if (!nsCRT::strcmp(aTopic, "reload-default-prefs")) {
+ // Reload the default prefs from file.
+ Unused << InitInitialObjects(/* isStartup */ false);
+
+ } else if (!nsCRT::strcmp(aTopic, "suspend_process_notification")) {
+ // Our process is being suspended. The OS may wake our process later,
+ // or it may kill the process. In case our process is going to be killed
+ // from the suspended state, we save preferences before suspending.
+ rv = SavePrefFileBlocking();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+Preferences::ReadDefaultPrefsFromFile(nsIFile* aFile) {
+ ENSURE_PARENT_PROCESS("Preferences::ReadDefaultPrefsFromFile", "all prefs");
+
+ if (!aFile) {
+ NS_ERROR("ReadDefaultPrefsFromFile requires a parameter");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return openPrefFile(aFile, PrefValueKind::Default);
+}
+
+NS_IMETHODIMP
+Preferences::ReadUserPrefsFromFile(nsIFile* aFile) {
+ ENSURE_PARENT_PROCESS("Preferences::ReadUserPrefsFromFile", "all prefs");
+
+ if (!aFile) {
+ NS_ERROR("ReadUserPrefsFromFile requires a parameter");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return openPrefFile(aFile, PrefValueKind::User);
+}
+
+NS_IMETHODIMP
+Preferences::ResetPrefs() {
+ ENSURE_PARENT_PROCESS("Preferences::ResetPrefs", "all prefs");
+
+ if (gSharedMap) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ HashTable()->clearAndCompact();
+ Unused << HashTable()->reserve(kHashTableInitialLengthParent);
+
+ PrefNameArena().Clear();
+
+ return InitInitialObjects(/* isStartup */ false);
+}
+
+nsresult Preferences::ResetUserPrefs() {
+ ENSURE_PARENT_PROCESS("Preferences::ResetUserPrefs", "all prefs");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Vector<const char*> prefNames;
+ for (auto iter = HashTable()->modIter(); !iter.done(); iter.next()) {
+ Pref* pref = iter.get().get();
+
+ if (pref->HasUserValue()) {
+ if (!prefNames.append(pref->Name())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ pref->ClearUserValue();
+ if (!pref->HasDefaultValue()) {
+ iter.remove();
+ }
+ }
+ }
+
+ for (const char* prefName : prefNames) {
+ NotifyCallbacks(nsDependentCString(prefName));
+ }
+
+ Preferences::HandleDirty();
+ return NS_OK;
+}
+
+bool Preferences::AllowOffMainThreadSave() {
+ // Put in a preference that allows us to disable off main thread preference
+ // file save.
+ if (sAllowOMTPrefWrite < 0) {
+ bool value = false;
+ Preferences::GetBool("preferences.allow.omt-write", &value);
+ sAllowOMTPrefWrite = value ? 1 : 0;
+ }
+
+ return !!sAllowOMTPrefWrite;
+}
+
+nsresult Preferences::SavePrefFileBlocking() {
+ if (mDirty) {
+ return SavePrefFileInternal(nullptr, SaveMethod::Blocking);
+ }
+
+ // If we weren't dirty to start, SavePrefFileInternal will early exit so
+ // there is no guarantee that we don't have oustanding async saves in the
+ // pipe. Since the contract of SavePrefFileOnMainThread is that the file on
+ // disk matches the preferences, we have to make sure those requests are
+ // completed.
+
+ if (AllowOffMainThreadSave()) {
+ PreferencesWriter::Flush();
+ }
+
+ return NS_OK;
+}
+
+nsresult Preferences::SavePrefFileAsynchronous() {
+ return SavePrefFileInternal(nullptr, SaveMethod::Asynchronous);
+}
+
+NS_IMETHODIMP
+Preferences::SavePrefFile(nsIFile* aFile) {
+ // This is the method accessible from service API. Make it off main thread.
+ return SavePrefFileInternal(aFile, SaveMethod::Asynchronous);
+}
+
+/* static */
+void Preferences::SetPreference(const dom::Pref& aDomPref) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ NS_ENSURE_TRUE(InitStaticMembers(), (void)0);
+
+ const nsCString& prefName = aDomPref.name();
+
+ Pref* pref;
+ auto p = HashTable()->lookupForAdd(prefName.get());
+ if (!p) {
+ pref = new Pref(prefName);
+ if (!HashTable()->add(p, pref)) {
+ delete pref;
+ return;
+ }
+ } else {
+ pref = p->get();
+ }
+
+ bool valueChanged = false;
+ pref->FromDomPref(aDomPref, &valueChanged);
+
+ // When the parent process clears a pref's user value we get a DomPref here
+ // with no default value and no user value. There are two possibilities.
+ //
+ // - There was an existing pref with only a user value. FromDomPref() will
+ // have just cleared that user value, so the pref can be removed.
+ //
+ // - There was no existing pref. FromDomPref() will have done nothing, and
+ // `pref` will be valueless. We will end up adding and removing the value
+ // needlessly, but that's ok because this case is rare.
+ //
+ if (!pref->HasDefaultValue() && !pref->HasUserValue() &&
+ !pref->IsSanitized()) {
+ // If the preference exists in the shared map, we need to keep the dynamic
+ // entry around to mask it.
+ if (gSharedMap->Has(pref->Name())) {
+ pref->SetType(PrefType::None);
+ } else {
+ HashTable()->remove(prefName.get());
+ }
+ pref = nullptr;
+ }
+
+ // Note: we don't have to worry about HandleDirty() because we are setting
+ // prefs in the content process that have come from the parent process.
+
+ if (valueChanged) {
+ if (pref) {
+ NotifyCallbacks(prefName, PrefWrapper(pref));
+ } else {
+ NotifyCallbacks(prefName);
+ }
+ }
+}
+
+/* static */
+void Preferences::GetPreference(dom::Pref* aDomPref,
+ const GeckoProcessType aDestinationProcessType,
+ const nsACString& aDestinationRemoteType) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ bool destIsWebContent =
+ aDestinationProcessType == GeckoProcessType_Content &&
+ (StringBeginsWith(aDestinationRemoteType, WEB_REMOTE_TYPE) ||
+ StringBeginsWith(aDestinationRemoteType, PREALLOC_REMOTE_TYPE) ||
+ StringBeginsWith(aDestinationRemoteType, PRIVILEGEDMOZILLA_REMOTE_TYPE));
+
+ Pref* pref = pref_HashTableLookup(aDomPref->name().get());
+ if (pref && pref->HasAdvisablySizedValues()) {
+ pref->ToDomPref(aDomPref, destIsWebContent);
+ }
+}
+
+#ifdef DEBUG
+bool Preferences::ArePrefsInitedInContentProcess() {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ return gContentProcessPrefsAreInited;
+}
+#endif
+
+NS_IMETHODIMP
+Preferences::GetBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal) {
+ if ((nullptr != aPrefRoot) && (*aPrefRoot != '\0')) {
+ // TODO: Cache this stuff and allow consumers to share branches (hold weak
+ // references, I think).
+ RefPtr<nsPrefBranch> prefBranch =
+ new nsPrefBranch(aPrefRoot, PrefValueKind::User);
+ prefBranch.forget(aRetVal);
+ } else {
+ // Special case: caching the default root.
+ nsCOMPtr<nsIPrefBranch> root(sPreferences->mRootBranch);
+ root.forget(aRetVal);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Preferences::GetDefaultBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal) {
+ if (!aPrefRoot || !aPrefRoot[0]) {
+ nsCOMPtr<nsIPrefBranch> root(sPreferences->mDefaultRootBranch);
+ root.forget(aRetVal);
+ return NS_OK;
+ }
+
+ // TODO: Cache this stuff and allow consumers to share branches (hold weak
+ // references, I think).
+ RefPtr<nsPrefBranch> prefBranch =
+ new nsPrefBranch(aPrefRoot, PrefValueKind::Default);
+ if (!prefBranch) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ prefBranch.forget(aRetVal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Preferences::ReadStats(nsIPrefStatsCallback* aCallback) {
+#ifdef ACCESS_COUNTS
+ for (const auto& entry : *gAccessCounts) {
+ aCallback->Visit(entry.GetKey(), entry.GetData());
+ }
+
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+NS_IMETHODIMP
+Preferences::ResetStats() {
+#ifdef ACCESS_COUNTS
+ gAccessCounts->Clear();
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+// We would much prefer to use C++ lambdas, but we cannot convert
+// lambdas that capture (here, the underlying observer) to C pointer
+// to functions. So, here we are, with icky C callbacks. Be aware
+// that nothing is thread-safe here because there's a single global
+// `nsIPrefObserver` instance. Use this from the main thread only.
+nsIPrefObserver* PrefObserver = nullptr;
+
+void HandlePref(const char* aPrefName, PrefType aType, PrefValueKind aKind,
+ PrefValue aValue, bool aIsSticky, bool aIsLocked) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!PrefObserver) {
+ return;
+ }
+
+ const char* kind = aKind == PrefValueKind::Default ? "Default" : "User";
+
+ switch (aType) {
+ case PrefType::String:
+ PrefObserver->OnStringPref(kind, aPrefName, aValue.mStringVal, aIsSticky,
+ aIsLocked);
+ break;
+ case PrefType::Int:
+ PrefObserver->OnIntPref(kind, aPrefName, aValue.mIntVal, aIsSticky,
+ aIsLocked);
+ break;
+ case PrefType::Bool:
+ PrefObserver->OnBoolPref(kind, aPrefName, aValue.mBoolVal, aIsSticky,
+ aIsLocked);
+ break;
+ default:
+ PrefObserver->OnError("Unexpected pref type.");
+ }
+}
+
+void HandleError(const char* aMsg) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!PrefObserver) {
+ return;
+ }
+
+ PrefObserver->OnError(aMsg);
+}
+
+NS_IMETHODIMP
+Preferences::ParsePrefsFromBuffer(const nsTArray<uint8_t>& aBytes,
+ nsIPrefObserver* aObserver,
+ const char* aPathLabel) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We need a null-terminated buffer.
+ nsTArray<uint8_t> data = aBytes.Clone();
+ data.AppendElement(0);
+
+ // Parsing as default handles both `pref` and `user_pref`.
+ PrefObserver = aObserver;
+ prefs_parser_parse(aPathLabel ? aPathLabel : "<ParsePrefsFromBuffer data>",
+ PrefValueKind::Default, (const char*)data.Elements(),
+ data.Length() - 1, HandlePref, HandleError);
+ PrefObserver = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Preferences::GetDirty(bool* aRetVal) {
+ *aRetVal = mDirty;
+ return NS_OK;
+}
+
+nsresult Preferences::NotifyServiceObservers(const char* aTopic) {
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (!observerService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto subject = static_cast<nsIPrefService*>(this);
+ observerService->NotifyObservers(subject, aTopic, nullptr);
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIFile> Preferences::ReadSavedPrefs() {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ rv = openPrefFile(file, PrefValueKind::User);
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ // This is a normal case for new users.
+ rv = NS_OK;
+ } else if (NS_FAILED(rv)) {
+ // Save a backup copy of the current (invalid) prefs file, since all prefs
+ // from the error line to the end of the file will be lost (bug 361102).
+ // TODO we should notify the user about it (bug 523725).
+ Telemetry::ScalarSet(
+ Telemetry::ScalarID::PREFERENCES_PREFS_FILE_WAS_INVALID, true);
+ MakeBackupPrefFile(file);
+ }
+
+ return file.forget();
+}
+
+void Preferences::ReadUserOverridePrefs() {
+ nsCOMPtr<nsIFile> aFile;
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_APP_PREFS_50_DIR, getter_AddRefs(aFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ aFile->AppendNative("user.js"_ns);
+ rv = openPrefFile(aFile, PrefValueKind::User);
+}
+
+nsresult Preferences::MakeBackupPrefFile(nsIFile* aFile) {
+ // Example: this copies "prefs.js" to "Invalidprefs.js" in the same directory.
+ // "Invalidprefs.js" is removed if it exists, prior to making the copy.
+ nsAutoString newFilename;
+ nsresult rv = aFile->GetLeafName(newFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newFilename.InsertLiteral(u"Invalid", 0);
+ nsCOMPtr<nsIFile> newFile;
+ rv = aFile->GetParent(getter_AddRefs(newFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = newFile->Append(newFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ newFile->Exists(&exists);
+ if (exists) {
+ rv = newFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = aFile->CopyTo(nullptr, newFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+nsresult Preferences::SavePrefFileInternal(nsIFile* aFile,
+ SaveMethod aSaveMethod) {
+ ENSURE_PARENT_PROCESS("Preferences::SavePrefFileInternal", "all prefs");
+
+ // We allow different behavior here when aFile argument is not null, but it
+ // happens to be the same as the current file. It is not clear that we
+ // should, but it does give us a "force" save on the unmodified pref file
+ // (see the original bug 160377 when we added this.)
+
+ if (nullptr == aFile) {
+ mSavePending = false;
+
+ // Off main thread writing only if allowed.
+ if (!AllowOffMainThreadSave()) {
+ aSaveMethod = SaveMethod::Blocking;
+ }
+
+ // The mDirty flag tells us if we should write to mCurrentFile. We only
+ // check this flag when the caller wants to write to the default.
+ if (!mDirty) {
+ return NS_OK;
+ }
+
+ // Check for profile shutdown after mDirty because the runnables from
+ // HandleDirty() can still be pending.
+ if (mProfileShutdown) {
+ NS_WARNING("Cannot save pref file after profile shutdown.");
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ // It's possible that we never got a prefs file.
+ nsresult rv = NS_OK;
+ if (mCurrentFile) {
+ rv = WritePrefFile(mCurrentFile, aSaveMethod);
+ }
+
+ // If we succeeded writing to mCurrentFile, reset the dirty flag.
+ if (NS_SUCCEEDED(rv)) {
+ mDirty = false;
+ }
+ return rv;
+
+ } else {
+ // We only allow off main thread writes on mCurrentFile.
+ return WritePrefFile(aFile, SaveMethod::Blocking);
+ }
+}
+
+nsresult Preferences::WritePrefFile(nsIFile* aFile, SaveMethod aSaveMethod) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!HashTable()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ AUTO_PROFILER_LABEL("Preferences::WritePrefFile", OTHER);
+
+ if (AllowOffMainThreadSave()) {
+ nsresult rv = NS_OK;
+ UniquePtr<PrefSaveData> prefs = MakeUnique<PrefSaveData>(pref_savePrefs());
+
+ // Put the newly constructed preference data into sPendingWriteData
+ // for the next request to pick up
+ prefs.reset(PreferencesWriter::sPendingWriteData.exchange(prefs.release()));
+ if (prefs) {
+ // There was a previous request that hasn't been processed,
+ // and this is the data it had.
+ return rv;
+ }
+
+ // There were no previous requests. Dispatch one since sPendingWriteData has
+ // the up to date information.
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ bool async = aSaveMethod == SaveMethod::Asynchronous;
+
+ // Increment sPendingWriteCount, even though it's redundant to track this
+ // in the case of a sync runnable; it just makes it easier to simply
+ // decrement this inside PWRunnable. We cannot use the constructor /
+ // destructor for increment/decrement, as on dispatch failure we might
+ // leak the runnable in order to not destroy it on the wrong thread, which
+ // would make us get stuck in an infinite SpinEventLoopUntil inside
+ // PreferencesWriter::Flush. Better that in future code we miss an
+ // increment of sPendingWriteCount and cause a simple crash due to it
+ // ending up negative.
+ PreferencesWriter::sPendingWriteCount++;
+ if (async) {
+ rv = target->Dispatch(new PWRunnable(aFile),
+ nsIEventTarget::DISPATCH_NORMAL);
+ } else {
+ rv =
+ SyncRunnable::DispatchToThread(target, new PWRunnable(aFile), true);
+ }
+ if (NS_FAILED(rv)) {
+ // If our dispatch failed, we should correct our bookkeeping to
+ // avoid shutdown hangs.
+ PreferencesWriter::sPendingWriteCount--;
+ }
+ return rv;
+ }
+
+ // If we can't get the thread for writing, for whatever reason, do the main
+ // thread write after making some noise.
+ MOZ_ASSERT(false, "failed to get the target thread for OMT pref write");
+ }
+
+ // This will do a main thread write. It is safe to do it this way because
+ // AllowOffMainThreadSave() returns a consistent value for the lifetime of
+ // the parent process.
+ PrefSaveData prefsData = pref_savePrefs();
+ return PreferencesWriter::Write(aFile, prefsData);
+}
+
+static nsresult openPrefFile(nsIFile* aFile, PrefValueKind aKind) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCString data;
+ MOZ_TRY_VAR(data, URLPreloader::ReadFile(aFile));
+
+ nsAutoString filenameUtf16;
+ aFile->GetLeafName(filenameUtf16);
+ NS_ConvertUTF16toUTF8 filename(filenameUtf16);
+
+ nsAutoString path;
+ aFile->GetPath(path);
+
+ Parser parser;
+ if (!parser.Parse(aKind, NS_ConvertUTF16toUTF8(path).get(), data)) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ return NS_OK;
+}
+
+static nsresult parsePrefData(const nsCString& aData, PrefValueKind aKind) {
+ const nsCString path = "$MOZ_DEFAULT_PREFS"_ns;
+
+ Parser parser;
+ if (!parser.Parse(aKind, path.get(), aData)) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ return NS_OK;
+}
+
+static int pref_CompareFileNames(nsIFile* aFile1, nsIFile* aFile2,
+ void* /* unused */) {
+ nsAutoCString filename1, filename2;
+ aFile1->GetNativeLeafName(filename1);
+ aFile2->GetNativeLeafName(filename2);
+
+ return Compare(filename2, filename1);
+}
+
+// Load default pref files from a directory. The files in the directory are
+// sorted reverse-alphabetically; a set of "special file names" may be
+// specified which are loaded after all the others.
+static nsresult pref_LoadPrefsInDir(nsIFile* aDir,
+ char const* const* aSpecialFiles,
+ uint32_t aSpecialFilesCount) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsresult rv, rv2;
+
+ nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
+
+ // This may fail in some normal cases, such as embedders who do not use a
+ // GRE.
+ rv = aDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
+ if (NS_FAILED(rv)) {
+ // If the directory doesn't exist, then we have no reason to complain. We
+ // loaded everything (and nothing) successfully.
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ rv = NS_OK;
+ }
+ return rv;
+ }
+
+ nsCOMArray<nsIFile> prefFiles(INITIAL_PREF_FILES);
+ nsCOMArray<nsIFile> specialFiles(aSpecialFilesCount);
+ nsCOMPtr<nsIFile> prefFile;
+
+ while (NS_SUCCEEDED(dirIterator->GetNextFile(getter_AddRefs(prefFile))) &&
+ prefFile) {
+ nsAutoCString leafName;
+ prefFile->GetNativeLeafName(leafName);
+ MOZ_ASSERT(
+ !leafName.IsEmpty(),
+ "Failure in default prefs: directory enumerator returned empty file?");
+
+ // Skip non-js files.
+ if (StringEndsWith(leafName, ".js"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ bool shouldParse = true;
+
+ // Separate out special files.
+ for (uint32_t i = 0; i < aSpecialFilesCount; ++i) {
+ if (leafName.Equals(nsDependentCString(aSpecialFiles[i]))) {
+ shouldParse = false;
+ // Special files should be processed in order. We put them into the
+ // array by index, which can make the array sparse.
+ specialFiles.ReplaceObjectAt(prefFile, i);
+ }
+ }
+
+ if (shouldParse) {
+ prefFiles.AppendObject(prefFile);
+ }
+ }
+ }
+
+ if (prefFiles.Count() + specialFiles.Count() == 0) {
+ NS_WARNING("No default pref files found.");
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_SUCCESS_FILE_DIRECTORY_EMPTY;
+ }
+ return rv;
+ }
+
+ prefFiles.Sort(pref_CompareFileNames, nullptr);
+
+ uint32_t arrayCount = prefFiles.Count();
+ uint32_t i;
+ for (i = 0; i < arrayCount; ++i) {
+ rv2 = openPrefFile(prefFiles[i], PrefValueKind::Default);
+ if (NS_FAILED(rv2)) {
+ NS_ERROR("Default pref file not parsed successfully.");
+ rv = rv2;
+ }
+ }
+
+ arrayCount = specialFiles.Count();
+ for (i = 0; i < arrayCount; ++i) {
+ // This may be a sparse array; test before parsing.
+ nsIFile* file = specialFiles[i];
+ if (file) {
+ rv2 = openPrefFile(file, PrefValueKind::Default);
+ if (NS_FAILED(rv2)) {
+ NS_ERROR("Special default pref file not parsed successfully.");
+ rv = rv2;
+ }
+ }
+ }
+
+ return rv;
+}
+
+static nsresult pref_ReadPrefFromJar(nsZipArchive* aJarReader,
+ const char* aName) {
+ nsCString manifest;
+ MOZ_TRY_VAR(manifest,
+ URLPreloader::ReadZip(aJarReader, nsDependentCString(aName)));
+
+ Parser parser;
+ if (!parser.Parse(PrefValueKind::Default, aName, manifest)) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ return NS_OK;
+}
+
+static nsresult pref_ReadDefaultPrefs(const RefPtr<nsZipArchive> jarReader,
+ const char* path) {
+ UniquePtr<nsZipFind> find;
+ nsTArray<nsCString> prefEntries;
+ const char* entryName;
+ uint16_t entryNameLen;
+
+ nsresult rv = jarReader->FindInit(path, getter_Transfers(find));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
+ prefEntries.AppendElement(Substring(entryName, entryNameLen));
+ }
+
+ prefEntries.Sort();
+ for (uint32_t i = prefEntries.Length(); i--;) {
+ rv = pref_ReadPrefFromJar(jarReader, prefEntries[i].get());
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error parsing preferences.");
+ }
+ }
+
+ return NS_OK;
+}
+
+static nsCString PrefValueToString(const bool* b) {
+ return nsCString(*b ? "true" : "false");
+}
+static nsCString PrefValueToString(const int* i) {
+ return nsPrintfCString("%d", *i);
+}
+static nsCString PrefValueToString(const uint32_t* u) {
+ return nsPrintfCString("%d", *u);
+}
+static nsCString PrefValueToString(const float* f) {
+ return nsPrintfCString("%f", *f);
+}
+static nsCString PrefValueToString(const nsACString* s) {
+ return nsCString(*s);
+}
+static nsCString PrefValueToString(const nsACString& s) { return nsCString(s); }
+
+// These preference getter wrappers allow us to look up the value for static
+// preferences based on their native types, rather than manually mapping them to
+// the appropriate Preferences::Get* functions.
+// We define these methods in a struct which is made friend of Preferences in
+// order to access private members.
+struct Internals {
+ template <typename T>
+ static nsresult GetPrefValue(const char* aPrefName, T&& aResult,
+ PrefValueKind aKind) {
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ NS_ENSURE_TRUE(Preferences::InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+
+ if (Maybe<PrefWrapper> pref = pref_Lookup(aPrefName)) {
+ rv = pref->GetValue(aKind, std::forward<T>(aResult));
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ profiler_add_marker(
+ "Preference Read", baseprofiler::category::OTHER_PreferenceRead, {},
+ PreferenceMarker{},
+ ProfilerString8View::WrapNullTerminatedString(aPrefName),
+ Some(aKind), pref->Type(), PrefValueToString(aResult));
+ }
+ }
+
+ return rv;
+ }
+
+ template <typename T>
+ static nsresult GetSharedPrefValue(const char* aName, T* aResult) {
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ if (Maybe<PrefWrapper> pref = pref_SharedLookup(aName)) {
+ rv = pref->GetValue(PrefValueKind::User, aResult);
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ profiler_add_marker(
+ "Preference Read", baseprofiler::category::OTHER_PreferenceRead, {},
+ PreferenceMarker{},
+ ProfilerString8View::WrapNullTerminatedString(aName),
+ Nothing() /* indicates Shared */, pref->Type(),
+ PrefValueToString(aResult));
+ }
+ }
+
+ return rv;
+ }
+
+ template <typename T>
+ static T GetPref(const char* aPrefName, T aFallback,
+ PrefValueKind aKind = PrefValueKind::User) {
+ T result = aFallback;
+ GetPrefValue(aPrefName, &result, aKind);
+ return result;
+ }
+
+ template <typename T, typename V>
+ static void MOZ_NEVER_INLINE AssignMirror(T& aMirror, V aValue) {
+ aMirror = aValue;
+ }
+
+ static void MOZ_NEVER_INLINE AssignMirror(DataMutexString& aMirror,
+ nsCString&& aValue) {
+ auto lock = aMirror.Lock();
+ lock->Assign(std::move(aValue));
+ }
+
+ static void MOZ_NEVER_INLINE AssignMirror(DataMutexString& aMirror,
+ const nsLiteralCString& aValue) {
+ auto lock = aMirror.Lock();
+ lock->Assign(aValue);
+ }
+
+ static void ClearMirror(DataMutexString& aMirror) {
+ auto lock = aMirror.Lock();
+ lock->Assign(nsCString());
+ }
+
+ template <typename T>
+ static void UpdateMirror(const char* aPref, void* aMirror) {
+ StripAtomic<T> value;
+
+ nsresult rv = GetPrefValue(aPref, &value, PrefValueKind::User);
+ if (NS_SUCCEEDED(rv)) {
+ AssignMirror(*static_cast<T*>(aMirror),
+ std::forward<StripAtomic<T>>(value));
+ } else {
+ // GetPrefValue() can fail if the update is caused by the pref being
+ // deleted or if it fails to make a cast. This assertion is the only place
+ // where we safeguard these. In this case the mirror variable will be
+ // untouched, thus keeping the value it had prior to the change.
+ // (Note that this case won't happen for a deletion via DeleteBranch()
+ // unless bug 343600 is fixed, but it will happen for a deletion via
+ // ClearUserPref().)
+ NS_WARNING(nsPrintfCString("Pref changed failure: %s\n", aPref).get());
+ MOZ_ASSERT(false);
+ }
+ }
+
+ template <typename T>
+ static nsresult RegisterCallback(void* aMirror, const nsACString& aPref) {
+ return Preferences::RegisterCallback(UpdateMirror<T>, aPref, aMirror,
+ Preferences::ExactMatch,
+ /* isPriority */ true);
+ }
+};
+
+// Initialize default preference JavaScript buffers from appropriate TEXT
+// resources.
+/* static */
+nsresult Preferences::InitInitialObjects(bool aIsStartup) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!XRE_IsParentProcess()) {
+ MOZ_DIAGNOSTIC_ASSERT(gSharedMap);
+ if (aIsStartup) {
+ StaticPrefs::StartObservingAlwaysPrefs();
+ }
+ return NS_OK;
+ }
+
+ // Initialize static prefs before prefs from data files so that the latter
+ // will override the former.
+ StaticPrefs::InitAll();
+
+ // In the omni.jar case, we load the following prefs:
+ // - jar:$gre/omni.jar!/greprefs.js
+ // - jar:$gre/omni.jar!/defaults/pref/*.js
+ //
+ // In the non-omni.jar case, we load:
+ // - $gre/greprefs.js
+ //
+ // In both cases, we also load:
+ // - $gre/defaults/pref/*.js
+ //
+ // This is kept for bug 591866 (channel-prefs.js should not be in omni.jar)
+ // in the `$app == $gre` case; we load all files instead of channel-prefs.js
+ // only to have the same behaviour as `$app != $gre`, where this is required
+ // as a supported location for GRE preferences.
+ //
+ // When `$app != $gre`, we additionally load, in the omni.jar case:
+ // - jar:$app/omni.jar!/defaults/preferences/*.js
+ // - $app/defaults/preferences/*.js
+ //
+ // and in the non-omni.jar case:
+ // - $app/defaults/preferences/*.js
+ //
+ // When `$app == $gre`, we additionally load, in the omni.jar case:
+ // - jar:$gre/omni.jar!/defaults/preferences/*.js
+ //
+ // Thus, in the omni.jar case, we always load app-specific default
+ // preferences from omni.jar, whether or not `$app == $gre`.
+
+ nsresult rv = NS_ERROR_FAILURE;
+ UniquePtr<nsZipFind> find;
+ nsTArray<nsCString> prefEntries;
+ const char* entryName;
+ uint16_t entryNameLen;
+
+ RefPtr<nsZipArchive> jarReader = Omnijar::GetReader(Omnijar::GRE);
+ if (jarReader) {
+#ifdef MOZ_WIDGET_ANDROID
+ // Try to load an architecture-specific greprefs.js first. This will be
+ // present in FAT AAR builds of GeckoView on Android.
+ const char* abi = getenv("MOZ_ANDROID_CPU_ABI");
+ if (abi) {
+ nsAutoCString path;
+ path.AppendPrintf("%s/greprefs.js", abi);
+ rv = pref_ReadPrefFromJar(jarReader, path.get());
+ }
+
+ if (NS_FAILED(rv)) {
+ // Fallback to toplevel greprefs.js if arch-specific load fails.
+ rv = pref_ReadPrefFromJar(jarReader, "greprefs.js");
+ }
+#else
+ // Load jar:$gre/omni.jar!/greprefs.js.
+ rv = pref_ReadPrefFromJar(jarReader, "greprefs.js");
+#endif
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Load jar:$gre/omni.jar!/defaults/pref/*.js.
+ rv = pref_ReadDefaultPrefs(jarReader, "defaults/pref/*.js$");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ rv = pref_ReadDefaultPrefs(jarReader, "defaults/backgroundtasks/*.js$");
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+ // Load jar:$gre/omni.jar!/defaults/pref/$MOZ_ANDROID_CPU_ABI/*.js.
+ nsAutoCString path;
+ path.AppendPrintf("jar:$gre/omni.jar!/defaults/pref/%s/*.js$", abi);
+ pref_ReadDefaultPrefs(jarReader, path.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ } else {
+ // Load $gre/greprefs.js.
+ nsCOMPtr<nsIFile> greprefsFile;
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greprefsFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = greprefsFile->AppendNative("greprefs.js"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = openPrefFile(greprefsFile, PrefValueKind::Default);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "Error parsing GRE default preferences. Is this an old-style "
+ "embedding app?");
+ }
+ }
+
+ // Load $gre/defaults/pref/*.js.
+ nsCOMPtr<nsIFile> defaultPrefDir;
+ rv = NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR,
+ getter_AddRefs(defaultPrefDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // These pref file names should not be used: we process them after all other
+ // application pref files for backwards compatibility.
+ static const char* specialFiles[] = {
+#if defined(XP_MACOSX)
+ "macprefs.js"
+#elif defined(XP_WIN)
+ "winpref.js"
+#elif defined(XP_UNIX)
+ "unix.js"
+# if defined(_AIX)
+ ,
+ "aix.js"
+# endif
+#endif
+ };
+
+ rv = pref_LoadPrefsInDir(defaultPrefDir, specialFiles,
+ ArrayLength(specialFiles));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error parsing application default preferences.");
+ }
+
+ // Load jar:$app/omni.jar!/defaults/preferences/*.js
+ // or jar:$gre/omni.jar!/defaults/preferences/*.js.
+ RefPtr<nsZipArchive> appJarReader = Omnijar::GetReader(Omnijar::APP);
+
+ // GetReader(Omnijar::APP) returns null when `$app == $gre`, in
+ // which case we look for app-specific default preferences in $gre.
+ if (!appJarReader) {
+ appJarReader = Omnijar::GetReader(Omnijar::GRE);
+ }
+
+ if (appJarReader) {
+ rv = appJarReader->FindInit("defaults/preferences/*.js$",
+ getter_Transfers(find));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefEntries.Clear();
+ while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
+ prefEntries.AppendElement(Substring(entryName, entryNameLen));
+ }
+ prefEntries.Sort();
+ for (uint32_t i = prefEntries.Length(); i--;) {
+ rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get());
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error parsing preferences.");
+ }
+ }
+
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ rv = appJarReader->FindInit("defaults/backgroundtasks/*.js$",
+ getter_Transfers(find));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefEntries.Clear();
+ while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
+ prefEntries.AppendElement(Substring(entryName, entryNameLen));
+ }
+ prefEntries.Sort();
+ for (uint32_t i = prefEntries.Length(); i--;) {
+ rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get());
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error parsing preferences.");
+ }
+ }
+ }
+#endif
+ }
+
+ nsCOMPtr<nsIProperties> dirSvc(
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> list;
+ dirSvc->Get(NS_APP_PREFS_DEFAULTS_DIR_LIST, NS_GET_IID(nsISimpleEnumerator),
+ getter_AddRefs(list));
+ if (list) {
+ bool hasMore;
+ while (NS_SUCCEEDED(list->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> elem;
+ list->GetNext(getter_AddRefs(elem));
+ if (!elem) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> path = do_QueryInterface(elem);
+ if (!path) {
+ continue;
+ }
+
+ // Do we care if a file provided by this process fails to load?
+ pref_LoadPrefsInDir(path, nullptr, 0);
+ }
+ }
+
+#if defined(MOZ_WIDGET_GTK)
+ // To ensure the system-wide preferences are not overwritten by
+ // firefox/browser/defauts/preferences/*.js we need to load
+ // the /etc/firefox/defaults/pref/*.js settings as last.
+ // Under Flatpak, the NS_OS_SYSTEM_CONFIG_DIR points to /app/etc/firefox
+ nsCOMPtr<nsIFile> defaultSystemPrefDir;
+ rv = NS_GetSpecialDirectory(NS_OS_SYSTEM_CONFIG_DIR,
+ getter_AddRefs(defaultSystemPrefDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ defaultSystemPrefDir->AppendNative("defaults"_ns);
+ defaultSystemPrefDir->AppendNative("pref"_ns);
+
+ rv = pref_LoadPrefsInDir(defaultSystemPrefDir, nullptr, 0);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error parsing application default preferences.");
+ }
+#endif
+
+ if (XRE_IsParentProcess()) {
+ SetupTelemetryPref();
+ }
+
+ if (aIsStartup) {
+ // Now that all prefs have their initial values, install the callbacks for
+ // `always`-mirrored static prefs. We do this now rather than in
+ // StaticPrefs::InitAll() so that the callbacks don't need to be traversed
+ // while we load prefs from data files.
+ StaticPrefs::StartObservingAlwaysPrefs();
+ }
+
+ NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, nullptr,
+ NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID);
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ observerService->NotifyObservers(nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID,
+ nullptr);
+
+ return NS_OK;
+}
+
+/* static */
+nsresult Preferences::GetBool(const char* aPrefName, bool* aResult,
+ PrefValueKind aKind) {
+ MOZ_ASSERT(aResult);
+ return Internals::GetPrefValue(aPrefName, aResult, aKind);
+}
+
+/* static */
+nsresult Preferences::GetInt(const char* aPrefName, int32_t* aResult,
+ PrefValueKind aKind) {
+ MOZ_ASSERT(aResult);
+ return Internals::GetPrefValue(aPrefName, aResult, aKind);
+}
+
+/* static */
+nsresult Preferences::GetFloat(const char* aPrefName, float* aResult,
+ PrefValueKind aKind) {
+ MOZ_ASSERT(aResult);
+ return Internals::GetPrefValue(aPrefName, aResult, aKind);
+}
+
+/* static */
+nsresult Preferences::GetCString(const char* aPrefName, nsACString& aResult,
+ PrefValueKind aKind) {
+ aResult.SetIsVoid(true);
+ return Internals::GetPrefValue(aPrefName, aResult, aKind);
+}
+
+/* static */
+nsresult Preferences::GetString(const char* aPrefName, nsAString& aResult,
+ PrefValueKind aKind) {
+ nsAutoCString result;
+ nsresult rv = Preferences::GetCString(aPrefName, result, aKind);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(result, aResult);
+ }
+ return rv;
+}
+
+/* static */
+nsresult Preferences::GetLocalizedCString(const char* aPrefName,
+ nsACString& aResult,
+ PrefValueKind aKind) {
+ nsAutoString result;
+ nsresult rv = GetLocalizedString(aPrefName, result, aKind);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF16toUTF8(result, aResult);
+ }
+ return rv;
+}
+
+/* static */
+nsresult Preferences::GetLocalizedString(const char* aPrefName,
+ nsAString& aResult,
+ PrefValueKind aKind) {
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ nsCOMPtr<nsIPrefLocalizedString> prefLocalString;
+ nsresult rv = GetRootBranch(aKind)->GetComplexValue(
+ aPrefName, NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(prefLocalString));
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(prefLocalString, "Succeeded but the result is NULL");
+ prefLocalString->GetData(aResult);
+ }
+ return rv;
+}
+
+/* static */
+nsresult Preferences::GetComplex(const char* aPrefName, const nsIID& aType,
+ void** aResult, PrefValueKind aKind) {
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return GetRootBranch(aKind)->GetComplexValue(aPrefName, aType, aResult);
+}
+
+/* static */
+bool Preferences::GetBool(const char* aPrefName, bool aFallback,
+ PrefValueKind aKind) {
+ return Internals::GetPref(aPrefName, aFallback, aKind);
+}
+
+/* static */
+int32_t Preferences::GetInt(const char* aPrefName, int32_t aFallback,
+ PrefValueKind aKind) {
+ return Internals::GetPref(aPrefName, aFallback, aKind);
+}
+
+/* static */
+uint32_t Preferences::GetUint(const char* aPrefName, uint32_t aFallback,
+ PrefValueKind aKind) {
+ return Internals::GetPref(aPrefName, aFallback, aKind);
+}
+
+/* static */
+float Preferences::GetFloat(const char* aPrefName, float aFallback,
+ PrefValueKind aKind) {
+ return Internals::GetPref(aPrefName, aFallback, aKind);
+}
+
+/* static */
+nsresult Preferences::SetCString(const char* aPrefName,
+ const nsACString& aValue,
+ PrefValueKind aKind) {
+ ENSURE_PARENT_PROCESS("SetCString", aPrefName);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+
+ if (aValue.Length() > MAX_PREF_LENGTH) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // It's ok to stash a pointer to the temporary PromiseFlatCString's chars in
+ // pref because pref_SetPref() duplicates those chars.
+ PrefValue prefValue;
+ const nsCString& flat = PromiseFlatCString(aValue);
+ prefValue.mStringVal = flat.get();
+ return pref_SetPref(nsDependentCString(aPrefName), PrefType::String, aKind,
+ prefValue,
+ /* isSticky */ false,
+ /* isLocked */ false,
+ /* fromInit */ false);
+}
+
+/* static */
+nsresult Preferences::SetBool(const char* aPrefName, bool aValue,
+ PrefValueKind aKind) {
+ ENSURE_PARENT_PROCESS("SetBool", aPrefName);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+
+ PrefValue prefValue;
+ prefValue.mBoolVal = aValue;
+ return pref_SetPref(nsDependentCString(aPrefName), PrefType::Bool, aKind,
+ prefValue,
+ /* isSticky */ false,
+ /* isLocked */ false,
+ /* fromInit */ false);
+}
+
+/* static */
+nsresult Preferences::SetInt(const char* aPrefName, int32_t aValue,
+ PrefValueKind aKind) {
+ ENSURE_PARENT_PROCESS("SetInt", aPrefName);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+
+ PrefValue prefValue;
+ prefValue.mIntVal = aValue;
+ return pref_SetPref(nsDependentCString(aPrefName), PrefType::Int, aKind,
+ prefValue,
+ /* isSticky */ false,
+ /* isLocked */ false,
+ /* fromInit */ false);
+}
+
+/* static */
+nsresult Preferences::SetComplex(const char* aPrefName, const nsIID& aType,
+ nsISupports* aValue, PrefValueKind aKind) {
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return GetRootBranch(aKind)->SetComplexValue(aPrefName, aType, aValue);
+}
+
+/* static */
+nsresult Preferences::Lock(const char* aPrefName) {
+ ENSURE_PARENT_PROCESS("Lock", aPrefName);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+
+ const auto& prefName = nsDependentCString(aPrefName);
+
+ Pref* pref;
+ MOZ_TRY_VAR(pref,
+ pref_LookupForModify(prefName, [](const PrefWrapper& aPref) {
+ return !aPref.IsLocked();
+ }));
+
+ if (pref) {
+ pref->SetIsLocked(true);
+ NotifyCallbacks(prefName, PrefWrapper(pref));
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult Preferences::Unlock(const char* aPrefName) {
+ ENSURE_PARENT_PROCESS("Unlock", aPrefName);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+
+ const auto& prefName = nsDependentCString(aPrefName);
+
+ Pref* pref;
+ MOZ_TRY_VAR(pref,
+ pref_LookupForModify(prefName, [](const PrefWrapper& aPref) {
+ return aPref.IsLocked();
+ }));
+
+ if (pref) {
+ pref->SetIsLocked(false);
+ NotifyCallbacks(prefName, PrefWrapper(pref));
+ }
+
+ return NS_OK;
+}
+
+/* static */
+bool Preferences::IsLocked(const char* aPrefName) {
+ NS_ENSURE_TRUE(InitStaticMembers(), false);
+
+ Maybe<PrefWrapper> pref = pref_Lookup(aPrefName);
+ return pref.isSome() && pref->IsLocked();
+}
+
+/* static */
+bool Preferences::IsSanitized(const char* aPrefName) {
+ NS_ENSURE_TRUE(InitStaticMembers(), false);
+
+ Maybe<PrefWrapper> pref = pref_Lookup(aPrefName);
+ return pref.isSome() && pref->IsSanitized();
+}
+
+/* static */
+nsresult Preferences::ClearUser(const char* aPrefName) {
+ ENSURE_PARENT_PROCESS("ClearUser", aPrefName);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+
+ const auto& prefName = nsDependentCString{aPrefName};
+ auto result = pref_LookupForModify(
+ prefName, [](const PrefWrapper& aPref) { return aPref.HasUserValue(); });
+ if (result.isErr()) {
+ return NS_OK;
+ }
+
+ if (Pref* pref = result.unwrap()) {
+ pref->ClearUserValue();
+
+ if (!pref->HasDefaultValue()) {
+ MOZ_ASSERT(
+ !gSharedMap || !pref->IsSanitized() || !gSharedMap->Has(pref->Name()),
+ "A sanitized pref should never be in the shared pref map.");
+ if (!pref->IsSanitized() &&
+ (!gSharedMap || !gSharedMap->Has(pref->Name()))) {
+ HashTable()->remove(aPrefName);
+ } else {
+ pref->SetType(PrefType::None);
+ }
+
+ NotifyCallbacks(prefName);
+ } else {
+ NotifyCallbacks(prefName, PrefWrapper(pref));
+ }
+
+ Preferences::HandleDirty();
+ }
+ return NS_OK;
+}
+
+/* static */
+bool Preferences::HasUserValue(const char* aPrefName) {
+ NS_ENSURE_TRUE(InitStaticMembers(), false);
+
+ Maybe<PrefWrapper> pref = pref_Lookup(aPrefName);
+ return pref.isSome() && pref->HasUserValue();
+}
+
+/* static */
+bool Preferences::HasDefaultValue(const char* aPrefName) {
+ NS_ENSURE_TRUE(InitStaticMembers(), false);
+
+ Maybe<PrefWrapper> pref = pref_Lookup(aPrefName);
+ return pref.isSome() && pref->HasDefaultValue();
+}
+
+/* static */
+int32_t Preferences::GetType(const char* aPrefName) {
+ NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
+
+ if (!HashTable()) {
+ return PREF_INVALID;
+ }
+
+ Maybe<PrefWrapper> pref = pref_Lookup(aPrefName);
+ if (!pref.isSome()) {
+ return PREF_INVALID;
+ }
+
+ switch (pref->Type()) {
+ case PrefType::String:
+ return PREF_STRING;
+
+ case PrefType::Int:
+ return PREF_INT;
+
+ case PrefType::Bool:
+ return PREF_BOOL;
+
+ case PrefType::None:
+ if (IsPreferenceSanitized(aPrefName)) {
+ if (!sPrefTelemetryEventEnabled.exchange(true)) {
+ sPrefTelemetryEventEnabled = true;
+ Telemetry::SetEventRecordingEnabled("security"_ns, true);
+ }
+
+ Telemetry::RecordEvent(
+ Telemetry::EventID::Security_Prefusage_Contentprocess,
+ mozilla::Some(aPrefName), mozilla::Nothing());
+
+ if (sCrashOnBlocklistedPref) {
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "Should not access the preference '%s' in the Content Processes",
+ aPrefName);
+ } else {
+ return PREF_INVALID;
+ }
+ }
+ [[fallthrough]];
+
+ default:
+ MOZ_CRASH();
+ }
+}
+
+/* static */
+nsresult Preferences::AddStrongObserver(nsIObserver* aObserver,
+ const nsACString& aPref) {
+ MOZ_ASSERT(aObserver);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return sPreferences->mRootBranch->AddObserver(aPref, aObserver, false);
+}
+
+/* static */
+nsresult Preferences::AddWeakObserver(nsIObserver* aObserver,
+ const nsACString& aPref) {
+ MOZ_ASSERT(aObserver);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return sPreferences->mRootBranch->AddObserver(aPref, aObserver, true);
+}
+
+/* static */
+nsresult Preferences::RemoveObserver(nsIObserver* aObserver,
+ const nsACString& aPref) {
+ MOZ_ASSERT(aObserver);
+ if (sShutdown) {
+ MOZ_ASSERT(!sPreferences);
+ return NS_OK; // Observers have been released automatically.
+ }
+ NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
+ return sPreferences->mRootBranch->RemoveObserver(aPref, aObserver);
+}
+
+template <typename T>
+static void AssertNotMallocAllocated(T* aPtr) {
+#if defined(DEBUG) && defined(MOZ_MEMORY)
+ jemalloc_ptr_info_t info;
+ jemalloc_ptr_info((void*)aPtr, &info);
+ MOZ_ASSERT(info.tag == TagUnknown);
+#endif
+}
+
+/* static */
+nsresult Preferences::AddStrongObservers(nsIObserver* aObserver,
+ const char** aPrefs) {
+ MOZ_ASSERT(aObserver);
+ for (uint32_t i = 0; aPrefs[i]; i++) {
+ AssertNotMallocAllocated(aPrefs[i]);
+
+ nsCString pref;
+ pref.AssignLiteral(aPrefs[i], strlen(aPrefs[i]));
+ nsresult rv = AddStrongObserver(aObserver, pref);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+/* static */
+nsresult Preferences::AddWeakObservers(nsIObserver* aObserver,
+ const char** aPrefs) {
+ MOZ_ASSERT(aObserver);
+ for (uint32_t i = 0; aPrefs[i]; i++) {
+ AssertNotMallocAllocated(aPrefs[i]);
+
+ nsCString pref;
+ pref.AssignLiteral(aPrefs[i], strlen(aPrefs[i]));
+ nsresult rv = AddWeakObserver(aObserver, pref);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+/* static */
+nsresult Preferences::RemoveObservers(nsIObserver* aObserver,
+ const char** aPrefs) {
+ MOZ_ASSERT(aObserver);
+ if (sShutdown) {
+ MOZ_ASSERT(!sPreferences);
+ return NS_OK; // Observers have been released automatically.
+ }
+ NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
+
+ for (uint32_t i = 0; aPrefs[i]; i++) {
+ nsresult rv = RemoveObserver(aObserver, nsDependentCString(aPrefs[i]));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+template <typename T>
+/* static */
+nsresult Preferences::RegisterCallbackImpl(PrefChangedFunc aCallback,
+ T& aPrefNode, void* aData,
+ MatchKind aMatchKind,
+ bool aIsPriority) {
+ NS_ENSURE_ARG(aCallback);
+
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+
+ auto node = new CallbackNode(aPrefNode, aCallback, aData, aMatchKind);
+
+ if (aIsPriority) {
+ // Add to the start of the list.
+ node->SetNext(gFirstCallback);
+ gFirstCallback = node;
+ if (!gLastPriorityNode) {
+ gLastPriorityNode = node;
+ }
+ } else {
+ // Add to the start of the non-priority part of the list.
+ if (gLastPriorityNode) {
+ node->SetNext(gLastPriorityNode->Next());
+ gLastPriorityNode->SetNext(node);
+ } else {
+ node->SetNext(gFirstCallback);
+ gFirstCallback = node;
+ }
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult Preferences::RegisterCallback(PrefChangedFunc aCallback,
+ const nsACString& aPrefNode, void* aData,
+ MatchKind aMatchKind, bool aIsPriority) {
+ return RegisterCallbackImpl(aCallback, aPrefNode, aData, aMatchKind,
+ aIsPriority);
+}
+
+/* static */
+nsresult Preferences::RegisterCallbacks(PrefChangedFunc aCallback,
+ const char** aPrefs, void* aData,
+ MatchKind aMatchKind) {
+ return RegisterCallbackImpl(aCallback, aPrefs, aData, aMatchKind);
+}
+
+/* static */
+nsresult Preferences::RegisterCallbackAndCall(PrefChangedFunc aCallback,
+ const nsACString& aPref,
+ void* aClosure,
+ MatchKind aMatchKind) {
+ MOZ_ASSERT(aCallback);
+ nsresult rv = RegisterCallback(aCallback, aPref, aClosure, aMatchKind);
+ if (NS_SUCCEEDED(rv)) {
+ (*aCallback)(PromiseFlatCString(aPref).get(), aClosure);
+ }
+ return rv;
+}
+
+/* static */
+nsresult Preferences::RegisterCallbacksAndCall(PrefChangedFunc aCallback,
+ const char** aPrefs,
+ void* aClosure) {
+ MOZ_ASSERT(aCallback);
+
+ nsresult rv =
+ RegisterCallbacks(aCallback, aPrefs, aClosure, MatchKind::ExactMatch);
+ if (NS_SUCCEEDED(rv)) {
+ for (const char** ptr = aPrefs; *ptr; ptr++) {
+ (*aCallback)(*ptr, aClosure);
+ }
+ }
+ return rv;
+}
+
+template <typename T>
+/* static */
+nsresult Preferences::UnregisterCallbackImpl(PrefChangedFunc aCallback,
+ T& aPrefNode, void* aData,
+ MatchKind aMatchKind) {
+ MOZ_ASSERT(aCallback);
+ if (sShutdown) {
+ MOZ_ASSERT(!sPreferences);
+ return NS_OK; // Observers have been released automatically.
+ }
+ NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
+
+ nsresult rv = NS_ERROR_FAILURE;
+ CallbackNode* node = gFirstCallback;
+ CallbackNode* prev_node = nullptr;
+
+ while (node) {
+ if (node->Func() == aCallback && node->Data() == aData &&
+ node->MatchKind() == aMatchKind && node->DomainIs(aPrefNode)) {
+ if (gCallbacksInProgress) {
+ // Postpone the node removal until after callbacks enumeration is
+ // finished.
+ node->ClearFunc();
+ gShouldCleanupDeadNodes = true;
+ prev_node = node;
+ node = node->Next();
+ } else {
+ node = pref_RemoveCallbackNode(node, prev_node);
+ }
+ rv = NS_OK;
+ } else {
+ prev_node = node;
+ node = node->Next();
+ }
+ }
+ return rv;
+}
+
+/* static */
+nsresult Preferences::UnregisterCallback(PrefChangedFunc aCallback,
+ const nsACString& aPrefNode,
+ void* aData, MatchKind aMatchKind) {
+ return UnregisterCallbackImpl<const nsACString&>(aCallback, aPrefNode, aData,
+ aMatchKind);
+}
+
+/* static */
+nsresult Preferences::UnregisterCallbacks(PrefChangedFunc aCallback,
+ const char** aPrefs, void* aData,
+ MatchKind aMatchKind) {
+ return UnregisterCallbackImpl(aCallback, aPrefs, aData, aMatchKind);
+}
+
+template <typename T>
+static void AddMirrorCallback(T* aMirror, const nsACString& aPref) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Internals::RegisterCallback<T>(aMirror, aPref);
+}
+
+// Don't inline because it explodes compile times.
+template <typename T>
+static MOZ_NEVER_INLINE void AddMirror(T* aMirror, const nsACString& aPref,
+ StripAtomic<T> aDefault) {
+ *aMirror = Internals::GetPref(PromiseFlatCString(aPref).get(), aDefault);
+ AddMirrorCallback(aMirror, aPref);
+}
+
+static MOZ_NEVER_INLINE void AddMirror(DataMutexString& aMirror,
+ const nsACString& aPref) {
+ auto lock = aMirror.Lock();
+ nsCString result(*lock);
+ Internals::GetPrefValue(PromiseFlatCString(aPref).get(), result,
+ PrefValueKind::User);
+ lock->Assign(std::move(result));
+ AddMirrorCallback(&aMirror, aPref);
+}
+
+// The InitPref_*() functions below end in a `_<type>` suffix because they are
+// used by the PREF macro definition in InitAll() below.
+
+static void InitPref_bool(const nsCString& aName, bool aDefaultValue) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ PrefValue value;
+ value.mBoolVal = aDefaultValue;
+ pref_SetPref(aName, PrefType::Bool, PrefValueKind::Default, value,
+ /* isSticky */ false,
+ /* isLocked */ false,
+ /* fromInit */ true);
+}
+
+static void InitPref_int32_t(const nsCString& aName, int32_t aDefaultValue) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ PrefValue value;
+ value.mIntVal = aDefaultValue;
+ pref_SetPref(aName, PrefType::Int, PrefValueKind::Default, value,
+ /* isSticky */ false,
+ /* isLocked */ false,
+ /* fromInit */ true);
+}
+
+static void InitPref_uint32_t(const nsCString& aName, uint32_t aDefaultValue) {
+ InitPref_int32_t(aName, int32_t(aDefaultValue));
+}
+
+static void InitPref_float(const nsCString& aName, float aDefaultValue) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ PrefValue value;
+ // Convert the value in a locale-independent way, including a trailing ".0"
+ // if necessary to distinguish floating-point from integer prefs when viewing
+ // them in about:config.
+ nsAutoCString defaultValue;
+ defaultValue.AppendFloat(aDefaultValue);
+ if (!defaultValue.Contains('.') && !defaultValue.Contains('e')) {
+ defaultValue.AppendLiteral(".0");
+ }
+ value.mStringVal = defaultValue.get();
+ pref_SetPref(aName, PrefType::String, PrefValueKind::Default, value,
+ /* isSticky */ false,
+ /* isLocked */ false,
+ /* fromInit */ true);
+}
+
+static void InitPref_String(const nsCString& aName, const char* aDefaultValue) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ PrefValue value;
+ value.mStringVal = aDefaultValue;
+ pref_SetPref(aName, PrefType::String, PrefValueKind::Default, value,
+ /* isSticky */ false,
+ /* isLocked */ false,
+ /* fromInit */ true);
+}
+
+static void InitPref(const nsCString& aName, bool aDefaultValue) {
+ InitPref_bool(aName, aDefaultValue);
+}
+static void InitPref(const nsCString& aName, int32_t aDefaultValue) {
+ InitPref_int32_t(aName, aDefaultValue);
+}
+static void InitPref(const nsCString& aName, uint32_t aDefaultValue) {
+ InitPref_uint32_t(aName, aDefaultValue);
+}
+static void InitPref(const nsCString& aName, float aDefaultValue) {
+ InitPref_float(aName, aDefaultValue);
+}
+
+template <typename T>
+static void InitAlwaysPref(const nsCString& aName, T* aCache,
+ StripAtomic<T> aDefaultValue) {
+ // Only called in the parent process. Set/reset the pref value and the
+ // `always` mirror to the default value.
+ // `once` mirrors will be initialized lazily in InitOncePrefs().
+ InitPref(aName, aDefaultValue);
+ *aCache = aDefaultValue;
+}
+
+static void InitAlwaysPref(const nsCString& aName, DataMutexString& aCache,
+ const nsLiteralCString& aDefaultValue) {
+ // Only called in the parent process. Set/reset the pref value and the
+ // `always` mirror to the default value.
+ // `once` mirrors will be initialized lazily in InitOncePrefs().
+ InitPref_String(aName, aDefaultValue.get());
+ Internals::AssignMirror(aCache, aDefaultValue);
+}
+
+static Atomic<bool> sOncePrefRead(false);
+static StaticMutex sOncePrefMutex MOZ_UNANNOTATED;
+
+namespace StaticPrefs {
+
+void MaybeInitOncePrefs() {
+ if (MOZ_LIKELY(sOncePrefRead)) {
+ // `once`-mirrored prefs have already been initialized to their default
+ // value.
+ return;
+ }
+ StaticMutexAutoLock lock(sOncePrefMutex);
+ if (NS_IsMainThread()) {
+ InitOncePrefs();
+ } else {
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "Preferences::MaybeInitOncePrefs", [&]() { InitOncePrefs(); });
+ // This logic needs to run on the main thread
+ SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), runnable);
+ }
+ sOncePrefRead = true;
+}
+
+// For mirrored prefs we generate a variable definition.
+#define NEVER_PREF(name, cpp_type, value)
+#define ALWAYS_PREF(name, base_id, full_id, cpp_type, default_value) \
+ cpp_type sMirror_##full_id(default_value);
+#define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, default_value) \
+ cpp_type sMirror_##full_id("DataMutexString");
+#define ONCE_PREF(name, base_id, full_id, cpp_type, default_value) \
+ cpp_type sMirror_##full_id(default_value);
+#include "mozilla/StaticPrefListAll.h"
+#undef NEVER_PREF
+#undef ALWAYS_PREF
+#undef ALWAYS_DATAMUTEX_PREF
+#undef ONCE_PREF
+
+static void InitAll() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // For all prefs we generate some initialization code.
+ //
+ // The InitPref_*() functions have a type suffix to avoid ambiguity between
+ // prefs having int32_t and float default values. That suffix is not needed
+ // for the InitAlwaysPref() functions because they take a pointer parameter,
+ // which prevents automatic int-to-float coercion.
+#define NEVER_PREF(name, cpp_type, value) \
+ InitPref_##cpp_type(name ""_ns, value);
+#define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) \
+ InitAlwaysPref(name ""_ns, &sMirror_##full_id, value);
+#define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value) \
+ InitAlwaysPref(name ""_ns, sMirror_##full_id, value);
+#define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
+ InitPref_##cpp_type(name ""_ns, value);
+#include "mozilla/StaticPrefListAll.h"
+#undef NEVER_PREF
+#undef ALWAYS_PREF
+#undef ALWAYS_DATAMUTEX_PREF
+#undef ONCE_PREF
+}
+
+static void StartObservingAlwaysPrefs() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Call AddMirror so that our mirrors for `always` prefs will stay updated.
+ // The call to AddMirror re-reads the current pref value into the mirror, so
+ // our mirror will now be up-to-date even if some of the prefs have changed
+ // since the call to InitAll().
+#define NEVER_PREF(name, cpp_type, value)
+#define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) \
+ AddMirror(&sMirror_##full_id, name ""_ns, sMirror_##full_id);
+#define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value) \
+ AddMirror(sMirror_##full_id, name ""_ns);
+#define ONCE_PREF(name, base_id, full_id, cpp_type, value)
+#include "mozilla/StaticPrefListAll.h"
+#undef NEVER_PREF
+#undef ALWAYS_PREF
+#undef ALWAYS_DATAMUTEX_PREF
+#undef ONCE_PREF
+}
+
+static void InitOncePrefs() {
+ // For `once`-mirrored prefs we generate some initialization code. This is
+ // done in case the pref value was updated when reading pref data files. It's
+ // necessary because we don't have callbacks registered for `once`-mirrored
+ // prefs.
+ //
+ // In debug builds, we also install a mechanism that can check if the
+ // preference value is modified after `once`-mirrored prefs are initialized.
+ // In tests this would indicate a likely misuse of a `once`-mirrored pref and
+ // suggest that it should instead be `always`-mirrored.
+#define NEVER_PREF(name, cpp_type, value)
+#define ALWAYS_PREF(name, base_id, full_id, cpp_type, value)
+#define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value)
+#ifdef DEBUG
+# define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
+ { \
+ MOZ_ASSERT(gOnceStaticPrefsAntiFootgun); \
+ sMirror_##full_id = Internals::GetPref(name, cpp_type(value)); \
+ auto checkPref = [&]() { \
+ MOZ_ASSERT(sOncePrefRead); \
+ cpp_type staticPrefValue = full_id(); \
+ cpp_type preferenceValue = \
+ Internals::GetPref(GetPrefName_##base_id(), cpp_type(value)); \
+ MOZ_ASSERT(staticPrefValue == preferenceValue, \
+ "Preference '" name \
+ "' got modified since StaticPrefs::" #full_id \
+ " was initialized. Consider using an `always` mirror kind " \
+ "instead"); \
+ }; \
+ gOnceStaticPrefsAntiFootgun->insert( \
+ std::pair<const char*, AntiFootgunCallback>(GetPrefName_##base_id(), \
+ std::move(checkPref))); \
+ }
+#else
+# define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
+ sMirror_##full_id = Internals::GetPref(name, cpp_type(value));
+#endif
+
+#include "mozilla/StaticPrefListAll.h"
+#undef NEVER_PREF
+#undef ALWAYS_PREF
+#undef ALWAYS_DATAMUTEX_PREF
+#undef ONCE_PREF
+}
+
+static void ShutdownAlwaysPrefs() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We may need to do clean up for leak detection for some StaticPrefs.
+#define NEVER_PREF(name, cpp_type, value)
+#define ALWAYS_PREF(name, base_id, full_id, cpp_type, value)
+#define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value) \
+ Internals::ClearMirror(sMirror_##full_id);
+#define ONCE_PREF(name, base_id, full_id, cpp_type, value)
+#include "mozilla/StaticPrefListAll.h"
+#undef NEVER_PREF
+#undef ALWAYS_PREF
+#undef ALWAYS_DATAMUTEX_PREF
+#undef ONCE_PREF
+}
+
+} // namespace StaticPrefs
+
+static MOZ_MAYBE_UNUSED void SaveOncePrefToSharedMap(
+ SharedPrefMapBuilder& aBuilder, const nsACString& aName, bool aValue) {
+ auto oncePref = MakeUnique<Pref>(aName);
+ oncePref->SetType(PrefType::Bool);
+ oncePref->SetIsSkippedByIteration(true);
+ bool valueChanged = false;
+ MOZ_ALWAYS_SUCCEEDS(
+ oncePref->SetDefaultValue(PrefType::Bool, PrefValue(aValue),
+ /* isSticky */ true,
+ /* isLocked */ true, &valueChanged));
+ oncePref->AddToMap(aBuilder);
+}
+
+static MOZ_MAYBE_UNUSED void SaveOncePrefToSharedMap(
+ SharedPrefMapBuilder& aBuilder, const nsACString& aName, int32_t aValue) {
+ auto oncePref = MakeUnique<Pref>(aName);
+ oncePref->SetType(PrefType::Int);
+ oncePref->SetIsSkippedByIteration(true);
+ bool valueChanged = false;
+ MOZ_ALWAYS_SUCCEEDS(
+ oncePref->SetDefaultValue(PrefType::Int, PrefValue(aValue),
+ /* isSticky */ true,
+ /* isLocked */ true, &valueChanged));
+ oncePref->AddToMap(aBuilder);
+}
+
+static MOZ_MAYBE_UNUSED void SaveOncePrefToSharedMap(
+ SharedPrefMapBuilder& aBuilder, const nsACString& aName, uint32_t aValue) {
+ SaveOncePrefToSharedMap(aBuilder, aName, int32_t(aValue));
+}
+
+static MOZ_MAYBE_UNUSED void SaveOncePrefToSharedMap(
+ SharedPrefMapBuilder& aBuilder, const nsACString& aName, float aValue) {
+ auto oncePref = MakeUnique<Pref>(aName);
+ oncePref->SetType(PrefType::String);
+ oncePref->SetIsSkippedByIteration(true);
+ nsAutoCString value;
+ value.AppendFloat(aValue);
+ bool valueChanged = false;
+ // It's ok to stash a pointer to the temporary PromiseFlatCString's chars in
+ // pref because pref_SetPref() duplicates those chars.
+ const nsCString& flat = PromiseFlatCString(value);
+ MOZ_ALWAYS_SUCCEEDS(
+ oncePref->SetDefaultValue(PrefType::String, PrefValue(flat.get()),
+ /* isSticky */ true,
+ /* isLocked */ true, &valueChanged));
+ oncePref->AddToMap(aBuilder);
+}
+
+#define ONCE_PREF_NAME(name) "$$$" name "$$$"
+
+namespace StaticPrefs {
+
+static void RegisterOncePrefs(SharedPrefMapBuilder& aBuilder) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_DIAGNOSTIC_ASSERT(!gSharedMap,
+ "Must be called before gSharedMap has been created");
+ MaybeInitOncePrefs();
+
+ // For `once`-mirrored prefs we generate a save call, which saves the value
+ // as it was at parent startup. It is stored in a special (hidden and locked)
+ // entry in the global SharedPreferenceMap. In order for the entry to be
+ // hidden and not appear in about:config nor ever be stored to disk, we set
+ // its IsSkippedByIteration flag to true. We also distinguish it by adding a
+ // "$$$" prefix and suffix to the preference name.
+#define NEVER_PREF(name, cpp_type, value)
+#define ALWAYS_PREF(name, base_id, full_id, cpp_type, value)
+#define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value)
+#define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
+ SaveOncePrefToSharedMap(aBuilder, ONCE_PREF_NAME(name) ""_ns, \
+ cpp_type(sMirror_##full_id));
+#include "mozilla/StaticPrefListAll.h"
+#undef NEVER_PREF
+#undef ALWAYS_PREF
+#undef ALWAYS_DATAMUTEX_PREF
+#undef ONCE_PREF
+}
+
+// Disable thread safety analysis on this function, because it explodes build
+// times and memory usage.
+MOZ_NO_THREAD_SAFETY_ANALYSIS
+static void InitStaticPrefsFromShared() {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_DIAGNOSTIC_ASSERT(gSharedMap,
+ "Must be called once gSharedMap has been created");
+
+ // For mirrored static prefs we generate some initialization code. Each
+ // mirror variable is already initialized in the binary with the default
+ // value. If the pref value hasn't changed from the default in the main
+ // process (the common case) then the overwriting here won't change the
+ // mirror variable's value.
+ //
+ // Note that the MOZ_ASSERT calls below can fail in one obscure case: when a
+ // Firefox update occurs and we get a main process from the old binary (with
+ // static prefs {A,B,C,D}) plus a new content process from the new binary
+ // (with static prefs {A,B,C,D,E}). The content process' call to
+ // GetSharedPrefValue() for pref E will fail because the shared pref map was
+ // created by the main process, which doesn't have pref E.
+ //
+ // This silent failure is safe. The mirror variable for pref E is already
+ // initialized to the default value in the content process, and the main
+ // process cannot have changed pref E because it doesn't know about it!
+ //
+ // Nonetheless, it's useful to have the MOZ_ASSERT here for testing of debug
+ // builds, where this scenario involving inconsistent binaries should not
+ // occur.
+#define NEVER_PREF(name, cpp_type, default_value)
+#define ALWAYS_PREF(name, base_id, full_id, cpp_type, default_value) \
+ { \
+ StripAtomic<cpp_type> val; \
+ if (IsString<cpp_type>::value && IsPreferenceSanitized(name)) { \
+ if (!sPrefTelemetryEventEnabled.exchange(true)) { \
+ sPrefTelemetryEventEnabled = true; \
+ Telemetry::SetEventRecordingEnabled("security"_ns, true); \
+ } \
+ Telemetry::RecordEvent( \
+ Telemetry::EventID::Security_Prefusage_Contentprocess, \
+ mozilla::Some(name##_ns), mozilla::Nothing()); \
+ MOZ_DIAGNOSTIC_ASSERT(!sCrashOnBlocklistedPref, \
+ "Should not access the preference '" name \
+ "' in Content Processes"); \
+ } \
+ DebugOnly<nsresult> rv = Internals::GetSharedPrefValue(name, &val); \
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \
+ StaticPrefs::sMirror_##full_id = val; \
+ }
+#define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, default_value) \
+ { \
+ StripAtomic<cpp_type> val; \
+ if (IsString<cpp_type>::value && IsPreferenceSanitized(name)) { \
+ if (!sPrefTelemetryEventEnabled.exchange(true)) { \
+ sPrefTelemetryEventEnabled = true; \
+ Telemetry::SetEventRecordingEnabled("security"_ns, true); \
+ } \
+ Telemetry::RecordEvent( \
+ Telemetry::EventID::Security_Prefusage_Contentprocess, \
+ mozilla::Some(name##_ns), mozilla::Nothing()); \
+ MOZ_DIAGNOSTIC_ASSERT(!sCrashOnBlocklistedPref, \
+ "Should not access the preference '" name \
+ "' in Content Processes"); \
+ } \
+ DebugOnly<nsresult> rv = Internals::GetSharedPrefValue(name, &val); \
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \
+ Internals::AssignMirror(StaticPrefs::sMirror_##full_id, \
+ std::forward<StripAtomic<cpp_type>>(val)); \
+ }
+#define ONCE_PREF(name, base_id, full_id, cpp_type, default_value) \
+ { \
+ cpp_type val; \
+ if (IsString<cpp_type>::value && IsPreferenceSanitized(name)) { \
+ if (!sPrefTelemetryEventEnabled.exchange(true)) { \
+ sPrefTelemetryEventEnabled = true; \
+ Telemetry::SetEventRecordingEnabled("security"_ns, true); \
+ } \
+ Telemetry::RecordEvent( \
+ Telemetry::EventID::Security_Prefusage_Contentprocess, \
+ mozilla::Some(name##_ns), mozilla::Nothing()); \
+ MOZ_DIAGNOSTIC_ASSERT(!sCrashOnBlocklistedPref, \
+ "Should not access the preference '" name \
+ "' in Content Processes"); \
+ } \
+ DebugOnly<nsresult> rv = \
+ Internals::GetSharedPrefValue(ONCE_PREF_NAME(name), &val); \
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \
+ StaticPrefs::sMirror_##full_id = val; \
+ }
+#include "mozilla/StaticPrefListAll.h"
+#undef NEVER_PREF
+#undef ALWAYS_PREF
+#undef ALWAYS_DATAMUTEX_PREF
+#undef ONCE_PREF
+
+ // `once`-mirrored prefs have been set to their value in the step above and
+ // outside the parent process they are immutable. We set sOncePrefRead so
+ // that we can directly skip any lazy initializations.
+ sOncePrefRead = true;
+}
+
+} // namespace StaticPrefs
+
+} // namespace mozilla
+
+#undef ENSURE_PARENT_PROCESS
+
+//===========================================================================
+// Module and factory stuff
+//===========================================================================
+
+NS_IMPL_COMPONENT_FACTORY(nsPrefLocalizedString) {
+ auto str = MakeRefPtr<nsPrefLocalizedString>();
+ if (NS_SUCCEEDED(str->Init())) {
+ return str.forget().downcast<nsISupports>();
+ }
+ return nullptr;
+}
+
+namespace mozilla {
+
+void UnloadPrefsModule() { Preferences::Shutdown(); }
+
+} // namespace mozilla
+
+// Preference Sanitization Related Code ---------------------------------------
+
+#define PREF_LIST_ENTRY(s) \
+ { s, (sizeof(s) / sizeof(char)) - 1 }
+struct PrefListEntry {
+ const char* mPrefBranch;
+ size_t mLen;
+};
+
+// A preference is 'sanitized' (i.e. not sent to web content processes) if
+// one of two criteria are met:
+// 1. The pref name matches one of the prefixes in the following list
+// 2. The pref is dynamically named (i.e. not specified in all.js or
+// StaticPrefList.yml), a string pref, and it is NOT exempted in
+// sDynamicPrefOverrideList
+//
+// This behavior is codified in ShouldSanitizePreference() below
+static const PrefListEntry sRestrictFromWebContentProcesses[] = {
+ // Remove prefs with user data
+ PREF_LIST_ENTRY("datareporting.policy."),
+ PREF_LIST_ENTRY("browser.download.lastDir"),
+ PREF_LIST_ENTRY("browser.newtabpage.pinned"),
+ PREF_LIST_ENTRY("browser.uiCustomization.state"),
+ PREF_LIST_ENTRY("browser.urlbar"),
+ PREF_LIST_ENTRY("devtools.debugger.pending-selected-location"),
+ PREF_LIST_ENTRY("identity.fxaccounts.account.device.name"),
+ PREF_LIST_ENTRY("identity.fxaccounts.account.telemetry.sanitized_uid"),
+ PREF_LIST_ENTRY("identity.fxaccounts.lastSignedInUserHash"),
+ PREF_LIST_ENTRY("print_printer"),
+ PREF_LIST_ENTRY("services."),
+
+ // Remove UUIDs
+ PREF_LIST_ENTRY("app.normandy.user_id"),
+ PREF_LIST_ENTRY("browser.newtabpage.activity-stream.impressionId"),
+ PREF_LIST_ENTRY("browser.pageActions.persistedActions"),
+ PREF_LIST_ENTRY("browser.startup.lastColdStartupCheck"),
+ PREF_LIST_ENTRY("dom.push.userAgentID"),
+ PREF_LIST_ENTRY("extensions.webextensions.uuids"),
+ PREF_LIST_ENTRY("privacy.userContext.extension"),
+ PREF_LIST_ENTRY("toolkit.telemetry.cachedClientID"),
+
+ // Remove IDs that could be used to correlate across origins
+ PREF_LIST_ENTRY("app.update.lastUpdateTime."),
+ PREF_LIST_ENTRY(
+ "browser.contentblocking.cfr-milestone.milestone-shown-time"),
+ PREF_LIST_ENTRY("browser.contextual-services.contextId"),
+ PREF_LIST_ENTRY("browser.laterrun.bookkeeping.profileCreationTime"),
+ PREF_LIST_ENTRY("browser.newtabpage.activity-stream.discoverystream."),
+ PREF_LIST_ENTRY("browser.sessionstore.upgradeBackup.latestBuildID"),
+ PREF_LIST_ENTRY("browser.shell.mostRecentDateSetAsDefault"),
+ PREF_LIST_ENTRY("idle.lastDailyNotification"),
+ PREF_LIST_ENTRY("media.gmp-gmpopenh264.lastUpdate"),
+ PREF_LIST_ENTRY("media.gmp-manager.lastCheck"),
+ PREF_LIST_ENTRY("places.database.lastMaintenance"),
+ PREF_LIST_ENTRY("privacy.purge_trackers.last_purge"),
+ PREF_LIST_ENTRY("storage.vacuum.last.places.sqlite"),
+ PREF_LIST_ENTRY("toolkit.startup.last_success"),
+
+ // Remove fingerprintable things
+ PREF_LIST_ENTRY("browser.startup.homepage_override.buildID"),
+ PREF_LIST_ENTRY("extensions.lastAppBuildId"),
+ PREF_LIST_ENTRY("media.gmp-manager.buildID"),
+ PREF_LIST_ENTRY("toolkit.telemetry.previousBuildID"),
+};
+
+// These prefs are dynamically-named (i.e. not specified in prefs.js or
+// StaticPrefList) and would normally by blocklisted but we allow them through
+// anyway, so this override list acts as an allowlist
+static const PrefListEntry sDynamicPrefOverrideList[]{
+ PREF_LIST_ENTRY("app.update.channel"),
+ PREF_LIST_ENTRY("apz.subtest"),
+ PREF_LIST_ENTRY("autoadmin.global_config_url"), // Bug 1780575
+ PREF_LIST_ENTRY("browser.contentblocking.category"),
+ PREF_LIST_ENTRY("browser.dom.window.dump.file"),
+ PREF_LIST_ENTRY("browser.search.region"),
+ PREF_LIST_ENTRY(
+ "browser.tabs.remote.testOnly.failPBrowserCreation.browsingContext"),
+ PREF_LIST_ENTRY("browser.translation.bing.authURL"),
+ PREF_LIST_ENTRY("browser.translation.bing.clientIdOverride"),
+ PREF_LIST_ENTRY("browser.translation.bing.translateArrayURL"),
+ PREF_LIST_ENTRY("browser.translation.bing.apiKeyOverride"),
+ PREF_LIST_ENTRY("browser.translation.yandex.apiKeyOverride"),
+ PREF_LIST_ENTRY("browser.translation.yandex.translateURLOverride"),
+ PREF_LIST_ENTRY("browser.uitour.testingOrigins"),
+ PREF_LIST_ENTRY("browser.urlbar.loglevel"),
+ PREF_LIST_ENTRY("browser.urlbar.opencompanionsearch.enabled"),
+ PREF_LIST_ENTRY("capability.policy"),
+ PREF_LIST_ENTRY("dom.securecontext.allowlist"),
+ PREF_LIST_ENTRY("extensions.foobaz"),
+ PREF_LIST_ENTRY(
+ "extensions.formautofill.creditCards.heuristics.testConfidence"),
+ PREF_LIST_ENTRY("general.appname.override"),
+ PREF_LIST_ENTRY("general.appversion.override"),
+ PREF_LIST_ENTRY("general.buildID.override"),
+ PREF_LIST_ENTRY("general.oscpu.override"),
+ PREF_LIST_ENTRY("general.useragent.override"),
+ PREF_LIST_ENTRY("general.platform.override"),
+ PREF_LIST_ENTRY("gfx.blacklist."),
+ PREF_LIST_ENTRY("font.system.whitelist"),
+ PREF_LIST_ENTRY("font.name."),
+ PREF_LIST_ENTRY("intl.date_time.pattern_override."),
+ PREF_LIST_ENTRY("intl.hyphenation-alias."),
+ PREF_LIST_ENTRY("logging.config.LOG_FILE"),
+ PREF_LIST_ENTRY("media.audio_loopback_dev"),
+ PREF_LIST_ENTRY("media.decoder-doctor."),
+ PREF_LIST_ENTRY("media.cubeb.output_device"),
+ PREF_LIST_ENTRY("media.getusermedia.fake-camera-name"),
+ PREF_LIST_ENTRY("media.hls.server.url"),
+ PREF_LIST_ENTRY("media.peerconnection.nat_simulator.filtering_type"),
+ PREF_LIST_ENTRY("media.peerconnection.nat_simulator.mapping_type"),
+ PREF_LIST_ENTRY("media.peerconnection.nat_simulator.redirect_address"),
+ PREF_LIST_ENTRY("media.peerconnection.nat_simulator.redirect_targets"),
+ PREF_LIST_ENTRY("media.video_loopback_dev"),
+ PREF_LIST_ENTRY("media.webspeech.service.endpoint"),
+ PREF_LIST_ENTRY("network.gio.supported-protocols"),
+ PREF_LIST_ENTRY("network.protocol-handler.external."),
+ PREF_LIST_ENTRY("network.security.ports.banned"),
+ PREF_LIST_ENTRY("nimbus.syncdatastore."),
+ PREF_LIST_ENTRY("pdfjs."),
+ PREF_LIST_ENTRY("print.printer_"),
+ PREF_LIST_ENTRY("print_printer"),
+ PREF_LIST_ENTRY("places.interactions.customBlocklist"),
+ PREF_LIST_ENTRY("remote.log.level"),
+ PREF_LIST_ENTRY(
+ "services.settings.preview_enabled"), // This is really a boolean
+ // dynamic pref, but one Nightly
+ // user has it set as a string...
+ PREF_LIST_ENTRY("spellchecker.dictionary"),
+ PREF_LIST_ENTRY("test.char"),
+ PREF_LIST_ENTRY("Test.IPC."),
+ PREF_LIST_ENTRY("exists.thenDoesNot"),
+ PREF_LIST_ENTRY("type.String."),
+ PREF_LIST_ENTRY("toolkit.mozprotocol.url"),
+ PREF_LIST_ENTRY("toolkit.telemetry.log.level"),
+ PREF_LIST_ENTRY("ui."),
+};
+
+#undef PREF_LIST_ENTRY
+
+static bool ShouldSanitizePreference(const Pref* const aPref) {
+ // In the parent process, we use a heuristic to decide if a pref
+ // value should be sanitized before sending to subprocesses.
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+ const char* prefName = aPref->Name();
+
+ // If a pref starts with this magic string, it is a Once-Initialized pref
+ // from Static Prefs. It should* not be in the above list and while it looks
+ // like a dnyamically named pref, it is not.
+ // * nothing enforces this
+ if (strncmp(prefName, "$$$", 3) == 0) {
+ return false;
+ }
+
+ // First check against the denylist.
+ // The services pref is an annoying one - it's much easier to blocklist
+ // the whole branch and then add this one check to let this one annoying
+ // pref through.
+ for (const auto& entry : sRestrictFromWebContentProcesses) {
+ if (strncmp(entry.mPrefBranch, prefName, entry.mLen) == 0) {
+ const auto* p = prefName; // This avoids clang-format doing ugly things.
+ return !(strncmp("services.settings.clock_skew_seconds", p, 36) == 0 ||
+ strncmp("services.settings.last_update_seconds", p, 37) == 0 ||
+ strncmp("services.settings.server", p, 24) == 0);
+ }
+ }
+
+ // Then check if it's a dynamically named string preference and not
+ // in the override list
+ if (aPref->Type() == PrefType::String && !aPref->HasDefaultValue()) {
+ for (const auto& entry : sDynamicPrefOverrideList) {
+ if (strncmp(entry.mPrefBranch, prefName, entry.mLen) == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return false;
+}
+
+// Forward Declaration - it's not defined in the .h, because we don't need to;
+// it's only used here.
+template <class T>
+static bool IsPreferenceSanitized_Impl(const T& aPref);
+
+static bool IsPreferenceSanitized(const Pref* const aPref) {
+ return IsPreferenceSanitized_Impl(*aPref);
+}
+
+static bool IsPreferenceSanitized(const PrefWrapper& aPref) {
+ return IsPreferenceSanitized_Impl(aPref);
+}
+
+template <class T>
+static bool IsPreferenceSanitized_Impl(const T& aPref) {
+ if (aPref.IsSanitized()) {
+ MOZ_DIAGNOSTIC_ASSERT(!XRE_IsParentProcess());
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
+ return true;
+ }
+ return false;
+}
+
+namespace mozilla {
+
+// This is the only Check Sanitization function exposed outside of
+// Preferences.cpp, because this is the only one ever called from
+// outside this file.
+bool IsPreferenceSanitized(const char* aPrefName) {
+ // Perform this comparison (see notes above) early to avoid a lookup
+ // if we can avoid it.
+ if (strncmp(aPrefName, "$$$", 3) == 0) {
+ return false;
+ }
+
+ if (!gContentProcessPrefsAreInited) {
+ return false;
+ }
+
+ if (Maybe<PrefWrapper> pref = pref_Lookup(aPrefName)) {
+ if (pref.isNothing()) {
+ return true;
+ }
+ return IsPreferenceSanitized(pref.value());
+ }
+
+ return true;
+}
+
+Atomic<bool, Relaxed> sOmitBlocklistedPrefValues(false);
+Atomic<bool, Relaxed> sCrashOnBlocklistedPref(false);
+
+void OnFissionBlocklistPrefChange(const char* aPref, void* aData) {
+ if (strcmp(aPref, kFissionEnforceBlockList) == 0) {
+ sCrashOnBlocklistedPref =
+ StaticPrefs::fission_enforceBlocklistedPrefsInSubprocesses();
+ } else if (strcmp(aPref, kFissionOmitBlockListValues) == 0) {
+ sOmitBlocklistedPrefValues =
+ StaticPrefs::fission_omitBlocklistedPrefsInSubprocesses();
+ } else {
+ MOZ_CRASH("Unknown pref passed to callback");
+ }
+}
+
+} // namespace mozilla
+
+// This file contains the C wrappers for the C++ static pref getters, as used
+// by Rust code.
+#include "init/StaticPrefsCGetters.cpp"