From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- modules/libpref/Preferences.cpp | 6193 +++++++ modules/libpref/Preferences.h | 553 + modules/libpref/SharedPrefMap.cpp | 237 + modules/libpref/SharedPrefMap.h | 848 + modules/libpref/StaticPrefsBase.h | 93 + modules/libpref/components.conf | 31 + modules/libpref/docs/index.md | 466 + modules/libpref/greprefs.js | 5 + modules/libpref/init/StaticPrefList.yaml | 16000 +++++++++++++++++++ modules/libpref/init/StaticPrefListBegin.h | 50 + modules/libpref/init/StaticPrefListEnd.h | 20 + modules/libpref/init/__init__.py | 0 modules/libpref/init/all.js | 4039 +++++ modules/libpref/init/generate_static_pref_list.py | 472 + modules/libpref/init/static_prefs/Cargo.toml | 12 + modules/libpref/init/static_prefs/src/lib.rs | 11 + modules/libpref/moz.build | 177 + modules/libpref/nsIPrefBranch.idl | 503 + modules/libpref/nsIPrefLocalizedString.idl | 34 + modules/libpref/nsIPrefService.idl | 206 + modules/libpref/nsIRelativeFilePref.idl | 38 + modules/libpref/nsRelativeFilePref.h | 33 + modules/libpref/parser/Cargo.toml | 7 + modules/libpref/parser/src/lib.rs | 991 ++ modules/libpref/test/browser/browser.toml | 9 + .../test/browser/browser_sanitization_events.js | 89 + .../test/browser/file_access_sanitized_pref.html | 10 + modules/libpref/test/gtest/Basics.cpp | 58 + modules/libpref/test/gtest/Parser.cpp | 496 + modules/libpref/test/gtest/moz.build | 23 + modules/libpref/test/python.toml | 4 + .../libpref/test/test_generate_static_pref_list.py | 493 + modules/libpref/test/unit/data/testParser.js | 98 + modules/libpref/test/unit/data/testPref.js | 6 + modules/libpref/test/unit/data/testPrefLocked.js | 2 + .../libpref/test/unit/data/testPrefLockedUser.js | 3 + modules/libpref/test/unit/data/testPrefSticky.js | 2 + .../libpref/test/unit/data/testPrefStickyUser.js | 5 + modules/libpref/test/unit/data/testPrefUTF8.js | 6 + modules/libpref/test/unit/extdata/testExt.js | 2 + modules/libpref/test/unit/head_libPrefs.js | 37 + modules/libpref/test/unit/test_bug1354613.js | 21 + modules/libpref/test/unit/test_bug345529.js | 25 + modules/libpref/test/unit/test_bug506224.js | 25 + modules/libpref/test/unit/test_bug577950.js | 17 + modules/libpref/test/unit/test_bug790374.js | 50 + modules/libpref/test/unit/test_changeType.js | 164 + modules/libpref/test/unit/test_defaultValues.js | 59 + modules/libpref/test/unit/test_dirtyPrefs.js | 69 + modules/libpref/test/unit/test_libPrefs.js | 440 + .../libpref/test/unit/test_locked_file_prefs.js | 51 + modules/libpref/test/unit/test_parsePrefs.js | 136 + modules/libpref/test/unit/test_parser.js | 116 + modules/libpref/test/unit/test_stickyprefs.js | 196 + modules/libpref/test/unit/test_warnings.js | 62 + modules/libpref/test/unit/xpcshell.toml | 45 + .../libpref/test/unit_ipc/test_existing_prefs.js | 20 + .../libpref/test/unit_ipc/test_initial_prefs.js | 14 + modules/libpref/test/unit_ipc/test_large_pref.js | 104 + modules/libpref/test/unit_ipc/test_locked_prefs.js | 39 + .../libpref/test/unit_ipc/test_observed_prefs.js | 12 + modules/libpref/test/unit_ipc/test_sharedMap.js | 362 + .../test/unit_ipc/test_sharedMap_static_prefs.js | 76 + modules/libpref/test/unit_ipc/test_update_prefs.js | 34 + .../test/unit_ipc/test_user_default_prefs.js | 72 + modules/libpref/test/unit_ipc/xpcshell.toml | 22 + 66 files changed, 34593 insertions(+) create mode 100644 modules/libpref/Preferences.cpp create mode 100644 modules/libpref/Preferences.h create mode 100644 modules/libpref/SharedPrefMap.cpp create mode 100644 modules/libpref/SharedPrefMap.h create mode 100644 modules/libpref/StaticPrefsBase.h create mode 100644 modules/libpref/components.conf create mode 100644 modules/libpref/docs/index.md create mode 100644 modules/libpref/greprefs.js create mode 100644 modules/libpref/init/StaticPrefList.yaml create mode 100644 modules/libpref/init/StaticPrefListBegin.h create mode 100644 modules/libpref/init/StaticPrefListEnd.h create mode 100644 modules/libpref/init/__init__.py create mode 100644 modules/libpref/init/all.js create mode 100644 modules/libpref/init/generate_static_pref_list.py create mode 100644 modules/libpref/init/static_prefs/Cargo.toml create mode 100644 modules/libpref/init/static_prefs/src/lib.rs create mode 100644 modules/libpref/moz.build create mode 100644 modules/libpref/nsIPrefBranch.idl create mode 100644 modules/libpref/nsIPrefLocalizedString.idl create mode 100644 modules/libpref/nsIPrefService.idl create mode 100644 modules/libpref/nsIRelativeFilePref.idl create mode 100644 modules/libpref/nsRelativeFilePref.h create mode 100644 modules/libpref/parser/Cargo.toml create mode 100644 modules/libpref/parser/src/lib.rs create mode 100644 modules/libpref/test/browser/browser.toml create mode 100644 modules/libpref/test/browser/browser_sanitization_events.js create mode 100644 modules/libpref/test/browser/file_access_sanitized_pref.html create mode 100644 modules/libpref/test/gtest/Basics.cpp create mode 100644 modules/libpref/test/gtest/Parser.cpp create mode 100644 modules/libpref/test/gtest/moz.build create mode 100644 modules/libpref/test/python.toml create mode 100644 modules/libpref/test/test_generate_static_pref_list.py create mode 100644 modules/libpref/test/unit/data/testParser.js create mode 100644 modules/libpref/test/unit/data/testPref.js create mode 100644 modules/libpref/test/unit/data/testPrefLocked.js create mode 100644 modules/libpref/test/unit/data/testPrefLockedUser.js create mode 100644 modules/libpref/test/unit/data/testPrefSticky.js create mode 100644 modules/libpref/test/unit/data/testPrefStickyUser.js create mode 100644 modules/libpref/test/unit/data/testPrefUTF8.js create mode 100644 modules/libpref/test/unit/extdata/testExt.js create mode 100644 modules/libpref/test/unit/head_libPrefs.js create mode 100644 modules/libpref/test/unit/test_bug1354613.js create mode 100644 modules/libpref/test/unit/test_bug345529.js create mode 100644 modules/libpref/test/unit/test_bug506224.js create mode 100644 modules/libpref/test/unit/test_bug577950.js create mode 100644 modules/libpref/test/unit/test_bug790374.js create mode 100644 modules/libpref/test/unit/test_changeType.js create mode 100644 modules/libpref/test/unit/test_defaultValues.js create mode 100644 modules/libpref/test/unit/test_dirtyPrefs.js create mode 100644 modules/libpref/test/unit/test_libPrefs.js create mode 100644 modules/libpref/test/unit/test_locked_file_prefs.js create mode 100644 modules/libpref/test/unit/test_parsePrefs.js create mode 100644 modules/libpref/test/unit/test_parser.js create mode 100644 modules/libpref/test/unit/test_stickyprefs.js create mode 100644 modules/libpref/test/unit/test_warnings.js create mode 100644 modules/libpref/test/unit/xpcshell.toml create mode 100644 modules/libpref/test/unit_ipc/test_existing_prefs.js create mode 100644 modules/libpref/test/unit_ipc/test_initial_prefs.js create mode 100644 modules/libpref/test/unit_ipc/test_large_pref.js create mode 100644 modules/libpref/test/unit_ipc/test_locked_prefs.js create mode 100644 modules/libpref/test/unit_ipc/test_observed_prefs.js create mode 100644 modules/libpref/test/unit_ipc/test_sharedMap.js create mode 100644 modules/libpref/test/unit_ipc/test_sharedMap_static_prefs.js create mode 100644 modules/libpref/test/unit_ipc/test_update_prefs.js create mode 100644 modules/libpref/test/unit_ipc/test_user_default_prefs.js create mode 100644 modules/libpref/test/unit_ipc/xpcshell.toml (limited to 'modules/libpref') diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp new file mode 100644 index 0000000000..47759ffadc --- /dev/null +++ b/modules/libpref/Preferences.cpp @@ -0,0 +1,6193 @@ +/* -*- 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 +#include +#include + +#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/Try.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/URLPreloader.h" +#include "mozilla/Variant.h" +#include "mozilla/Vector.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsCategoryManagerUtils.h" +#include "nsClassHashtable.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsCRT.h" +#include "nsTHashMap.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIConsoleService.h" +#include "nsIFile.h" +#include "nsIMemoryReporter.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIOutputStream.h" +#include "nsIPrefBranch.h" +#include "nsIPrefLocalizedString.h" +#include "nsIRelativeFilePref.h" +#include "nsISafeOutputStream.h" +#include "nsISimpleEnumerator.h" +#include "nsIStringBundle.h" +#include "nsISupportsImpl.h" +#include "nsISupportsPrimitives.h" +#include "nsIZipReader.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "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 +#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 sPrefTelemetryEventEnabled(false); + +typedef nsTArray 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 + 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(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* 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 MarkerTypeName() { + return MakeStringSpan("Preference"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + const ProfilerString8View& aPrefName, + const Maybe& 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 PrefValueKindToString( + const Maybe& aKind) { + if (aKind) { + return *aKind == PrefValueKind::Default ? MakeStringSpan("Default") + : MakeStringSpan("User"); + } + return "Shared"; + } + + static Span 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 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(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(mType); } + void SetType(PrefType aType) { mType = static_cast(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 + 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(), + HasUserValue() ? mUserValue.Get() : T()); + } + + void AddToMap(SharedPrefMapBuilder& aMap) { + if (IsTypeBool()) { + AddToMap(aMap); + } else if (IsTypeInt()) { + AddToMap(aMap); + } else if (IsTypeString()) { + AddToMap(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& 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& 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. + // + // = ':' ':' ? ':' + // ? '\n' + // = 'B' | 'I' | 'S' + // = 'L' | '-' + // = 'S' | '-' + // = + // = | | + // = 'T' | 'F' + // = an integer literal accepted by strtol() + // = '/' + // = any char sequence of length dictated by the preceding + // . + // + // No whitespace is tolerated between tokens. 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 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 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; + 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; +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 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() && IsPreferenceSanitized(this->as())) { + 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()) { + // 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(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()); + auto pref = aWrapper.as(); + + 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* const* 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& 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(mNextAndMatchKind & + kMatchKindMask); + } + + bool DomainIs(const nsACString& aDomain) const { + return mDomain.is() && mDomain.as() == aDomain; + } + + bool DomainIs(const char* const* 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()) { + return match(mDomain.as()); + } + for (const char* const* ptr = mDomain.as(); *ptr; + ptr++) { + if (match(nsDependentCString(*ptr))) { + return true; + } + } + return false; + } + + CallbackNode* Next() const { + return reinterpret_cast(mNextAndMatchKind & kNextMask); + } + + void SetNext(CallbackNode* aNext) { + uintptr_t matchKind = mNextAndMatchKind & kMatchKindMask; + mNextAndMatchKind = reinterpret_cast(aNext); + MOZ_ASSERT((mNextAndMatchKind & kMatchKindMask) == 0); + mNextAndMatchKind |= matchKind; + } + + void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, PrefsSizes& aSizes) { + aSizes.mCallbacksObjects += aMallocSizeOf(this); + if (mDomain.is()) { + aSizes.mCallbacksDomains += + mDomain.as().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + } + + private: + static const uintptr_t kMatchKindMask = uintptr_t(0x1); + static const uintptr_t kNextMask = ~kMatchKindMask; + + Variant 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, 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 (see bug 1557617). +typedef std::function AntiFootgunCallback; +struct CompareStr { + bool operator()(char const* a, char const* b) const { + return std::strcmp(a, b) < 0; + } +}; +typedef std::map AntiFootgunMap; +static StaticAutoPtr 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; +static StaticAutoPtr 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(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; + + SharedPrefMap* mSharedMap; + PrefsHashTable* mHashTable; + PrefsHashIter mIter; + + ElemTypeVariant mPos; + ElemTypeVariant mEnd; + + Maybe 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(); \ + __VA_ARGS__; \ + } \ + type operator()(SharedElem& pos) { \ + SharedElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as(); \ + __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() && mSharedMap) { + mPos = AsVariant(mSharedMap->begin()); + mEnd = AsVariant(mSharedMap->end()); + return !Done(); + } + return false; + } + + bool IteratingBase() { return mPos.is(); } + + 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(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 pref_SharedLookup(const char* aPrefName) { + MOZ_DIAGNOSTIC_ASSERT(gSharedMap, "gSharedMap must be initialized"); + if (Maybe pref = gSharedMap->Get(aPrefName)) { + return Some(*pref); + } + return Nothing(); +} + +Maybe 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_LookupForModify( + const nsCString& aPrefName, + const std::function& aCheckFn) { + Maybe wrapper = + pref_Lookup(aPrefName.get(), /* includeTypeNone */ true); + if (wrapper.isNothing()) { + return Err(NS_ERROR_INVALID_ARG); + } + if (!aCheckFn(*wrapper)) { + return nullptr; + } + if (wrapper->is()) { + return wrapper->as(); + } + + 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 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 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 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(this); } + + // Get a reference to the callback's observer, or null if the observer was + // weakly referenced and has been destroyed. + already_AddRefed GetObserver() const { + if (!IsWeak()) { + nsCOMPtr copy = mStrongRef; + return copy.forget(); + } + + nsCOMPtr 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 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 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 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 mUnicodeString; +}; + +//---------------------------------------------------------------------------- +// nsPrefBranch +//---------------------------------------------------------------------------- + +nsPrefBranch::nsPrefBranch(const char* aPrefRoot, PrefValueKind aKind) + : mPrefRoot(aPrefRoot), mKind(aKind), mFreeingObserverList(false) { + nsCOMPtr 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 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(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 file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + + if (NS_SUCCEEDED(rv)) { + rv = file->SetPersistentDescriptor(utf8String); + if (NS_SUCCEEDED(rv)) { + file.forget(reinterpret_cast(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 fromFile; + nsCOMPtr 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 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 relativePref = new nsRelativeFilePref(); + Unused << relativePref->SetFile(theFile); + Unused << relativePref->SetRelativeToKey(key); + + relativePref.forget(reinterpret_cast(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 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 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 relFilePref = do_QueryInterface(aValue); + if (!relFilePref) { + return NS_NOINTERFACE; + } + + nsCOMPtr file; + relFilePref->GetFile(getter_AddRefs(file)); + if (!file) { + return NS_NOINTERFACE; + } + + nsAutoCString relativeToKey; + (void)relFilePref->GetRelativeToKey(relativeToKey); + + nsCOMPtr relativeToFile; + nsCOMPtr 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 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::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& 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 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 pCallback; + + NS_ENSURE_ARG(aObserver); + + const nsCString& prefName = GetPrefName(aDomain); + + // Hold a weak reference to the observer if so requested. + if (aHoldWeak) { + nsCOMPtr 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(prefName, weakRefFactory, this); + + } else { + // Construct a PrefCallback with a strong reference to the observer. + pCallback = MakeUnique(prefName, aObserver, this); + } + + mObservers.WithEntryHandle(pCallback.get(), [&](auto&& p) { + if (p) { + NS_WARNING( + nsPrintfCString("Ignoring duplicate observer: %s", prefName.get()) + .get()); + } 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 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 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(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 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 bundleService = + components::StringBundle::Service(); + if (!bundleService) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr 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::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 outStreamSink; + nsCOMPtr 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 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 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 sPendingWriteCount; + + // See PWRunnable::Run for details on why we need this lock. + static StaticMutex sWritingToFile MOZ_UNANNOTATED; +}; + +Atomic PreferencesWriter::sPendingWriteData(nullptr); +Atomic 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 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 fileCopy(mFile); + SchedulerGroup::Dispatch(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 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(sPreferences->mRootBranch.get()) + ->SizeOfIncludingThis(aMallocSizeOf) + + static_cast(sPreferences->mDefaultRootBranch.get()) + ->SizeOfIncludingThis(aMallocSizeOf); +} + +class PreferenceServiceReporter final : public nsIMemoryReporter { + ~PreferenceServiceReporter() = default; + + 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(Preferences::GetRootBranch()); + if (!rootBranch) { + return NS_OK; + } + + size_t numStrong = 0; + size_t numWeakAlive = 0; + size_t numWeakDead = 0; + nsTArray suspectPreferences; + // Count of the number of referents for each preference. + nsTHashMap prefCounter; + + for (const auto& entry : rootBranch->mObservers) { + auto* callback = entry.GetWeak(); + + if (callback->IsWeak()) { + nsCOMPtr 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> gChangedDomPrefs; + +static const char kTelemetryPref[] = "toolkit.telemetry.enabled"; +static const char kChannelPref[] = "app.update.channel"; + +#ifdef MOZ_WIDGET_ANDROID + +static Maybe 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 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); +} + +#endif // MOZ_WIDGET_ANDROID + +/* static */ +already_AddRefed 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; + } 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(static_cast(sPreferences)), + "pref-config-startup"); + } + + nsCOMPtr 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 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 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(); + + 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 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 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, "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 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 prefBranch = + new nsPrefBranch(aPrefRoot, PrefValueKind::User); + prefBranch.forget(aRetVal); + } else { + // Special case: caching the default root. + nsCOMPtr root(sPreferences->mRootBranch); + root.forget(aRetVal); + } + + return NS_OK; +} + +NS_IMETHODIMP +Preferences::GetDefaultBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal) { + if (!aPrefRoot || !aPrefRoot[0]) { + nsCOMPtr 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 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& aBytes, + nsIPrefObserver* aObserver, + const char* aPathLabel) { + MOZ_ASSERT(NS_IsMainThread()); + + // We need a null-terminated buffer. + nsTArray data = aBytes.Clone(); + data.AppendElement(0); + + // Parsing as default handles both `pref` and `user_pref`. + PrefObserver = aObserver; + prefs_parser_parse(aPathLabel ? aPathLabel : "", + 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 observerService = services::GetObserverService(); + if (!observerService) { + return NS_ERROR_FAILURE; + } + + auto subject = static_cast(this); + observerService->NotifyObservers(subject, aTopic, nullptr); + + return NS_OK; +} + +already_AddRefed Preferences::ReadSavedPrefs() { + nsCOMPtr 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 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 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 prefs = MakeUnique(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 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) { + 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. +static nsresult pref_LoadPrefsInDir(nsIFile* aDir) { + MOZ_ASSERT(XRE_IsParentProcess()); + + nsresult rv, rv2; + + nsCOMPtr 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 prefFiles(INITIAL_PREF_FILES); + nsCOMPtr 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)) { + prefFiles.AppendObject(prefFile); + } + } + + if (prefFiles.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); + + 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; + } + } + + 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 jarReader, + const char* path) { + UniquePtr find; + nsTArray 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 + 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 pref = pref_Lookup(aPrefName)) { + rv = pref->GetValue(aKind, std::forward(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 + static nsresult GetSharedPrefValue(const char* aName, T* aResult) { + nsresult rv = NS_ERROR_UNEXPECTED; + + if (Maybe 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 + static T GetPref(const char* aPrefName, T aFallback, + PrefValueKind aKind = PrefValueKind::User) { + T result = aFallback; + GetPrefValue(aPrefName, &result, aKind); + return result; + } + + template + 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 + static void UpdateMirror(const char* aPref, void* aMirror) { + StripAtomic value; + + nsresult rv = GetPrefValue(aPref, &value, PrefValueKind::User); + if (NS_SUCCEEDED(rv)) { + AssignMirror(*static_cast(aMirror), + std::forward>(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 + static nsresult RegisterCallback(void* aMirror, const nsACString& aPref) { + return Preferences::RegisterCallback(UpdateMirror, 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 find; + nsTArray prefEntries; + const char* entryName; + uint16_t entryNameLen; + + RefPtr 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 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 defaultPrefDir; + rv = NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR, + getter_AddRefs(defaultPrefDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pref_LoadPrefsInDir(defaultPrefDir); + 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 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 dirSvc( + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 elem; + list->GetNext(getter_AddRefs(elem)); + if (!elem) { + continue; + } + + nsCOMPtr path = do_QueryInterface(elem); + if (!path) { + continue; + } + + // Do we care if a file provided by this process fails to load? + pref_LoadPrefsInDir(path); + } + } + +#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 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); + 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 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 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 pref = pref_Lookup(aPrefName); + return pref.isSome() && pref->IsLocked(); +} + +/* static */ +bool Preferences::IsSanitized(const char* aPrefName) { + NS_ENSURE_TRUE(InitStaticMembers(), false); + + Maybe 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 pref = pref_Lookup(aPrefName); + return pref.isSome() && pref->HasUserValue(); +} + +/* static */ +bool Preferences::HasDefaultValue(const char* aPrefName) { + NS_ENSURE_TRUE(InitStaticMembers(), false); + + Maybe 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 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 +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* const* 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* const* 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* const* 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 +/* 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* const* 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* const* aPrefs, + void* aClosure) { + MOZ_ASSERT(aCallback); + + nsresult rv = + RegisterCallbacks(aCallback, aPrefs, aClosure, MatchKind::ExactMatch); + if (NS_SUCCEEDED(rv)) { + for (const char* const* ptr = aPrefs; *ptr; ptr++) { + (*aCallback)(*ptr, aClosure); + } + } + return rv; +} + +template +/* 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(aCallback, aPrefNode, aData, + aMatchKind); +} + +/* static */ +nsresult Preferences::UnregisterCallbacks(PrefChangedFunc aCallback, + const char* const* aPrefs, + void* aData, MatchKind aMatchKind) { + return UnregisterCallbackImpl(aCallback, aPrefs, aData, aMatchKind); +} + +template +static void AddMirrorCallback(T* aMirror, const nsACString& aPref) { + MOZ_ASSERT(NS_IsMainThread()); + + Internals::RegisterCallback(aMirror, aPref); +} + +// Don't inline because it explodes compile times. +template +static MOZ_NEVER_INLINE void AddMirror(T* aMirror, const nsACString& aPref, + StripAtomic 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 `_` 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 +static void InitAlwaysPref(const nsCString& aName, T* aCache, + StripAtomic 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 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 = 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(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(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(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(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"); + +#ifdef DEBUG +# define ASSERT_PREF_NOT_SANITIZED(name, cpp_type) \ + if (IsString::value && IsPreferenceSanitized(name)) { \ + MOZ_CRASH("Unexpected sanitized string preference '" name \ + "'. " \ + "Static Preferences cannot be sanitized currently, because " \ + "they expect to be initialized from the Static Map, and " \ + "sanitized preferences are not present there."); \ + } +#else +# define ASSERT_PREF_NOT_SANITIZED(name, cpp_type) +#endif + + // 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 val; \ + ASSERT_PREF_NOT_SANITIZED(name, cpp_type); \ + DebugOnly 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 val; \ + ASSERT_PREF_NOT_SANITIZED(name, cpp_type); \ + DebugOnly rv = Internals::GetSharedPrefValue(name, &val); \ + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \ + Internals::AssignMirror(StaticPrefs::sMirror_##full_id, \ + std::forward>(val)); \ + } +#define ONCE_PREF(name, base_id, full_id, cpp_type, default_value) \ + { \ + cpp_type val; \ + ASSERT_PREF_NOT_SANITIZED(name, cpp_type); \ + DebugOnly 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 +#undef ASSERT_PREF_NOT_SANITIZED + + // `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(); + if (NS_SUCCEEDED(str->Init())) { + return str.forget().downcast(); + } + 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. +// Exclusions of preferences can be defined in sOverrideRestrictionsList[]. +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"), +}; + +// Allowlist for prefs and branches blocklisted in +// sRestrictFromWebContentProcesses[], including prefs from +// StaticPrefList.yaml and *.js, to let them pass. +static const PrefListEntry sOverrideRestrictionsList[]{ + PREF_LIST_ENTRY("services.settings.clock_skew_seconds"), + PREF_LIST_ENTRY("services.settings.last_update_seconds"), + PREF_LIST_ENTRY("services.settings.loglevel"), + // This is really a boolean dynamic pref, but one Nightly user + // has it set as a string... + PREF_LIST_ENTRY("services.settings.preview_enabled"), + PREF_LIST_ENTRY("services.settings.server"), +}; + +// 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("accessibility.tabfocus"), + 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.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.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.backend"), + 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("plugins.force.wmode"), + PREF_LIST_ENTRY("print.printer_"), + PREF_LIST_ENTRY("print_printer"), + PREF_LIST_ENTRY("places.interactions.customBlocklist"), + PREF_LIST_ENTRY("remote.log.level"), + // services.* preferences should be added in sOverrideRestrictionsList[] - + // the whole preference branch gets sanitized by default. + 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) { + for (const auto& pasEnt : sOverrideRestrictionsList) { + if (strncmp(pasEnt.mPrefBranch, prefName, pasEnt.mLen) == 0) { + return false; + } + } + return true; + } + } + + // 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 +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 +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 pref = pref_Lookup(aPrefName)) { + if (pref.isNothing()) { + return true; + } + return IsPreferenceSanitized(pref.value()); + } + + return true; +} + +Atomic sOmitBlocklistedPrefValues(false); +Atomic 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" diff --git a/modules/libpref/Preferences.h b/modules/libpref/Preferences.h new file mode 100644 index 0000000000..81c99590da --- /dev/null +++ b/modules/libpref/Preferences.h @@ -0,0 +1,553 @@ +/* -*- 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. + +#ifndef mozilla_Preferences_h +#define mozilla_Preferences_h + +#ifndef MOZILLA_INTERNAL_API +# error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)." +#endif + +#include "mozilla/Atomics.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/StaticPtr.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsWeakReference.h" +#include "nsXULAppAPI.h" +#include +#include + +class nsIFile; + +// The callback function will get passed the pref name which triggered the call +// and the void* data which was passed to the registered callback function. +typedef void (*PrefChangedFunc)(const char* aPref, void* aData); + +class nsPrefBranch; + +namespace mozilla { + +struct RegisterCallbacksInternal; + +void UnloadPrefsModule(); + +class PreferenceServiceReporter; + +namespace dom { +class Pref; +class PrefValue; +} // namespace dom + +namespace ipc { +class FileDescriptor; +} // namespace ipc + +struct PrefsSizes; + +// Xlib.h defines Bool as a macro constant. Don't try to define this enum if +// it's already been included. +#ifndef Bool + +// Keep this in sync with PrefType in parser/src/lib.rs. +enum class PrefType : uint8_t { + None = 0, // only used when neither the default nor user value is set + String = 1, + Int = 2, + Bool = 3, +}; + +#endif + +#ifdef XP_UNIX +// We need to send two shared memory descriptors to every child process: +// +// 1) A read-only/write-protected snapshot of the initial state of the +// preference database. This memory is shared between all processes, and +// therefore cannot be modified once it has been created. +// +// 2) A set of changes on top of the snapshot, containing the current values of +// all preferences which have changed since it was created. +// +// Since the second set will be different for every process, and the first set +// cannot be modified, it is unfortunately not possible to combine them into a +// single file descriptor. +// +// XXX: bug 1440207 is about improving how fixed fds such as this are used. +static const int kPrefsFileDescriptor = 8; +static const int kPrefMapFileDescriptor = 9; +#endif + +// Keep this in sync with PrefType in parser/src/lib.rs. +enum class PrefValueKind : uint8_t { Default, User }; + +class Preferences final : public nsIPrefService, + public nsIObserver, + public nsIPrefBranch, + public nsSupportsWeakReference { + friend class ::nsPrefBranch; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPREFSERVICE + NS_FORWARD_NSIPREFBRANCH(mRootBranch->) + NS_DECL_NSIOBSERVER + + Preferences(); + + // Returns true if the Preferences service is available, false otherwise. + static bool IsServiceAvailable(); + + // Initialize user prefs from prefs.js/user.js + static void InitializeUserPrefs(); + static void FinishInitializingUserPrefs(); + + // Returns the singleton instance which is addreffed. + static already_AddRefed GetInstanceForService(); + + // Finallizes global members. + static void Shutdown(); + + // Returns shared pref service instance NOTE: not addreffed. + static nsIPrefService* GetService() { + NS_ENSURE_TRUE(InitStaticMembers(), nullptr); + return sPreferences; + } + + // Returns shared pref branch instance. NOTE: not addreffed. + static nsIPrefBranch* GetRootBranch( + PrefValueKind aKind = PrefValueKind::User) { + NS_ENSURE_TRUE(InitStaticMembers(), nullptr); + return (aKind == PrefValueKind::Default) ? sPreferences->mDefaultRootBranch + : sPreferences->mRootBranch; + } + + // Gets the type of the pref. + static int32_t GetType(const char* aPrefName); + + // Fallible value getters. When `aKind` is `User` they will get the user + // value if possible, and fall back to the default value otherwise. + static nsresult GetBool(const char* aPrefName, bool* aResult, + PrefValueKind aKind = PrefValueKind::User); + static nsresult GetInt(const char* aPrefName, int32_t* aResult, + PrefValueKind aKind = PrefValueKind::User); + static nsresult GetUint(const char* aPrefName, uint32_t* aResult, + PrefValueKind aKind = PrefValueKind::User) { + return GetInt(aPrefName, reinterpret_cast(aResult), aKind); + } + static nsresult GetFloat(const char* aPrefName, float* aResult, + PrefValueKind aKind = PrefValueKind::User); + static nsresult GetCString(const char* aPrefName, nsACString& aResult, + PrefValueKind aKind = PrefValueKind::User); + static nsresult GetString(const char* aPrefName, nsAString& aResult, + PrefValueKind aKind = PrefValueKind::User); + static nsresult GetLocalizedCString( + const char* aPrefName, nsACString& aResult, + PrefValueKind aKind = PrefValueKind::User); + static nsresult GetLocalizedString(const char* aPrefName, nsAString& aResult, + PrefValueKind aKind = PrefValueKind::User); + static nsresult GetComplex(const char* aPrefName, const nsIID& aType, + void** aResult, + PrefValueKind aKind = PrefValueKind::User); + + // Infallible getters of user or default values, with fallback results on + // failure. When `aKind` is `User` they will get the user value if possible, + // and fall back to the default value otherwise. + static bool GetBool(const char* aPrefName, bool aFallback = false, + PrefValueKind aKind = PrefValueKind::User); + static int32_t GetInt(const char* aPrefName, int32_t aFallback = 0, + PrefValueKind aKind = PrefValueKind::User); + static uint32_t GetUint(const char* aPrefName, uint32_t aFallback = 0, + PrefValueKind aKind = PrefValueKind::User); + static float GetFloat(const char* aPrefName, float aFallback = 0.0f, + PrefValueKind aKind = PrefValueKind::User); + + // Value setters. These fail if run outside the parent process. + + static nsresult SetBool(const char* aPrefName, bool aValue, + PrefValueKind aKind = PrefValueKind::User); + static nsresult SetInt(const char* aPrefName, int32_t aValue, + PrefValueKind aKind = PrefValueKind::User); + static nsresult SetCString(const char* aPrefName, const nsACString& aValue, + PrefValueKind aKind = PrefValueKind::User); + + static nsresult SetUint(const char* aPrefName, uint32_t aValue, + PrefValueKind aKind = PrefValueKind::User) { + return SetInt(aPrefName, static_cast(aValue), aKind); + } + + static nsresult SetFloat(const char* aPrefName, float aValue, + PrefValueKind aKind = PrefValueKind::User) { + nsAutoCString value; + value.AppendFloat(aValue); + return SetCString(aPrefName, value, aKind); + } + + static nsresult SetCString(const char* aPrefName, const char* aValue, + PrefValueKind aKind = PrefValueKind::User) { + return Preferences::SetCString(aPrefName, nsDependentCString(aValue), + aKind); + } + + static nsresult SetString(const char* aPrefName, const char16ptr_t aValue, + PrefValueKind aKind = PrefValueKind::User) { + return Preferences::SetCString(aPrefName, NS_ConvertUTF16toUTF8(aValue), + aKind); + } + + static nsresult SetString(const char* aPrefName, const nsAString& aValue, + PrefValueKind aKind = PrefValueKind::User) { + return Preferences::SetCString(aPrefName, NS_ConvertUTF16toUTF8(aValue), + aKind); + } + + static nsresult SetComplex(const char* aPrefName, const nsIID& aType, + nsISupports* aValue, + PrefValueKind aKind = PrefValueKind::User); + + static nsresult Lock(const char* aPrefName); + static nsresult Unlock(const char* aPrefName); + static bool IsLocked(const char* aPrefName); + static bool IsSanitized(const char* aPrefName); + + // Clears user set pref. Fails if run outside the parent process. + static nsresult ClearUser(const char* aPrefName); + + // Whether the pref has a user value or not. + static bool HasUserValue(const char* aPref); + + // Whether the pref has a user value or not. + static bool HasDefaultValue(const char* aPref); + + // Adds/Removes the observer for the root pref branch. See nsIPrefBranch.idl + // for details. + static nsresult AddStrongObserver(nsIObserver* aObserver, + const nsACString& aPref); + static nsresult AddWeakObserver(nsIObserver* aObserver, + const nsACString& aPref); + static nsresult RemoveObserver(nsIObserver* aObserver, + const nsACString& aPref); + + template + static nsresult AddStrongObserver(nsIObserver* aObserver, + const char (&aPref)[N]) { + return AddStrongObserver(aObserver, nsLiteralCString(aPref)); + } + template + static nsresult AddWeakObserver(nsIObserver* aObserver, + const char (&aPref)[N]) { + return AddWeakObserver(aObserver, nsLiteralCString(aPref)); + } + template + static nsresult RemoveObserver(nsIObserver* aObserver, + const char (&aPref)[N]) { + return RemoveObserver(aObserver, nsLiteralCString(aPref)); + } + + // Adds/Removes two or more observers for the root pref branch. Pass to + // aPrefs an array of const char* whose last item is nullptr. + // Note: All preference strings *must* be statically-allocated string + // literals. + static nsresult AddStrongObservers(nsIObserver* aObserver, + const char* const* aPrefs); + static nsresult AddWeakObservers(nsIObserver* aObserver, + const char* const* aPrefs); + static nsresult RemoveObservers(nsIObserver* aObserver, + const char* const* aPrefs); + + // Registers/Unregisters the callback function for the aPref. + template + static nsresult RegisterCallback(PrefChangedFunc aCallback, + const nsACString& aPref, + T* aClosure = nullptr) { + return RegisterCallback(aCallback, aPref, aClosure, ExactMatch); + } + + template + static nsresult UnregisterCallback(PrefChangedFunc aCallback, + const nsACString& aPref, + T* aClosure = nullptr) { + return UnregisterCallback(aCallback, aPref, aClosure, ExactMatch); + } + + // Like RegisterCallback, but also calls the callback immediately for + // initialization. + template + static nsresult RegisterCallbackAndCall(PrefChangedFunc aCallback, + const nsACString& aPref, + T* aClosure = nullptr) { + return RegisterCallbackAndCall(aCallback, aPref, aClosure, ExactMatch); + } + + // Like RegisterCallback, but registers a callback for a prefix of multiple + // pref names, not a single pref name. + template + static nsresult RegisterPrefixCallback(PrefChangedFunc aCallback, + const nsACString& aPref, + T* aClosure = nullptr) { + return RegisterCallback(aCallback, aPref, aClosure, PrefixMatch); + } + + // Like RegisterPrefixCallback, but also calls the callback immediately for + // initialization. + template + static nsresult RegisterPrefixCallbackAndCall(PrefChangedFunc aCallback, + const nsACString& aPref, + T* aClosure = nullptr) { + return RegisterCallbackAndCall(aCallback, aPref, aClosure, PrefixMatch); + } + + // Unregister a callback registered with RegisterPrefixCallback or + // RegisterPrefixCallbackAndCall. + template + static nsresult UnregisterPrefixCallback(PrefChangedFunc aCallback, + const nsACString& aPref, + T* aClosure = nullptr) { + return UnregisterCallback(aCallback, aPref, aClosure, PrefixMatch); + } + + // Variants of the above which register a single callback to handle multiple + // preferences. + // + // The array of preference names must be null terminated. It may be + // dynamically allocated, but the caller is responsible for keeping it alive + // until the callback is unregistered. + // + // Also note that the exact same aPrefs pointer must be passed to the + // Unregister call as was passed to the Register call. + template + static nsresult RegisterCallbacks(PrefChangedFunc aCallback, + const char* const* aPrefs, + T* aClosure = nullptr) { + return RegisterCallbacks(aCallback, aPrefs, aClosure, ExactMatch); + } + static nsresult RegisterCallbacksAndCall(PrefChangedFunc aCallback, + const char* const* aPrefs, + void* aClosure = nullptr); + template + static nsresult UnregisterCallbacks(PrefChangedFunc aCallback, + const char* const* aPrefs, + T* aClosure = nullptr) { + return UnregisterCallbacks(aCallback, aPrefs, aClosure, ExactMatch); + } + template + static nsresult RegisterPrefixCallbacks(PrefChangedFunc aCallback, + const char* const* aPrefs, + T* aClosure = nullptr) { + return RegisterCallbacks(aCallback, aPrefs, aClosure, PrefixMatch); + } + template + static nsresult UnregisterPrefixCallbacks(PrefChangedFunc aCallback, + const char* const* aPrefs, + T* aClosure = nullptr) { + return UnregisterCallbacks(aCallback, aPrefs, aClosure, PrefixMatch); + } + + template + static nsresult RegisterCallback(PrefChangedFunc aCallback, + const char (&aPref)[N], + T* aClosure = nullptr) { + return RegisterCallback(aCallback, nsLiteralCString(aPref), aClosure, + ExactMatch); + } + + template + static nsresult UnregisterCallback(PrefChangedFunc aCallback, + const char (&aPref)[N], + T* aClosure = nullptr) { + return UnregisterCallback(aCallback, nsLiteralCString(aPref), aClosure, + ExactMatch); + } + + template + static nsresult RegisterCallbackAndCall(PrefChangedFunc aCallback, + const char (&aPref)[N], + T* aClosure = nullptr) { + return RegisterCallbackAndCall(aCallback, nsLiteralCString(aPref), aClosure, + ExactMatch); + } + + template + static nsresult RegisterPrefixCallback(PrefChangedFunc aCallback, + const char (&aPref)[N], + T* aClosure = nullptr) { + return RegisterCallback(aCallback, nsLiteralCString(aPref), aClosure, + PrefixMatch); + } + + template + static nsresult RegisterPrefixCallbackAndCall(PrefChangedFunc aCallback, + const char (&aPref)[N], + T* aClosure = nullptr) { + return RegisterCallbackAndCall(aCallback, nsLiteralCString(aPref), aClosure, + PrefixMatch); + } + + template + static nsresult UnregisterPrefixCallback(PrefChangedFunc aCallback, + const char (&aPref)[N], + T* aClosure = nullptr) { + return UnregisterCallback(aCallback, nsLiteralCString(aPref), aClosure, + PrefixMatch); + } + + // When a content process is created these methods are used to pass changed + // prefs in bulk from the parent process, via shared memory. + static void SerializePreferences(nsCString& aStr, + bool aIsDestinationWebContentProcess); + static void DeserializePreferences(char* aStr, size_t aPrefsLen); + + static mozilla::ipc::FileDescriptor EnsureSnapshot(size_t* aSize); + static void InitSnapshot(const mozilla::ipc::FileDescriptor&, size_t aSize); + + // When a single pref is changed in the parent process, these methods are + // used to pass the update to content processes. + static void GetPreference(dom::Pref* aPref, + const GeckoProcessType aDestinationProcessType, + const nsACString& aDestinationRemoteType); + static void SetPreference(const dom::Pref& aPref); + +#ifdef DEBUG + static bool ArePrefsInitedInContentProcess(); +#endif + + static void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + PrefsSizes& aSizes); + + static void HandleDirty(); + + // Explicitly choosing synchronous or asynchronous (if allowed) preferences + // file write. Only for the default file. The guarantee for the "blocking" + // is that when it returns, the file on disk reflect the current state of + // preferences. + nsresult SavePrefFileBlocking(); + nsresult SavePrefFileAsynchronous(); + + // If this is false, only blocking writes, on main thread are allowed. + bool AllowOffMainThreadSave(); + + private: + virtual ~Preferences(); + + nsresult NotifyServiceObservers(const char* aSubject); + + // Loads the prefs.js file from the profile, or creates a new one. Returns + // the prefs file if successful, or nullptr on failure. + already_AddRefed ReadSavedPrefs(); + + // Loads the user.js file from the profile if present. + void ReadUserOverridePrefs(); + + nsresult MakeBackupPrefFile(nsIFile* aFile); + + // Default pref file save can be blocking or not. + enum class SaveMethod { Blocking, Asynchronous }; + + // Off main thread is only respected for the default aFile value (nullptr). + nsresult SavePrefFileInternal(nsIFile* aFile, SaveMethod aSaveMethod); + nsresult WritePrefFile(nsIFile* aFile, SaveMethod aSaveMethod); + + nsresult ResetUserPrefs(); + + // Helpers for implementing + // Register(Prefix)Callback/Unregister(Prefix)Callback. + public: + // Public so the ValueObserver classes can use it. + enum MatchKind { + PrefixMatch, + ExactMatch, + }; + + private: + static void SetupTelemetryPref(); + static nsresult InitInitialObjects(bool aIsStartup); + + friend struct Internals; + + static nsresult RegisterCallback(PrefChangedFunc aCallback, + const nsACString& aPref, void* aClosure, + MatchKind aMatchKind, + bool aIsPriority = false); + static nsresult UnregisterCallback(PrefChangedFunc aCallback, + const nsACString& aPref, void* aClosure, + MatchKind aMatchKind); + static nsresult RegisterCallbackAndCall(PrefChangedFunc aCallback, + const nsACString& aPref, + void* aClosure, MatchKind aMatchKind); + + static nsresult RegisterCallbacks(PrefChangedFunc aCallback, + const char* const* aPrefs, void* aClosure, + MatchKind aMatchKind); + static nsresult UnregisterCallbacks(PrefChangedFunc aCallback, + const char* const* aPrefs, void* aClosure, + MatchKind aMatchKind); + + template + static nsresult RegisterCallbackImpl(PrefChangedFunc aCallback, T& aPref, + void* aClosure, MatchKind aMatchKind, + bool aIsPriority = false); + template + static nsresult UnregisterCallbackImpl(PrefChangedFunc aCallback, T& aPref, + void* aClosure, MatchKind aMatchKind); + + static nsresult RegisterCallback(PrefChangedFunc aCallback, const char* aPref, + void* aClosure, MatchKind aMatchKind, + bool aIsPriority = false) { + return RegisterCallback(aCallback, nsDependentCString(aPref), aClosure, + aMatchKind, aIsPriority); + } + static nsresult UnregisterCallback(PrefChangedFunc aCallback, + const char* aPref, void* aClosure, + MatchKind aMatchKind) { + return UnregisterCallback(aCallback, nsDependentCString(aPref), aClosure, + aMatchKind); + } + static nsresult RegisterCallbackAndCall(PrefChangedFunc aCallback, + const char* aPref, void* aClosure, + MatchKind aMatchKind) { + return RegisterCallbackAndCall(aCallback, nsDependentCString(aPref), + aClosure, aMatchKind); + } + + private: + nsCOMPtr mCurrentFile; + bool mDirty = false; + bool mProfileShutdown = false; + // We wait a bit after prefs are dirty before writing them. In this period, + // mDirty and mSavePending will both be true. + bool mSavePending = false; + + nsCOMPtr mRootBranch; + nsCOMPtr mDefaultRootBranch; + + static StaticRefPtr sPreferences; + static bool sShutdown; + + // Init static members. Returns true on success. + static bool InitStaticMembers(); +}; + +extern Atomic sOmitBlocklistedPrefValues; +extern Atomic sCrashOnBlocklistedPref; + +bool IsPreferenceSanitized(const char* aPref); + +const char kFissionEnforceBlockList[] = + "fission.enforceBlocklistedPrefsInSubprocesses"; +const char kFissionOmitBlockListValues[] = + "fission.omitBlocklistedPrefsInSubprocesses"; + +void OnFissionBlocklistPrefChange(const char* aPref, void* aData); + +} // namespace mozilla + +#endif // mozilla_Preferences_h diff --git a/modules/libpref/SharedPrefMap.cpp b/modules/libpref/SharedPrefMap.cpp new file mode 100644 index 0000000000..7782ffdcbf --- /dev/null +++ b/modules/libpref/SharedPrefMap.cpp @@ -0,0 +1,237 @@ +/* -*- 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/. */ + +#include "SharedPrefMap.h" + +#include "mozilla/dom/ipc/MemMapSnapshot.h" + +#include "mozilla/BinarySearch.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Try.h" +#include "mozilla/ipc/FileDescriptor.h" + +using namespace mozilla::loader; + +namespace mozilla { + +using namespace ipc; + +static inline size_t GetAlignmentOffset(size_t aOffset, size_t aAlign) { + auto mod = aOffset % aAlign; + return mod ? aAlign - mod : 0; +} + +SharedPrefMap::SharedPrefMap(const FileDescriptor& aMapFile, size_t aMapSize) { + auto result = mMap.initWithHandle(aMapFile, aMapSize); + MOZ_RELEASE_ASSERT(result.isOk()); + // We return literal nsCStrings pointing to the mapped data for preference + // names and string values, which means that we may still have references to + // the mapped data even after this instance is destroyed. That means that we + // need to keep the mapping alive until process shutdown, in order to be safe. + mMap.setPersistent(); +} + +SharedPrefMap::SharedPrefMap(SharedPrefMapBuilder&& aBuilder) { + auto result = aBuilder.Finalize(mMap); + MOZ_RELEASE_ASSERT(result.isOk()); + mMap.setPersistent(); +} + +mozilla::ipc::FileDescriptor SharedPrefMap::CloneFileDescriptor() const { + return mMap.cloneHandle(); +} + +bool SharedPrefMap::Has(const char* aKey) const { + size_t index; + return Find(aKey, &index); +} + +Maybe SharedPrefMap::Get(const char* aKey) const { + Maybe result; + + size_t index; + if (Find(aKey, &index)) { + result.emplace(Pref{this, &Entries()[index]}); + } + + return result; +} + +bool SharedPrefMap::Find(const char* aKey, size_t* aIndex) const { + const auto& keys = KeyTable(); + + return BinarySearchIf( + Entries(), 0, EntryCount(), + [&](const Entry& aEntry) { + return strcmp(aKey, keys.GetBare(aEntry.mKey)); + }, + aIndex); +} + +void SharedPrefMapBuilder::Add(const nsCString& aKey, const Flags& aFlags, + bool aDefaultValue, bool aUserValue) { + mEntries.AppendElement(Entry{ + aKey.get(), + mKeyTable.Add(aKey), + {aDefaultValue, aUserValue}, + uint8_t(PrefType::Bool), + aFlags.mHasDefaultValue, + aFlags.mHasUserValue, + aFlags.mIsSticky, + aFlags.mIsLocked, + aFlags.mIsSanitized, + aFlags.mIsSkippedByIteration, + }); +} + +void SharedPrefMapBuilder::Add(const nsCString& aKey, const Flags& aFlags, + int32_t aDefaultValue, int32_t aUserValue) { + ValueIdx index; + if (aFlags.mHasUserValue) { + index = mIntValueTable.Add(aDefaultValue, aUserValue); + } else { + index = mIntValueTable.Add(aDefaultValue); + } + + mEntries.AppendElement(Entry{ + aKey.get(), + mKeyTable.Add(aKey), + {index}, + uint8_t(PrefType::Int), + aFlags.mHasDefaultValue, + aFlags.mHasUserValue, + aFlags.mIsSticky, + aFlags.mIsLocked, + aFlags.mIsSanitized, + aFlags.mIsSkippedByIteration, + }); +} + +void SharedPrefMapBuilder::Add(const nsCString& aKey, const Flags& aFlags, + const nsCString& aDefaultValue, + const nsCString& aUserValue) { + ValueIdx index; + StringTableEntry defaultVal = mValueStringTable.Add(aDefaultValue); + if (aFlags.mHasUserValue) { + StringTableEntry userVal = mValueStringTable.Add(aUserValue); + index = mStringValueTable.Add(defaultVal, userVal); + } else { + index = mStringValueTable.Add(defaultVal); + } + + mEntries.AppendElement(Entry{ + aKey.get(), + mKeyTable.Add(aKey), + {index}, + uint8_t(PrefType::String), + aFlags.mHasDefaultValue, + aFlags.mHasUserValue, + aFlags.mIsSticky, + aFlags.mIsLocked, + aFlags.mIsSanitized, + aFlags.mIsSkippedByIteration, + }); +} + +Result SharedPrefMapBuilder::Finalize(loader::AutoMemMap& aMap) { + using Header = SharedPrefMap::Header; + + // Create an array of entry pointers for the entry array, and sort it by + // preference name prior to serialization, so that entries can be looked up + // using binary search. + nsTArray entries(mEntries.Length()); + for (auto& entry : mEntries) { + entries.AppendElement(&entry); + } + entries.Sort([](const Entry* aA, const Entry* aB) { + return strcmp(aA->mKeyString, aB->mKeyString); + }); + + Header header = {uint32_t(entries.Length())}; + + size_t offset = sizeof(header); + offset += GetAlignmentOffset(offset, alignof(Header)); + + offset += entries.Length() * sizeof(SharedPrefMap::Entry); + + header.mKeyStrings.mOffset = offset; + header.mKeyStrings.mSize = mKeyTable.Size(); + offset += header.mKeyStrings.mSize; + + offset += GetAlignmentOffset(offset, mIntValueTable.Alignment()); + header.mUserIntValues.mOffset = offset; + header.mUserIntValues.mSize = mIntValueTable.UserSize(); + offset += header.mUserIntValues.mSize; + + offset += GetAlignmentOffset(offset, mIntValueTable.Alignment()); + header.mDefaultIntValues.mOffset = offset; + header.mDefaultIntValues.mSize = mIntValueTable.DefaultSize(); + offset += header.mDefaultIntValues.mSize; + + offset += GetAlignmentOffset(offset, mStringValueTable.Alignment()); + header.mUserStringValues.mOffset = offset; + header.mUserStringValues.mSize = mStringValueTable.UserSize(); + offset += header.mUserStringValues.mSize; + + offset += GetAlignmentOffset(offset, mStringValueTable.Alignment()); + header.mDefaultStringValues.mOffset = offset; + header.mDefaultStringValues.mSize = mStringValueTable.DefaultSize(); + offset += header.mDefaultStringValues.mSize; + + header.mValueStrings.mOffset = offset; + header.mValueStrings.mSize = mValueStringTable.Size(); + offset += header.mValueStrings.mSize; + + MemMapSnapshot mem; + MOZ_TRY(mem.Init(offset)); + + auto headerPtr = mem.Get
(); + headerPtr[0] = header; + + auto* entryPtr = reinterpret_cast(&headerPtr[1]); + for (auto* entry : entries) { + *entryPtr = { + entry->mKey, + GetValue(*entry), + entry->mType, + entry->mHasDefaultValue, + entry->mHasUserValue, + entry->mIsSticky, + entry->mIsLocked, + entry->mIsSanitized, + entry->mIsSkippedByIteration, + }; + entryPtr++; + } + + auto ptr = mem.Get(); + + mKeyTable.Write({&ptr[header.mKeyStrings.mOffset], header.mKeyStrings.mSize}); + + mValueStringTable.Write( + {&ptr[header.mValueStrings.mOffset], header.mValueStrings.mSize}); + + mIntValueTable.WriteDefaultValues( + {&ptr[header.mDefaultIntValues.mOffset], header.mDefaultIntValues.mSize}); + mIntValueTable.WriteUserValues( + {&ptr[header.mUserIntValues.mOffset], header.mUserIntValues.mSize}); + + mStringValueTable.WriteDefaultValues( + {&ptr[header.mDefaultStringValues.mOffset], + header.mDefaultStringValues.mSize}); + mStringValueTable.WriteUserValues( + {&ptr[header.mUserStringValues.mOffset], header.mUserStringValues.mSize}); + + mKeyTable.Clear(); + mValueStringTable.Clear(); + mIntValueTable.Clear(); + mStringValueTable.Clear(); + mEntries.Clear(); + + return mem.Finalize(aMap); +} + +} // namespace mozilla diff --git a/modules/libpref/SharedPrefMap.h b/modules/libpref/SharedPrefMap.h new file mode 100644 index 0000000000..7c949e137d --- /dev/null +++ b/modules/libpref/SharedPrefMap.h @@ -0,0 +1,848 @@ +/* -*- 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/. */ + +#ifndef dom_ipc_SharedPrefMap_h +#define dom_ipc_SharedPrefMap_h + +#include "mozilla/AutoMemMap.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/Preferences.h" +#include "mozilla/Result.h" +#include "mozilla/dom/ipc/StringTable.h" +#include "nsTHashMap.h" + +namespace mozilla { + +// The approximate number of preferences expected to be in an ordinary +// preferences database. +// +// This number is used to determine initial allocation sizes for data structures +// when building the shared preference map, and should be slightly higher than +// the expected number of preferences in an ordinary database to avoid +// unnecessary reallocations/rehashes. +constexpr size_t kExpectedPrefCount = 4000; + +class SharedPrefMapBuilder; + +// This class provides access to a compact, read-only copy of a preference +// database, backed by a shared memory buffer which can be shared between +// processes. All state data for the database is stored in the shared memory +// region, so individual instances require no dynamic memory allocation. +// +// Further, all strings returned from this API are nsLiteralCStrings with +// pointers into the shared memory region, which means that they can be copied +// into new nsCString instances without additional allocations. For instance, +// the following (where `pref` is a Pref object) will not cause any string +// copies, memory allocations, or atomic refcount changes: +// +// nsCString prefName(pref.NameString()); +// +// whereas if we returned a nsDependentCString or a dynamically allocated +// nsCString, it would. +// +// The set of entries is stored in sorted order by preference name, so look-ups +// are done by binary search. This means that look-ups have O(log n) complexity, +// rather than the O(1) complexity of a dynamic hashtable. Consumers should keep +// this in mind when planning their accesses. +// +// Important: The mapped memory created by this class is persistent. Once an +// instance has been initialized, the memory that it allocates can never be +// freed before process shutdown. Do not use it for short-lived mappings. +class SharedPrefMap { + using FileDescriptor = mozilla::ipc::FileDescriptor; + + friend class SharedPrefMapBuilder; + + // Describes a block of memory within the shared memory region. + struct DataBlock { + // The byte offset from the start of the shared memory region to the start + // of the block. + size_t mOffset; + // The size of the block, in bytes. This is typically used only for bounds + // checking in debug builds. + size_t mSize; + }; + + // Describes the contents of the shared memory region, which is laid-out as + // follows: + // + // - The Header struct + // + // - An array of Entry structs with mEntryCount elements, lexicographically + // sorted by preference name. + // + // - A set of data blocks, with offsets and sizes described by the DataBlock + // entries in the header, described below. + // + // Each entry stores its name string and values as indices into these blocks, + // as documented in the Entry struct, but with some important optimizations: + // + // - Boolean values are always stored inline. Both the default and user + // values can be retrieved directly from the entry. Other types have only + // one value index, and their values appear at the same indices in the + // default and user value arrays. + // + // Aside from reducing our memory footprint, this space-efficiency means + // that we can fit more entries in the CPU cache at once, and reduces the + // number of likely cache misses during lookups. + // + // - Key strings are stored in a separate string table from value strings. As + // above, this makes it more likely that the strings we need will be + // available in the CPU cache during lookups by not interleaving them with + // extraneous data. + // + // - Default and user values are stored in separate arrays. Entries with user + // values always appear before entries with default values in the value + // arrays, and entries without user values do not have entries in the user + // array at all. Since the same index is used for both arrays, this means + // that entries with a default value but no user value do not allocate any + // space to store their user value. + // + // - For preferences with no user value, the entries in the default value are + // de-duplicated. All preferences with the same default value (and no user + // value) point to the same index in the default value array. + // + // + // For example, a preference database containing: + // + // +---------+-------------------------------+-------------------------------+ + // | Name | Default Value | User Value | | + // +---------+---------------+---------------+-------------------------------+ + // | string1 | "meh" | "hem" | | + // | string2 | | "b" | | + // | string3 | "a" | | | + // | string4 | "foo" | | | + // | string5 | "foo" | | | + // | string6 | "meh" | | | + // +---------+---------------+---------------+-------------------------------+ + // | bool1 | false | true | | + // | bool2 | | false | | + // | bool3 | true | | | + // +---------+---------------+---------------+-------------------------------+ + // | int1 | 18 | 16 | | + // | int2 | | 24 | | + // | int3 | 42 | | | + // | int4 | 12 | | | + // | int5 | 12 | | | + // | int6 | 18 | | | + // +---------+---------------+---------------+-------------------------------+ + // + // Results in a database that looks like: + // + // +-------------------------------------------------------------------------+ + // | Header: | + // +-------------------------------------------------------------------------+ + // | mEntryCount = 15 | + // | ... | + // +-------------------------------------------------------------------------+ + // + // +-------------------------------------------------------------------------+ + // | Key strings: | + // +--------+----------------------------------------------------------------+ + // | Offset | Value | + // +--------+----------------------------------------------------------------+ + // | 0 | string1\0 | + // | 8 | string2\0 | + // | 16 | string3\0 | + // | 24 | string4\0 | + // | 32 | string5\0 | + // | 40 | string6\0 | + // | 48 | bool1\0 | + // | 54 | bool2\0 | + // | 60 | bool3\0 | + // | 66 | int1\0 | + // | 71 | int2\0 | + // | 76 | int3\0 | + // | 81 | int4\0 | + // | 86 | int6\0 | + // | 91 | int6\0 | + // +--------+----------------------------------------------------------------+ + // + // +-------------------------------------------------------------------------+ + // | Entries: | + // +---------------------+------+------------+------------+------------------+ + // | Key[1] | Type | HasDefault | HasUser | Value | + // +---------------------+------+------------+------------+------------------+ + // | K["bool1", 48, 5] | 3 | true | true | { false, true } | + // | K["bool2", 54, 5] | 3 | false | true | { 0, false } | + // | K["bool3", 60, 5] | 3 | true | false | { true, 0 } | + // | K["int1", 66, 4] | 2 | true | true | 0 | + // | K["int2", 71, 4] | 2 | false | true | 1 | + // | K["int3", 76, 4] | 2 | true | false | 2 | + // | K["int4", 81, 4] | 2 | true | false | 3 | + // | K["int5", 86, 4] | 2 | true | false | 3 | + // | K["int6", 91, 4] | 2 | true | false | 4 | + // | K["string1", 0, 6] | 1 | true | true | 0 | + // | K["string2", 8, 6] | 1 | false | true | 1 | + // | K["string3", 16, 6] | 1 | true | false | 2 | + // | K["string4", 24, 6] | 1 | true | false | 3 | + // | K["string5", 32, 6] | 1 | true | false | 3 | + // | K["string6", 40, 6] | 1 | true | false | 4 | + // +---------------------+------+------------+------------+------------------+ + // | [1]: Encoded as an offset into the key table and a length. Specified | + // | as K[string, offset, length] for clarity. | + // +-------------------------------------------------------------------------+ + // + // +------------------------------------+------------------------------------+ + // | User integer values | Default integer values | + // +-------+----------------------------+-------+----------------------------+ + // | Index | Contents | Index | Contents | + // +-------+----------------------------+-------+----------------------------+ + // | 0 | 16 | 0 | 18 | + // | 1 | 24 | 1 | | + // | | | 2 | 42 | + // | | | 3 | 12 | + // | | | 4 | 18 | + // +-------+----------------------------+-------+----------------------------+ + // | * Note: Tables are laid out sequentially in memory, but displayed | + // | here side-by-side for clarity. | + // +-------------------------------------------------------------------------+ + // + // +------------------------------------+------------------------------------+ + // | User string values | Default string values | + // +-------+----------------------------+-------+----------------------------+ + // | Index | Contents[1] | Index | Contents[1] | + // +-------+----------------------------+-------+----------------------------+ + // | 0 | V["hem", 0, 3] | 0 | V["meh", 4, 3] | + // | 1 | V["b", 8, 1] | 1 | | + // | | | 2 | V["a", 10, 1] | + // | | | 3 | V["foo", 12, 3] | + // | | | 4 | V["meh", 4, 3] | + // |-------+----------------------------+-------+----------------------------+ + // | [1]: Encoded as an offset into the value table and a length. Specified | + // | as V[string, offset, length] for clarity. | + // +-------------------------------------------------------------------------+ + // | * Note: Tables are laid out sequentially in memory, but displayed | + // | here side-by-side for clarity. | + // +-------------------------------------------------------------------------+ + // + // +-------------------------------------------------------------------------+ + // | Value strings: | + // +--------+----------------------------------------------------------------+ + // | Offset | Value | + // +--------+----------------------------------------------------------------+ + // | 0 | hem\0 | + // | 4 | meh\0 | + // | 8 | b\0 | + // | 10 | a\0 | + // | 12 | foo\0 | + // +--------+----------------------------------------------------------------+ + struct Header { + // The number of entries in this map. + uint32_t mEntryCount; + + // The StringTable data block for preference name strings, which act as keys + // in the map. + DataBlock mKeyStrings; + + // The int32_t arrays of user and default int preference values. Entries in + // the map store their values as indices into these arrays. + DataBlock mUserIntValues; + DataBlock mDefaultIntValues; + + // The StringTableEntry arrays of user and default string preference values. + // + // Strings are stored as StringTableEntry structs with character offsets + // into the mValueStrings string table and their corresponding lengths. + // + // Entries in the map, likewise, store their string values as indices into + // these arrays. + DataBlock mUserStringValues; + DataBlock mDefaultStringValues; + + // The StringTable data block for string preference values, referenced by + // the above two data blocks. + DataBlock mValueStrings; + }; + + using StringTableEntry = mozilla::dom::ipc::StringTableEntry; + + // Represents a preference value, as either a pair of boolean values, or an + // index into one of the above value arrays. + union Value { + Value(bool aDefaultValue, bool aUserValue) + : mDefaultBool(aDefaultValue), mUserBool(aUserValue) {} + + MOZ_IMPLICIT Value(uint16_t aIndex) : mIndex(aIndex) {} + + // The index of this entry in the value arrays. + // + // User and default preference values have the same indices in their + // respective arrays. However, entries without a user value are not + // guaranteed to have space allocated for them in the user value array, and + // likewise for preferences without default values in the default value + // array. This means that callers must only access value entries for entries + // which claim to have a value of that type. + uint16_t mIndex; + struct { + bool mDefaultBool; + bool mUserBool; + }; + }; + + // Represents a preference entry in the map, containing its name, type info, + // flags, and a reference to its value. + struct Entry { + // A pointer to the preference name in the KeyTable string table. + StringTableEntry mKey; + + // The preference's value, either as a pair of booleans, or an index into + // the value arrays. Please see the documentation for the Value struct + // above. + Value mValue; + + // The preference's type, as a PrefType enum value. This must *never* be + // PrefType::None for values in a shared array. + uint8_t mType : 2; + // True if the preference has a default value. Callers must not attempt to + // access the entry's default value if this is false. + uint8_t mHasDefaultValue : 1; + // True if the preference has a user value. Callers must not attempt to + // access the entry's user value if this is false. + uint8_t mHasUserValue : 1; + // True if the preference is sticky, as defined by the preference service. + uint8_t mIsSticky : 1; + // True if the preference is locked, as defined by the preference service. + uint8_t mIsLocked : 1; + // True if the preference is sanitized, as defined by the preference + // service. + uint8_t mIsSanitized : 1; + // True if the preference should be skipped while iterating over the + // SharedPrefMap. This is used to internally store Once StaticPrefs. + // This property is not visible to users the way sticky and locked are. + uint8_t mIsSkippedByIteration : 1; + }; + + public: + NS_INLINE_DECL_REFCOUNTING(SharedPrefMap) + + // A temporary wrapper class for accessing entries in the array. Instances of + // this class are valid as long as SharedPrefMap instance is alive, but + // generally should not be stored long term, or allocated on the heap. + // + // The class is implemented as two pointers, one to the SharedPrefMap + // instance, and one to the Entry that corresponds to the preference, and is + // meant to be cheaply returned by value from preference lookups and + // iterators. All property accessors lazily fetch the appropriate values from + // the shared memory region. + class MOZ_STACK_CLASS Pref final { + public: + const char* Name() const { return mMap->KeyTable().GetBare(mEntry->mKey); } + + nsCString NameString() const { return mMap->KeyTable().Get(mEntry->mKey); } + + PrefType Type() const { + MOZ_ASSERT(PrefType(mEntry->mType) != PrefType::None); + return PrefType(mEntry->mType); + } + + bool HasDefaultValue() const { return mEntry->mHasDefaultValue; } + bool HasUserValue() const { return mEntry->mHasUserValue; } + bool IsLocked() const { return mEntry->mIsLocked; } + bool IsSanitized() const { return mEntry->mIsSanitized; } + bool IsSticky() const { return mEntry->mIsSticky; } + bool IsSkippedByIteration() const { return mEntry->mIsSkippedByIteration; } + + bool GetBoolValue(PrefValueKind aKind = PrefValueKind::User) const { + MOZ_ASSERT(Type() == PrefType::Bool); + MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue() + : HasUserValue()); + + return aKind == PrefValueKind::Default ? mEntry->mValue.mDefaultBool + : mEntry->mValue.mUserBool; + } + + int32_t GetIntValue(PrefValueKind aKind = PrefValueKind::User) const { + MOZ_ASSERT(Type() == PrefType::Int); + MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue() + : HasUserValue()); + + return aKind == PrefValueKind::Default + ? mMap->DefaultIntValues()[mEntry->mValue.mIndex] + : mMap->UserIntValues()[mEntry->mValue.mIndex]; + } + + private: + const StringTableEntry& GetStringEntry(PrefValueKind aKind) const { + MOZ_ASSERT(Type() == PrefType::String); + MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue() + : HasUserValue()); + + return aKind == PrefValueKind::Default + ? mMap->DefaultStringValues()[mEntry->mValue.mIndex] + : mMap->UserStringValues()[mEntry->mValue.mIndex]; + } + + public: + nsCString GetStringValue(PrefValueKind aKind = PrefValueKind::User) const { + return mMap->ValueTable().Get(GetStringEntry(aKind)); + } + + const char* GetBareStringValue( + PrefValueKind aKind = PrefValueKind::User) const { + return mMap->ValueTable().GetBare(GetStringEntry(aKind)); + } + + // Returns the entry's index in the map, as understood by GetKeyAt() and + // GetValueAt(). + size_t Index() const { return mEntry - mMap->Entries().get(); } + + bool operator==(const Pref& aPref) const { return mEntry == aPref.mEntry; } + bool operator!=(const Pref& aPref) const { return !(*this == aPref); } + + // This is odd, but necessary in order for the C++ range iterator protocol + // to work here. + Pref& operator*() { return *this; } + + // Updates this wrapper to point to the next visible entry in the map. This + // should not be attempted unless Index() is less than the map's Count(). + Pref& operator++() { + do { + mEntry++; + } while (mEntry->mIsSkippedByIteration && Index() < mMap->Count()); + return *this; + } + + Pref(const Pref& aPref) = default; + + protected: + friend class SharedPrefMap; + + Pref(const SharedPrefMap* aPrefMap, const Entry* aEntry) + : mMap(aPrefMap), mEntry(aEntry) {} + + private: + const SharedPrefMap* const mMap; + const Entry* mEntry; + }; + + // Note: These constructors are infallible, because the preference database is + // critical to platform functionality, and we cannot operate without it. + SharedPrefMap(const FileDescriptor&, size_t); + explicit SharedPrefMap(SharedPrefMapBuilder&&); + + // Searches for the given preference in the map, and returns true if it + // exists. + bool Has(const char* aKey) const; + + bool Has(const nsCString& aKey) const { return Has(aKey.get()); } + + // Searches for the given preference in the map, and if it exists, returns + // a Some containing its details. + Maybe Get(const char* aKey) const; + + Maybe Get(const nsCString& aKey) const { return Get(aKey.get()); } + + private: + // Searches for an entry for the given key. If found, returns true, and + // places its index in the entry array in aIndex. + bool Find(const char* aKey, size_t* aIndex) const; + + public: + // Returns the number of entries in the map. + uint32_t Count() const { return EntryCount(); } + + // Returns the string entry at the given index. Keys are guaranteed to be + // sorted lexicographically. + // + // The given index *must* be less than the value returned by Count(). + // + // The returned value is a literal string which references the mapped memory + // region. + nsCString GetKeyAt(uint32_t aIndex) const { + MOZ_ASSERT(aIndex < Count()); + return KeyTable().Get(Entries()[aIndex].mKey); + } + + // Returns the value for the entry at the given index. + // + // The given index *must* be less than the value returned by Count(). + // + // The returned value is valid for the lifetime of this map instance. + const Pref GetValueAt(uint32_t aIndex) const { + MOZ_ASSERT(aIndex < Count()); + return UncheckedGetValueAt(aIndex); + } + + private: + // Returns a wrapper with a pointer to an entry without checking its bounds. + // This should only be used by range iterators, to check their end positions. + // + // Note: In debug builds, the RangePtr returned by entries will still assert + // that aIndex is no more than 1 past the last element in the array, since it + // also takes into account the ranged iteration use case. + Pref UncheckedGetValueAt(uint32_t aIndex) const { + return {this, (Entries() + aIndex).get()}; + } + + public: + // C++ range iterator protocol. begin() and end() return references to the + // first (non-skippable) and last entries in the array. The begin wrapper + // can be incremented until it matches the last element in the array, at which + // point it becomes invalid and the iteration is over. + Pref begin() const { + for (uint32_t aIndex = 0; aIndex < Count(); aIndex++) { + Pref pref = UncheckedGetValueAt(aIndex); + if (!pref.IsSkippedByIteration()) { + return pref; + } + } + return end(); + } + Pref end() const { return UncheckedGetValueAt(Count()); } + + // A cosmetic helper for range iteration. Returns a reference value from a + // pointer to this instance so that its .begin() and .end() methods can be + // accessed in a ranged for loop. `map->Iter()` is equivalent to `*map`, but + // makes its purpose slightly clearer. + const SharedPrefMap& Iter() const { return *this; } + + // Returns a copy of the read-only file descriptor which backs the shared + // memory region for this map. The file descriptor may be passed between + // processes, and used to construct new instances of SharedPrefMap with + // the same data as this instance. + FileDescriptor CloneFileDescriptor() const; + + // Returns the size of the mapped memory region. This size must be passed to + // the constructor when mapping the shared region in another process. + size_t MapSize() const { return mMap.size(); } + + protected: + ~SharedPrefMap() = default; + + private: + template + using StringTable = mozilla::dom::ipc::StringTable; + + // Type-safe getters for values in the shared memory region: + const Header& GetHeader() const { return mMap.get
()[0]; } + + RangedPtr Entries() const { + return {reinterpret_cast(&GetHeader() + 1), EntryCount()}; + } + + uint32_t EntryCount() const { return GetHeader().mEntryCount; } + + template + RangedPtr GetBlock(const DataBlock& aBlock) const { + return RangedPtr(&mMap.get()[aBlock.mOffset], + aBlock.mSize) + .ReinterpretCast(); + } + + RangedPtr DefaultIntValues() const { + return GetBlock(GetHeader().mDefaultIntValues); + } + RangedPtr UserIntValues() const { + return GetBlock(GetHeader().mUserIntValues); + } + + RangedPtr DefaultStringValues() const { + return GetBlock(GetHeader().mDefaultStringValues); + } + RangedPtr UserStringValues() const { + return GetBlock(GetHeader().mUserStringValues); + } + + StringTable KeyTable() const { + auto& block = GetHeader().mKeyStrings; + return {{&mMap.get()[block.mOffset], block.mSize}}; + } + + StringTable ValueTable() const { + auto& block = GetHeader().mValueStrings; + return {{&mMap.get()[block.mOffset], block.mSize}}; + } + + loader::AutoMemMap mMap; +}; + +// A helper class which builds the contiguous look-up table used by +// SharedPrefMap. Each preference in the final map is added to the builder, +// before it is finalized and transformed into a read-only snapshot. +class MOZ_RAII SharedPrefMapBuilder { + public: + SharedPrefMapBuilder() = default; + + // The set of flags for the preference, as documented in SharedPrefMap::Entry. + struct Flags { + uint8_t mHasDefaultValue : 1; + uint8_t mHasUserValue : 1; + uint8_t mIsSticky : 1; + uint8_t mIsLocked : 1; + uint8_t mIsSanitized : 1; + uint8_t mIsSkippedByIteration : 1; + }; + + void Add(const nsCString& aKey, const Flags& aFlags, bool aDefaultValue, + bool aUserValue); + + void Add(const nsCString& aKey, const Flags& aFlags, int32_t aDefaultValue, + int32_t aUserValue); + + void Add(const nsCString& aKey, const Flags& aFlags, + const nsCString& aDefaultValue, const nsCString& aUserValue); + + // Finalizes the binary representation of the map, writes it to a shared + // memory region, and then initializes the given AutoMemMap with a reference + // to the read-only copy of it. + // + // This should generally not be used directly by callers. The + // SharedPrefMapBuilder instance should instead be passed to the SharedPrefMap + // constructor as a move reference. + Result Finalize(loader::AutoMemMap& aMap); + + private: + using StringTableEntry = mozilla::dom::ipc::StringTableEntry; + template + using StringTableBuilder = mozilla::dom::ipc::StringTableBuilder; + + // An opaque descriptor of the index of a preference entry in a value array, + // which can be converted numeric index after the ValueTableBuilder is + // finalized. + struct ValueIdx { + // The relative index of the entry, based on its class. Entries for + // preferences with user values appear at the value arrays. Entries with + // only default values begin after the last entry with a user value. + uint16_t mIndex; + bool mHasUserValue; + }; + + // A helper class for building default and user value arrays for preferences. + // + // As described in the SharedPrefMap class, this helper optimizes the way that + // it builds its value arrays, in that: + // + // - It stores value entries for all preferences with user values before + // entries for preferences with only default values, and allocates no + // entries for preferences with only default values in the user value array. + // Since most preferences have only default values, this dramatically + // reduces the space required for value storage. + // + // - For preferences with only default values, it de-duplicates value entries, + // and returns the same indices for all preferences with the same value. + // + // One important complication of this approach is that it means we cannot know + // the final index of any entry with only a default value until all entries + // have been added to the builder, since it depends on the final number of + // user entries in the output. + // + // To deal with this, when entries are added, we return an opaque ValueIndex + // struct, from which we can calculate the final index after the map has been + // finalized. + template + class ValueTableBuilder { + public: + using ValueType = ValueType_; + + // Adds an entry for a preference with only a default value to the array, + // and returns an opaque descriptor for its index. + ValueIdx Add(const ValueType& aDefaultValue) { + auto index = uint16_t(mDefaultEntries.Count()); + + return mDefaultEntries.WithEntryHandle(aDefaultValue, [&](auto&& entry) { + entry.OrInsertWith([&] { return Entry{index, false, aDefaultValue}; }); + + return ValueIdx{entry->mIndex, false}; + }); + } + + // Adds an entry for a preference with a user value to the array. Regardless + // of whether the preference has a default value, space must be allocated + // for it. For preferences with no default value, the actual value which + // appears in the array at its value index is ignored. + ValueIdx Add(const ValueType& aDefaultValue, const ValueType& aUserValue) { + auto index = uint16_t(mUserEntries.Length()); + + mUserEntries.AppendElement(Entry{index, true, aDefaultValue, aUserValue}); + + return {index, true}; + } + + // Returns the final index for an entry based on its opaque index + // descriptor. This must only be called after the caller has finished adding + // entries to the builder. + uint16_t GetIndex(const ValueIdx& aIndex) const { + uint16_t base = aIndex.mHasUserValue ? 0 : UserCount(); + return base + aIndex.mIndex; + } + + // Writes out the array of default values at the block beginning at the + // given pointer. The block must be at least as large as the value returned + // by DefaultSize(). + void WriteDefaultValues(const RangedPtr& aBuffer) const { + auto buffer = aBuffer.ReinterpretCast(); + + for (const auto& entry : mUserEntries) { + buffer[entry.mIndex] = entry.mDefaultValue; + } + + size_t defaultsOffset = UserCount(); + for (const auto& data : mDefaultEntries.Values()) { + buffer[defaultsOffset + data.mIndex] = data.mDefaultValue; + } + } + + // Writes out the array of user values at the block beginning at the + // given pointer. The block must be at least as large as the value returned + // by UserSize(). + void WriteUserValues(const RangedPtr& aBuffer) const { + auto buffer = aBuffer.ReinterpretCast(); + + for (const auto& entry : mUserEntries) { + buffer[entry.mIndex] = entry.mUserValue; + } + } + + // These return the number of entries in the default and user value arrays, + // respectively. + uint32_t DefaultCount() const { + return UserCount() + mDefaultEntries.Count(); + } + uint32_t UserCount() const { return mUserEntries.Length(); } + + // These return the byte sizes of the default and user value arrays, + // respectively. + uint32_t DefaultSize() const { return DefaultCount() * sizeof(ValueType); } + uint32_t UserSize() const { return UserCount() * sizeof(ValueType); } + + void Clear() { + mUserEntries.Clear(); + mDefaultEntries.Clear(); + } + + static constexpr size_t Alignment() { return alignof(ValueType); } + + private: + struct Entry { + uint16_t mIndex; + bool mHasUserValue; + ValueType mDefaultValue; + ValueType mUserValue{}; + }; + + AutoTArray mUserEntries; + + nsTHashMap mDefaultEntries; + }; + + // A special-purpose string table builder for keys which are already + // guaranteed to be unique. Duplicate values will not be detected or + // de-duplicated. + template + class UniqueStringTableBuilder { + public: + using ElemType = CharType; + + explicit UniqueStringTableBuilder(size_t aCapacity) : mEntries(aCapacity) {} + + StringTableEntry Add(const nsTString& aKey) { + auto entry = mEntries.AppendElement( + Entry{mSize, uint32_t(aKey.Length()), aKey.get()}); + + mSize += entry->mLength + 1; + + return {entry->mOffset, entry->mLength}; + } + + void Write(const RangedPtr& aBuffer) { + auto buffer = aBuffer.ReinterpretCast(); + + for (auto& entry : mEntries) { + memcpy(&buffer[entry.mOffset], entry.mValue, + sizeof(ElemType) * (entry.mLength + 1)); + } + } + + uint32_t Count() const { return mEntries.Length(); } + + uint32_t Size() const { return mSize * sizeof(ElemType); } + + void Clear() { mEntries.Clear(); } + + static constexpr size_t Alignment() { return alignof(ElemType); } + + private: + struct Entry { + uint32_t mOffset; + uint32_t mLength; + const CharType* mValue; + }; + + nsTArray mEntries; + uint32_t mSize = 0; + }; + + // A preference value entry, roughly corresponding to the + // SharedPrefMap::Value struct, but with a temporary place-holder value rather + // than a final value index. + union Value { + Value(bool aDefaultValue, bool aUserValue) + : mDefaultBool(aDefaultValue), mUserBool(aUserValue) {} + + MOZ_IMPLICIT Value(const ValueIdx& aIndex) : mIndex(aIndex) {} + + // For Bool preferences, their default and user bool values. + struct { + bool mDefaultBool; + bool mUserBool; + }; + // For Int and String preferences, an opaque descriptor for their entries in + // their value arrays. This must be passed to the appropriate + // ValueTableBuilder to obtain the final index when the entry is serialized. + ValueIdx mIndex; + }; + + // A preference entry, to be converted to a SharedPrefMap::Entry struct during + // serialization. + struct Entry { + // The entry's preference name, as passed to Add(). The caller is + // responsible for keeping this pointer alive until the builder is + // finalized. + const char* mKeyString; + // The entry in mKeyTable corresponding to mKeyString. + StringTableEntry mKey; + Value mValue; + + uint8_t mType : 2; + uint8_t mHasDefaultValue : 1; + uint8_t mHasUserValue : 1; + uint8_t mIsSticky : 1; + uint8_t mIsLocked : 1; + uint8_t mIsSanitized : 1; + uint8_t mIsSkippedByIteration : 1; + }; + + // Converts a builder Value struct to a SharedPrefMap::Value struct for + // serialization. This must not be called before callers have finished adding + // entries to the value array builders. + SharedPrefMap::Value GetValue(const Entry& aEntry) const { + switch (PrefType(aEntry.mType)) { + case PrefType::Bool: + return {aEntry.mValue.mDefaultBool, aEntry.mValue.mUserBool}; + case PrefType::Int: + return {mIntValueTable.GetIndex(aEntry.mValue.mIndex)}; + case PrefType::String: + return {mStringValueTable.GetIndex(aEntry.mValue.mIndex)}; + default: + MOZ_ASSERT_UNREACHABLE("Invalid pref type"); + return {false, false}; + } + } + + UniqueStringTableBuilder mKeyTable{kExpectedPrefCount}; + StringTableBuilder mValueStringTable; + + ValueTableBuilder mIntValueTable; + ValueTableBuilder, StringTableEntry> + mStringValueTable; + + nsTArray mEntries{kExpectedPrefCount}; +}; + +} // namespace mozilla + +#endif // dom_ipc_SharedPrefMap_h diff --git a/modules/libpref/StaticPrefsBase.h b/modules/libpref/StaticPrefsBase.h new file mode 100644 index 0000000000..597a5beda2 --- /dev/null +++ b/modules/libpref/StaticPrefsBase.h @@ -0,0 +1,93 @@ +/* -*- 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/. */ + +#ifndef mozilla_StaticPrefsBase_h +#define mozilla_StaticPrefsBase_h + +#include + +#include "mozilla/Atomics.h" +#include "mozilla/DataMutex.h" +#include "nsString.h" + +namespace mozilla { + +class SharedPrefMapBuilder; + +// These typedefs are for use within init/StaticPrefList*.h. + +typedef const char* String; + +using DataMutexString = StaticDataMutex; + +template +struct IsString : std::false_type {}; + +template <> +struct IsString : std::true_type {}; + +template <> +struct IsString : std::true_type {}; + +typedef Atomic RelaxedAtomicBool; +typedef Atomic ReleaseAcquireAtomicBool; +typedef Atomic SequentiallyConsistentAtomicBool; + +typedef Atomic RelaxedAtomicInt32; +typedef Atomic ReleaseAcquireAtomicInt32; +typedef Atomic + SequentiallyConsistentAtomicInt32; + +typedef Atomic RelaxedAtomicUint32; +typedef Atomic ReleaseAcquireAtomicUint32; +typedef Atomic + SequentiallyConsistentAtomicUint32; + +// XXX: Atomic currently doesn't work (see bug 1552086). Once it's +// supported we will be able to use Atomic here. +typedef std::atomic AtomicFloat; + +template +struct StripAtomicImpl { + typedef T Type; +}; + +template +struct StripAtomicImpl> { + typedef T Type; +}; + +template +struct StripAtomicImpl> { + typedef T Type; +}; + +template <> +struct StripAtomicImpl { + typedef nsCString Type; +}; + +template +using StripAtomic = typename StripAtomicImpl::Type; + +template +struct IsAtomic : std::false_type {}; + +template +struct IsAtomic> : std::true_type {}; + +template +struct IsAtomic> : std::true_type {}; + +namespace StaticPrefs { + +void MaybeInitOncePrefs(); + +} // namespace StaticPrefs + +} // namespace mozilla + +#endif // mozilla_StaticPrefsBase_h diff --git a/modules/libpref/components.conf b/modules/libpref/components.conf new file mode 100644 index 0000000000..20f4e61d2f --- /dev/null +++ b/modules/libpref/components.conf @@ -0,0 +1,31 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Headers = [ + 'mozilla/Preferences.h', +] + +UnloadFunc = 'mozilla::UnloadPrefsModule' + +Classes = [ + { + 'name': 'Preferences', + 'js_name': 'prefs', + 'cid': '{91ca2441-050f-4f7c-9df8-75b40ea40156}', + 'contract_ids': ['@mozilla.org/preferences-service;1'], + 'interfaces': ['nsIPrefService', 'nsIPrefBranch'], + 'singleton': True, + 'type': 'mozilla::Preferences', + 'headers': ['mozilla/Preferences.h'], + 'constructor': 'mozilla::Preferences::GetInstanceForService', + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS, + }, + { + 'cid': '{064d9cee-1dd2-11b2-83e3-d25ab0193c26}', + 'contract_ids': ['@mozilla.org/pref-localizedstring;1'], + 'type': 'nsPrefLocalizedString', + }, +] diff --git a/modules/libpref/docs/index.md b/modules/libpref/docs/index.md new file mode 100644 index 0000000000..30bf66d278 --- /dev/null +++ b/modules/libpref/docs/index.md @@ -0,0 +1,466 @@ +# libpref +libpref is a generic key/value store that is used to implement *prefs*, a term +that encompasses a variety of things. + +- Feature enable/disable flags (e.g. `xpinstall.signatures.required`). +- User preferences (e.g. things set from `about:preferences`) +- Internal application parameters (e.g. + `javascript.options.mem.nursery.max_kb`). +- Testing and debugging flags (e.g. `network.dns.native-is-localhost`). +- Things that might need locking in an enterprise installation. +- Application data (e.g. + `browser.onboarding.tour.onboarding-tour-addons.completed`, + `services.sync.clients.lastSync`). +- A cheap and dirty form of IPC(!) (some devtools prefs). + +Some of these (particularly the last two) are not an ideal use of libpref. + +The C++ API is in the `Preferences` class. The XPIDL API is in the +`nsIPrefService` and `nsIPrefBranch` interfaces. + +## High-level design + +### Keys +Keys (a.k.a. *pref names*) are 8-bit strings, and ASCII in practice. The +convention is to use a dotted segmented form, e.g. `foo.bar.baz`, but the +segments have no built-in meaning. + +Naming is inconsistent, e.g. segments have various forms: `foo_bar`, +`foo-bar`, `fooBar`, etc. Pref names for feature flags are likewise +inconsistent: `foo.enabled`, `foo.enable`, `foo.disable`, `fooEnabled`, +`enable-foo`, `foo.enabled.bar`, etc. + +The grouping of prefs into families, via pref name segments, is ad hoc. Some of +these families are closely related, e.g. there are many font prefs that are +present for every language script. + +Some prefs only make sense when considered in combination with other prefs. + +Many pref names are known at compile time, but some are computed at runtime. + +### Basic values +The basic types of pref values are bools, 32-bit ints, and 8-bit C strings. + +Strings are used to encode many types of data: identifiers, alphanumeric IDs, +UUIDs, SHA1 hashes, CSS color hex values, large integers that don't fit into +32-bit ints (e.g. timestamps), directory names, URLs, comma-separated lists, +space-separated lists, JSON blobs, etc. There is a 1 MiB length limit on string +values; longer strings are rejected outright. + +**Problem:** The C string encoding is unclear; some API functions deal with +unrestricted 8-bit strings (i.e. Latin1), but some require UTF-8. + +There is some API support for faking floats, by converting them from/to strings when getting/setting. + +**Problem:** confusion between ints and floats can lead to bugs. + +Each pref consists of a default value and/or a user value. Default values can +be initialized from file at startup, and can be added and modified at runtime +via the API. User values can be initialized from file at startup, and can be +added, modified and removed at runtime via the API and `about:config`. + +If both values are present the user value takes precedence for most operations, +though there are operations that specifically work on the default value. + +If a user value is set to the same value as the default value, the user value +is removed, unless the pref is marked as *sticky* at startup. + +**Problem:** it would be better to have a clear notion of "reset to default", +at least for prefs that have a default value. + +Prefs can be locked. This prevents them from being given a user value, or +hides the existing user value if there is one. + +### Complex values +There is API support for some complex values. + +`nsIFile` objects are handled by storing the filename as a string, similar to +how floats are faked by storing them as strings. + +`nsIPrefLocalizedString` objects are ones for which the default value +specifies a properties file that contains an entry whose name matches the +prefname. When gotten, the value from that entry is put into the user value. +When set, the given value just overwrites the user value, like a string pref. + +**Problem:** this is weird and unlike all the other pref types. + +`nsIRelativeFilePref` objects are only used in comm-central. + +### Pref Branches +XPIDL-based access to prefs is via `nsIPrefBranch`/`nsPrefBranch`, which +lets you specify a branch of the pref tree (e.g. `font.`) and pref names work +relative to that point. + +This API can be used from C++, but for C++ code there is also direct access +through the `Preferences` class, which uses absolute pref names. + +### Threads +For the most part, all the basic API functions only work on the main thread. +However, there are two exceptions to this. + +The narrow exception is that the Servo traversal thread is allowed to get pref +values. This only occurs when the main thread is paused, which makes it safe. +(Note: [bug 1474789](https://bugzilla.mozilla.org/show_bug.cgi?id=1474789) +indicates that this may not be true.) + +The broad exception is that static prefs can have a cached copy of a pref value +that can be accessed from other threads. See below. + +### Notifications +There is a notification API for being told when a pref's value changes. C++ +code can register a callback function and JS code can register an observer (via +`nsIObserver`, which requires XPCOM). In both cases, the registered entity +will be notified when the value of the named pref value changes, or when the +value of any pref matching a given prefix changes. E.g. all font pref changes +can be observed by adding a `font.` prefix-matching observer. + +See also the section on static prefs below. + +### Static prefs +There is a special kind of pref called a static pref. Static prefs are defined +in `StaticPrefList.yaml`. See that file for more documentation. + +If a static pref is defined in both `StaticPrefList.yaml` and a pref data +file, the latter definition will take precedence. A pref shouldn't appear in +both `StaticPrefList.yaml` and `all.js`, but it may make sense for a pref +to appear in both `StaticPrefList.yaml` and an app-specific pref data file +such as `firefox.js`. + +Each static pref has a *mirror* kind. + +* `always`: A C++ *mirror variable* is associated with the pref. The variable + is always kept in sync with the pref value. This kind is common. +* `once`: A C++ mirror variable is associated with the pref. The variable is + synced once with the pref's value at startup, and then does not change. This + kind is less common, and mostly used for graphics prefs. +* `never`: No C++ mirror variable is associated with the pref. This is much + like a normal pref. + +An `always` or `once` static pref can only be used for prefs with +bool/int/float values, not strings or complex values. + +Each mirror variable is read-only, accessible via a getter function. The base +name of the getter function is the same as the pref's name, but with '.' or '-' +converted to '_'. Sometimes a suffix is added, e.g. _AtStartup for the mirror +once kind. + +Mirror variables have two benefits. First, they allow C++ and Rust code to get +the pref value directly from the variable instead of requiring a slow hash +table lookup, which is important for prefs that are consulted frequently. +Second, they allow C++ and Rust code to get the pref value off the main thread. +The mirror variable must have an atomic type if it is read off the main thread, +and assertions ensure this. + +Note that mirror variables could be implemented via vanilla callbacks without +API support, except for one detail: libpref gives their callbacks higher +priority than normal callbacks, ensuring that any static pref will be +up-to-date if read by a normal callback. + +**Problem:** It is not clear what should happen to a static pref's mirror +variable if the pref is deleted? Currently there is a missing +`NotifyCallbacks()` call so the mirror variable keeps its value from before +the deletion. The cleanest solution is probably to disallow static prefs from +being deleted. + +### Sanitized Prefs +We restrict certain prefs from entering web content subprocesses. In these +processes, a preference may be marked as 'Sanitized' to indicate that it may +or may not have a user value, but that value is not present in this process. +In the parent process no pref is marked as Sanitized. + +Pref Sanitization is used for two purposes: + 1. To protect private user data that may be stored in preferences from a + Spectre adversary. + 2. To reduce IPC use and thread wake-ups for commonly modified preferences. + +A pref is sanitized from entering the web content process if it matches a +denylist _or_ it is a dynamically-named string preference (that is not +exempted via an allowlist), See `ShouldSanitizePreference` in +`Preferences.cpp`. + +### Loading and Saving +Default pref values are initialized from various pref data files. Notable ones +include: + +- `modules/libpref/init/all.js`, used by all products; +- `browser/app/profile/firefox.js`, used by Firefox desktop; +- `mobile/android/app/geckoview-prefs.js`, used by GeckoView; +- `mail/app/profile/all-thunderbird.js`, used by Thunderbird (in comm-central); +- `suite/browser/browser-prefs.js`, used by SeaMonkey (in comm-central). + +In release builds these are all put into `omni.ja`. + +User pref values are initialized from `prefs.js` and (if present) +`user.js`, in the user's profile. This only happens once, in the parent +process. Note that `prefs.js` is managed by Firefox, and regularly +overwritten. `user.js` is created and managed by the user, and Firefox only +reads it. + +These files are not JavaScript; the `.js` suffix is present for historical +reasons. They are read by a custom parser within libpref. + +User pref file syntax is slightly more restrictive than default pref file +syntax. In user pref files `user_pref` definitions are allowed but `pref` and +`sticky_pref` definitions are not, and attributes (such as `locked`) are not +allowed. + +**Problem:** geckodriver has a separate prefs parser in the mozprofile crate. + +**Problem:** there is no versioning of these files, for either the syntax or +the data. This makes changing the file format difficult. + +There are API functions to save modified prefs, either synchronously or +asynchronously (via an off-main-thread runnable), either to the default file +(`prefs.js`) or to a named file. When saving to the default file, no action +will take place if no prefs have been modified. + +Also, whenever a pref is modified, we wait 500ms and then automatically do an +off-main-thread save to `prefs.js`. This provides an approximation of +[durability](https://en.wikipedia.org/wiki/ACID#Durability), but it is still +possible for something to go wrong (e.g. a parent process crash) and end up +with recently changed prefs not being saved. (If such a thing happens, it +compromises [atomicity](https://en.wikipedia.org/wiki/ACID#Atomicity), i.e. a +sequence of multiple related pref changes might only get partially written.) + +Only prefs whose values have changed from the default are saved to `prefs.js.` + +**Problem:** Each time prefs are saved, the entire file is overwritten -- 10s +or even 100s of KiBs -- even if only a single value has changed. This happens +at least every 5 minutes, due to sync. Furthermore, various prefs are changed +during and shortly after startup, which can result in 10s of MiBs of disk +activity. + +### about:support +about:support contains an "Important Modified Preferences" table. It contains +all prefs that (a) have had their value changed from the default, and (b) whose +prefix match a allowlist in `Troubleshoot.sys.mjs`. The allowlist matching is to +avoid exposing pref values that might be privacy-sensitive. + +**Problem:** The allowlist of prefixes is specified separately from the prefs +themselves. Having an attribute on a pref definition would be better. + +### Sync +On desktop, a pref is synced onto a device via Sync if there is an +accompanying `services.sync.prefs.sync.`-prefixed pref. I.e. the pref +`foo.bar` is synced if the pref `services.sync.prefs.sync.foo.bar` exists +and is true. + +Previously, one could push prefs onto a device even if a local +`services.sync.prefs.sync.`-prefixed pref was not present; however this +behavior changed in [bug 1538015](https://bugzilla.mozilla.org/show_bug.cgi?id=1538015) +to require the local prefixed pref to be present. The old (insecure) behavior +can be re-enabled by setting a single pref `services.sync.prefs.dangerously_allow_arbitrary` +to true on the target browser - subsequently any pref can be pushed there by +creating a *remote* `services.sync.prefs.sync.`-prefixed pref. + +In practice, only a small subset of prefs (about 70) have a `services.sync.prefs.sync.`-prefixed +pref by default. + +**Problem:** This is gross. An attribute on the pref definition would be +better, but it might be hard to change that at this point. + +The number of synced prefs is small because prefs are synced across versions; +any pref whose meaning might change shouldn't be synced. Also, we don't sync +prefs that may differ across different devices (such as a desktop machine +vs. a notebook). + +Prefs are not synced on mobile. + +### Rust +Static prefs mirror variables can be accessed from Rust code via the +`static_prefs::pref!` macro, for prefs which opt into this using +`rust: true`. Other prefs currently cannot be accessed. Parts +of libpref's C++ API could be made accessible to Rust code fairly +straightforwardly via C bindings, either hand-made or generated. + +### Cost of a pref +The cost of a single pref is low, but the cost of several thousand prefs is +reasonably high, and includes the following. + +- Parsing and initializing at startup. +- IPC costs at startup and on pref value changes. +- Disk writing costs of pref value changes, especially during startup. +- Memory usage for storing the prefs, callbacks and observers, and C++ mirror + variables. +- Complexity: most pref combinations are untested. Some can be set to a bogus + value by a curious user, which can have [serious effects](https://rejzor.wordpress.com/2015/06/14/improve-firefox-html5-video-playback-performance/) + (read the comments). Prefs can also have bugs. Real-life examples include + mistyped prefnames, `all.js` entries with incorrect types (e.g. confusing + int vs. float), both of which mean changing the pref value via about:config + or the API would have no effect (see [bug 1414150](https://bugzilla.mozilla.org/show_bug.cgi?id=1414150) for examples of + both). +- Sync cost, for synced prefs. + +### Guidelines +We have far too many prefs. This is at least partly because we have had, for a +long time, a culture of "when in doubt, add a pref". Also, we don't have any +system — either technical or cultural — for removing unnecessary prefs. See +[bug 90440] (https://bugzilla.mozilla.org/show_bug.cgi?id=90440) for a pref +that was unused for 17 years. + +In short, prefs are Firefox's equivalent of the Windows Registry: a dumping +ground for anything and everything. We should have guidelines for when to add a +pref. + +Here are some good reasons to add a pref. + +- *A user may genuinely want to change it.* E.g. it controls a feature that is + adjustable in about:preferences. +- *To enable/disable new features.* Once a feature is mature, consider removing + the pref. A pref expiry mechanism would help with this. +- *For certain testing/debugging flags.* Ideally, these would not be visible in + about:config. + +Here are some less good reasons to add a pref. + +- *I'm not confident about this numeric parameter (cache size, timeout, etc.)* + Get confident! In practice, few if any users will change it. Adding a pref + doesn't absolve you of the responsibility of finding a good default. Then + make it a code constant. +- *I need to experiment with different parameters during development.* This is + reasonable, but consider removing the pref before landing or once the feature + has matured. An expiry mechanism would help with this. +- *I sometimes fiddle with this value for debugging or testing.* + Is it worth exposing it to the whole world to save yourself a recompile every + once in a while? Consider making it a code constant. +- *Different values are needed on different platforms.* This can be done in + other ways, e.g. `#ifdef` in C++ code. + +These guidelines do not consider application data prefs (i.e. ones that +typically don't have a default value). They are quite different from the other +kinds. They arguably shouldn't prefs at all, and should be stored via some +other mechanism. + +## Low-level details +The key idea is that the prefs database consists of two pieces. The first is an +initial snapshot of pref values that is created when the first child process is +created. This snapshot is stored in immutable, shared memory, and shared by all +processes. + +Pref value changes that occur after this point are stored in a second hash +table. Each process has its own copy of this hash table. When pref values +change in the parent process, it performs IPC to inform child processes about +the changes, so they can update their copy. + +The motivation for this design is memory usage. It's not tenable for every +child process to have a full copy of the prefs database. + +Not all child processes need access to prefs. Those that do include web content +processes, the GPU process, and the RDD process. + +### Parent process startup +The parent process initially has only a hash table. + +Early in startup, the parent process loads all of the static prefs and default +prefs (mainly from `omni.ja`) into that hash table. The parent process also +registers C++ mirror variables for static prefs, initializes them, and +registers callbacks so they will be updated appropriately for all subsequent +updates. + +Slightly later in startup, the parent process loads all user prefs files, +mainly from the profile directory. + +When the first getter for a `once` static pref is called, all the `once` +static prefs have their mirror variables set and special frozen prefs are put +into the hash table. These frozen prefs are copies of the `once` prefs that +are given `$$$` prefixes and suffixes on their names. They are also marked +specially so they are ignored for all cases except when starting a new child +process. They exist so that all child processes can be given the same `once` +values as the parent process. + +### Child process startup (parent side) +When the first child process is created, the parent process serializes most of +its hash table into a shared, immutable snapshot. This snapshot is stored in a +shared memory region managed by a `SharedPrefMap` instance. + +Sanitized preferences (matching _either_ the denylist of the dynamically named +heuristic) are not included in the shared memory region. After building the +shared memory region, the parent process clears the hash table and then +re-enters sanitized prefs into it. Besides the sanitized prefs, the hash table +is subsequently used only to store changed pref values. + +When any child process is created, the parent process serializes all pref +values present in the hash table (i.e. those that have changed since the +snapshot was made) _except sanitized prefs__ and stores them in a second, +short-lived shared memory region. This represents the set of changes the child +process needs to apply on top of the snapshot, and allows it to build a hash +table which should exactly match the parent's, modulo the sanitized prefs. + +The parent process passes two file descriptors to the child process, one for +each region of memory. The snapshot is the same for all child processes. + +### Child process startup (child side) +Early in child process startup, the prefs service maps in and deserializes both +shared memory regions sent from the parent process, but defers further +initialization until requested by XPCOM initialization. Once that happens, +mirror variables are initialized for static prefs, but no default values are +set in the hash table, and no prefs files are loaded. + +Once the mirror variables have been initialized, we dispatch pref change +callbacks for any prefs in the shared snapshot which have user values or are +locked. This causes the mirror variables to be updated. + +After that, the changed pref values received from the parent process (via +`changedPrefsFd`) are added to the prefs database. Their values override the +values in the snapshot, and pref change callbacks are dispatched for them as +appropriate. `once` mirror variable are initialized from the special frozen +pref values. + +### Pref lookups +Each prefs database has both a hash table and a shared memory snapshot. A given +pref may have an entry in either or both of these. If a pref exists in both, +the hash table entry takes precedence. + +For pref lookups, the hash table is checked first, followed by the shared +snapshot. The entry in the hash table may have the type `None`, in which case +the pref is treated as if it did not exist. The entry in the static snapshot +never has the type `None`. + +For pref enumeration, both maps are enumerated, starting with the hash table. +While iterating over the hash table, any entry with the type `None` is +skipped. While iterating over the shared snapshot, any entry which also exists +in the hash table is skipped. The combined result of the two iterations +represents the full contents of the prefs database. + +### Pref changes +Pref changes can only be initiated in the parent process. All API methods that +modify prefs fail noisily (with `NS_ERROR`) if run outside the parent +process. + +Pref changes that happen before the initial snapshot have been made are simple, +and take place in the hash table. There is no shared snapshot to update, and no +child processes to synchronize with. + +Once a snapshot has been created, any changes need to happen in the hash table. + +If an entry for a changed pref already exists in the hash table, that entry can +be updated directly. Likewise for prefs that do not exist in either the hash +table or the shared snapshot: a new hash table entry can be created. + +More care is needed when a changed pref exists in the snapshot but not in the +hash table. In that case, we create a hash table entry with the same values as +the snapshot entry, and then update it... but *only* if the changes will have +an effect. If a caller attempts to set a pref to its existing value, we do not +want to waste memory creating an unnecessary hash table entry. + +Content processes must be told about any visible pref value changes. (A change +to a default value that is hidden by a user value is unimportant.) When this +happens, `ContentParent` detects the change (via an observer). Sanitized prefs +do not produce an update; and for string prefs it also checks the value(s) +don't exceed 4 KiB. If the checks pass, it sends an IPC message +(`PreferenceUpdate`) to the child process, and the child process updates the +pref (default and user value) accordingly. + +**Problem:** The denylist of prefixes is specified separately from the prefs +themselves. Having an attribute on a pref definition would be better. + +**Problem:** The 4 KiB limit can lead to inconsistencies between the parent +process and child processes. E.g. see +[bug 1303051](https://bugzilla.mozilla.org/show_bug.cgi?id=1303051#c28). + +### Pref deletions +Pref deletion is more complicated. If a pref to be deleted exists only in the +hash table of the parent process, its entry can simply be removed. If it exists +in the shared snapshot, however, its hash table entry needs to be kept (or +created), and its type changed to `None`. The presence of this entry masks +the snapshot entry, causing it to be ignored by pref enumerators. diff --git a/modules/libpref/greprefs.js b/modules/libpref/greprefs.js new file mode 100644 index 0000000000..c18e30b24e --- /dev/null +++ b/modules/libpref/greprefs.js @@ -0,0 +1,5 @@ +#filter dumbComments emptyLines + +// Do not add anything else to this file. Additions should go in all.js. + +#include init/all.js diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml new file mode 100644 index 0000000000..82a2582c8a --- /dev/null +++ b/modules/libpref/init/StaticPrefList.yaml @@ -0,0 +1,16000 @@ +# 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/. */ + +# This file defines static prefs, i.e. those that are defined at startup and +# used entirely or mostly from C++ and/or Rust code. +# +# The file is separated into sections, where each section contains a group of +# prefs that all share the same first segment of their name -- all the "gfx.*" +# prefs are together, all the "network.*" prefs are together, etc. Sections +# must be kept in alphabetical order, but prefs within sections need not be. +# +# Basics +# ------ +# Any pref defined in one of the files included here should *not* be defined +# in a data file such as all.js; that would just be useless duplication. +# +# (Except under unusual circumstances where the value defined here must be +# overridden, e.g. for some Thunderbird prefs. In those cases the default +# value from the data file will override the static default value defined +# here.) +# +# Please follow the existing prefs naming convention when considering adding a +# new pref, and don't create a new pref group unless it's appropriate and there +# are likely to be multiple prefs within that group. (If you do, you'll need to +# update the `pref_groups` variable in modules/libpref/moz.build.) +# +# Definitions +# ----------- +# A pref definition looks like this: +# +# - name: # mandatory +# type: # mandatory +# value: # mandatory +# mirror: # mandatory +# do_not_use_directly: # optional +# include: # optional +# rust: # optional +# set_spidermonkey_pref: # optional +# +# - `name` is the name of the pref, without double-quotes, as it appears +# in about:config. It is used in most libpref API functions (from both C++ +# and JS code). +# +# - `type` is one of `bool`, `int32_t`, `uint32_t`, `float`, an atomic version +# of one of those, `String` or `DataMutexString`. Note that float prefs are +# stored internally as strings. The C++ preprocessor doesn't like template +# syntax in a macro argument, so use the typedefs defined in +# StaticPrefsBase.h; for example, use `RelaxedAtomicBool` instead of +# `Atomic`. +# +# - `value` is the default value. Its type should be appropriate for +# , otherwise the generated code will fail to compile. A complex +# C++ numeric expressions like `60 * 60` (which the YAML parser cannot treat +# as an integer or float) is treated as a string and passed through without +# change, which is useful. +# +# - `mirror` indicates how the pref value is mirrored into a C++ variable. +# +# * `never`: There is no C++ mirror variable. The pref value can only be +# accessed via the standard libpref API functions. +# +# * `once`: The pref value is mirrored into a variable at startup; the +# mirror variable is left unchanged after that. (The exact point at which +# all `once` mirror variables are set is when the first `once` mirror +# variable is accessed, via its getter function.) This is mostly useful for +# graphics prefs where we often don't want a new pref value to apply until +# restart. Otherwise, this update policy is best avoided because its +# behaviour can cause confusion and bugs. +# +# * `always`: The mirror variable is always kept in sync with the pref value. +# This is the most common choice. +# +# When a mirror variable is present, a getter will be created that can access +# it. Using the getter function to read the pref's value has the two +# following advantages over the normal API functions. +# +# * A direct variable access is faster than a hash table lookup. +# +# * A mirror variable can be accessed off the main thread. If a pref *is* +# accessed off the main thread, it should have an atomic type. Assertions +# enforce this. +# +# Note that Rust code must access the mirror variable directly, rather than +# via the getter function. +# +# - `do_not_use_directly` indicates if `_DoNotUseDirectly` should be appended to +# the name of the getter function. This is simply a naming convention +# indicating that there is some other wrapper getter function that should be +# used in preference to the normal static pref getter. Defaults to `false` if +# not present. Cannot be used with a `never` mirror value, because there is +# no getter function in that case. +# +# - `include` names a header file that must be included for the pref value to +# compile correctly, e.g. because it refers to a code constant. System +# headers should be surrounded with angle brackets, e.g. ``. +# +# - `rust` indicates if the mirror variable is used by Rust code. If so, it +# will be usable via the `static_prefs::pref!` macro, e.g. +# `static_prefs::pref!("layout.css.cross-fade.enabled")`. +# +# - `set_spidermonkey_pref` indicates whether SpiderMonkey boilerplate code +# should be generated for this pref. If this is set to 'startup', the +# pref on the SpiderMonkey side is only set during process startup. If set to +# 'always', the SpiderMonkey pref value is also updated when this pref is +# changed at runtime. +# This option is only valid for javascript.options.* prefs. +# +# The getter function's base name is the same as the pref's name, but with +# '.' or '-' chars converted to '_', to make a valid identifier. For example, +# the getter for `foo.bar_baz` is `foo_bar_baz()`. This is ugly but clear, +# and you can search for both the pref name and the getter using the regexp +# /foo.bar.baz/. Suffixes are added as follows: +# +# - If the `mirror` value is `once`, `_AtStartup` is appended, to indicate the +# value was obtained at startup. +# +# - If the `do_not_use_directly` value is true, `_DoNotUseDirectly` is +# appended. +# +# Preprocessor +# ------------ +# Note finally that this file is preprocessed by preprocessor.py, not the C++ +# preprocessor. As a result, the following things may be surprising. +# +# - YAML comments start with a '#', so putting a comment on the same line as a +# preprocessor directive is dubious. E.g. avoid lines like `#define X 3 # +# three` because the ` # three` will be part of `X`. +# +# - '@' use is required for substitutions to occur. E.g. with `#define FOO 1`, +# `FOO` won't be replaced with `1` unless it has '@' chars around it. +# +# - Spaces aren't permitted between the leading '#' and the name of a +# directive, e.g. `#ifdef XYZ` works but `# ifdef XYZ` does not. +# +# Please indent all prefs defined within #ifdef/#ifndef conditions. This +# improves readability, particular for conditional blocks that exceed a single +# screen. But note that the leading '-' in a definition must remain in the +# first column for it to be valid YAML. + +#ifdef RELEASE_OR_BETA +#define IS_NOT_RELEASE_OR_BETA false +#else +#define IS_NOT_RELEASE_OR_BETA true +#endif + +#ifdef NIGHTLY_BUILD +#define IS_NIGHTLY_BUILD true +#define IS_NOT_NIGHTLY_BUILD false +#else +#define IS_NIGHTLY_BUILD false +#define IS_NOT_NIGHTLY_BUILD true +#endif + +#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) +#define IS_NIGHTLY_OR_DEV_EDITION true +#else +#define IS_NIGHTLY_OR_DEV_EDITION false +#endif + +#ifdef MOZILLA_OFFICIAL +#define IS_NOT_MOZILLA_OFFICIAL false +#else +#define IS_NOT_MOZILLA_OFFICIAL true +#endif + +#ifdef EARLY_BETA_OR_EARLIER +#define IS_EARLY_BETA_OR_EARLIER true +#define IS_NOT_EARLY_BETA_OR_EARLIER false +#else +#define IS_EARLY_BETA_OR_EARLIER false +#define IS_NOT_EARLY_BETA_OR_EARLIER true +#endif + +#ifdef ANDROID +#define IS_ANDROID true +#define IS_NOT_ANDROID false +#else +#define IS_ANDROID false +#define IS_NOT_ANDROID true +#endif + +#ifdef XP_WIN +#define IS_XP_WIN true +#define IS_NOT_XP_WIN false +#else +#define IS_XP_WIN false +#define IS_NOT_XP_WIN true +#endif + +#ifdef XP_MACOSX +#define IS_XP_MACOSX true +#define IS_NOT_XP_MACOSX false +#else +#define IS_XP_MACOSX false +#define IS_NOT_XP_MACOSX true +#endif + +#--------------------------------------------------------------------------- +# Prefs starting with "accessibility." +#--------------------------------------------------------------------------- + +- name: accessibility.accesskeycausesactivation + type: bool + value: true + mirror: always + +- name: accessibility.monoaudio.enable + type: RelaxedAtomicBool + value: false + mirror: always + +- name: accessibility.browsewithcaret + type: RelaxedAtomicBool + value: false + mirror: always + +- name: accessibility.AOM.enabled + type: bool + value: false + mirror: always + +- name: accessibility.ARIAReflection.enabled + type: bool + value: true + mirror: always + +# Whether form controls and images should be focusable with mouse, in content +# documents. +# +# This matches historical macOS / Safari behavior. +# +# * 0: never +# * 1: always +# * 2: on content documents +- name: accessibility.mouse_focuses_formcontrol + type: int32_t +#ifdef XP_MACOSX + value: 2 +#else + value: 1 +#endif + mirror: always + +# Whether to avoid accessibility activation on Windows shortly after clipboard +# copy. +# +# Possible values are: +# * 0: never +# * 1: always +# * 2 (or others): when needed +- name: accessibility.windows.suppress-after-clipboard-copy + type: uint32_t + value: 2 + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "alerts." +#--------------------------------------------------------------------------- + +# Whether to use platform-specific backends for showing desktop notifications. +# If no such backend is available, or if the pref is false, then XUL +# notifications are used. +- name: alerts.useSystemBackend + type: bool + value: true + mirror: always + +#if defined(XP_WIN) + # On Windows, a COM Surrogate notification server receives notification events + # and can relaunch the application after it has been closed. +- name: alerts.useSystemBackend.windows.notificationserver.enabled + type: bool + value: true + mirror: never +#endif + +#ifdef ANDROID + #--------------------------------------------------------------------------- + # Prefs starting with "android." + #--------------------------------------------------------------------------- + + # On Android, we want an opaque background to be visible under the page, + # so layout should not force a default background. +- name: android.widget_paints_background + type: RelaxedAtomicBool + value: true + mirror: always + +- name: android.touch_resampling.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +#endif + +#--------------------------------------------------------------------------- +# Prefs starting with "apz." +# The apz prefs are explained in AsyncPanZoomController.cpp +#--------------------------------------------------------------------------- + +# amount we zoom in for a double tap gesture if we couldn't find any content +# based rect to zoom to +- name: apz.doubletapzoom.defaultzoomin + type: AtomicFloat + value: 1.2f + mirror: always + +- name: apz.scrollbarbuttonrepeat.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# After a user has executed a pan gesture, we may receive momentum phase pan +# gestures from the OS. This specifies how long we should wait following the +# pan end gesture for possible momentum phase pan gestures before sending the +# TransformEnd notification. +- name: apz.scrollend-event.content.delay_ms + type: RelaxedAtomicInt32 + value: 100 + mirror: always + +- name: apz.wr.activate_all_scroll_frames + type: RelaxedAtomicBool + value: false + mirror: always + +- name: apz.wr.activate_all_scroll_frames_when_fission + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.prefer_jank_minimal_displayports + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.allow_double_tap_zooming + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.mac.enable_double_tap_zoom_touchpad_gesture + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.allow_immediate_handoff + type: RelaxedAtomicBool + value: false + mirror: always + +- name: apz.allow_zooming + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.max_zoom + type: AtomicFloat + value: 10.0f + mirror: always + +- name: apz.min_zoom + type: AtomicFloat + value: 0.25f + mirror: always + +- name: apz.allow_zooming_out + type: RelaxedAtomicBool + value: false + mirror: always + +- name: apz.android.chrome_fling_physics.friction + type: AtomicFloat + value: 0.015f + mirror: always + +- name: apz.android.chrome_fling_physics.inflexion + type: AtomicFloat + value: 0.35f + mirror: always + +- name: apz.android.chrome_fling_physics.stop_threshold + type: AtomicFloat + value: 0.1f + mirror: always + +- name: apz.autoscroll.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.axis_lock.breakout_angle + type: AtomicFloat + value: float(M_PI / 8.0) # 22.5 degrees + mirror: always + include: + +- name: apz.axis_lock.breakout_threshold + type: AtomicFloat + value: 1.0f / 32.0f + mirror: always + +- name: apz.axis_lock.direct_pan_angle + type: AtomicFloat + value: float(M_PI / 3.0) # 60 degrees + mirror: always + include: + +- name: apz.axis_lock.lock_angle + type: AtomicFloat + value: float(M_PI / 6.0) # 30 degrees + mirror: always + include: + +# Whether to lock touch scrolling to one axis at a time. When a new +# axis lock mode is added, the APZCAxisLockCompatTester GTest shoud +# be updated to include the lock mode value. +# 0 = FREE (No locking at all) +# 1 = STANDARD (Once locked, remain locked until scrolling ends) +# 2 = STICKY (Allow lock to be broken, with hysteresis) +# 3 = DOMINANT_AXIS (Only allow movement on one axis at a time, only +# applies to touchpad scrolling) +- name: apz.axis_lock.mode + type: RelaxedAtomicInt32 +#if defined(XP_MACOSX) + value: 3 +#else + value: 2 +#endif + mirror: always + +- name: apz.content_response_timeout + type: RelaxedAtomicInt32 + value: 400 + mirror: always + +- name: apz.danger_zone_x + type: RelaxedAtomicInt32 + value: 50 + mirror: always + +- name: apz.danger_zone_y + type: RelaxedAtomicInt32 + value: 100 + mirror: always + +- name: apz.disable_for_scroll_linked_effects + type: RelaxedAtomicBool + value: false + mirror: always + +- name: apz.displayport_expiry_ms + type: RelaxedAtomicUint32 + value: 15000 + mirror: always + +- name: apz.drag.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.drag.initial.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.drag.touch.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.enlarge_displayport_when_clipped + type: RelaxedAtomicBool + value: @IS_ANDROID@ + mirror: always + +# Test only. +- name: apz.fixed-margin-override.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Test only. +- name: apz.fixed-margin-override.bottom + type: RelaxedAtomicInt32 + value: 0 + mirror: always + +# Test only. +- name: apz.fixed-margin-override.top + type: RelaxedAtomicInt32 + value: 0 + mirror: always + +- name: apz.fling_accel_base_mult + type: AtomicFloat + value: 1.0f + mirror: always + +- name: apz.fling_accel_supplemental_mult + type: AtomicFloat + value: 1.0f + mirror: always + +- name: apz.fling_accel_min_fling_velocity + type: AtomicFloat + value: 1.5f + mirror: always + +- name: apz.fling_accel_min_pan_velocity + type: AtomicFloat + value: 0.8f + mirror: always + +- name: apz.fling_accel_max_pause_interval_ms + type: RelaxedAtomicInt32 + value: 50 + mirror: always + +- name: apz.fling_curve_function_x1 + type: float + value: 0.0f + mirror: once + +- name: apz.fling_curve_function_x2 + type: float + value: 1.0f + mirror: once + +- name: apz.fling_curve_function_y1 + type: float + value: 0.0f + mirror: once + +- name: apz.fling_curve_function_y2 + type: float + value: 1.0f + mirror: once + +- name: apz.fling_curve_threshold_inches_per_ms + type: AtomicFloat + value: -1.0f + mirror: always + +- name: apz.fling_friction + type: AtomicFloat + value: 0.002f + mirror: always + +- name: apz.fling_min_velocity_threshold + type: AtomicFloat + value: 0.5f + mirror: always + +- name: apz.fling_stop_on_tap_threshold + type: AtomicFloat + value: 0.05f + mirror: always + +- name: apz.fling_stopped_threshold + type: AtomicFloat + value: 0.01f + mirror: always + +- name: apz.touch_acceleration_factor_x + type: float + value: 1.0f + mirror: always + +- name: apz.touch_acceleration_factor_y + type: float + value: 1.0f + mirror: always + +# new scrollbar code for desktop zooming +- name: apz.force_disable_desktop_zooming_scrollbars + type: RelaxedAtomicBool + value: false + mirror: always + +#ifdef MOZ_WIDGET_GTK +- name: apz.gtk.kinetic_scroll.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.gtk.pangesture.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Mode to use when receiving pan gesture input. +# +# * 0: Auto mode (uses the default behavior, subject to change). +# * 1: Page mode: Uses gtk deltas as a percentage of the page size to scroll. This mode matches: +# +# https://gitlab.gnome.org/GNOME/gtk/blob/c734c7e9188b56f56c3a504abee05fa40c5475ac/gtk/gtkrange.c#L3063-3074 +# +# * 2: Pixel mode: Uses gtk deltas as a fixed pixel multiplier. This mode matches e.g. GNOME web. +# +# https://webkit-search.igalia.com/webkit/rev/215039ef09d6bfd6e088175bfe30788d95b9705d/Source/WebKit/Shared/gtk/WebEventFactory.cpp#265-296 +# (multiplied then by pixelsPerLineStep which in GNOME-web is 40). +- name: apz.gtk.pangesture.delta_mode + type: uint32_t + value: 0 + mirror: always + +- name: apz.gtk.pangesture.page_delta_mode_multiplier + type: float + value: 1.0f + mirror: always + +- name: apz.gtk.pangesture.pixel_delta_mode_multiplier + type: float + value: 40.0f + mirror: always + +- name: apz.gtk.touchpad_pinch.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.gtk.touchpad_pinch.three_fingers.enabled + type: RelaxedAtomicBool + value: false + mirror: always +#endif + +- name: apz.keyboard.enabled + type: bool + value: @IS_NOT_ANDROID@ + mirror: once + +- name: apz.keyboard.passive-listeners + type: RelaxedAtomicBool + value: @IS_NOT_ANDROID@ + mirror: always + +- name: apz.max_tap_time + type: RelaxedAtomicInt32 + value: 300 + mirror: always + +- name: apz.max_velocity_inches_per_ms + type: AtomicFloat + value: -1.0f + mirror: always + +- name: apz.max_velocity_queue_size + type: uint32_t + value: 5 + mirror: once + +- name: apz.min_skate_speed + type: AtomicFloat + value: 1.0f + mirror: always + +- name: apz.minimap.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +- name: apz.mvm.force-enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.one_touch_pinch.enabled + type: RelaxedAtomicBool + value: @IS_ANDROID@ + mirror: always + +- name: apz.overscroll.enabled + type: RelaxedAtomicBool +#if defined(XP_MACOSX) || defined(XP_WIN) + value: true +#else + value: false +#endif + mirror: always + +# The "test async scroll offset" (used via reftest-async-scroll +# or nsIDOMWindowUtils.setAsyncScrollOffset()) can be used to +# trigger overscroll. Used for tests only. +- name: apz.overscroll.test_async_scroll_offset.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +- name: apz.overscroll.min_pan_distance_ratio + type: AtomicFloat + value: 1.0f + mirror: always + +- name: apz.overscroll.stop_distance_threshold + type: AtomicFloat + value: 5.0f + mirror: always + +- name: apz.overscroll.spring_stiffness + type: AtomicFloat + value: 200 + mirror: always + +- name: apz.overscroll.damping + type: AtomicFloat + value: 1.1 + mirror: always + +- name: apz.overscroll.max_velocity + type: AtomicFloat + value: 10 + mirror: always + +- name: apz.paint_skipping.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Fetch displayport updates early from the message queue. +- name: apz.pinch_lock.mode + type: RelaxedAtomicInt32 + value: 2 + mirror: always + +- name: apz.pinch_lock.scroll_lock_threshold + type: AtomicFloat + value: 1.0f / 16.0f # 1/16 inches + mirror: always + +- name: apz.pinch_lock.span_breakout_threshold + type: AtomicFloat + value: 1.0f / 32.0f # 1/32 inches + mirror: always + +- name: apz.pinch_lock.span_lock_threshold + type: AtomicFloat + value: 1.0f / 32.0f # 1/32 inches + mirror: always + +- name: apz.pinch_lock.buffer_max_age + type: int32_t + value: 80 # milliseconds + mirror: once + +- name: apz.popups.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Whether to print the APZC tree for debugging. +- name: apz.printtree + type: RelaxedAtomicBool + value: false + mirror: always + +- name: apz.record_checkerboarding + type: RelaxedAtomicBool + value: @IS_NIGHTLY_BUILD@ + mirror: always + +- name: apz.second_tap_tolerance + type: AtomicFloat + value: 0.5f + mirror: always + +# If this is true, APZ fully recalculates the scroll thumb size and +# position in the compositor. This leads to the size and position +# being more accurate in scenarios such as async zooming. +- name: apz.scrollthumb.recalc + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.test.fails_with_native_injection + type: RelaxedAtomicBool + value: false + mirror: always + +- name: apz.test.logging_enabled + type: RelaxedAtomicBool + value: false + mirror: always + +- name: apz.touch_move_tolerance + type: AtomicFloat + value: 0.1f + mirror: always + +- name: apz.touch_start_tolerance + type: AtomicFloat + value: 0.1f + mirror: always + +- name: apz.velocity_bias + type: AtomicFloat + value: 0.0f + mirror: always + +- name: apz.velocity_relevance_time_ms + type: RelaxedAtomicUint32 + value: 100 + mirror: always + +- name: apz.windows.force_disable_direct_manipulation + type: RelaxedAtomicBool + value: false + mirror: always + +- name: apz.windows.use_direct_manipulation + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.windows.check_for_pan_gesture_conversion + type: RelaxedAtomicBool + value: true + mirror: always + +- name: apz.x_skate_highmem_adjust + type: AtomicFloat + value: 0.0f + mirror: always + +- name: apz.x_skate_size_multiplier + type: AtomicFloat + value: 1.25f + mirror: always + +- name: apz.x_stationary_size_multiplier + type: AtomicFloat + value: 1.5f + mirror: always + +- name: apz.y_skate_highmem_adjust + type: AtomicFloat + value: 0.0f + mirror: always + +- name: apz.y_skate_size_multiplier + type: AtomicFloat +#if defined(MOZ_WIDGET_ANDROID) + value: 1.5f +#else + value: 3.5f +#endif + mirror: always + +- name: apz.y_stationary_size_multiplier + type: AtomicFloat +#if defined(MOZ_WIDGET_ANDROID) + value: 1.5f +#else + value: 3.5f +#endif + mirror: always + +- name: apz.zoom_animation_duration_ms + type: RelaxedAtomicInt32 +#if defined(MOZ_WIDGET_ANDROID) + value: 250 +#else + value: 350 +#endif + mirror: always + +- name: apz.scale_repaint_delay_ms + type: RelaxedAtomicInt32 + value: 500 + mirror: always + +# Whether to use rounded external scroll offsets. +- name: apz.rounded_external_scroll_offset + type: bool + value: false + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "beacon." +#--------------------------------------------------------------------------- + +# Is support for Navigator.sendBeacon enabled? +- name: beacon.enabled + type: bool + value: true + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "bidi." +#--------------------------------------------------------------------------- + +# Whether delete and backspace should immediately delete characters not +# visually adjacent to the caret, or adjust the visual position of the caret +# on the first keypress and delete the character on a second keypress +- name: bidi.edit.delete_immediately + type: bool + value: true + mirror: always + +# Bidi caret movement style: +# 0 = logical +# 1 = visual +# 2 = visual, but logical during selection +- name: bidi.edit.caret_movement_style + type: int32_t +#if !defined(XP_LINUX) && defined(NIGHTLY_BUILD) + value: 1 +#else + value: 2 # See Bug 1638240 +#endif + mirror: always + +# Bidi numeral style: +# 0 = nominalnumeralBidi * +# 1 = regularcontextnumeralBidi +# 2 = hindicontextnumeralBidi +# 3 = arabicnumeralBidi +# 4 = hindinumeralBidi +# 5 = persiancontextnumeralBidi +# 6 = persiannumeralBidi +- name: bidi.numeral + type: RelaxedAtomicUint32 + value: 0 + mirror: always + +# Bidi text type +# 1 = charsettexttypeBidi * +# 2 = logicaltexttypeBidi +# 3 = visualtexttypeBidi +- name: bidi.texttype + type: RelaxedAtomicUint32 + value: 1 + mirror: always + +# Bidi direction +# 1 = directionLTRBidi * +# 2 = directionRTLBidi +- name: bidi.direction + type: RelaxedAtomicUint32 + value: 1 + mirror: always + +# Setting this pref to |true| forces Bidi UI menu items and keyboard shortcuts +# to be exposed, and enables the directional caret hook. By default, only +# expose it for bidi-associated system locales. +- name: bidi.browser.ui + type: bool + value: false + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "browser." +#--------------------------------------------------------------------------- + +- name: browser.active_color + type: String + value: "#EE0000" + mirror: never + +- name: browser.active_color.dark + type: String + value: "#FF6666" + mirror: never + +- name: browser.anchor_color + type: String + value: "#0000EE" + mirror: never + +# If you change this, you probably also want to change +# nsXPLookAndFeel::GenericDarkColor for MozNativehyperlinktext. +- name: browser.anchor_color.dark + type: String + value: "#8C8CFF" + mirror: never + +# See http://dev.w3.org/html5/spec/forms.html#attr-fe-autofocus +- name: browser.autofocus + type: bool + value: true + mirror: always + +- name: browser.cache.disk.enable + type: RelaxedAtomicBool + value: true + mirror: always + +- name: browser.cache.memory.enable + type: RelaxedAtomicBool + value: true + mirror: always + +# Limit of recent metadata we keep in memory for faster access, in KB. +- name: browser.cache.disk.metadata_memory_limit + type: RelaxedAtomicUint32 + value: 250 # 0.25 MB + mirror: always + +# Does the user want smart-sizing? +- name: browser.cache.disk.smart_size.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Disk cache capacity in kilobytes. It's used only when +# browser.cache.disk.smart_size.enabled == false +- name: browser.cache.disk.capacity + type: RelaxedAtomicUint32 + value: 256000 + mirror: always + +# -1 = determine dynamically, 0 = none, n = memory capacity in kilobytes. +- name: browser.cache.memory.capacity + type: RelaxedAtomicInt32 + value: -1 + mirror: always + +# When smartsizing is disabled we could potentially fill all disk space by +# cache data when the disk capacity is not set correctly. To avoid that we +# check the free space every time we write some data to the cache. The free +# space is checked against two limits. Once the soft limit is reached we start +# evicting the least useful entries, when we reach the hard limit writing to +# the entry fails. +- name: browser.cache.disk.free_space_soft_limit + type: RelaxedAtomicUint32 + value: 5 * 1024 # 5MB + mirror: always + +- name: browser.cache.disk.free_space_hard_limit + type: RelaxedAtomicUint32 + value: 1024 # 1MB + mirror: always + +# The number of chunks we preload ahead of read. One chunk currently has +# 256kB. +- name: browser.cache.disk.preload_chunk_count + type: RelaxedAtomicUint32 + value: 4 # 1 MB of read ahead + mirror: always + +# Max-size (in KB) for entries in disk cache. Set to -1 for no limit. +# (Note: entries bigger than 1/8 of disk-cache are never cached) +- name: browser.cache.disk.max_entry_size + type: RelaxedAtomicUint32 + value: 50 * 1024 # 50 MB + mirror: always + +# Max-size (in KB) for entries in memory cache. Set to -1 for no limit. +# (Note: entries bigger than than 90% of the mem-cache are never cached.) +- name: browser.cache.memory.max_entry_size + type: RelaxedAtomicInt32 + value: 5 * 1024 + mirror: always + +# Memory limit (in kB) for new cache data not yet written to disk. Writes to +# the cache are buffered and written to disk on background with low priority. +# With a slow persistent storage these buffers may grow when data is coming +# fast from the network. When the amount of unwritten data is exceeded, new +# writes will simply fail. We have two buckets, one for important data +# (priority) like html, css, fonts and js, and one for other data like images, +# video, etc. +# Note: 0 means no limit. +- name: browser.cache.disk.max_chunks_memory_usage + type: RelaxedAtomicUint32 + value: 40 * 1024 + mirror: always +- name: browser.cache.disk.max_priority_chunks_memory_usage + type: RelaxedAtomicUint32 + value: 40 * 1024 + mirror: always + + +# Number of seconds the cache spends writing pending data and closing files +# after shutdown has been signalled. Past that time data is not written and +# files are left open for the OS to clean up. +- name: browser.cache.max_shutdown_io_lag + type: RelaxedAtomicUint32 + value: 2 + mirror: always + +# After the max_shutdown_io_lag has passed, we will attempt to cancel +# blocking IO (on windows). The CacheIOThread may pick up more blocking +# tasks so we want to cancel those too. The main thread will be woken +# up every shutdown_io_time_between_cancellations_ms to cancel the IO +# on the other thread. +- name: browser.cache.shutdown_io_time_between_cancellations_ms + type: RelaxedAtomicUint32 + value: 5 + mirror: always + +# A percentage limit for media content type in the disk cache. When some entries +# need to be evicted and media is over the limit, it's evicted first. +- name: browser.cache.disk.content_type_media_limit + type: RelaxedAtomicInt32 + value: 50 + mirror: always + +# How often to validate document in cache +# 0 = once-per-session, +# 1 = each-time, +# 2 = never, +# 3 = when-appropriate/automatically +- name: browser.cache.check_doc_frequency + type: RelaxedAtomicUint32 + value: 3 + mirror: always + +# Compression level for cached JavaScript bytecode +# 0 = do not compress, +# 1 = minimal compression, +# 9 = maximal compression +- name: browser.cache.jsbc_compression_level + type: RelaxedAtomicUint32 + value: 0 + mirror: always + +# Whether tooltips are enabled. +- name: browser.chrome.toolbar_tips + type: bool + value: true + mirror: always + +# Whether tooltips are hidden on keydown. +# 0: never +# 1: always +# 2: only on non-modifier keys +- name: browser.chrome.toolbar_tips.hide_on_keydown + type: uint32_t +#if defined(XP_WIN) + value: 0 +#else + value: 2 +#endif + mirror: always + +# Content analysis by external applications, e.g. data-loss prevention apps +- name: browser.contentanalysis.enabled + type: bool + value: false + mirror: never + +# Whether content analysis should allow content if there is a problem communicating +# with the agent. +- name: browser.contentanalysis.default_allow + type: bool + value: false + mirror: never + +# Is the IPC pipe to the DLP tool specific to the user or to the system? +- name: browser.contentanalysis.is_per_user + type: bool + value: true + mirror: never + +# Path name of pipe used to connect to a configured DLP agent. +- name: browser.contentanalysis.pipe_path_name + type: String + value: "path_user" + mirror: never + +# Should CA ignore the system setting and use silent notifications? +- name: browser.contentanalysis.silent_notifications + type: bool + value: false + mirror: always + +# Content blocking for Enhanced Tracking Protection +- name: browser.contentblocking.database.enabled + type: bool + value: false + mirror: always + +# How many recent block/unblock actions per origins we remember in the +# Content Blocking log for each top-level window. +- name: browser.contentblocking.originlog.length + type: uint32_t + value: 32 + mirror: always + +# Min font device pixel size at which to turn on high quality. +- name: browser.display.auto_quality_min_font_size + type: RelaxedAtomicUint32 + value: 20 + mirror: always + +- name: browser.display.background_color + type: String + value: "#FFFFFF" + mirror: never + +- name: browser.display.background_color.dark + type: String + value: "#1C1B22" + mirror: never + +# This preference is a bit confusing because we use the opposite +# string value in the colors dialog to indicate to users how FF HCM +# will behave. +# With resect to document colors, these values mean: +# 0 = "default" = always, except in high contrast mode +# 1 = "always" +# 2 = "never" +# +# On windows, we set this to 0, which means FF HCM will mirror OS HCM. +# Everywhere else, we set this to 1, disabling FF HCM. +- name: browser.display.document_color_use + type: RelaxedAtomicUint32 +#if defined(XP_WIN) + value: 0 +#else + value: 1 +#endif + mirror: always + rust: true + +# 0 = always native +# 1 = never native +# other = default +- name: browser.display.windows.non_native_menus + type: RelaxedAtomicUint32 + value: 2 + mirror: always + rust: true + +# This pref dictates whether or not backplates and background images +# are to be drawn, when in high-contrast mode: +# false: do not draw backplates or render background images +# true: render background images and draw backplates +# This condition is only considered when high-contrast mode is enabled +# in Firefox, ie. when the user has: +# (1) mUseAccessibilityMode set to true (Widows high-contrast mode is on) +# AND browser.display.document_color_use set to 0 +# (only with high-contrast themes) OR +# (2) browser.display.document_color_use set to 2 (always) +- name: browser.display.permit_backplate + type: RelaxedAtomicBool + value: true + mirror: always + rust: true + +# Whether we should suppress the background-image of the canvas (the root +# frame) if we're in forced colors mode. +# +# This is important because some sites use background-image with a plain color +# and it causes undesirable results in high-contrast mode. +# +# See bug 1614921 for example. +- name: browser.display.suppress_canvas_background_image_on_forced_colors + type: bool + value: true + mirror: always + +- name: browser.display.foreground_color + type: String + value: "#000000" + mirror: never + +- name: browser.display.foreground_color.dark + type: String + value: "#FBFBFE" + mirror: never + +# Determines the behavior of OS zoom settings. +# +# 0: doesn't affect rendering at all +# 1: affects full zoom (dpi, effectively). +# 2: affects text zoom. +# +# Default is (1): Historical behavior on Linux, matches other browsers on +# Windows, and generally creates more consistent rendering. +- name: browser.display.os-zoom-behavior + type: RelaxedAtomicInt32 + value: 1 + mirror: always + rust: true + +# Whether focus rings are always shown by default. +# +# This is the initial value of nsWindowRoot::mShowFocusRings, but it can be +# overridden by system preferences. +- name: browser.display.show_focus_rings + type: bool + value: false + mirror: always + +# Enable showing image placeholders while image is loading or when image is broken. +- name: browser.display.show_image_placeholders + type: bool + value: true + mirror: always + +# Whether we should always enable focus rings after focus was moved by keyboard. +# +# This behavior matches both historical and GTK / Windows focus behavior. +# +# :focus-visible is intended to provide better heuristics than this. +- name: browser.display.always_show_rings_after_key_focus + type: bool + value: false + mirror: always + +# In theory: 0 = never, 1 = quick, 2 = always, though we always just use it as +# a bool! +- name: browser.display.use_document_fonts + type: RelaxedAtomicInt32 + value: 1 + mirror: always + rust: true + +# font-family names for which we'll override use_document_fonts=0, and always +# use the specified font. +# This is to support ligature-icon fonts, which render literal strings like +# "arrow_drop_down" with an icon, even when use_document_fonts is disabled. +# If an author provides & uses such a font, and we decline to use it, we'll end +# up rendering these literal strings where the author intended an icon, which +# can cause all sorts of overlapping/unreadable content. +- name: browser.display.use_document_fonts.icon_font_allowlist + type: String + value: >- + Material Icons, + Material Icons Extended, + Material Icons Outlined, + Material Icons Round, + Material Icons Sharp, + Material Icons Two Tone, + Google Material Icons, + Material Symbols Outlined, + Material Symbols Round, + Material Symbols Rounded, + Material Symbols Sharp + mirror: never + +- name: browser.display.use_system_colors + type: RelaxedAtomicBool +#ifdef XP_WIN + value: true +#else + value: false +#endif + mirror: always + +- name: browser.dom.window.dump.enabled + type: RelaxedAtomicBool + value: @IS_NOT_MOZILLA_OFFICIAL@ + mirror: always + +# See bug 1738574 +- name: browser.download.start_downloads_in_tmp_dir + type: bool + value: false + mirror: always + +# See bug 1747343 +- name: browser.download.always_ask_before_handling_new_types + type: bool + value: false + mirror: always + +# See bug 1731668 +- name: browser.download.enable_spam_prevention + type: bool + value: false + mirror: always + +# See bug 1772569 +- name: browser.download.open_pdf_attachments_inline + type: bool + value: false + mirror: always + +# See bug 1811830 +- name: browser.download.force_save_internally_handled_attachments + type: bool + value: false + mirror: always + +- name: browser.download.sanitize_non_media_extensions + type: bool + value: true + mirror: always + +# Image document's automatic image sizing. +- name: browser.enable_automatic_image_resizing + type: bool + value: true + mirror: always + +# Image document's click-to-resize. +- name: browser.enable_click_image_resizing + type: bool + value: @IS_NOT_ANDROID@ + mirror: always + +- name: browser.find.ignore_ruby_annotations + type: bool + value: true + mirror: always + +#if defined(XP_MACOSX) +# Whether pressing Esc will exit fullscreen. +- name: browser.fullscreen.exit_on_escape + type: bool + value: true + mirror: always +#endif + +# The max url length we'll store in history. +# +# The default value is mostly a guess based on various facts: +# +# * IE didn't support urls longer than 2083 chars +# * Sitemaps protocol used to support a maximum of 2048 chars +# * Various SEO guides suggest to not go over 2000 chars +# * Various apps/services are known to have issues over 2000 chars +# * RFC 2616 - HTTP/1.1 suggests being cautious about depending +# on URI lengths above 255 bytes +# +- name: browser.history.maxUrlLength + type: uint32_t + value: 2000 + mirror: always + +# Max size of push/replaceState data parameter +- name: browser.history.maxStateObjectSize + type: int32_t + value: 16777216 + mirror: always + +# True to collect wireframes upon navigations / pushState +- name: browser.history.collectWireframes + type: bool + value: false + mirror: always + +# The minimum area for a rect to be included in a wireframe, in CSS pixels. +# +# The current value of 50 is pretty arbitrary, and will be tuned as we refine +# and test the wireframing capability. +- name: browser.history.wireframeAreaThreshold + type: uint32_t + value: 50 + mirror: always + +#if defined(XP_WIN) || defined(XP_LINUX) + # Notify TabUnloader or send the memory pressure if the memory resource + # notification is signaled AND the available commit space is lower than + # this value. +- name: browser.low_commit_space_threshold_mb + type: RelaxedAtomicUint32 + value: 200 + mirror: always +#endif + +#ifdef XP_LINUX + # On Linux we also check available memory in comparison to total memory, + # and use this percent value (out of 100) to determine if we are in a + # low memory scenario. +- name: browser.low_commit_space_threshold_percent + type: RelaxedAtomicUint32 + value: 5 + mirror: always +#endif + +# Render animations and videos as a solid color +- name: browser.measurement.render_anims_and_video_solid + type: RelaxedAtomicBool + value: false + mirror: always + +- name: browser.navigation.requireUserInteraction + type: bool + value: false + mirror: always + +# Indicates if about:newtab shows content (enabled) or just blank. +- name: browser.newtabpage.enabled + type: bool + value: true + mirror: always + +# Open PDFs in Edge with the --app flag if it is the default. +- name: browser.pdf.launchDefaultEdgeAsApp + type: bool + value: true + mirror: always + +# Maximium delay between keystrokes that will be considered typing (milliseconds). +- name: browser.places.interactions.typing_timeout_ms + type: RelaxedAtomicUint32 + value: 3000 + mirror: always + +# Maximum delay between scroll input events that will be considered a scrolling interaction (milliseconds). +- name: browser.places.interactions.scrolling_timeout_ms + type: RelaxedAtomicUint32 + value: 5000 + mirror: always + +# Number of seconds till the sponsored session is timeout. +- name: browser.places.sponsoredSession.timeoutSecs + type: RelaxedAtomicUint32 + value: 3600 + mirror: always + +# Whether to start the private browsing mode at application startup +- name: browser.privatebrowsing.autostart + type: bool + value: false + mirror: always + +# Force usage of in-memory (rather than file on disk) media cache for video streaming when private browsing +- name: browser.privatebrowsing.forceMediaMemoryCache + type: bool + value: false + mirror: always + +# Communicates the toolbar color to platform (for e.g., prefers-color-scheme). +# +# Returns whether the toolbar is dark (0), light (1), or system (2). The +# theming code overrides it if appropriate. +- name: browser.theme.toolbar-theme + type: RelaxedAtomicUint32 + value: 2 + mirror: always + +# Communicates the preferred content theme color to platform (for e.g., +# prefers-color-scheme). +# +# dark (0), light (1), system (2), or toolbar (3). +# +# Default to "toolbar", the theming code sets it appropriately. +- name: browser.theme.content-theme + type: RelaxedAtomicUint32 + value: 2 + mirror: always + rust: true + +# Whether the firefox titlebar respects the +# -moz-windows-accent-color-in-titlebar setting on the tab strip. +- name: browser.theme.windows.accent-color-in-tabs.enabled + type: RelaxedAtomicBool + value: false + mirror: always + rust: true + +# Blocked plugin content +- name: browser.safebrowsing.blockedURIs.enabled + type: bool + value: true + mirror: always + +# Malware protection +- name: browser.safebrowsing.malware.enabled + type: bool + value: true + mirror: always + +# Phishing protection +- name: browser.safebrowsing.phishing.enabled + type: bool + value: true + mirror: always + +# Maximum size for an array to store the safebrowsing prefixset. +- name: browser.safebrowsing.prefixset_max_array_size + type: RelaxedAtomicUint32 + value: 512*1024 + mirror: always + +# SessionStore prefs +# Maximum number of bytes of DOMSessionStorage data we collect per origin. +- name: browser.sessionstore.dom_storage_limit + type: uint32_t + value: 2048 + mirror: always + +# Maximum number of characters of form field data per field we collect. +- name: browser.sessionstore.dom_form_limit + type: uint32_t + value: 1024*1024*2 + mirror: always + +# Maximum number of characters of form data we collect per origin. +- name: browser.sessionstore.dom_form_max_limit + type: uint32_t + value: 1024*1024*50 + mirror: always + +# Minimal interval between two save operations in milliseconds (while the user is active). +- name: browser.sessionstore.interval + type: RelaxedAtomicUint32 + value: 15000 + mirror: always + +# Platform collection of data for session store +- name: browser.sessionstore.platform_collection + type: bool +#if defined(ANDROID) || defined(MOZ_THUNDERBIRD) + value: false +#else + value: true +#endif + mirror: once + +# Platform collection of session storage data for session store +- name: browser.sessionstore.collect_session_storage + type: bool + value: @IS_NOT_ANDROID@ + mirror: once + +# Platform collection of zoom data for session store +- name: browser.sessionstore.collect_zoom + type: bool + value: @IS_NOT_ANDROID@ + mirror: once + +# Causes SessionStore to ignore non-final update messages from +# browser tabs that were not caused by a flush from the parent. +# This is a testing flag and should not be used by end-users. +- name: browser.sessionstore.debug.no_auto_updates + type: RelaxedAtomicBool + value: false + mirror: always + +# Whether we should draw the tabs on top of the titlebar. +# +# no (0), yes (1), or default (2), which is true everywhere except Linux. +- name: browser.tabs.inTitlebar + type: int32_t + value: 2 + mirror: always + +# If set, use DocumentChannel to directly initiate loads entirely +# from parent-process BrowsingContexts +- name: browser.tabs.documentchannel.parent-controlled + type: bool + value: false + mirror: always + +# Testing-only pref which makes data: URIs be loaded in a "web" content process +# instead of within a process based on the URI's loader. +- name: browser.tabs.remote.dataUriInDefaultWebProcess + type: bool + value: false + mirror: always + +# Testing-only pref to force system-triggered about:blank loads to not change +# content processes. This is used for performance tests which load an +# about:blank document between navigations for historical reasons to avoid +# unnecessary process switches. +- name: browser.tabs.remote.systemTriggeredAboutBlankAnywhere + type: bool + value: false + mirror: always + +# Testing-only pref to cause PBrowser creation for a specific BrowsingContext to +# fail, to test the errored codepath. +- name: browser.tabs.remote.testOnly.failPBrowserCreation.enabled + type: bool + value: false + mirror: always + +- name: browser.tabs.remote.force-paint + type: bool + value: true + mirror: always + +# When this pref is enabled document loads with a mismatched +# Cross-Origin-Embedder-Policy header will fail to load +- name: browser.tabs.remote.useCrossOriginEmbedderPolicy + type: RelaxedAtomicBool + value: true + mirror: always + +# This pref makes `credentialless` a valid value for +# Cross-Origin-Embedder-Policy header +- name: browser.tabs.remote.coep.credentialless + type: RelaxedAtomicBool +#if defined(ANDROID) + value: @IS_NIGHTLY_BUILD@ +#else + value: true +#endif + mirror: always + do_not_use_directly: true + +# When this pref is enabled top level loads with a mismatched +# Cross-Origin-Opener-Policy header will be loaded in a separate process. +- name: browser.tabs.remote.useCrossOriginOpenerPolicy + type: RelaxedAtomicBool + value: true + mirror: always + +# When this pref is enabled then we use a separate content process for +# top-level load of file:// URIs +- name: browser.tabs.remote.separateFileUriProcess + type: RelaxedAtomicBool +#if !defined(ANDROID) + value: true +#else + value: false +#endif + mirror: always + +# Pref to control whether we use a separate privileged content process +# for certain mozilla webpages (which are listed in the pref +# browser.tabs.remote.separatedMozillaDomains). +- name: browser.tabs.remote.separatePrivilegedMozillaWebContentProcess + type: bool + value: false + mirror: always + +# Whether or not process selection for subframes will prefer re-using an +# existing content process over creating a new one. Enabling this pref should +# reduce the number of processes allocated for non-first-party domains if +# dom.ipc.processCount.webIsolated > 1. +- name: browser.tabs.remote.subframesPreferUsed + type: bool + value: true + mirror: always + +# When this pref is enabled, opaque response is only allowed to enter the +# content process if it's a response for media (audio, image, video), CSS, or +# JavaScript. +- name: browser.opaqueResponseBlocking + type: RelaxedAtomicBool +#if defined(ANDROID) + value: false +#else + value: true +#endif + mirror: always + +# When this pref is enabled, the JS validator will be enabled for +# ORB. +- name: browser.opaqueResponseBlocking.javascriptValidator + type: bool + value: true + mirror: always + +# This pref controls how filtering of opaque responses for calls to `Window.fetch`. +# (and similar) is performed in the parent process. This is intended to make sure +# that data that would be filtered in a content process never actually reaches that +# content process. +# See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque +# 0) Don't filter in the parent process at all, and let content processes handle +# opaque filtering. Regardless of if ORB is enabled or not. N.B. that if ORB +# is enabled opaque responses will be blocked. +# 1) If ORB is enabled, in the parent process, filter the responses that ORB allows. +# N.B. any responses ORB doesn't allow will not send data to a content process +# since they will return a NetworkError. If the request is allowed by ORB, the +# internal response will be intact and sent to the content process as is. +# 2) If ORB is enabled, in the parent process, filter the responses that ORB blocks, +# when they were issued by `Window.fetch` (and similar). +# 3) Filter all responses in the parent, regardless of if ORB is enabled or not. +# This means that opaque responses coming from `Window.fetch` won't even be +# considered for being blocked by ORB. +- name: browser.opaqueResponseBlocking.filterFetchResponse + type: uint32_t + value: 2 + mirror: always + do_not_use_directly: true + +# This pref controls how exceptions to opaque response blocking for the media MIME types +# `audio/*` and `video/*` are handled. This is because step 8 in the spec that performs +# audio or video type pattern matching cannot handle certain MIME types (yet). +# See https://whatpr.org/fetch/1442.html#orb-algorithm +# 0) No exceptions +# 1) Some exceptions, explicitly hard coded in `IsOpaqueSafeListedSpecBreakingMIMEType` +# 2) Allow all MIME types beginning with `audio/*` or `video/*`. +- name: browser.opaqueResponseBlocking.mediaExceptionsStrategy + type: uint32_t + value: 1 + mirror: always + do_not_use_directly: true + +# When true, zooming will be enabled on all sites, even ones that declare +# user-scalable=no. +- name: browser.ui.zoom.force-user-scalable + type: RelaxedAtomicBool + value: false + mirror: always + +- name: browser.viewport.desktopWidth + type: RelaxedAtomicInt32 + value: 980 + mirror: always + +- name: browser.visited_color + type: String + value: "#551A8B" + mirror: never + +# If you change this, you probably also want to change +# nsXPLookAndFeel::GenericDarkColor for MozNativevisitedhyperlinktext. +- name: browser.visited_color.dark + type: String + value: "#FFADFF" + mirror: never + +# When true, soft reloads (including location.reload()) +# will only froce validate the top level document, subresources will +# be loaded normally as-if users normally navigated to the page. +- name: browser.soft_reload.only_force_validate_top_level_document + type: bool + value: true + mirror: always + +# Whether or not to save and restore zoom levels on a per-site basis. +- name: browser.zoom.siteSpecific + type: bool + value: @IS_NOT_ANDROID@ + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "channelclassifier." +#--------------------------------------------------------------------------- + +- name: channelclassifier.allowlist_example + type: bool + value: false + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "clipboard." +#--------------------------------------------------------------------------- + +# Clipboard behavior. +- name: clipboard.autocopy + type: bool +#if !defined(ANDROID) && !defined(XP_MACOSX) && defined(XP_UNIX) + value: true +#else + value: false +#endif + mirror: always + +#ifdef XP_WIN + # allow to copy clipboard data to Clipboard History/Cloud + # (used on sensitive data in about:logins and Private Browsing) +- name: clipboard.copyPrivateDataToClipboardCloudOrHistory + type: bool + value: false + mirror: always + + # Whether to put a file promise onto the clipboard when copying images on Windows +- name: clipboard.imageAsFile.enabled + type: bool + value: @IS_NOT_EARLY_BETA_OR_EARLIER@ + mirror: always +#endif + +#--------------------------------------------------------------------------- +# Prefs starting with "consoleservice." +#--------------------------------------------------------------------------- + +#if defined(ANDROID) + # Disable sending console to logcat on release builds. +- name: consoleservice.logcat + type: RelaxedAtomicBool + value: @IS_NOT_RELEASE_OR_BETA@ + mirror: always +#endif + +#--------------------------------------------------------------------------- +# Prefs starting with "content." +#--------------------------------------------------------------------------- + +- name: content.cors.disable + type: bool + value: false + mirror: always + +# Back off timer notification after count. +# -1 means never. +- name: content.notify.backoffcount + type: int32_t + value: -1 + mirror: always + +# Notification interval in microseconds. +# The notification interval has a dramatic effect on how long it takes to +# initially display content for slow connections. The current value +# provides good incremental display of content without causing an increase +# in page load time. If this value is set below 1/10 of a second it starts +# to impact page load performance. +# See bugzilla bug 72138 for more info. +- name: content.notify.interval + type: int32_t + value: 120000 + mirror: always + +# Do we notify based on time? +- name: content.notify.ontimer + type: bool + value: true + mirror: always + +# How many times to deflect in interactive mode. +- name: content.sink.interactive_deflect_count + type: int32_t + value: 0 + mirror: always + +# How many times to deflect in perf mode. +- name: content.sink.perf_deflect_count + type: int32_t + value: 200 + mirror: always + +# Parse mode for handling pending events. +# 0 = don't check for pending events +# 1 = don't deflect if there are pending events +# 2 = bail if there are pending events +- name: content.sink.pending_event_mode + type: int32_t +#ifdef XP_WIN + value: 1 +#else + value: 0 +#endif + mirror: always + +# How often to probe for pending events. 1 = every token. +- name: content.sink.event_probe_rate + type: int32_t + value: 1 + mirror: always + +# How long to stay off the event loop in interactive mode (microseconds). +- name: content.sink.interactive_parse_time + type: int32_t + value: 3000 + mirror: always + +# How long to stay off the event loop in perf mode. +- name: content.sink.perf_parse_time + type: int32_t + value: 30000 + mirror: always + +# How long to be in interactive mode after an event. +- name: content.sink.interactive_time + type: uint32_t + value: 750000 + mirror: always + +# How long to stay in perf mode after initial loading. +- name: content.sink.initial_perf_time + type: uint32_t + value: 2000000 + mirror: always + +# Should we switch between perf-mode and interactive-mode? +# 0 = Switch +# 1 = Interactive mode +# 2 = Perf mode +- name: content.sink.enable_perf_mode + type: int32_t + value: 0 + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "converter." +#--------------------------------------------------------------------------- + +# Whether we include ruby annotation in the text despite whether it +# is requested. This was true because we didn't explicitly strip out +# annotations. Set false by default to provide a better behavior, but +# we want to be able to pref-off it if user doesn't like it. +- name: converter.html2txt.always_include_ruby + type: bool + value: false + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "cookiebanners." +#--------------------------------------------------------------------------- + +# Controls the cookie banner handling mode in normal browsing. +# 0: Disables all cookie banner handling. +# 1: Reject-all if possible, otherwise do nothing. +# 2: Reject-all if possible, otherwise accept-all. +- name: cookiebanners.service.mode + type: uint32_t + value: 0 + mirror: always + +# When set to true, cookie banners are detected and detection events are +# dispatched, but they will not be handled. Requires the service to be enabled +# for the desired mode via pref cookiebanners.service.mode* +- name: cookiebanners.service.detectOnly + type: bool + value: false + mirror: always + +# Controls the cookie banner handling mode in private browsing. Same mode +# options as the normal browsing pref above. +- name: cookiebanners.service.mode.privateBrowsing + type: uint32_t + value: 0 + mirror: always + +# Enables use of global CookieBannerRules, which apply to all sites. This is +# used for click rules that can handle common Consent Management Providers +# (CMP). +# Enabling this (when the cookie handling feature is enabled) may negatively +# impact site performance since it requires us to run rule-defined query +# selectors for every page. +- name: cookiebanners.service.enableGlobalRules + type: bool + value: true + mirror: always + +# Whether global rules are allowed to run in sub-frames. Running query selectors +# in every sub-frame may negatively impact performance, but is required for some +# CMPs. +- name: cookiebanners.service.enableGlobalRules.subFrames + type: bool + value: true + mirror: always + +# Enables the cookie banner cookie injector. The cookie banner cookie injector +# depends on the `cookiebanners.service.mode` pref above. +- name: cookiebanners.cookieInjector.enabled + type: bool + value: true + mirror: always + +# By default, how many seconds in the future cookies should expire after they +# have been injected. Defaults to 12 months. Individual cookie rules may +# override this. +- name: cookiebanners.cookieInjector.defaultExpiryRelative + type: uint32_t + value: 31536000 + mirror: always + +# How many times per site and site load to check for cookie banners after which +# the mechanism is considered on cooldown for the site in the current browsing +# session. If the threshold is set to zero, banner clicking won't be considered +# as being on cooldown regardless of how many times the site is loaded. The +# maximum value for the retry is 255, any value over than that will be capped. +- name: cookiebanners.bannerClicking.maxTriesPerSiteAndSession + type: uint32_t + value: 3 + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "datareporting." +#--------------------------------------------------------------------------- + +- name: datareporting.healthreport.uploadEnabled + type: RelaxedAtomicBool + value: false + mirror: always + rust: true + +#--------------------------------------------------------------------------- +# Prefs starting with "device." +#--------------------------------------------------------------------------- + +# Is support for the device sensors API enabled? +- name: device.sensors.enabled + type: bool + value: true + mirror: always + +# KaiOS-only, see https://bugzilla.mozilla.org/show_bug.cgi?id=1699707#c10 +- name: device.sensors.ambientLight.enabled + type: bool + value: false + mirror: always + +- name: device.sensors.motion.enabled + type: bool + value: true + mirror: always + +- name: device.sensors.orientation.enabled + type: bool + value: true + mirror: always + +# KaiOS-only, see https://bugzilla.mozilla.org/show_bug.cgi?id=1699707#c10 +- name: device.sensors.proximity.enabled + type: bool + value: false + mirror: always + +- name: device.sensors.test.events + type: bool + value: false + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "devtools." +#--------------------------------------------------------------------------- + +- name: devtools.console.stdout.chrome + type: RelaxedAtomicBool + value: @IS_NOT_MOZILLA_OFFICIAL@ + mirror: always + +- name: devtools.console.stdout.content + type: RelaxedAtomicBool + value: false + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "docshell." +#--------------------------------------------------------------------------- + +# Used to indicate whether session history listeners should be notified +# about content viewer eviction. Used only for testing. +- name: docshell.shistory.testing.bfevict + type: bool + value: false + mirror: always + +# If true, pages with an opener won't be bfcached. +- name: docshell.shistory.bfcache.require_no_opener + type: bool + value: @IS_ANDROID@ + mirror: always + +# If true, page with beforeunload or unload event listeners can be bfcached. +- name: docshell.shistory.bfcache.allow_unload_listeners + type: bool + value: @IS_ANDROID@ + mirror: always + +# If true, page with beforeunload event listeners can be bfcached. +# This only works when sessionHistoryInParent is enabled. +- name: docshell.shistory.bfcache.ship_allow_beforeunload_listeners + type: bool + value: true + mirror: always + +#--------------------------------------------------------------------------- +# Prefs starting with "dom." +#--------------------------------------------------------------------------- + +# Allow cut/copy +- name: dom.allow_cut_copy + type: bool + value: true + mirror: always + +# Checks if offscreen animation throttling is enabled. +- name: dom.animations.offscreen-throttling + type: bool + value: true + mirror: always + +# Is support for composite operations from the Web Animations API enabled? +- name: dom.animations-api.compositing.enabled + type: bool + value: true + mirror: always + +# Is support for timelines from the Web Animations API enabled? +- name: dom.animations-api.timelines.enabled + type: bool + value: true + mirror: always + +# Is support for Navigator.getBattery enabled? +- name: dom.battery.enabled + type: bool + value: true + mirror: always + +# Block multiple external protocol URLs in iframes per single event. +- name: dom.block_external_protocol_in_iframes + type: bool + value: true + mirror: always + +# Block sandboxed BrowsingContexts from navigating to external protocols. +- name: dom.block_external_protocol_navigation_from_sandbox + type: bool + value: true + mirror: always + +# Block Insecure downloads from Secure Origins +- name: dom.block_download_insecure + type: bool + value: true + mirror: always + +# Block multiple window.open() per single event. +- name: dom.block_multiple_popups + type: bool + value: true + mirror: always + +# The maximum number of popup that is allowed to be opened. Set to -1 for no +# limit. +- name: dom.popup_maximum + type: int32_t + value: 20 + mirror: always + +# Whether window.location.reload() and window.history.go(0) should be blocked +# when called directly from a window resize event handler. +# +# This used to be necessary long ago to prevent terrible UX when using stuff +# like TypeAheadFind (bug 258917), but it also causes compat issues on mobile +# (bug 1570566). +- name: dom.block_reload_from_resize_event_handler + type: bool + value: false + mirror: always + + # Enable CacheAPI in private browsing mode with encryption +- name: dom.cache.privateBrowsing.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Exposes window.caches and skips SecureContext check. +# dom.serviceWorkers.testing.enabled also includes the same effect. +- name: dom.caches.testing.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Disable capture attribute for input elements; only supported on GeckoView. +- name: dom.capture.enabled + type: bool + value: false + mirror: always + +# HTML specification says the level should be 5 +# https://html.spec.whatwg.org/#timer-initialisation-steps +- name: dom.clamp.timeout.nesting.level + type: uint32_t + value: 5 + mirror: once + +# Disable custom highlight API; implementation pending. +- name: dom.customHighlightAPI.enabled + type: RelaxedAtomicBool + value: @IS_NIGHTLY_BUILD@ + mirror: always + rust: true + +# Allow control characters appear in composition string. +# When this is false, control characters except +# CHARACTER TABULATION (horizontal tab) are removed from +# both composition string and data attribute of compositionupdate +# and compositionend events. +- name: dom.compositionevent.allow_control_characters + type: bool + value: false + mirror: always + +# Compression Streams (CompressionStream/DecompressionStream) +- name: dom.compression_streams.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Is support for CSSPseudoElement enabled? +- name: dom.css_pseudo_element.enabled + type: bool + value: false + mirror: always + +# After how many seconds we allow external protocol URLs in iframe when not in +# single events +- name: dom.delay.block_external_protocol_in_iframes + type: uint32_t + value: 10 # in seconds + mirror: always + +# Whether the above pref has any effect at all. +# Make sure cases like bug 1795380 work before trying to turn this off. See +# bug 1680721 for some other context that might be relevant. +- name: dom.delay.block_external_protocol_in_iframes.enabled + type: bool + value: true + mirror: always + +# Only propagate the open window click permission if the setTimeout() is equal +# to or less than this value. +- name: dom.disable_open_click_delay + type: int32_t + value: 1000 + mirror: always + +- name: dom.disable_open_during_load + type: bool + value: false + mirror: always + +- name: dom.disable_beforeunload + type: bool + value: false + mirror: always + +- name: dom.require_user_interaction_for_beforeunload + type: bool + value: true + mirror: always + +# Enable/disable Gecko specific edit commands +- name: dom.document.edit_command.contentReadOnly.enabled + type: bool + value: @IS_NOT_EARLY_BETA_OR_EARLIER@ + mirror: always + +- name: dom.document.edit_command.insertBrOnReturn.enabled + type: bool + value: @IS_NOT_EARLY_BETA_OR_EARLIER@ + mirror: always + +# If set this to true, `Document.execCommand` may be performed nestedly. +# Otherwise, nested calls just return false. +- name: dom.document.exec_command.nested_calls_allowed + type: bool + value: false + mirror: always + +- name: dom.domrequest.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Only intended for fuzzing purposes, this will break mozPrintCallback, etc. +- name: dom.window_print.fuzzing.block_while_printing + type: bool + value: false + mirror: always + +- name: dom.element.transform-getters.enabled + type: bool + value: false + mirror: always + +# Whether the popover attribute implementation is enabled, +# see https://html.spec.whatwg.org/#the-popover-attribute +- name: dom.element.popover.enabled + type: RelaxedAtomicBool + value: @IS_NIGHTLY_BUILD@ + mirror: always + rust: true + +# Whether the blocking attribute implementation is enabled, +# see https://html.spec.whatwg.org/#blocking-attributes +- name: dom.element.blocking.enabled + type: bool + value: false + mirror: always + +# Whether CustomStateSet is enabled +- name: dom.element.customstateset.enabled + type: RelaxedAtomicBool + value: false + mirror: always + rust: true + +# Whether the invoketarget attribute implementation is enabled +- name: dom.element.invokers.enabled + type: bool + value: false + mirror: always + +- name: dom.mouse_capture.enabled + type: bool + value: true + mirror: always + +# Is support for Performance.mozMemory enabled? +- name: dom.enable_memory_stats + type: bool + value: false + mirror: always + +# Enable Performance API +# Whether nonzero values can be returned from performance.timing.* +- name: dom.enable_performance + type: RelaxedAtomicBool + value: true + mirror: always + +# Enable Performance Observer API +- name: dom.enable_performance_observer + type: RelaxedAtomicBool + value: true + mirror: always + +# Whether resource timing will be gathered and returned by performance.GetEntries* +- name: dom.enable_resource_timing + type: bool + value: true + mirror: always + +# Whether event timing will be gathered and returned by performance observer* +- name: dom.enable_event_timing + type: RelaxedAtomicBool + value: true + mirror: always + +# Whether the LargestContentfulPaint API will be gathered and returned by performance observer* +- name: dom.enable_largest_contentful_paint + type: RelaxedAtomicBool + value: true + mirror: always + +# Whether performance.GetEntries* will contain an entry for the active document +- name: dom.enable_performance_navigation_timing + type: bool + value: true + mirror: always + +# Whether the scheduler interface will be exposed +- name: dom.enable_web_task_scheduling + type: RelaxedAtomicBool + value: @IS_NIGHTLY_BUILD@ + mirror: always + +# If this is true, it's allowed to fire "cut", "copy" and "paste" events. +# Additionally, "input" events may expose clipboard content when inputType +# is "insertFromPaste" or something. +- name: dom.event.clipboardevents.enabled + type: bool + value: true + mirror: always + +# Whether Shift+Right click force-opens the context menu +- name: dom.event.contextmenu.shift_suppresses_event + type: bool + value: true + mirror: always + +- name: dom.event.dragexit.enabled + type: bool + value: @IS_NOT_NIGHTLY_BUILD@ + mirror: always + +# If this pref is set to true, typing a surrogate pair causes one `keypress` +# event whose `charCode` stores the unicode code point over 0xFFFF. This is +# compatible with Safari and Chrome in non-Windows platforms. +# Otherwise, typing a surrogate pair causes two `keypress` events. This is +# compatible with legacy web apps which does +# `String.fromCharCode(event.charCode)`. +- name: dom.event.keypress.dispatch_once_per_surrogate_pair + type: bool + value: false + mirror: always + +# This is meaningful only when `dispatch_once_per_surrogate_pair` is false. +# If this pref is set to true, `.key` of the first `keypress` is set to the +# high-surrogate and `.key` of the other is set to the low-surrogate. +# Therefore, setting this exposing ill-formed UTF-16 string with `.key`. +# (And also `InputEvent.data` if pressed in an editable element.) +# Otherwise, `.key` of the first `keypress` is set to the surrogate pair, and +# `.key` of the second `keypress` is set to the empty string. +- name: dom.event.keypress.key.allow_lone_surrogate + type: bool + value: @IS_NOT_EARLY_BETA_OR_EARLIER@ + mirror: always + +# Whether wheel event target's should be grouped. When enabled, all wheel +# events that occur in a given wheel transaction have the same event target. +- name: dom.event.wheel-event-groups.enabled + type: bool + value: true + mirror: always + +# Whether WheelEvent should return pixels instead of lines for +# WheelEvent.deltaX/Y/Z, when deltaMode hasn't been checked. +# +# Other browsers don't use line deltas and websites forget to check for it, see +# bug 1392460. +- name: dom.event.wheel-deltaMode-lines.disabled + type: bool + value: true + mirror: always + +# Mostly for debugging. Whether we should do the same as +# dom.event.wheel-deltaMode-lines.disabled, but unconditionally rather than +# only when deltaMode hasn't been checked. +- name: dom.event.wheel-deltaMode-lines.always-disabled + type: bool + value: false + mirror: always + +# A blocklist (list of domains) for the +# dom.event.wheel-deltaMode-lines.disabled behavior, in case potential +# unforeseen problems with it arrive. +- name: dom.event.wheel-deltaMode-lines.always-enabled + type: String + value: "" + mirror: never + +#if defined(XP_MACOSX) +# Whether to disable treating ctrl click as right click +- name: dom.event.treat_ctrl_click_as_right_click.disabled + type: bool + value: @IS_NIGHTLY_BUILD@ + mirror: always +#endif + +# Whether Gecko keeps store or forgets the last deepest "enter" event target for +# the next "enter" or "leave" event dispatching when the last "over" event +# target is removed from the DOM tree. +- name: dom.events.mouse-pointer-boundary.keep-enter-targets-after-over-target-removed + type: bool + value: @IS_EARLY_BETA_OR_EARLIER@ + mirror: always + +# Whether .offset{X,Y} for events targeted at SVG nodes returns bounds relative +# to the outer SVG. +- name: dom.events.offset-in-svg-relative-to-svg-root + type: bool + value: true + mirror: always + +# Control whether clipboard.read(), clipboard.write() and ClipboardItem are exposed +# to content. +- name: dom.events.asyncClipboard.clipboardItem + type: bool + value: @IS_EARLY_BETA_OR_EARLIER@ + mirror: always + +# Skips checking permission and user activation when accessing the clipboard. +# Should only be enabled in tests. +# Access with Clipboard::IsTestingPrefEnabled(). +- name: dom.events.testing.asyncClipboard + type: bool + value: false + mirror: always + do_not_use_directly: true + +# Control whether `navigator.clipboard.readText()` is exposed to content. +- name: dom.events.asyncClipboard.readText + type: bool + value: @IS_EARLY_BETA_OR_EARLIER@ + mirror: always + do_not_use_directly: true + +# This pref controls whether or not the `protected` dataTransfer state is +# enabled. If the `protected` dataTransfer stae is disabled, then the +# DataTransfer will be read-only whenever it should be protected, and will not +# be disconnected after a drag event is completed. +- name: dom.events.dataTransfer.protected.enabled + type: bool + value: false + mirror: always + +# Whether to hide normal files (i.e. non-images) in dataTransfer inside +# the content process. +- name: dom.events.dataTransfer.mozFile.enabled + type: bool + value: true + mirror: always + +- name: dom.events.dataTransfer.imageAsFile.enabled + type: bool + value: false + mirror: always + +# User interaction timer interval, in ms +- name: dom.events.user_interaction_interval + type: uint32_t + value: 5000 + mirror: always + +# Whether to try to compress touchmove events on IPC layer. +- name: dom.events.compress.touchmove + type: bool + value: true + mirror: always + +# In addition to the above IPC layer compresison, allow touchmove +# events to be further coalesced in the child side after they +# are sent. +- name: dom.events.coalesce.touchmove + type: bool + value: true + mirror: always + +# Allow mousemove events to be coalesced in the child side after they are sent. +- name: dom.events.coalesce.mousemove + type: bool + value: true + mirror: always + +# Whether to expose test interfaces of various sorts +- name: dom.expose_test_interfaces + type: bool + value: false + mirror: always + +- name: dom.fetchObserver.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Allow the content process to create a File from a path. This is allowed just +# on parent process, on 'file' Content process, or for testing. +- name: dom.file.createInChild + type: RelaxedAtomicBool + value: false + mirror: always + +# Support @autocomplete values for form autofill feature. +- name: dom.forms.autocomplete.formautofill + type: bool + value: false + mirror: always + +# Only trusted submit event could trigger form submission. +- name: dom.forms.submit.trusted_event_only + type: bool + value: false + mirror: always + +# This pref just controls whether we format the number with grouping separator +# characters when the internal value is set or updated. It does not stop the +# user from typing in a number and using grouping separators. +- name: dom.forms.number.grouping + type: bool + value: false + mirror: always + +# Whether the Gamepad API is enabled +- name: dom.gamepad.enabled + type: bool + value: true + mirror: always + +# Is Gamepad Extension API enabled? +- name: dom.gamepad.extensions.enabled + type: bool + value: true + mirror: always + +# Is LightIndicator API enabled in Gamepad Extension API? +- name: dom.gamepad.extensions.lightindicator + type: bool + value: false + mirror: always + +# Is MultiTouch API enabled in Gamepad Extension API? +- name: dom.gamepad.extensions.multitouch + type: bool + value: false + mirror: always + +# Is Gamepad vibrate haptic feedback function enabled? +- name: dom.gamepad.haptic_feedback.enabled + type: bool + value: true + mirror: always + +- name: dom.gamepad.non_standard_events.enabled + type: bool + value: @IS_NOT_RELEASE_OR_BETA@ + mirror: always + +- name: dom.gamepad.test.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# W3C draft ImageCapture API +- name: dom.imagecapture.enabled + type: bool + value: false + mirror: always + +# The root margin for image lazy loading, defined as four (value, percentage) +# pairs. +- name: dom.image-lazy-loading.root-margin.top + type: float + value: 600 + mirror: always + +- name: dom.image-lazy-loading.root-margin.top.percentage + type: bool + value: false + mirror: always + +- name: dom.image-lazy-loading.root-margin.bottom + type: float + value: 600 + mirror: always + +- name: dom.image-lazy-loading.root-margin.bottom.percentage + type: bool + value: false + mirror: always + +- name: dom.image-lazy-loading.root-margin.left + type: float + value: 600 + mirror: always + +- name: dom.image-lazy-loading.root-margin.left.percentage + type: bool + value: false + mirror: always + +- name: dom.image-lazy-loading.root-margin.right + type: float + value: 600 + mirror: always + +- name: dom.image-lazy-loading.root-margin.right.percentage + type: bool + value: false + mirror: always + +# Enable indexedDB in private browsing mode with encryption +- name: dom.indexedDB.privateBrowsing.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Whether or not indexedDB test mode is enabled. +- name: dom.indexedDB.testing + type: RelaxedAtomicBool + value: false + mirror: always + +# Whether or not indexedDB experimental features are enabled. +- name: dom.indexedDB.experimental + type: RelaxedAtomicBool + value: false + mirror: always + +# Whether or not indexedDB preprocessing is enabled. +- name: dom.indexedDB.preprocessing + type: RelaxedAtomicBool + value: false + mirror: always + +# Whether innerWidth / innerHeight return rounded or fractional sizes. +# +# NOTE(emilio): Fractional sizes are not web-compatible, see the regressions +# from bug 1676843, but we want to expose the fractional sizes (probably in +# another API) one way or another, see [1], so we're keeping the code for the +# time being. +# +# [1]: https://github.com/w3c/csswg-drafts/issues/5260 +- name: dom.innerSize.rounded + type: bool + value: true + mirror: always + +# Whether we conform to Input Events Level 1 or Input Events Level 2. +# true: conforming to Level 1 +# false: conforming to Level 2 +- name: dom.input_events.conform_to_level_1 + type: bool + value: true + mirror: always + +# Whether we allow BrowsingContextGroup to suspend input events +- name: dom.input_events.canSuspendInBCG.enabled + type: bool + value: false + mirror: always + +# The minimum number of ticks after page navigation +# that need to occur before user input events are allowed to be handled. +- name: dom.input_events.security.minNumTicks + type: uint32_t + value: 3 + mirror: always + +# The minimum elapsed time (in milliseconds) after page navigation +# for user input events are allowed to be handled. +- name: dom.input_events.security.minTimeElapsedInMS + type: uint32_t + value: 100 + mirror: always + +# By default user input handling delay is disabled (mostly) for testing , +# this is used for forcefully enable it for certain tests. +- name: dom.input_events.security.isUserInputHandlingDelayTest + type: bool + value: false + mirror: always + +# The maximum time (milliseconds) we reserve for handling input events in each +# frame. +- name: dom.input_event_queue.duration.max + type: uint32_t + value: 8 + mirror: always + +# Enable not moving the cursor to end when a text input or textarea has .value +# set to the value it already has. By default, enabled. +- name: dom.input.skip_cursor_move_for_same_value_set + type: bool + value: true + mirror: always + +# How often to check for CPOW timeouts (ms). CPOWs are only timed +# out by the hang monitor. +- name: dom.ipc.cpow.timeout + type: uint32_t + value: 500 + mirror: always + +#ifdef MOZ_ENABLE_FORKSERVER +- name: dom.ipc.forkserver.enable + type: bool + value: false + mirror: once +#endif + +#ifdef MOZ_WIDGET_GTK +# +# Avoid the use of GTK in content processes if possible, by running +# them in headless mode, to conserve resources (e.g., connections to +# the X server). See the usage in `ContentParent.cpp` for the full +# definition of "if possible". +# +# This does not affect sandbox policies; content processes may still +# dynamically connect to the display server for, e.g., WebGL. +- name: dom.ipc.avoid-gtk + type: bool + value: true + mirror: always +#endif + +# Whether or not to collect a paired minidump when force-killing a +# content process. +- name: dom.ipc.tabs.createKillHardCrashReports + type: bool + value: @IS_NOT_RELEASE_OR_BETA@ + mirror: once + +# Enable e10s hang monitoring (slow script checking and plugin hang detection). +- name: dom.ipc.processHangMonitor + type: bool + value: true + mirror: once + +# Whether we report such process hangs +- name: dom.ipc.reportProcessHangs + type: RelaxedAtomicBool +# Don't report hangs in DEBUG builds. They're too slow and often a +# debugger is attached. +#ifdef DEBUG + value: false +#else + value: true +#endif + mirror: always + +# Process launch delay (in milliseconds). +- name: dom.ipc.processPrelaunch.delayMs + type: uint32_t +# This number is fairly arbitrary ... the intention is to put off +# launching another app process until the last one has finished +# loading its content, to reduce CPU/memory/IO contention. + value: 1000 + mirror: always + +- name: dom.ipc.processPrelaunch.startupDelayMs + type: uint32_t +# delay starting content processes for a short time after browser start +# to provide time for the UI to come up + value: 1000 + mirror: always + +# Process preallocation cache +# Only used in fission; in e10s we use 1 always +- name: dom.ipc.processPrelaunch.fission.number + type: uint32_t + value: 3 + mirror: always + +# Limit preallocated processes below this memory size (in MB) +- name: dom.ipc.processPrelaunch.lowmem_mb + type: uint32_t + value: 4096 + mirror: always + +- name: dom.ipc.processPriorityManager.enabled + type: bool + value: true + mirror: always + +- name: dom.ipc.processPriorityManager.testMode + type: bool + value: false + mirror: always + +- name: dom.ipc.processPriorityManager.backgroundPerceivableGracePeriodMS + type: uint32_t +#if defined(MOZ_WIDGET_ANDROID) && defined(NIGHTLY_BUILD) + value: 3000 +#else + value: 0 +#endif + mirror: always + +- name: dom.ipc.processPriorityManager.backgroundGracePeriodMS + type: uint32_t +#if defined(MOZ_WIDGET_ANDROID) && defined(NIGHTLY_BUILD) + value: 3000 +#else + value: 0 +#endif + mirror: always + +#ifdef XP_WIN +- name: dom.ipc.processPriorityManager.backgroundUsesEcoQoS + type: bool + value: false + mirror: always +#endif + +# Support for input type=month, type=week. By default, disabled. +- name: dom.forms.datetime.others + type: bool + value: @IS_ANDROID@ + mirror: always + +- name: dom.forms.always_allow_pointer_events.enabled + type: bool + value: true + mirror: always + +# Is support for key events and focus events on disabled elements enabled? +- name: dom.forms.always_allow_key_and_focus_events.enabled + type: bool + value: @IS_EARLY_BETA_OR_EARLIER@ + mirror: always + +# Whether to disable only the descendants or the parent fieldset element too +# Note that this still allows it to be selected by `:disable`. +- name: dom.forms.fieldset_disable_only_descendants.enabled + type: bool + value: @IS_EARLY_BETA_OR_EARLIER@ + mirror: always + +# Whether to allow or disallow web apps to cancel `beforeinput` events caused +# by MozEditableElement#setUserInput() which is used by autocomplete, autofill +# and password manager. +- name: dom.input_event.allow_to_cancel_set_user_input + type: bool + value: false + mirror: always + +# How long a content process can take before closing its IPC channel +# after shutdown is initiated. If the process exceeds the timeout, +# we fear the worst and kill it. +- name: dom.ipc.tabs.shutdownTimeoutSecs + type: RelaxedAtomicUint32 +#if !defined(DEBUG) && !defined(MOZ_ASAN) && !defined(MOZ_VALGRIND) && !defined(MOZ_TSAN) + value: 20 +#else + value: 0 +#endif + mirror: always + +# Whether a native event loop should be used in the content process. +- name: dom.ipc.useNativeEventProcessing.content + type: RelaxedAtomicBool +#if defined(XP_WIN) || defined(XP_MACOSX) + value: false +#else + value: true +#endif + mirror: always + +# If this is true, TextEventDispatcher dispatches keydown and keyup events +# even during composition (keypress events are never fired during composition +# even if this is true). +- name: dom.keyboardevent.dispatch_during_composition + type: bool + value: true + mirror: always + +# Enable/disable KeyboardEvent.initKeyEvent function +- name: dom.keyboardevent.init_key_event.enabled + type: bool + value: false + mirror: always + +# Enable/disable KeyboardEvent.initKeyEvent function in addons even if it's +# disabled. +- name: dom.keyboardevent.init_key_event.enabled_in_addons + type: bool + value: @IS_NOT_NIGHTLY_BUILD@ + mirror: always + +# If this is true, keypress events for non-printable keys are dispatched only +# for event listeners of the system event group in web content. +- name: dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content + type: bool + value: true + mirror: always + +# If this is true, "keypress" event's keyCode value and charCode value always +# become same if the event is not created/initialized by JS. +- name: dom.keyboardevent.keypress.set_keycode_and_charcode_to_same_value + type: bool + value: true + mirror: always + +# Whether "W3C Web Manifest" processing is enabled +- name: dom.manifest.enabled + type: bool + value: true + mirror: always + +# Enable mapped array buffer by default. +- name: dom.mapped_arraybuffer.enabled + type: bool + value: true + mirror: always + +# Autoplay Policy Detection https://w3c.github.io/autoplay/ +- name: dom.media.autoplay-policy-detection.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# WebCodecs API +- name: dom.media.webcodecs.enabled + type: RelaxedAtomicBool + value: @IS_NIGHTLY_BUILD@ + mirror: always + +- name: dom.media.webcodecs.force-osx-h264-enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Number of seconds of very quiet or silent audio before considering the audio +# inaudible. +- name: dom.media.silence_duration_for_audibility + type: AtomicFloat + value: 2.0f + mirror: always + +# Inform mozjemalloc that the foreground content processes can keep more dirty +# pages in memory. +- name: dom.memory.foreground_content_processes_have_larger_page_cache + type: bool + value: true + mirror: always + +# Enable meta-viewport support in remote APZ-enabled frames. +- name: dom.meta-viewport.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Timeout clamp in ms for timeouts we clamp. +- name: dom.min_timeout_value + type: RelaxedAtomicInt32 + value: 4 + mirror: always + +# Timeout clamp in ms for background windows. +- name: dom.min_background_timeout_value + type: int32_t + value: 1000 + mirror: always + +# Timeout clamp in ms for background windows when throttling isn't enabled. +- name: dom.min_background_timeout_value_without_budget_throttling + type: int32_t + value: 1000 + mirror: always + +# Are missing-property use counters for certain DOM attributes enabled? +- name: dom.missing_prop_counters.enabled + type: bool + value: true + mirror: always + +# Whether we disable triggering mutation events for changes to style +# attribute via CSSOM. +# NOTE: This preference is used in unit tests. If it is removed or its default +# value changes, please update test_sharedMap_static_prefs.js accordingly. +- name: dom.mutation-events.cssom.disabled + type: bool + value: true + mirror: always + +# Limit of location change caused by content scripts in a time span per +# BrowsingContext. This includes calls to History and Location APIs. +- name: dom.navigation.locationChangeRateLimit.count + type: uint32_t + value: 200 + mirror: always + +# Time span in seconds for location change rate limit. +- name: dom.navigation.locationChangeRateLimit.timespan + type: uint32_t + value: 10 + mirror: always + +# Network Information API +# This feature is not available on Firefox desktop. It exposes too much +# user information. Let's be consistent and disable it on Android. +# But let's keep it around in case it becomes necessary for webcompat +# reasons +# https://bugzilla.mozilla.org/show_bug.cgi?id=1637922 +- name: dom.netinfo.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Whether we should open noopener links in a new process. +- name: dom.noopener.newprocess.enabled + type: bool + value: true + mirror: always + +# Whether we shouldn't show an error page for unknown protocols (and should +# show a console warning instead). +- name: dom.no_unknown_protocol_error.enabled + type: bool + value: true + mirror: always + +# Whether origin trials are enabled. +- name: dom.origin-trials.enabled + type: bool + value: true + mirror: always + +# Whether we use the test key to verify tokens. +- name: dom.origin-trials.test-key.enabled + type: bool + value: false + mirror: always + +# Origin trial state for "TestTrial". +# 0: normal, 1: always-enabled, 2: always-disabled +- name: dom.origin-trials.test-trial.state + type: RelaxedAtomicInt32 + value: 0 + mirror: always + +# Origin trial state for COEP: Credentialless. +# 0: normal, 1: always-enabled, 2: always-disabled +- name: dom.origin-trials.coep-credentialless.state + type: RelaxedAtomicInt32 + value: 0 + mirror: always + +# Is support for Window.paintWorklet enabled? +- name: dom.paintWorklet.enabled + type: bool + value: false + mirror: always + +# Enable/disable the PaymentRequest API +- name: dom.payments.request.enabled + type: bool + value: false + mirror: always + +# Whether a user gesture is required to call PaymentRequest.prototype.show(). +- name: dom.payments.request.user_interaction_required + type: bool + value: true + mirror: always + +# Time in milliseconds for PaymentResponse to wait for +# the Web page to call complete(). +- name: dom.payments.response.timeout + type: uint32_t + value: 5000 + mirror: always + +# Enable printing performance marks/measures to log +- name: dom.performance.enable_user_timing_logging + type: RelaxedAtomicBool + value: false + mirror: always + +# Enable notification of performance timing +- name: dom.performance.enable_notify_performance_timing + type: bool + value: false + mirror: always + +# Is support for PerformanceTiming.timeToContentfulPaint enabled? +- name: dom.performance.time_to_contentful_paint.enabled + type: bool + value: false + mirror: always + +# Is support for PerformanceTiming.timeToDOMContentFlushed enabled? +- name: dom.performance.time_to_dom_content_flushed.enabled + type: bool + value: false + mirror: always + +# Is support for PerformanceTiming.timeToFirstInteractive enabled? +- name: dom.performance.time_to_first_interactive.enabled + type: bool + value: false + mirror: always + +# Is support for PerformanceTiming.timeToNonBlankPaint enabled? +- name: dom.performance.time_to_non_blank_paint.enabled + type: bool + value: false + mirror: always + +# Is support for Permissions.revoke enabled? +- name: dom.permissions.revoke.enable + type: bool + value: false + mirror: always + +# Is support for Element.requestPointerLock enabled? +# This is added for accessibility purpose. When user has no way to exit +# pointer lock (e.g. no keyboard available), they can use this pref to +# disable the Pointer Lock API altogether. +- name: dom.pointer-lock.enabled + type: bool + value: true + mirror: always + +# re-SAB: Whether to allow postMessage of a SharedArrayBuffer if various +# preconditions related to COOP and COEP are met +- name: dom.postMessage.sharedArrayBuffer.withCOOP_COEP + type: bool + value: true + mirror: once + +# Overridden in all.js on RELEASE_OR_BETA in order to add the locked attribute. +- name: dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# This currently only affects XHTML. For XUL the cache is always allowed. +- name: dom.prototype_document_cache.enabled + type: bool + value: true + mirror: always + +# Push +- name: dom.push.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# This enables the SVGPathSeg APIs +- name: dom.svg.pathSeg.enabled + type: bool + value: false + mirror: always + +# Preference that is primarily used for testing of problematic file paths. +# It can also be used for switching between different storage directories, but +# such feature is not officially supported. +- name: dom.quotaManager.storageName + type: String + value: "storage" + mirror: never + +# An upper limit for the "age" of an origin. Any origin which is older than the +# threshold is considered as unaccessed. That doesn't necessarily mean that +# such origins will be immediatelly archived. They will be archived only when +# dom.quotaManager.checkQuotaInfoLoadTime is true and loading of quota info +# takes a long time (dom.quotaManager.longQuotaInfoLoadTimeThresholdMs is used +# to decide what is a long quota info load time). +- name: dom.quotaManager.unaccessedForLongTimeThresholdSec + type: RelaxedAtomicUint32 + value: 33696000 # 13 months + mirror: always + +# Should we try to load origin information from the cache? +# See bug 1563023 for more details. +- name: dom.quotaManager.loadQuotaFromCache + type: RelaxedAtomicBool + value: true + mirror: always + +# Should we check build ID as part of the cache validation? +# When enabled, the cache is invalidated on any upgrade (or downgrade), +# ensuring that changes in how quota usage is calculated can't cause +# inconsistencies at the cost of a slower initialization. Currently, this +# should only be set to false in tests using a packaged profile that inherently +# includes a build id different from the building running the tests. In the +# future this may be set to false if we are confident that we have sufficiently +# thorough schema versioning. +- name: dom.quotaManager.caching.checkBuildId + type: RelaxedAtomicBool + value: true + mirror: always + +# Should we check quota info load time and eventually archive some unaccessed +# origins if loading of quota info takes a long time ? +- name: dom.quotaManager.checkQuotaInfoLoadTime + type: RelaxedAtomicBool + value: true + mirror: always + +# An upper limit for quota info load time, anything which takes longer than the +# threshold is considered as long quota info load time. +- name: dom.quotaManager.longQuotaInfoLoadTimeThresholdMs + type: RelaxedAtomicUint32 + value: 21000 # 21 seconds + mirror: always + +# Preference that users can set to override temporary storage smart limit +# calculation. +- name: dom.quotaManager.temporaryStorage.fixedLimit + type: RelaxedAtomicInt32 + value: -1 + mirror: always + +# A pref that is used to enable testing features. +- name: dom.quotaManager.testing + type: SequentiallyConsistentAtomicBool + value: false + mirror: always + +#if defined(XP_WIN) + # Preference that is used to set nsILocalFileWin::useDOSDevicePathSyntax + # attribute for all local file instances created by QuotaManager and its + # clients. The value of this preference is cached so changing the preference + # during runtime has no effect. + # See bug 1626846 for setting this to false by default. +- name: dom.quotaManager.useDOSDevicePathSyntax + type: RelaxedAtomicBool + value: true + mirror: always + do_not_use_directly: true + + # Preference that is used to enable the hack for overrriding xFullPathname in + # QuotaVFS. +- name: dom.quotaManager.overrideXFullPathname + type: RelaxedAtomicBool + value: true + mirror: always +#elif defined(XP_UNIX) + # Preference that is used to enable the overriding of Unix xFullPathname + # implementation in QuotaVFS. +- name: dom.quotaManager.overrideXFullPathnameUnix + type: RelaxedAtomicBool + value: true + mirror: always +#endif + +# How many times we should retry directory removal or renaming if access was +# denied? +- name: dom.quotaManager.directoryRemovalOrRenaming.maxRetries + type: RelaxedAtomicUint32 +#ifdef XP_WIN + value: 10 +#else + value: 0 +#endif + mirror: always + +# How long we should wait between retries (in milliseconds)? +- name: dom.quotaManager.directoryRemovalOrRenaming.delayMs + type: RelaxedAtomicUint32 + value: 200 + mirror: always + +#ifdef MOZ_BACKGROUNDTASKS +# Use a Background Task to delete files at shutdown. +- name: dom.quotaManager.backgroundTask.enabled + type: bool +#ifdef XP_MACOSX +# Needs to figure out how to prevent bug 1827486. + value: false +#else + value: true +#endif + mirror: never +#endif + +# Use to control to dump CheckedUnsafePtr creation stack and assignment stacks. +- name: dom.checkedUnsafePtr.dumpStacks.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Determines within what distance of a tick mark, in pixels, dragging an input +# range range will snap the range's value to that tick mark. By default, this is +# half the default width of the range thumb. +- name: dom.range_element.magnet_effect_threshold + type: float + value: 10.0f + mirror: always + +# Reporting API. +- name: dom.reporting.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +- name: dom.reporting.testing.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +- name: dom.reporting.featurePolicy.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +- name: dom.reporting.crash.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +- name: dom.reporting.header.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# In seconds. The timeout to remove not-active report-to endpoints. +- name: dom.reporting.cleanup.timeout + type: uint32_t + value: 3600 + mirror: always + +# Any X seconds the reports are dispatched to endpoints. +- name: dom.reporting.delivering.timeout + type: uint32_t + value: 5 + mirror: always + +# How many times the delivering of a report should be tried. +- name: dom.reporting.delivering.maxFailures + type: uint32_t + value: 3 + mirror: always + +# How many reports should be stored in the report queue before being delivered. +- name: dom.reporting.delivering.maxReports + type: uint32_t + value: 100 + mirror: always + +# Enable Screen Orientation lock +- name: dom.screenorientation.allow-lock + type: bool + value: @IS_NIGHTLY_BUILD@ + mirror: always + +# Enable Screen Wake Lock API +- name: dom.screenwakelock.enabled + type: bool + value: @IS_EARLY_BETA_OR_EARLIER@ + mirror: always + +# Whether to enable the JavaScript start-up cache. This causes one of the first +# execution to record the bytecode of the JavaScript function used, and save it +# in the existing cache entry. On the following loads of the same script, the +# bytecode would be loaded from the cache instead of being generated once more. +- name: dom.script_loader.bytecode_cache.enabled + type: bool + value: true + mirror: always + +# Ignore the heuristics of the bytecode cache, and always record on the first +# visit. (used for testing purposes). + +# Choose one strategy to use to decide when the bytecode should be encoded and +# saved. The following strategies are available right now: +# * -2 : (reader mode) The bytecode cache would be read, but it would never +# be saved. +# * -1 : (eager mode) The bytecode would be saved as soon as the script is +# seen for the first time, independently of the size or last access +# time. +# * 0 : (default) The bytecode would be saved in order to minimize the +# page-load time. +# +# Other values might lead to experimental strategies. For more details, have a +# look at: ScriptLoader::ShouldCacheBytecode function. +- name: dom.script_loader.bytecode_cache.strategy + type: int32_t + value: 0 + mirror: always + +# Select which parse/delazification strategy should be used while parsing +# scripts off-main-thread. (see CompileOptions.h, DelazificationOption enum) +# +# 0: On-demand only. Delazification will be triggered only on the main thread +# before the execution of the function. +# +# 1: Compare on-demand delazification (= 0) with concurrent depth-first +# delazification (= 2). +# +# 2: Depth-first. Delazify all functions off-thread in the order of appearance +# in the source. +# +# 3: Large-first. Delazify all functions off-thread starting with the largest +# functions first, and the smallest as the last one to be delazified, where +# the size of function is measured in bytes between the start to the end of +# the function. +# +# 255: Parse everything eagerly, from the first parse. All functions are parsed +# at the same time as the top-level of a file. +- name: dom.script_loader.delazification.strategy + type: uint32_t + value: 255 + mirror: always + +# Maximum total size after which the delazification strategy, specified by +# `dom.script_loader.delazification.strategy`, is no longer applied, and the +# on-demand strategy is used by default. +# +# -1 disable the threshold, and delazification strategy is applied to all +# scripts. +# +# Default value is 10MB for utf8 scripts. +- name: dom.script_loader.delazification.max_size + type: int32_t + value: 10485760 + mirror: always + +# Minimum memory, in GB, required to enable delazification strategy, specified +# by `dom.script_loader.delazification.strategy`. Otherwise, the on-demand +# delazification strategy is used. +- name: dom.script_loader.delazification.min_mem + type: int32_t + value: 2 + mirror: always + +# Enable speculative off main thread parsing of external scripts as +# soon as they are fetched. +- name: dom.script_loader.external_scripts.speculative_omt_parse.enabled + type: bool + value: true + mirror: always + +# Speculatively compile non parser inserted scripts +- name: dom.script_loader.external_scripts.speculate_non_parser_inserted.enabled + type: bool + value: false + mirror: always + +# Speculatively compile async scripts +- name: dom.script_loader.external_scripts.speculate_async.enabled + type: bool + value: false + mirror: always + +# Speculatively compile link preload scripts +- name: dom.script_loader.external_scripts.speculate_link_preload.enabled + type: bool + value: false + mirror: always + +- name: dom.securecontext.allowlist_onions + type: bool + value: false + mirror: always + +# This pref enables the featurePolicy header support. +- name: dom.security.featurePolicy.header.enabled + type: bool + value: false + mirror: always + +- name: dom.security.featurePolicy.experimental.enabled + type: bool + value: false + mirror: always + +# Expose the 'featurePolicy' attribute in document and HTMLIFrameElement +- name: dom.security.featurePolicy.webidl.enabled + type: bool + value: false + mirror: always + +# Perform IPC based Principal vetting in ContentParent +- name: dom.security.enforceIPCBasedPrincipalVetting + type: RelaxedAtomicBool + value: true + mirror: always + +# For testing purposes only: Flipping this pref to true allows +# to skip the allowlist for about: pages and do not ship with a +# CSP and NS_ASSERT right away. +- name: dom.security.skip_about_page_csp_allowlist_and_assert + type: RelaxedAtomicBool + value: false + mirror: always + +# For testing purposes only: Flipping this pref to true allows +# to skip the assertion that every about page ships with a CSP. +- name: dom.security.skip_about_page_has_csp_assert + type: RelaxedAtomicBool + value: false + mirror: always + +# For testing purposes only: Flipping this pref to true allows +# to skip the assertion that HTML fragments (e.g. innerHTML) can +# not be used within chrome code or about: pages. +- name: dom.security.skip_html_fragment_assertion + type: RelaxedAtomicBool + value: false + mirror: always + +# For testing purposes only; Flipping this pref to true allows +# to skip the assertion that remote scripts can not be loaded +# in system privileged contexts. +- name: dom.security.skip_remote_script_assertion_in_system_priv_context + type: RelaxedAtomicBool + value: false + mirror: always + +# If true, all content requests will get upgraded to HTTPS:// +# (some Firefox functionality requests, like OCSP will not be affected) +- name: dom.security.https_only_mode + type: RelaxedAtomicBool + value: false + mirror: always + +# If true, all content requests in Private Browsing Mode will get +# upgraded to HTTPS://. (If dom.security.https_only_mode is set +# to true then this pref has no effect) +- name: dom.security.https_only_mode_pbm + type: RelaxedAtomicBool + value: false + mirror: always + +# If true, sends http background request for top-level sites to +# counter long timeouts. +- name: dom.security.https_only_mode_send_http_background_request + type: RelaxedAtomicBool + value: true + mirror: always + +# Time limit, in milliseconds, before sending the http background +# request for HTTPS-Only and HTTPS-First +- name: dom.security.https_only_fire_http_request_background_timer_ms + type: RelaxedAtomicUint32 + value: 3000 + mirror: always + +# If true, tries to break upgrade downgrade cycles where https-only tries +# to upgrad ethe connection, but the website tries to downgrade again. +- name: dom.security.https_only_mode_break_upgrade_downgrade_endless_loop + type: RelaxedAtomicBool + value: true + mirror: always + +# If true, when checking if it's upgrade downgrade cycles, the URI path will be +# also checked. +- name: dom.security.https_only_check_path_upgrade_downgrade_endless_loop + type: RelaxedAtomicBool + value: true + mirror: always + +# If true and HTTPS-only mode is enabled, requests +# to local IP addresses are also upgraded +- name: dom.security.https_only_mode.upgrade_local + type: RelaxedAtomicBool + value: false + mirror: always + +# If true and HTTPS-only mode is enabled, requests +# to .onion hosts are also upgraded +- name: dom.security.https_only_mode.upgrade_onion + type: RelaxedAtomicBool + value: false + mirror: always + +# WARNING: Don't ever update that pref manually! It is only used +# for telemetry purposes and allows to reason about retention of +# the pref dom.security.https_only_mode from above. +- name: dom.security.https_only_mode_ever_enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# WARNING: Don't ever update that pref manually! It is only used +# for telemetry purposes and allows to reason about retention of +# the pref dom.security.https_only_mode_pbm from above. +- name: dom.security.https_only_mode_ever_enabled_pbm + type: RelaxedAtomicBool + value: false + mirror: always + +# If true checks for secure www connections when https fails +# and gives the user suggestions on the error page +- name: dom.security.https_only_mode_error_page_user_suggestions + type: RelaxedAtomicBool + value: false + mirror: always + +# If true, top-level request will get upgraded to HTTPS and +# downgraded again if the request failed. +- name: dom.security.https_first + type: RelaxedAtomicBool + value: false + mirror: always + +# If true, top-level requests in Private Browsing Mode will get +# upgraded to HTTPS. (If dom.security.https_first +# is set to true then this pref has no effect) +- name: dom.security.https_first_pbm + type: RelaxedAtomicBool + value: true + mirror: always + +# If true, top-level requests that are initiated from the address +# bar and with an empty scheme get upgraded to HTTPS +# with a fallback +- name: dom.security.https_first_schemeless + type: RelaxedAtomicBool + value: @IS_EARLY_BETA_OR_EARLIER@ + mirror: always + +- name: dom.security.unexpected_system_load_telemetry_enabled + type: bool + value: true + mirror: always + +# pref controls `Sanitizer` API being exposed +# https://wicg.github.io/sanitizer-api/ +- name: dom.security.sanitizer.enabled + type: bool + value: false + mirror: always + +# Pref that controls the Element.setHTML API idenpendetly of the sanitizer +# API. +- name: dom.security.setHTML.enabled + type: bool + value: false + mirror: always + +# Logs elements and attributes removed by the Sanitizer API to the console. +- name: dom.security.sanitizer.logging + type: bool + value: false + mirror: always + +# pref controls `identity` credentials being exposed +- name: dom.security.credentialmanagement.identity.enabled + type: bool + value: false + mirror: always + +# pref controls `identity` credential UI for testing. When true, UI is not shown and +# the first option in the account and provider lists are chosen +- name: dom.security.credentialmanagement.identity.select_first_in_ui_lists + type: bool + value: false + mirror: always + +# pref controls `identity` credential platform behavior for testing. When true, +# the .well-known file check is not performed. +- name: dom.security.credentialmanagement.identity.test_ignore_well_known + type: bool + value: false + mirror: always + +# pref controls whether we should delay identity credential rejections at all +- name: dom.security.credentialmanagement.identity.reject_delay.enabled + type: bool + value: true + mirror: always + +# pref controls how long we should delay identity credential rejections if enabled +- name: dom.security.credentialmanagement.identity.reject_delay.duration_ms + type: uint32_t + value: 120000 + mirror: always + +# SetDocumentURI security option, enforces origin check +- name: dom.security.setdocumenturi + type: bool + value: @IS_EARLY_BETA_OR_EARLIER@ + mirror: always + +# Whether or not selection events on text controls are enabled. +- name: dom.select_events.textcontrols.selectionchange.enabled + type: bool + value: true + mirror: always + +- name: dom.select_events.textcontrols.selectstart.enabled + type: bool + value: false + mirror: always + +- name: dom.select.showPicker.enabled + type: bool + value: true + mirror: always + +- name: dom.send_after_paint_to_content + type: bool + value: false + mirror: always + +- name: dom.separate_event_queue_for_post_message.enabled + type: bool + value: true + mirror: always + +- name: dom.arena_allocator.enabled + type: bool + value: true + mirror: once + +- name: dom.serviceWorkers.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: dom.serviceWorkers.navigationPreload.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Mitigates ServiceWorker navigation faults by bypassing the ServiceWorker on +# navigation faults. This is more extensive than just resetting interception +# because we also mark the page as uncontrolled so that subresources will not +# go to the ServiceWorker either. +- name: dom.serviceWorkers.mitigations.bypass_on_fault + type: bool + value: true + mirror: always + +# Additional ServiceWorker navigation mitigation control to unregister the +# ServiceWorker after multiple faults are encountered. The mitigation is +# disabled when this is set to zero, otherwise this is the number of faults that +# need to occur for a specific ServiceWorker before it will be unregistered. +- name: dom.serviceWorkers.mitigations.navigation_fault_threshold + type: uint32_t + value: 3 + mirror: always + +# This is the group usage head room for service workers. +# The quota usage mitigation algorithm uses this preference to determine if the +# origin or also group data should be cleared or not. +# The default value is 400 MiB. +- name: dom.serviceWorkers.mitigations.group_usage_headroom_kb + type: uint32_t + value: 400 * 1024 + mirror: always + +- name: dom.serviceWorkers.testing.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Whether ServiceWorkerManager should persist the service worker +# registered by temporary installed extension (only meant to be used +# for testing purpose, to make it easier to test some particular scenario +# with a temporary installed addon, which doesn't need to be signed to be +# installed on release channel builds). +- name: dom.serviceWorkers.testing.persistTemporarilyInstalledAddons + type: RelaxedAtomicBool + value: false + mirror: always + +- name: dom.storage.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# ReadableStream.from(asyncIterable) +- name: dom.streams.from.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: dom.workers.pFetch.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: dom.workers.importScripts.enforceStrictMimeType + type: RelaxedAtomicBool + value: true + mirror: always + +# Is support for modules (new Worker(..., {type: "module"})) enabled for workers? +- name: dom.workers.modules.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: dom.workers.serialized-sab-access + type: RelaxedAtomicBool + value: false + mirror: always + +# Enable stronger diagnostics on worker shutdown. +# If this is true, we will potentially run an extra GCCC when a worker should +# exit its DoRunLoop but holds any WorkerRef and we will MOZ_DIAGNOSTIC_ASSERT +# when during that extra GCCC such a WorkerRef is freed. +- name: dom.workers.GCCC_on_potentially_last_event + type: RelaxedAtomicBool +#if defined(FUZZING) || defined(DEBUG) + value: true +#else + value: false +#endif + mirror: always + +- name: dom.sitepermsaddon-provider.enabled + type: bool + value: @IS_NOT_ANDROID@ + mirror: always + +# Whether automatic storage access granting heuristics should be turned on. +- name: dom.storage_access.auto_grants + type: bool + value: true + mirror: always + +- name: dom.storage_access.auto_grants.delayed + type: bool + value: true + mirror: always + +# Storage-access API. +- name: dom.storage_access.enabled + type: bool + value: true + mirror: always + +# Forward-Declared Storage-access API. +- name: dom.storage_access.forward_declared.enabled + type: bool + value: false + mirror: always + +# How long the Forward-Declared Storage-access API allows between pair requests +# in seconds +- name: dom.storage_access.forward_declared.lifetime + type: uint32_t + value: 15 * 60 + mirror: always + +# The maximum number of origins that a given third-party tracker is allowed +# to have concurrent access to before the user is presented with a storage +# access prompt. Only effective when the auto_grants pref is turned on. +- name: dom.storage_access.max_concurrent_auto_grants + type: int32_t + value: 5 + mirror: always + +- name: dom.storage_access.frame_only + type: bool + value: true + mirror: always + +# Only grant storage access to secure contexts. +- name: dom.storage_access.dont_grant_insecure_contexts + type: RelaxedAtomicBool + value: true + mirror: always + +# Whether the File System API is enabled +- name: dom.fs.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Whether the WritableFileStream is enabled or disabled. +- name: dom.fs.writable_file_stream.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# LocalStorage data limit as determined by summing up the lengths of all string +# keys and values. This is consistent with the legacy implementation and other +# browser engines. This value should really only ever change in unit testing +# where being able to lower it makes it easier for us to test certain edge +# cases. Measured in KiBs. +- name: dom.storage.default_quota + type: RelaxedAtomicUint32 + # Only allow relatively small amounts of data since performance of the + # synchronous IO is very bad. We are enforcing simple per-origin quota only. + value: 5 * 1024 + mirror: always + +# Per-site quota for legacy LocalStorage implementation. +- name: dom.storage.default_site_quota + type: RelaxedAtomicUint32 + value: 25 * 1024 + mirror: always + +# Whether or not the unsupported legacy implemenation should be enabled. Please +# don't advertise this pref as a way for disabling LSNG. This pref is intended +# for internal testing only and will be removed in near future. Accidental +# disabling of LSNG can lead to a data loss in a combination with disabled +# shadow writes. Disabling of shadow writes is the initial step towards +# removing legacy implementation and will be done soon. +- name: dom.storage.enable_unsupported_legacy_implementation + type: RelaxedAtomicBool + value: false + mirror: always + do_not_use_directly: true + +# The amount of snapshot peak usage which is attempted to be pre-incremented +# during snapshot creation. +- name: dom.storage.snapshot_peak_usage.initial_preincrement + type: RelaxedAtomicUint32 + value: 16384 + mirror: always + +# The amount of snapshot peak usage which is attempted to be pre-incremented +# during snapshot creation if the LocalStorage usage was already close to the +# limit (a fallback for dom.storage.snapshot_peak_usage.initial_preincrement). +- name: dom.storage.snapshot_peak_usage.reduced_initial_preincrement + type: RelaxedAtomicUint32 + value: 4096 + mirror: always + +# The amount of snapshot peak usage which is attempted to be pre-incremented +# beyond the specific values which are subsequently requested after snapshot +# creation. +- name: dom.storage.snapshot_peak_usage.gradual_preincrement + type: RelaxedAtomicUint32 + value: 4096 + mirror: always + +# The amount of snapshot peak usage which is attempted to be pre-incremented +# beyond the specific values which are subsequently requested after snapshot +# creation if the LocalStorage total usage was already close to the limit +# (a fallback for dom.storage.snapshot_peak_usage.gradual_preincrement). +- name: dom.storage.snapshot_peak_usage.reduced_gradual_preincrement + type: RelaxedAtomicUint32 + value: 1024 + mirror: always + +# How long between a snapshot becomes idle and when we actually finish the +# snapshot. This preference is only used when "dom.storage.snapshot_reusing" +# is true. +- name: dom.storage.snapshot_idle_timeout_ms + type: uint32_t + value: 5000 + mirror: always + +# Is support for Storage test APIs enabled? +- name: dom.storage.testing + type: bool + value: false + mirror: always + +# For area and anchor elements with target=_blank and no rel set to +# opener/noopener. +- name: dom.targetBlankNoOpener.enabled + type: bool + value: true + mirror: always + +# Is support for Selection.GetRangesForInterval enabled? +- name: dom.testing.selection.GetRangesForInterval + type: bool + value: false + mirror: always + +- name: dom.testing.structuredclonetester.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +- name: dom.testing.sync-content-blocking-notifications + type: bool + value: false + mirror: always + +# To enable TestUtils interface on WPT +- name: dom.testing.testutils.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +- name: dom.textMetrics.actualBoundingBox.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: dom.textMetrics.baselines.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: dom.textMetrics.emHeight.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: dom.textMetrics.fontBoundingBox.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Time (in ms) that it takes to regenerate 1ms. +- name: dom.timeout.background_budget_regeneration_rate + type: int32_t + value: 100 + mirror: always + +# Time (in ms) that it takes to regenerate 1ms. +- name: dom.timeout.foreground_budget_regeneration_rate + type: int32_t + value: 1 + mirror: always + +# Maximum value (in ms) for the background budget. Only valid for +# values greater than 0. +- name: dom.timeout.background_throttling_max_budget + type: int32_t + value: 50 + mirror: always + +# Maximum value (in ms) for the foreground budget. Only valid for +# values greater than 0. +- name: dom.timeout.foreground_throttling_max_budget + type: int32_t + value: -1 + mirror: always + +# The maximum amount a timeout can be delayed by budget throttling. +- name: dom.timeout.budget_throttling_max_delay + type: int32_t + value: 15000 + mirror: always + +# Turn on budget throttling by default. +- name: dom.timeout.enable_budget_timer_throttling + type: bool + value: true + mirror: always + +# Should we defer timeouts and intervals while loading a page. Released +# on Idle or when the page is loaded. +- name: dom.timeout.defer_during_load + type: bool + value: true + mirror: always + +# Maximum amount of time in milliseconds consecutive setTimeout()/setInterval() +# callback are allowed to run before yielding the event loop. +- name: dom.timeout.max_consecutive_callbacks_ms + type: uint32_t + value: 4 + mirror: always + +# Maximum deferral time for setTimeout/Interval in milliseconds +- name: dom.timeout.max_idle_defer_ms + type: uint32_t + value: 10*1000 + mirror: always + +# Delay in ms from document load until we start throttling background timeouts. +- name: dom.timeout.throttling_delay + type: int32_t + value: 30000 + mirror: always + +# UDPSocket API +- name: dom.udpsocket.enabled + type: bool + value: false + mirror: always + +# Whether to dump worker use counters +- name: dom.use_counters.dump.worker + type: RelaxedAtomicBool + value: false + mirror: always + +# Whether to dump document use counters +- name: dom.use_counters.dump.document + type: bool + value: false + mirror: always + +# Whether to dump page use counters +- name: dom.use_counters.dump.page + type: bool + value: false + mirror: always + +# Time limit, in milliseconds, for user gesture transient activation. +- name: dom.user_activation.transient.timeout + type: uint32_t + value: 5000 + mirror: always + +# Whether to treat the clicks on scrollbars as user interaction with web content. +- name: dom.user_activation.ignore_scrollbars + type: bool + value: true + mirror: always + +# Whether to shim a Components object on untrusted windows. +- name: dom.use_components_shim + type: bool + value: @IS_NOT_NIGHTLY_BUILD@ + mirror: always + +- name: dom.vibrator.enabled + type: bool + value: true + mirror: always + +- name: dom.vibrator.max_vibrate_ms + type: RelaxedAtomicUint32 + value: 10000 + mirror: always + +- name: dom.vibrator.max_vibrate_list_len + type: RelaxedAtomicUint32 + value: 128 + mirror: always + +# Is support for WebVR APIs enabled? +# Disabled everywhere, but not removed. +- name: dom.vr.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Should VR sessions always be reported as supported, without first +# checking for VR runtimes? This will prevent permission prompts +# from being suppressed on machines without VR runtimes and cause +# navigator.xr.isSessionSupported to always report that immersive-vr +# is supported. +- name: dom.vr.always_support_vr + type: RelaxedAtomicBool + value: false + mirror: always + +# Should AR sessions always be reported as supported, without first +# checking for AR runtimes? This will prevent permission prompts +# from being suppressed on machines without AR runtimes and cause +# navigator.xr.isSessionSupported to always report that immersive-ar +# is supported. +- name: dom.vr.always_support_ar + type: RelaxedAtomicBool + value: false + mirror: always + +# It is often desirable to automatically start vr presentation when +# a user puts on the VR headset. This is done by emitting the +# Window.vrdisplayactivate event when the headset's sensors detect it +# being worn. This can result in WebVR content taking over the headset +# when the user is using it outside the browser or inadvertent start of +# presentation due to the high sensitivity of the proximity sensor in some +# headsets, so it is off by default. +- name: dom.vr.autoactivate.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Minimum number of milliseconds that the browser will wait before +# attempting to poll again for connected VR controllers. The browser +# will not attempt to poll for VR controllers until it needs to use them. +- name: dom.vr.controller.enumerate.interval + type: RelaxedAtomicInt32 + value: 1000 + mirror: always + +# The threshold value of trigger inputs for VR controllers. +- name: dom.vr.controller_trigger_threshold + type: AtomicFloat + value: 0.1f + mirror: always + +# Minimum number of milliseconds that the browser will wait before +# attempting to poll again for connected VR displays. The browser +# will not attempt to poll for VR displays until it needs to use +# them, such as when detecting a WebVR site. +- name: dom.vr.display.enumerate.interval + type: RelaxedAtomicInt32 + value: 5000 + mirror: always + +# The number of milliseconds since last frame start before triggering a new +# frame. When content is failing to submit frames on time or the lower level +# VR platform APIs are rejecting frames, it determines the rate at which RAF +# callbacks will be called. +- name: dom.vr.display.rafMaxDuration + type: RelaxedAtomicUint32 + value: 50 + mirror: always + +# Minimum number of milliseconds the browser will wait before attempting +# to re-start the VR service after an enumeration returned no devices. +- name: dom.vr.external.notdetected.timeout + type: RelaxedAtomicInt32 + value: 60000 + mirror: always + +# Minimum number of milliseconds the browser will wait before attempting +# to re-start the VR service after a VR API (eg, OpenVR or Oculus) +# requests that we shutdown and unload its libraries. +# To ensure that we don't interfere with VR runtime software auto-updates, +# we will not attempt to re-load the service until this timeout has elapsed. +- name: dom.vr.external.quit.timeout + type: RelaxedAtomicInt32 + value: 10000 + mirror: always + +# Minimum number of milliseconds that the VR session will be kept +# alive after the browser and content no longer are using the +# hardware. If a VR multitasking environment, this should be set +# very low or set to 0. +- name: dom.vr.inactive.timeout + type: RelaxedAtomicInt32 + value: 5000 + mirror: always + +# Maximum number of milliseconds the browser will wait for content to call +# VRDisplay.requestPresent after emitting vrdisplayactivate during VR +# link traversal. This prevents a long running event handler for +# vrdisplayactivate from later calling VRDisplay.requestPresent, which would +# result in a non-responsive browser in the VR headset. +- name: dom.vr.navigation.timeout + type: RelaxedAtomicInt32 + value: 5000 + mirror: always + +# Oculus device +- name: dom.vr.oculus.enabled + type: RelaxedAtomicBool +#if defined(HAVE_64BIT_BUILD) && !defined(ANDROID) + # We are only enabling WebVR by default on 64-bit builds (Bug 1384459). + value: true +#else + # On Android, this pref is irrelevant. + value: false +#endif + mirror: always + +# When enabled, Oculus sessions may be created with the ovrInit_Invisible +# flag if a page is using tracking but not presenting. When a page +# begins presenting VR frames, the session will be re-initialized without +# the flag. This eliminates the "Firefox not responding" warnings in +# the headset, but might not be compatible with all versions of the Oculus +# runtime. +- name: dom.vr.oculus.invisible.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Minimum number of milliseconds after content has stopped VR presentation +# before the Oculus session is re-initialized to an invisible / tracking +# only mode. If this value is too high, users will need to wait longer +# after stopping WebVR presentation before automatically returning to the +# Oculus home interface. (They can immediately return to the Oculus Home +# interface through the Oculus HUD without waiting this duration) +# If this value is too low, the Oculus Home interface may be visible +# momentarily during VR link navigation. +- name: dom.vr.oculus.present.timeout + type: RelaxedAtomicInt32 + value: 500 + mirror: always + +# OpenVR device +- name: dom.vr.openvr.enabled + type: RelaxedAtomicBool +#if !defined(HAVE_64BIT_BUILD) && !defined(ANDROID) + # We are only enabling WebVR by default on 64-bit builds (Bug 1384459). + value: false +#elif defined(XP_WIN) || defined(XP_MACOSX) + # We enable OpenVR by default for Windows and macOS. + value: true +#else + # See Bug 1310663 (Linux). On Android, this pref is irrelevant. + value: false +#endif + mirror: always + +# OSVR device +- name: dom.vr.osvr.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Pose prediction reduces latency effects by returning future predicted HMD +# poses to callers of the WebVR API. This currently only has an effect for +# Oculus Rift on SDK 0.8 or greater. +- name: dom.vr.poseprediction.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +# Enable a separate process for VR module. +- name: dom.vr.process.enabled + type: bool +#if defined(XP_WIN) + value: true +#else + value: false +#endif + mirror: once + +- name: dom.vr.process.startup_timeout_ms + type: int32_t + value: 5000 + mirror: once + +# Puppet device, used for simulating VR hardware within tests and dev tools. +- name: dom.vr.puppet.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Starting VR presentation is only allowed within a user gesture or event such +# as VRDisplayActivate triggered by the system. dom.vr.require-gesture allows +# this requirement to be disabled for special cases such as during automated +# tests or in a headless kiosk system. +- name: dom.vr.require-gesture + type: RelaxedAtomicBool + value: true + mirror: always + +# Is support for WebXR APIs enabled? +- name: dom.vr.webxr.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Points in the native bounds geometry are required to be quantized +# sufficiently to prevent fingerprinting. The WebXR spec suggests +# quantizing to the nearest 5 centimeters. +- name: dom.vr.webxr.quantization + type: AtomicFloat + value: 0.05f + mirror: always + +#ifdef XP_WIN + # Control firing WidgetMouseEvent by handling Windows pointer messages or + # mouse messages. +- name: dom.w3c_pointer_events.dispatch_by_pointer_messages + type: bool + value: true + mirror: always + +- name: dom.w3c_pointer_events.scroll_by_pen.enabled + type: bool + value: true + mirror: always +#endif + +# If the value is >= 0, it will be used for max touch points in child processes. +- name: dom.maxtouchpoints.testing.value + type: int32_t + value: -1 + mirror: always + +# Maximum value of navigator.hardwareConcurrency. +- name: dom.maxHardwareConcurrency + type: RelaxedAtomicUint32 +#ifdef NIGHTLY_BUILD + value: 128 +#else + value: 16 +#endif + mirror: always + +# W3C pointer events draft. +- name: dom.w3c_pointer_events.implicit_capture + type: bool + value: true + mirror: always + +- name: dom.w3c_pointer_events.getcoalescedevents_only_in_securecontext + type: bool + value: @IS_NIGHTLY_BUILD@ + mirror: always + +# In case Touch API is enabled, this pref controls whether to support +# ontouch* event handlers, document.createTouch, document.createTouchList and +# document.createEvent("TouchEvent"). +- name: dom.w3c_touch_events.legacy_apis.enabled + type: bool + value: @IS_ANDROID@ + mirror: always + +# W3C touch events +# 0 - disabled, 1 - enabled, 2 - autodetect +# Autodetection is currently only supported on Windows and GTK3 (and assumed on +# Android). +- name: dom.w3c_touch_events.enabled + type: int32_t +#if defined(XP_MACOSX) + value: 0 +#else + value: 2 +#endif + mirror: always + +# Is support for the Web Audio API enabled? +- name: dom.webaudio.enabled + type: bool + value: true + mirror: always + +- name: dom.webkitBlink.dirPicker.enabled + type: RelaxedAtomicBool + value: @IS_NOT_ANDROID@ + mirror: always + +# NOTE: This preference is used in unit tests. If it is removed or its default +# value changes, please update test_sharedMap_static_prefs.js accordingly. +- name: dom.webcomponents.shadowdom.report_usage + type: bool + value: false + mirror: always + +# Is support for Declarative ShadowDOM enabled? +- name: dom.webcomponents.shadowdom.declarative.enabled + type: bool + value: true + mirror: always + +# Is support for the Web GPU API enabled? +- name: dom.webgpu.enabled + type: RelaxedAtomicBool + value: @IS_NIGHTLY_BUILD@ + mirror: always + +# Is support for the Web GPU API enabled on DOM workers? +- name: dom.webgpu.workers.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Are WebGPU indirect draws/dispatches enabled? +- name: dom.webgpu.indirect-dispatch.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# Comma-separated list of wgpu backend names to permit in WebGPU adapters. +# +# If non-empty, this is parsed by `wgpu_core::instance::parse_backends_from_comma_list` to +# produce a `wgpu_types::Backends` bitset used to create a `wgpu_core::hub::Global`. As of +# 2023-3-22, recognized names are: +# +# "vulkan" | "vk" => Backends::VULKAN, +# "dx12" | "d3d12" => Backends::DX12, +# "dx11" | "d3d11" => Backends::DX11, +# "metal" | "mtl" => Backends::METAL, +# "opengl" | "gles" | "gl" => Backends::GL, +# "webgpu" => Backends::BROWSER_WEBGPU, +- name: dom.webgpu.wgpu-backend + type: DataMutexString + value: "" + mirror: always + rust: true + +- name: dom.webgpu.swap-chain.external-texture-dx12 + type: RelaxedAtomicBool + value: false + mirror: always + rust: true + +# For testing purposes, crash if we don't get a hardware adapter. +- name: dom.webgpu.testing.assert-hardware-adapter + type: RelaxedAtomicBool + value: false + mirror: always + rust: true + +# Whether to pass labels to the hardware abstraction layer. This is only useful when +# inspecting a WebGPU workload in a GPU debugging tool like renderdoc. Enabling it +# exposes poorly tested driver API surfaces so it should not be enabled by default. +- name: dom.webgpu.hal-labels + type: bool + value: false + mirror: once + rust: true + +# Is support for HTMLInputElement.webkitEntries enabled? +- name: dom.webkitBlink.filesystem.enabled + type: bool + value: @IS_NOT_ANDROID@ + mirror: always + +# Whether the WebMIDI API is enabled +- name: dom.webmidi.enabled + type: bool + value: @IS_NOT_ANDROID@ + mirror: always + +# midi permission is addon-gated +- name: dom.webmidi.gated + type: bool + value: true + mirror: always + +- name: dom.webnotifications.allowcrossoriginiframe + type: RelaxedAtomicBool + value: false + mirror: always + +- name: dom.webnotifications.enabled + type: RelaxedAtomicBool + value: true + mirror: always + +- name: dom.webnotifications.privateBrowsing.enableDespiteLimitations + type: RelaxedAtomicBool + value: false + mirror: always + +- name: dom.webnotifications.requireuserinteraction + type: RelaxedAtomicBool + value: true + mirror: always + +- name: dom.webnotifications.requireinteraction.enabled + type: RelaxedAtomicBool +#if defined(XP_WIN) + value: true +#else + value: @IS_NIGHTLY_BUILD@ +#endif + mirror: always + +- name: dom.webnotifications.silent.enabled + type: RelaxedAtomicBool + value: @IS_NIGHTLY_BUILD@ + mirror: always + +- name: dom.webnotifications.vibrate.enabled + type: RelaxedAtomicBool +#if defined(MOZ_WIDGET_ANDROID) + value: @IS_NIGHTLY_BUILD@ +#else + value: false +#endif + mirror: always + +# Is support for Window.event enabled? +- name: dom.window.event.enabled + type: bool + value: true + mirror: always + +- name: dom.window.clientinformation.enabled + type: bool + value: true + mirror: always + +# Whether Window.sizeToContent() is enabled. +- name: dom.window.sizeToContent.enabled + type: bool + value: false + mirror: always + +- name: dom.worker.canceling.timeoutMilliseconds + type: RelaxedAtomicUint32 + value: 30000 # 30 seconds + mirror: always + +- name: dom.worker.use_medium_high_event_queue + type: RelaxedAtomicBool + value: true + mirror: always + +# Enables the dispatching of console log events from worker threads to the +# main-thread. +- name: dom.worker.console.dispatch_events_to_main_thread + type: RelaxedAtomicBool + value: true + mirror: always + +- name: dom.workers.testing.enabled + type: RelaxedAtomicBool + value: false + mirror: always + +# WebIDL test prefs. +- name: dom.webidl.test1 + type: bool + value: true + mirror: always +- name: dom.webidl.test2 + type: bool + value: true + mirror: always + +# WebShare API - exposes navigator.share() +- name: dom.webshare.enabled + type: bool +#ifdef XP_WIN + value: @IS_EARLY_BETA_OR_EARLIER@ +#else + value: false +#endif + mirror: always + +# WebShare API - allows WebShare without user interaction (for tests only). +- name: dom.webshare.requireinteraction + type: bool + value: true + mirror: always + +# Hide the confirm dialog when a POST request is reloaded. +- name: dom.confirm_repost.testing.always_accept + type: bool + value: false + mirror: always + +# Whether we should suspend inactive tabs or not +- name: dom.suspend_inactive.enabled + type: bool + value: @IS_ANDROID@ + mirror: always + +# The following three prefs control the maximum script run time before slow +# script warning. + +# Controls the time that a content script can run before showing a +# notification. +- name: dom.max_script_run_time + type: int32_t + value: 10 + mirror: always + +# Controls whether we want to wait for user input before surfacing notifying +# the parent process about a long-running script. +- name: dom.max_script_run_time.require_critical_input + type: bool +# On desktop, we don't want to annoy the user with a notification if they're +# not interacting with the browser. On Android however, we automatically +# terminate long-running scripts, so we want to make sure we don't get in the +# way of that by waiting for input. +#if defined(MOZ_WIDGET_ANDROID) + value: false +#else + value: true +#endif + mirror: always + +# Controls if a content script will be aborted on child process shutdown. +- name: dom.abort_script_on_child_shutdown + type: bool + value: true + mirror: always + +- name: dom.max_chrome_script_run_time + type: int32_t + value: 0 + mirror: always + +- name: dom.max_ext_content_script_run_time + type: int32_t + value: 5 + mirror: always + +# Let Resize Observer report the size of all fragments, and not just the +# first one, as per CSSWG resolution: +# https://github.com/w3c/csswg-drafts/issues/3673#issuecomment-467221565 +- name: dom.resize_observer.support_fragments + type: bool + value: false + mirror: always + +#