Backed out changeset 5f819082a015 (bug 1936189) for causing reftest failures @ native...
[gecko.git] / modules / libpref / Preferences.cpp
blobfb3badfe967aa7047aedf53f5609ba6b86f4d442
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // Documentation for libpref is in modules/libpref/docs/index.rst.
9 #include <ctype.h>
10 #include <stdlib.h>
11 #include <string.h>
13 #include "SharedPrefMap.h"
15 #include "base/basictypes.h"
16 #include "MainThreadUtils.h"
17 #include "mozilla/AppShutdown.h"
18 #include "mozilla/ArenaAllocatorExtensions.h"
19 #include "mozilla/ArenaAllocator.h"
20 #include "mozilla/ArrayUtils.h"
21 #include "mozilla/Attributes.h"
22 #include "mozilla/Components.h"
23 #include "mozilla/dom/PContent.h"
24 #include "mozilla/dom/Promise.h"
25 #include "mozilla/dom/RemoteType.h"
26 #include "mozilla/glean/GleanMetrics.h"
27 #include "mozilla/HashFunctions.h"
28 #include "mozilla/HashTable.h"
29 #include "mozilla/Logging.h"
30 #include "mozilla/Maybe.h"
31 #include "mozilla/MemoryReporting.h"
32 #include "mozilla/Omnijar.h"
33 #include "mozilla/Preferences.h"
34 #include "mozilla/ProfilerLabels.h"
35 #include "mozilla/ProfilerMarkers.h"
36 #include "mozilla/ResultExtensions.h"
37 #include "mozilla/SchedulerGroup.h"
38 #include "mozilla/ScopeExit.h"
39 #include "mozilla/ServoStyleSet.h"
40 #include "mozilla/SpinEventLoopUntil.h"
41 #include "mozilla/StaticMutex.h"
42 #include "mozilla/StaticPrefsAll.h"
43 #include "mozilla/StaticPtr.h"
44 #include "mozilla/SyncRunnable.h"
45 #include "mozilla/Try.h"
46 #include "mozilla/UniquePtrExtensions.h"
47 #include "mozilla/URLPreloader.h"
48 #include "mozilla/Variant.h"
49 #include "mozilla/Vector.h"
50 #include "nsAppDirectoryServiceDefs.h"
51 #include "nsCategoryManagerUtils.h"
52 #include "nsClassHashtable.h"
53 #include "nsCOMArray.h"
54 #include "nsCOMPtr.h"
55 #include "nsComponentManagerUtils.h"
56 #include "nsContentUtils.h"
57 #include "nsCRT.h"
58 #include "nsTHashMap.h"
59 #include "nsDirectoryServiceDefs.h"
60 #include "nsIConsoleService.h"
61 #include "nsIFile.h"
62 #include "nsIMemoryReporter.h"
63 #include "nsIObserver.h"
64 #include "nsIObserverService.h"
65 #include "nsIOutputStream.h"
66 #include "nsIPrefBranch.h"
67 #include "nsIPrefLocalizedString.h"
68 #include "nsIRelativeFilePref.h"
69 #include "nsISafeOutputStream.h"
70 #include "nsISimpleEnumerator.h"
71 #include "nsIStringBundle.h"
72 #include "nsISupportsImpl.h"
73 #include "nsISupportsPrimitives.h"
74 #include "nsIZipReader.h"
75 #include "nsNetUtil.h"
76 #include "nsPrintfCString.h"
77 #include "nsProxyRelease.h"
78 #include "nsReadableUtils.h"
79 #include "nsRefPtrHashtable.h"
80 #include "nsRelativeFilePref.h"
81 #include "nsString.h"
82 #include "nsTArray.h"
83 #include "nsThreadUtils.h"
84 #include "nsUTF8Utils.h"
85 #include "nsWeakReference.h"
86 #include "nsXPCOMCID.h"
87 #include "nsXPCOM.h"
88 #include "nsXULAppAPI.h"
89 #include "nsZipArchive.h"
90 #include "plbase64.h"
91 #include "PLDHashTable.h"
92 #include "prdtoa.h"
93 #include "prlink.h"
94 #include "xpcpublic.h"
95 #include "js/RootingAPI.h"
96 #ifdef MOZ_BACKGROUNDTASKS
97 # include "mozilla/BackgroundTasks.h"
98 #endif
100 #ifdef DEBUG
101 # include <map>
102 #endif
104 #ifdef MOZ_MEMORY
105 # include "mozmemory.h"
106 #endif
108 #ifdef XP_WIN
109 # include "windows.h"
110 #endif
112 #if defined(MOZ_WIDGET_GTK)
113 # include "mozilla/WidgetUtilsGtk.h"
114 #endif // defined(MOZ_WIDGET_GTK)
116 #ifdef MOZ_WIDGET_COCOA
117 # include "ChannelPrefsUtil.h"
118 #endif
120 using namespace mozilla;
122 using dom::Promise;
123 using ipc::FileDescriptor;
125 #ifdef DEBUG
127 # define ENSURE_PARENT_PROCESS(func, pref) \
128 do { \
129 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
130 nsPrintfCString msg( \
131 "ENSURE_PARENT_PROCESS: called %s on %s in a non-parent process", \
132 func, pref); \
133 NS_ERROR(msg.get()); \
134 return NS_ERROR_NOT_AVAILABLE; \
136 } while (0)
138 #else // DEBUG
140 # define ENSURE_PARENT_PROCESS(func, pref) \
141 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
142 return NS_ERROR_NOT_AVAILABLE; \
145 #endif // DEBUG
147 // Forward declarations.
148 namespace mozilla::StaticPrefs {
150 static void InitAll();
151 static void StartObservingAlwaysPrefs();
152 static void InitOncePrefs();
153 static void InitStaticPrefsFromShared();
154 static void RegisterOncePrefs(SharedPrefMapBuilder& aBuilder);
155 static void ShutdownAlwaysPrefs();
157 } // namespace mozilla::StaticPrefs
159 //===========================================================================
160 // Low-level types and operations
161 //===========================================================================
163 typedef nsTArray<nsCString> PrefSaveData;
165 // 1 MB should be enough for everyone.
166 static const uint32_t MAX_PREF_LENGTH = 1 * 1024 * 1024;
167 // Actually, 4kb should be enough for everyone.
168 static const uint32_t MAX_ADVISABLE_PREF_LENGTH = 4 * 1024;
170 // This is used for pref names and string pref values. We encode the string
171 // length, then a '/', then the string chars. This encoding means there are no
172 // special chars that are forbidden or require escaping.
173 static void SerializeAndAppendString(const nsCString& aChars, nsCString& aStr) {
174 aStr.AppendInt(uint64_t(aChars.Length()));
175 aStr.Append('/');
176 aStr.Append(aChars);
179 static char* DeserializeString(char* aChars, nsCString& aStr) {
180 char* p = aChars;
181 uint32_t length = strtol(p, &p, 10);
182 MOZ_ASSERT(p[0] == '/');
183 p++; // move past the '/'
184 aStr.Assign(p, length);
185 p += length; // move past the string itself
186 return p;
189 // Keep this in sync with PrefValue in parser/src/lib.rs.
190 union PrefValue {
191 // PrefValues within Pref objects own their chars. PrefValues passed around
192 // as arguments don't own their chars.
193 const char* mStringVal;
194 int32_t mIntVal;
195 bool mBoolVal;
197 PrefValue() = default;
199 explicit PrefValue(bool aVal) : mBoolVal(aVal) {}
201 explicit PrefValue(int32_t aVal) : mIntVal(aVal) {}
203 explicit PrefValue(const char* aVal) : mStringVal(aVal) {}
205 bool Equals(PrefType aType, PrefValue aValue) {
206 switch (aType) {
207 case PrefType::String: {
208 if (mStringVal && aValue.mStringVal) {
209 return strcmp(mStringVal, aValue.mStringVal) == 0;
211 if (!mStringVal && !aValue.mStringVal) {
212 return true;
214 return false;
217 case PrefType::Int:
218 return mIntVal == aValue.mIntVal;
220 case PrefType::Bool:
221 return mBoolVal == aValue.mBoolVal;
223 default:
224 MOZ_CRASH("Unhandled enum value");
228 template <typename T>
229 T Get() const;
231 void Init(PrefType aNewType, PrefValue aNewValue) {
232 if (aNewType == PrefType::String) {
233 MOZ_ASSERT(aNewValue.mStringVal);
234 aNewValue.mStringVal = moz_xstrdup(aNewValue.mStringVal);
236 *this = aNewValue;
239 void Clear(PrefType aType) {
240 if (aType == PrefType::String) {
241 free(const_cast<char*>(mStringVal));
244 // Zero the entire value (regardless of type) via mStringVal.
245 mStringVal = nullptr;
248 void Replace(bool aHasValue, PrefType aOldType, PrefType aNewType,
249 PrefValue aNewValue) {
250 if (aHasValue) {
251 Clear(aOldType);
253 Init(aNewType, aNewValue);
256 void ToDomPrefValue(PrefType aType, dom::PrefValue* aDomValue) {
257 switch (aType) {
258 case PrefType::String:
259 *aDomValue = nsDependentCString(mStringVal);
260 return;
262 case PrefType::Int:
263 *aDomValue = mIntVal;
264 return;
266 case PrefType::Bool:
267 *aDomValue = mBoolVal;
268 return;
270 default:
271 MOZ_CRASH();
275 PrefType FromDomPrefValue(const dom::PrefValue& aDomValue) {
276 switch (aDomValue.type()) {
277 case dom::PrefValue::TnsCString:
278 mStringVal = aDomValue.get_nsCString().get();
279 return PrefType::String;
281 case dom::PrefValue::Tint32_t:
282 mIntVal = aDomValue.get_int32_t();
283 return PrefType::Int;
285 case dom::PrefValue::Tbool:
286 mBoolVal = aDomValue.get_bool();
287 return PrefType::Bool;
289 default:
290 MOZ_CRASH();
294 void SerializeAndAppend(PrefType aType, nsCString& aStr) {
295 switch (aType) {
296 case PrefType::Bool:
297 aStr.Append(mBoolVal ? 'T' : 'F');
298 break;
300 case PrefType::Int:
301 aStr.AppendInt(mIntVal);
302 break;
304 case PrefType::String: {
305 SerializeAndAppendString(nsDependentCString(mStringVal), aStr);
306 break;
309 case PrefType::None:
310 default:
311 MOZ_CRASH();
315 void ToString(PrefType aType, nsCString& aStr) {
316 switch (aType) {
317 case PrefType::Bool:
318 aStr.Append(mBoolVal ? "true" : "false");
319 break;
321 case PrefType::Int:
322 aStr.AppendInt(mIntVal);
323 break;
325 case PrefType::String: {
326 aStr.Append(nsDependentCString(mStringVal));
327 break;
330 case PrefType::None:
331 default:;
335 static char* Deserialize(PrefType aType, char* aStr,
336 Maybe<dom::PrefValue>* aDomValue) {
337 char* p = aStr;
339 switch (aType) {
340 case PrefType::Bool:
341 if (*p == 'T') {
342 *aDomValue = Some(true);
343 } else if (*p == 'F') {
344 *aDomValue = Some(false);
345 } else {
346 *aDomValue = Some(false);
347 NS_ERROR("bad bool pref value");
349 p++;
350 return p;
352 case PrefType::Int: {
353 *aDomValue = Some(int32_t(strtol(p, &p, 10)));
354 return p;
357 case PrefType::String: {
358 nsCString str;
359 p = DeserializeString(p, str);
360 *aDomValue = Some(str);
361 return p;
364 default:
365 MOZ_CRASH();
370 template <>
371 bool PrefValue::Get() const {
372 return mBoolVal;
375 template <>
376 int32_t PrefValue::Get() const {
377 return mIntVal;
380 template <>
381 nsDependentCString PrefValue::Get() const {
382 return nsDependentCString(mStringVal);
385 #ifdef DEBUG
386 const char* PrefTypeToString(PrefType aType) {
387 switch (aType) {
388 case PrefType::None:
389 return "none";
390 case PrefType::String:
391 return "string";
392 case PrefType::Int:
393 return "int";
394 case PrefType::Bool:
395 return "bool";
396 default:
397 MOZ_CRASH("Unhandled enum value");
400 #endif
402 // Assign to aResult a quoted, escaped copy of aOriginal.
403 static void StrEscape(const char* aOriginal, nsCString& aResult) {
404 if (aOriginal == nullptr) {
405 aResult.AssignLiteral("\"\"");
406 return;
409 // JavaScript does not allow quotes, slashes, or line terminators inside
410 // strings so we must escape them. ECMAScript defines four line terminators,
411 // but we're only worrying about \r and \n here. We currently feed our pref
412 // script to the JS interpreter as Latin-1 so we won't encounter \u2028
413 // (line separator) or \u2029 (paragraph separator).
415 // WARNING: There are hints that we may be moving to storing prefs as utf8.
416 // If we ever feed them to the JS compiler as UTF8 then we'll have to worry
417 // about the multibyte sequences that would be interpreted as \u2028 and
418 // \u2029.
419 const char* p;
421 aResult.Assign('"');
423 // Paranoid worst case all slashes will free quickly.
424 for (p = aOriginal; *p; ++p) {
425 switch (*p) {
426 case '\n':
427 aResult.AppendLiteral("\\n");
428 break;
430 case '\r':
431 aResult.AppendLiteral("\\r");
432 break;
434 case '\\':
435 aResult.AppendLiteral("\\\\");
436 break;
438 case '\"':
439 aResult.AppendLiteral("\\\"");
440 break;
442 default:
443 aResult.Append(*p);
444 break;
448 aResult.Append('"');
451 // Mimic the behaviour of nsTStringRepr::ToFloat before bug 840706 to preserve
452 // error case handling for parsing pref strings. Many callers do not check error
453 // codes, so the returned values may be used even if an error is set.
455 // This method should never return NaN, but may return +-inf if the provided
456 // number is too large to fit in a float.
457 static float ParsePrefFloat(const nsCString& aString, nsresult* aError) {
458 if (aString.IsEmpty()) {
459 *aError = NS_ERROR_ILLEGAL_VALUE;
460 return 0.f;
463 // PR_strtod does a locale-independent conversion.
464 char* stopped = nullptr;
465 float result = PR_strtod(aString.get(), &stopped);
467 // Defensively avoid potential breakage caused by returning NaN into
468 // unsuspecting code. AFAIK this should never happen as PR_strtod cannot
469 // return NaN as currently configured.
470 if (std::isnan(result)) {
471 MOZ_ASSERT_UNREACHABLE("PR_strtod shouldn't return NaN");
472 *aError = NS_ERROR_ILLEGAL_VALUE;
473 return 0.f;
476 *aError = (stopped == aString.EndReading()) ? NS_OK : NS_ERROR_ILLEGAL_VALUE;
477 return result;
480 struct PreferenceMarker {
481 static constexpr Span<const char> MarkerTypeName() {
482 return MakeStringSpan("Preference");
484 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
485 const ProfilerString8View& aPrefName,
486 const Maybe<PrefValueKind>& aPrefKind,
487 PrefType aPrefType,
488 const ProfilerString8View& aPrefValue) {
489 aWriter.StringProperty("prefName", aPrefName);
490 aWriter.StringProperty("prefKind", PrefValueKindToString(aPrefKind));
491 aWriter.StringProperty("prefType", PrefTypeToString(aPrefType));
492 aWriter.StringProperty("prefValue", aPrefValue);
494 static MarkerSchema MarkerTypeDisplay() {
495 using MS = MarkerSchema;
496 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
497 schema.AddKeyLabelFormatSearchable("prefName", "Name", MS::Format::String,
498 MS::Searchable::Searchable);
499 schema.AddKeyLabelFormat("prefKind", "Kind", MS::Format::String);
500 schema.AddKeyLabelFormat("prefType", "Type", MS::Format::String);
501 schema.AddKeyLabelFormat("prefValue", "Value", MS::Format::String);
502 schema.SetTableLabel(
503 "{marker.name} — {marker.data.prefName}: {marker.data.prefValue} "
504 "({marker.data.prefType})");
505 return schema;
508 private:
509 static Span<const char> PrefValueKindToString(
510 const Maybe<PrefValueKind>& aKind) {
511 if (aKind) {
512 return *aKind == PrefValueKind::Default ? MakeStringSpan("Default")
513 : MakeStringSpan("User");
515 return "Shared";
518 static Span<const char> PrefTypeToString(PrefType type) {
519 switch (type) {
520 case PrefType::None:
521 return "None";
522 case PrefType::Int:
523 return "Int";
524 case PrefType::Bool:
525 return "Bool";
526 case PrefType::String:
527 return "String";
528 default:
529 MOZ_ASSERT_UNREACHABLE("Unknown preference type.");
530 return "Unknown";
535 namespace mozilla {
536 struct PrefsSizes {
537 PrefsSizes()
538 : mHashTable(0),
539 mPrefValues(0),
540 mStringValues(0),
541 mRootBranches(0),
542 mPrefNameArena(0),
543 mCallbacksObjects(0),
544 mCallbacksDomains(0),
545 mMisc(0) {}
547 size_t mHashTable;
548 size_t mPrefValues;
549 size_t mStringValues;
550 size_t mRootBranches;
551 size_t mPrefNameArena;
552 size_t mCallbacksObjects;
553 size_t mCallbacksDomains;
554 size_t mMisc;
556 } // namespace mozilla
558 static StaticRefPtr<SharedPrefMap> gSharedMap;
560 // Arena for Pref names.
561 // Never access sPrefNameArena directly, always use PrefNameArena()
562 // because it must only be accessed on the Main Thread
563 typedef ArenaAllocator<4096, 1> NameArena;
564 static NameArena* sPrefNameArena;
566 static inline NameArena& PrefNameArena() {
567 MOZ_ASSERT(NS_IsMainThread());
569 if (!sPrefNameArena) {
570 sPrefNameArena = new NameArena();
572 return *sPrefNameArena;
575 class PrefWrapper;
577 // Three forward declarations for immediately below
578 class Pref;
579 static bool IsPreferenceSanitized(const Pref* const aPref);
580 static bool ShouldSanitizePreference(const Pref* const aPref);
582 // Note that this never changes in the parent process, and is only read in
583 // content processes.
584 static bool gContentProcessPrefsAreInited = false;
586 class Pref {
587 public:
588 explicit Pref(const nsACString& aName)
589 : mName(ArenaStrdup(aName, PrefNameArena()), aName.Length()),
590 mType(static_cast<uint32_t>(PrefType::None)),
591 mIsSticky(false),
592 mIsLocked(false),
593 mIsSanitized(false),
594 mHasDefaultValue(false),
595 mHasUserValue(false),
596 mIsSkippedByIteration(false),
597 mDefaultValue(),
598 mUserValue() {}
600 ~Pref() {
601 // There's no need to free mName because it's allocated in memory owned by
602 // sPrefNameArena.
604 mDefaultValue.Clear(Type());
605 mUserValue.Clear(Type());
608 const char* Name() const { return mName.get(); }
609 const nsDependentCString& NameString() const { return mName; }
611 // Types.
613 PrefType Type() const { return static_cast<PrefType>(mType); }
614 void SetType(PrefType aType) { mType = static_cast<uint32_t>(aType); }
616 bool IsType(PrefType aType) const { return Type() == aType; }
617 bool IsTypeNone() const { return IsType(PrefType::None); }
618 bool IsTypeString() const { return IsType(PrefType::String); }
619 bool IsTypeInt() const { return IsType(PrefType::Int); }
620 bool IsTypeBool() const { return IsType(PrefType::Bool); }
622 // Other properties.
624 bool IsLocked() const { return mIsLocked; }
625 void SetIsLocked(bool aValue) { mIsLocked = aValue; }
626 bool IsSkippedByIteration() const { return mIsSkippedByIteration; }
627 void SetIsSkippedByIteration(bool aValue) { mIsSkippedByIteration = aValue; }
629 bool IsSticky() const { return mIsSticky; }
631 bool IsSanitized() const { return mIsSanitized; }
633 bool HasDefaultValue() const { return mHasDefaultValue; }
634 bool HasUserValue() const { return mHasUserValue; }
636 template <typename T>
637 void AddToMap(SharedPrefMapBuilder& aMap) {
638 // Sanitized preferences should never be added to the shared pref map
639 MOZ_ASSERT(!ShouldSanitizePreference(this));
640 aMap.Add(NameString(),
641 {HasDefaultValue(), HasUserValue(), IsSticky(), IsLocked(),
642 /* isSanitized */ false, IsSkippedByIteration()},
643 HasDefaultValue() ? mDefaultValue.Get<T>() : T(),
644 HasUserValue() ? mUserValue.Get<T>() : T());
647 void AddToMap(SharedPrefMapBuilder& aMap) {
648 if (IsTypeBool()) {
649 AddToMap<bool>(aMap);
650 } else if (IsTypeInt()) {
651 AddToMap<int32_t>(aMap);
652 } else if (IsTypeString()) {
653 AddToMap<nsDependentCString>(aMap);
654 } else {
655 MOZ_ASSERT_UNREACHABLE("Unexpected preference type");
659 // Other operations.
661 #define CHECK_SANITIZATION() \
662 if (IsPreferenceSanitized(this)) { \
663 glean::security::pref_usage_content_process.Record( \
664 Some(glean::security::PrefUsageContentProcessExtra{Some(Name())})); \
665 if (sCrashOnBlocklistedPref) { \
666 MOZ_CRASH_UNSAFE_PRINTF( \
667 "Should not access the preference '%s' in the Content Processes", \
668 Name()); \
672 bool GetBoolValue(PrefValueKind aKind = PrefValueKind::User) const {
673 MOZ_ASSERT(IsTypeBool());
674 MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
675 : HasUserValue());
677 CHECK_SANITIZATION();
679 return aKind == PrefValueKind::Default ? mDefaultValue.mBoolVal
680 : mUserValue.mBoolVal;
683 int32_t GetIntValue(PrefValueKind aKind = PrefValueKind::User) const {
684 MOZ_ASSERT(IsTypeInt());
685 MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
686 : HasUserValue());
688 CHECK_SANITIZATION();
690 return aKind == PrefValueKind::Default ? mDefaultValue.mIntVal
691 : mUserValue.mIntVal;
694 const char* GetBareStringValue(
695 PrefValueKind aKind = PrefValueKind::User) const {
696 MOZ_ASSERT(IsTypeString());
697 MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
698 : HasUserValue());
700 CHECK_SANITIZATION();
702 return aKind == PrefValueKind::Default ? mDefaultValue.mStringVal
703 : mUserValue.mStringVal;
706 #undef CHECK_SANITIZATION
708 nsDependentCString GetStringValue(
709 PrefValueKind aKind = PrefValueKind::User) const {
710 return nsDependentCString(GetBareStringValue(aKind));
713 void ToDomPref(dom::Pref* aDomPref, bool aIsDestinationWebContentProcess) {
714 MOZ_ASSERT(XRE_IsParentProcess());
716 aDomPref->name() = mName;
718 aDomPref->isLocked() = mIsLocked;
720 aDomPref->isSanitized() =
721 aIsDestinationWebContentProcess && ShouldSanitizePreference(this);
723 if (mHasDefaultValue) {
724 aDomPref->defaultValue() = Some(dom::PrefValue());
725 mDefaultValue.ToDomPrefValue(Type(), &aDomPref->defaultValue().ref());
726 } else {
727 aDomPref->defaultValue() = Nothing();
730 if (mHasUserValue &&
731 !(aDomPref->isSanitized() && sOmitBlocklistedPrefValues)) {
732 aDomPref->userValue() = Some(dom::PrefValue());
733 mUserValue.ToDomPrefValue(Type(), &aDomPref->userValue().ref());
734 } else {
735 aDomPref->userValue() = Nothing();
738 MOZ_ASSERT(aDomPref->defaultValue().isNothing() ||
739 aDomPref->userValue().isNothing() ||
740 (mIsSanitized && sOmitBlocklistedPrefValues) ||
741 (aDomPref->defaultValue().ref().type() ==
742 aDomPref->userValue().ref().type()));
745 void FromDomPref(const dom::Pref& aDomPref, bool* aValueChanged) {
746 MOZ_ASSERT(!XRE_IsParentProcess());
747 MOZ_ASSERT(mName == aDomPref.name());
749 mIsLocked = aDomPref.isLocked();
750 mIsSanitized = aDomPref.isSanitized();
752 const Maybe<dom::PrefValue>& defaultValue = aDomPref.defaultValue();
753 bool defaultValueChanged = false;
754 if (defaultValue.isSome()) {
755 PrefValue value;
756 PrefType type = value.FromDomPrefValue(defaultValue.ref());
757 if (!ValueMatches(PrefValueKind::Default, type, value)) {
758 // Type() is PrefType::None if it's a newly added pref. This is ok.
759 mDefaultValue.Replace(mHasDefaultValue, Type(), type, value);
760 SetType(type);
761 mHasDefaultValue = true;
762 defaultValueChanged = true;
765 // Note: we never clear a default value.
767 const Maybe<dom::PrefValue>& userValue = aDomPref.userValue();
768 bool userValueChanged = false;
769 if (userValue.isSome()) {
770 PrefValue value;
771 PrefType type = value.FromDomPrefValue(userValue.ref());
772 if (!ValueMatches(PrefValueKind::User, type, value)) {
773 // Type() is PrefType::None if it's a newly added pref. This is ok.
774 mUserValue.Replace(mHasUserValue, Type(), type, value);
775 SetType(type);
776 mHasUserValue = true;
777 userValueChanged = true;
779 } else if (mHasUserValue) {
780 ClearUserValue();
781 userValueChanged = true;
784 if (userValueChanged || (defaultValueChanged && !mHasUserValue)) {
785 *aValueChanged = true;
789 void FromWrapper(PrefWrapper& aWrapper);
791 bool HasAdvisablySizedValues() {
792 MOZ_ASSERT(XRE_IsParentProcess());
794 if (!IsTypeString()) {
795 return true;
798 if (mHasDefaultValue &&
799 strlen(mDefaultValue.mStringVal) > MAX_ADVISABLE_PREF_LENGTH) {
800 return false;
803 if (mHasUserValue &&
804 strlen(mUserValue.mStringVal) > MAX_ADVISABLE_PREF_LENGTH) {
805 return false;
808 return true;
811 private:
812 bool ValueMatches(PrefValueKind aKind, PrefType aType, PrefValue aValue) {
813 return IsType(aType) &&
814 (aKind == PrefValueKind::Default
815 ? mHasDefaultValue && mDefaultValue.Equals(aType, aValue)
816 : mHasUserValue && mUserValue.Equals(aType, aValue));
819 public:
820 void ClearUserValue() {
821 mUserValue.Clear(Type());
822 mHasUserValue = false;
825 nsresult SetDefaultValue(PrefType aType, PrefValue aValue, bool aIsSticky,
826 bool aIsLocked, bool* aValueChanged) {
827 // Types must always match when setting the default value.
828 if (!IsType(aType)) {
829 return NS_ERROR_UNEXPECTED;
832 // Should we set the default value? Only if the pref is not locked, and
833 // doing so would change the default value.
834 if (!IsLocked()) {
835 if (aIsLocked) {
836 SetIsLocked(true);
838 if (!ValueMatches(PrefValueKind::Default, aType, aValue)) {
839 mDefaultValue.Replace(mHasDefaultValue, Type(), aType, aValue);
840 mHasDefaultValue = true;
841 if (aIsSticky) {
842 mIsSticky = true;
844 if (!mHasUserValue) {
845 *aValueChanged = true;
847 // What if we change the default to be the same as the user value?
848 // Should we clear the user value? Currently we don't.
851 return NS_OK;
854 nsresult SetUserValue(PrefType aType, PrefValue aValue, bool aFromInit,
855 bool* aValueChanged) {
856 // If we have a default value, types must match when setting the user
857 // value.
858 if (mHasDefaultValue && !IsType(aType)) {
859 return NS_ERROR_UNEXPECTED;
862 // Should we clear the user value, if present? Only if the new user value
863 // matches the default value, and the pref isn't sticky, and we aren't
864 // force-setting it during initialization.
865 if (ValueMatches(PrefValueKind::Default, aType, aValue) && !mIsSticky &&
866 !aFromInit) {
867 if (mHasUserValue) {
868 ClearUserValue();
869 if (!IsLocked()) {
870 *aValueChanged = true;
874 // Otherwise, should we set the user value? Only if doing so would
875 // change the user value.
876 } else if (!ValueMatches(PrefValueKind::User, aType, aValue)) {
877 mUserValue.Replace(mHasUserValue, Type(), aType, aValue);
878 SetType(aType); // needed because we may have changed the type
879 mHasUserValue = true;
880 if (!IsLocked()) {
881 *aValueChanged = true;
884 return NS_OK;
887 // Prefs are serialized in a manner that mirrors dom::Pref. The two should be
888 // kept in sync. E.g. if something is added to one it should also be added to
889 // the other. (It would be nice to be able to use the code generated from
890 // IPDL for serializing dom::Pref here instead of writing by hand this
891 // serialization/deserialization. Unfortunately, that generated code is
892 // difficult to use directly, outside of the IPDL IPC code.)
894 // The grammar for the serialized prefs has the following form.
896 // <pref> = <type> <locked> <sanitized> ':' <name> ':' <value>? ':'
897 // <value>? '\n'
898 // <type> = 'B' | 'I' | 'S'
899 // <locked> = 'L' | '-'
900 // <sanitized> = 'S' | '-'
901 // <name> = <string-value>
902 // <value> = <bool-value> | <int-value> | <string-value>
903 // <bool-value> = 'T' | 'F'
904 // <int-value> = an integer literal accepted by strtol()
905 // <string-value> = <int-value> '/' <chars>
906 // <chars> = any char sequence of length dictated by the preceding
907 // <int-value>.
909 // No whitespace is tolerated between tokens. <type> must match the types of
910 // the values.
912 // The serialization is text-based, rather than binary, for the following
913 // reasons.
915 // - The size difference wouldn't be much different between text-based and
916 // binary. Most of the space is for strings (pref names and string pref
917 // values), which would be the same in both styles. And other differences
918 // would be minimal, e.g. small integers are shorter in text but long
919 // integers are longer in text.
921 // - Likewise, speed differences should be negligible.
923 // - It's much easier to debug a text-based serialization. E.g. you can
924 // print it and inspect it easily in a debugger.
926 // Examples of unlocked boolean prefs:
927 // - "B--:8/my.bool1:F:T\n"
928 // - "B--:8/my.bool2:F:\n"
929 // - "B--:8/my.bool3::T\n"
931 // Examples of sanitized, unlocked boolean prefs:
932 // - "B-S:8/my.bool1:F:T\n"
933 // - "B-S:8/my.bool2:F:\n"
934 // - "B-S:8/my.bool3::T\n"
936 // Examples of locked integer prefs:
937 // - "IL-:7/my.int1:0:1\n"
938 // - "IL-:7/my.int2:123:\n"
939 // - "IL-:7/my.int3::-99\n"
941 // Examples of unlocked string prefs:
942 // - "S--:10/my.string1:3/abc:4/wxyz\n"
943 // - "S--:10/my.string2:5/1.234:\n"
944 // - "S--:10/my.string3::7/string!\n"
946 void SerializeAndAppend(nsCString& aStr, bool aSanitizeUserValue) {
947 switch (Type()) {
948 case PrefType::Bool:
949 aStr.Append('B');
950 break;
952 case PrefType::Int:
953 aStr.Append('I');
954 break;
956 case PrefType::String: {
957 aStr.Append('S');
958 break;
961 case PrefType::None:
962 default:
963 MOZ_CRASH();
966 aStr.Append(mIsLocked ? 'L' : '-');
967 aStr.Append(aSanitizeUserValue ? 'S' : '-');
968 aStr.Append(':');
970 SerializeAndAppendString(mName, aStr);
971 aStr.Append(':');
973 if (mHasDefaultValue) {
974 mDefaultValue.SerializeAndAppend(Type(), aStr);
976 aStr.Append(':');
978 if (mHasUserValue && !(aSanitizeUserValue && sOmitBlocklistedPrefValues)) {
979 mUserValue.SerializeAndAppend(Type(), aStr);
981 aStr.Append('\n');
984 static char* Deserialize(char* aStr, dom::Pref* aDomPref) {
985 char* p = aStr;
987 // The type.
988 PrefType type;
989 if (*p == 'B') {
990 type = PrefType::Bool;
991 } else if (*p == 'I') {
992 type = PrefType::Int;
993 } else if (*p == 'S') {
994 type = PrefType::String;
995 } else {
996 NS_ERROR("bad pref type");
997 type = PrefType::None;
999 p++; // move past the type char
1001 // Locked?
1002 bool isLocked;
1003 if (*p == 'L') {
1004 isLocked = true;
1005 } else if (*p == '-') {
1006 isLocked = false;
1007 } else {
1008 NS_ERROR("bad pref locked status");
1009 isLocked = false;
1011 p++; // move past the isLocked char
1013 // Sanitize?
1014 bool isSanitized;
1015 if (*p == 'S') {
1016 isSanitized = true;
1017 } else if (*p == '-') {
1018 isSanitized = false;
1019 } else {
1020 NS_ERROR("bad pref sanitized status");
1021 isSanitized = false;
1023 p++; // move past the isSanitized char
1025 MOZ_ASSERT(*p == ':');
1026 p++; // move past the ':'
1028 // The pref name.
1029 nsCString name;
1030 p = DeserializeString(p, name);
1032 MOZ_ASSERT(*p == ':');
1033 p++; // move past the ':' preceding the default value
1035 Maybe<dom::PrefValue> maybeDefaultValue;
1036 if (*p != ':') {
1037 dom::PrefValue defaultValue;
1038 p = PrefValue::Deserialize(type, p, &maybeDefaultValue);
1041 MOZ_ASSERT(*p == ':');
1042 p++; // move past the ':' between the default and user values
1044 Maybe<dom::PrefValue> maybeUserValue;
1045 if (*p != '\n') {
1046 dom::PrefValue userValue;
1047 p = PrefValue::Deserialize(type, p, &maybeUserValue);
1050 MOZ_ASSERT(*p == '\n');
1051 p++; // move past the '\n' following the user value
1053 *aDomPref = dom::Pref(name, isLocked, isSanitized, maybeDefaultValue,
1054 maybeUserValue);
1056 return p;
1059 void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, PrefsSizes& aSizes) {
1060 // Note: mName is allocated in sPrefNameArena, measured elsewhere.
1061 aSizes.mPrefValues += aMallocSizeOf(this);
1062 if (IsTypeString()) {
1063 if (mHasDefaultValue) {
1064 aSizes.mStringValues += aMallocSizeOf(mDefaultValue.mStringVal);
1066 if (mHasUserValue) {
1067 aSizes.mStringValues += aMallocSizeOf(mUserValue.mStringVal);
1072 void RelocateName(NameArena* aArena) {
1073 mName.Rebind(ArenaStrdup(mName.get(), *aArena), mName.Length());
1076 private:
1077 nsDependentCString mName; // allocated in sPrefNameArena
1079 uint32_t mType : 2;
1080 uint32_t mIsSticky : 1;
1081 uint32_t mIsLocked : 1;
1082 uint32_t mIsSanitized : 1;
1083 uint32_t mHasDefaultValue : 1;
1084 uint32_t mHasUserValue : 1;
1085 uint32_t mIsSkippedByIteration : 1;
1087 PrefValue mDefaultValue;
1088 PrefValue mUserValue;
1091 struct PrefHasher {
1092 using Key = UniquePtr<Pref>;
1093 using Lookup = const char*;
1095 static HashNumber hash(const Lookup aLookup) { return HashString(aLookup); }
1097 static bool match(const Key& aKey, const Lookup aLookup) {
1098 if (!aLookup || !aKey->Name()) {
1099 return false;
1102 return strcmp(aLookup, aKey->Name()) == 0;
1106 using PrefWrapperBase = Variant<Pref*, SharedPrefMap::Pref>;
1107 class MOZ_STACK_CLASS PrefWrapper : public PrefWrapperBase {
1108 using SharedPref = const SharedPrefMap::Pref;
1110 public:
1111 MOZ_IMPLICIT PrefWrapper(Pref* aPref) : PrefWrapperBase(AsVariant(aPref)) {}
1113 MOZ_IMPLICIT PrefWrapper(const SharedPrefMap::Pref& aPref)
1114 : PrefWrapperBase(AsVariant(aPref)) {}
1116 // Types.
1118 bool IsType(PrefType aType) const { return Type() == aType; }
1119 bool IsTypeNone() const { return IsType(PrefType::None); }
1120 bool IsTypeString() const { return IsType(PrefType::String); }
1121 bool IsTypeInt() const { return IsType(PrefType::Int); }
1122 bool IsTypeBool() const { return IsType(PrefType::Bool); }
1124 #define FORWARD(retType, method) \
1125 retType method() const { \
1126 struct Matcher { \
1127 retType operator()(const Pref* aPref) { return aPref->method(); } \
1128 retType operator()(SharedPref& aPref) { return aPref.method(); } \
1129 }; \
1130 return match(Matcher()); \
1133 FORWARD(bool, IsLocked)
1134 FORWARD(bool, IsSanitized)
1135 FORWARD(bool, IsSticky)
1136 FORWARD(bool, HasDefaultValue)
1137 FORWARD(bool, HasUserValue)
1138 FORWARD(const char*, Name)
1139 FORWARD(nsCString, NameString)
1140 FORWARD(PrefType, Type)
1141 #undef FORWARD
1143 #define FORWARD(retType, method) \
1144 retType method(PrefValueKind aKind = PrefValueKind::User) const { \
1145 struct Matcher { \
1146 PrefValueKind mKind; \
1148 retType operator()(const Pref* aPref) { return aPref->method(mKind); } \
1149 retType operator()(SharedPref& aPref) { return aPref.method(mKind); } \
1150 }; \
1151 return match(Matcher{aKind}); \
1154 FORWARD(bool, GetBoolValue)
1155 FORWARD(int32_t, GetIntValue)
1156 FORWARD(nsCString, GetStringValue)
1157 FORWARD(const char*, GetBareStringValue)
1158 #undef FORWARD
1160 PrefValue GetValue(PrefValueKind aKind = PrefValueKind::User) const {
1161 switch (Type()) {
1162 case PrefType::Bool:
1163 return PrefValue{GetBoolValue(aKind)};
1164 case PrefType::Int:
1165 return PrefValue{GetIntValue(aKind)};
1166 case PrefType::String:
1167 return PrefValue{GetBareStringValue(aKind)};
1168 case PrefType::None:
1169 // This check will be performed in the above functions; but for NoneType
1170 // we need to do it explicitly, then fall-through.
1171 if (IsPreferenceSanitized(Name())) {
1172 glean::security::pref_usage_content_process.Record(Some(
1173 glean::security::PrefUsageContentProcessExtra{Some(Name())}));
1175 if (sCrashOnBlocklistedPref) {
1176 MOZ_CRASH_UNSAFE_PRINTF(
1177 "Should not access the preference '%s' in the Content "
1178 "Processes",
1179 Name());
1182 [[fallthrough]];
1183 default:
1184 MOZ_ASSERT_UNREACHABLE("Unexpected pref type");
1185 return PrefValue{};
1189 Result<PrefValueKind, nsresult> WantValueKind(PrefType aType,
1190 PrefValueKind aKind) const {
1191 // WantValueKind may short-circuit GetValue functions and cause them to
1192 // return early, before this check occurs in GetFooValue()
1193 if (this->is<Pref*>() && IsPreferenceSanitized(this->as<Pref*>())) {
1194 glean::security::pref_usage_content_process.Record(
1195 Some(glean::security::PrefUsageContentProcessExtra{Some(Name())}));
1197 if (sCrashOnBlocklistedPref) {
1198 MOZ_CRASH_UNSAFE_PRINTF(
1199 "Should not access the preference '%s' in the Content Processes",
1200 Name());
1202 } else if (!this->is<Pref*>()) {
1203 // While we could use Name() above, and avoid the Variant checks, it
1204 // would less efficient than needed and we can instead do a debug-only
1205 // assert here to limit the inefficientcy
1206 MOZ_ASSERT(!IsPreferenceSanitized(Name()),
1207 "We should never have a sanitized SharedPrefMap::Pref.");
1210 if (Type() != aType) {
1211 return Err(NS_ERROR_UNEXPECTED);
1214 if (aKind == PrefValueKind::Default || IsLocked() || !HasUserValue()) {
1215 if (!HasDefaultValue()) {
1216 return Err(NS_ERROR_UNEXPECTED);
1218 return PrefValueKind::Default;
1220 return PrefValueKind::User;
1223 nsresult GetValue(PrefValueKind aKind, bool* aResult) const {
1224 PrefValueKind kind;
1225 MOZ_TRY_VAR(kind, WantValueKind(PrefType::Bool, aKind));
1227 *aResult = GetBoolValue(kind);
1228 return NS_OK;
1231 nsresult GetValue(PrefValueKind aKind, int32_t* aResult) const {
1232 PrefValueKind kind;
1233 MOZ_TRY_VAR(kind, WantValueKind(PrefType::Int, aKind));
1235 *aResult = GetIntValue(kind);
1236 return NS_OK;
1239 nsresult GetValue(PrefValueKind aKind, uint32_t* aResult) const {
1240 return GetValue(aKind, reinterpret_cast<int32_t*>(aResult));
1243 nsresult GetValue(PrefValueKind aKind, float* aResult) const {
1244 nsAutoCString result;
1245 nsresult rv = GetValue(aKind, result);
1246 if (NS_SUCCEEDED(rv)) {
1247 // ParsePrefFloat() does a locale-independent conversion.
1248 // FIXME: Other `GetValue` overloads don't clobber `aResult` on error.
1249 *aResult = ParsePrefFloat(result, &rv);
1251 return rv;
1254 nsresult GetValue(PrefValueKind aKind, nsACString& aResult) const {
1255 PrefValueKind kind;
1256 MOZ_TRY_VAR(kind, WantValueKind(PrefType::String, aKind));
1258 aResult = GetStringValue(kind);
1259 return NS_OK;
1262 nsresult GetValue(PrefValueKind aKind, nsACString* aResult) const {
1263 return GetValue(aKind, *aResult);
1266 // Returns false if this pref doesn't have a user value worth saving.
1267 bool UserValueToStringForSaving(nsCString& aStr) {
1268 // Should we save the user value, if present? Only if it does not match the
1269 // default value, or it is sticky.
1270 if (HasUserValue() &&
1271 (!ValueMatches(PrefValueKind::Default, Type(), GetValue()) ||
1272 IsSticky())) {
1273 if (IsTypeString()) {
1274 StrEscape(GetStringValue().get(), aStr);
1276 } else if (IsTypeInt()) {
1277 aStr.AppendInt(GetIntValue());
1279 } else if (IsTypeBool()) {
1280 aStr = GetBoolValue() ? "true" : "false";
1282 return true;
1285 // Do not save default prefs that haven't changed.
1286 return false;
1289 bool Matches(PrefType aType, PrefValueKind aKind, PrefValue& aValue,
1290 bool aIsSticky, bool aIsLocked) const {
1291 return (ValueMatches(aKind, aType, aValue) && aIsSticky == IsSticky() &&
1292 aIsLocked == IsLocked());
1295 bool ValueMatches(PrefValueKind aKind, PrefType aType,
1296 const PrefValue& aValue) const {
1297 if (!IsType(aType)) {
1298 return false;
1300 if (!(aKind == PrefValueKind::Default ? HasDefaultValue()
1301 : HasUserValue())) {
1302 return false;
1304 switch (aType) {
1305 case PrefType::Bool:
1306 return GetBoolValue(aKind) == aValue.mBoolVal;
1307 case PrefType::Int:
1308 return GetIntValue(aKind) == aValue.mIntVal;
1309 case PrefType::String:
1310 return strcmp(GetBareStringValue(aKind), aValue.mStringVal) == 0;
1311 default:
1312 MOZ_ASSERT_UNREACHABLE("Unexpected preference type");
1313 return false;
1318 void Pref::FromWrapper(PrefWrapper& aWrapper) {
1319 MOZ_ASSERT(aWrapper.is<SharedPrefMap::Pref>());
1320 auto pref = aWrapper.as<SharedPrefMap::Pref>();
1322 MOZ_ASSERT(IsTypeNone());
1323 MOZ_ASSERT(mName == pref.NameString());
1325 mType = uint32_t(pref.Type());
1327 mIsLocked = pref.IsLocked();
1328 mIsSanitized = pref.IsSanitized();
1329 mIsSticky = pref.IsSticky();
1331 mHasDefaultValue = pref.HasDefaultValue();
1332 mHasUserValue = pref.HasUserValue();
1334 if (mHasDefaultValue) {
1335 mDefaultValue.Init(Type(), aWrapper.GetValue(PrefValueKind::Default));
1337 if (mHasUserValue) {
1338 mUserValue.Init(Type(), aWrapper.GetValue(PrefValueKind::User));
1342 class CallbackNode {
1343 public:
1344 CallbackNode(const nsACString& aDomain, PrefChangedFunc aFunc, void* aData,
1345 Preferences::MatchKind aMatchKind)
1346 : mDomain(AsVariant(nsCString(aDomain))),
1347 mFunc(aFunc),
1348 mData(aData),
1349 mNextAndMatchKind(aMatchKind) {}
1351 CallbackNode(const char* const* aDomains, PrefChangedFunc aFunc, void* aData,
1352 Preferences::MatchKind aMatchKind)
1353 : mDomain(AsVariant(aDomains)),
1354 mFunc(aFunc),
1355 mData(aData),
1356 mNextAndMatchKind(aMatchKind) {}
1358 // mDomain is a UniquePtr<>, so any uses of Domain() should only be temporary
1359 // borrows.
1360 const Variant<nsCString, const char* const*>& Domain() const {
1361 return mDomain;
1364 PrefChangedFunc Func() const { return mFunc; }
1365 void ClearFunc() { mFunc = nullptr; }
1367 void* Data() const { return mData; }
1369 Preferences::MatchKind MatchKind() const {
1370 return static_cast<Preferences::MatchKind>(mNextAndMatchKind &
1371 kMatchKindMask);
1374 bool DomainIs(const nsACString& aDomain) const {
1375 return mDomain.is<nsCString>() && mDomain.as<nsCString>() == aDomain;
1378 bool DomainIs(const char* const* aPrefs) const {
1379 return mDomain == AsVariant(aPrefs);
1382 bool Matches(const nsACString& aPrefName) const {
1383 auto match = [&](const nsACString& aStr) {
1384 return MatchKind() == Preferences::ExactMatch
1385 ? aPrefName == aStr
1386 : StringBeginsWith(aPrefName, aStr);
1389 if (mDomain.is<nsCString>()) {
1390 return match(mDomain.as<nsCString>());
1392 for (const char* const* ptr = mDomain.as<const char* const*>(); *ptr;
1393 ptr++) {
1394 if (match(nsDependentCString(*ptr))) {
1395 return true;
1398 return false;
1401 CallbackNode* Next() const {
1402 return reinterpret_cast<CallbackNode*>(mNextAndMatchKind & kNextMask);
1405 void SetNext(CallbackNode* aNext) {
1406 uintptr_t matchKind = mNextAndMatchKind & kMatchKindMask;
1407 mNextAndMatchKind = reinterpret_cast<uintptr_t>(aNext);
1408 MOZ_ASSERT((mNextAndMatchKind & kMatchKindMask) == 0);
1409 mNextAndMatchKind |= matchKind;
1412 void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, PrefsSizes& aSizes) {
1413 aSizes.mCallbacksObjects += aMallocSizeOf(this);
1414 if (mDomain.is<nsCString>()) {
1415 aSizes.mCallbacksDomains +=
1416 mDomain.as<nsCString>().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
1420 private:
1421 static const uintptr_t kMatchKindMask = uintptr_t(0x1);
1422 static const uintptr_t kNextMask = ~kMatchKindMask;
1424 Variant<nsCString, const char* const*> mDomain;
1426 // If someone attempts to remove the node from the callback list while
1427 // NotifyCallbacks() is running, |func| is set to nullptr. Such nodes will
1428 // be removed at the end of NotifyCallbacks().
1429 PrefChangedFunc mFunc;
1430 void* mData;
1432 // Conceptually this is two fields:
1433 // - CallbackNode* mNext;
1434 // - Preferences::MatchKind mMatchKind;
1435 // They are combined into a tagged pointer to save memory.
1436 uintptr_t mNextAndMatchKind;
1439 using PrefsHashTable = HashSet<UniquePtr<Pref>, PrefHasher>;
1441 // The main prefs hash table. Inside a function so we can assert it's only
1442 // accessed on the main thread. (That assertion can be avoided but only do so
1443 // with great care!)
1444 static inline PrefsHashTable*& HashTable(bool aOffMainThread = false) {
1445 MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
1446 static PrefsHashTable* sHashTable = nullptr;
1447 return sHashTable;
1450 #ifdef DEBUG
1451 // This defines the type used to store our `once` mirrors checker. We can't use
1452 // HashMap for now due to alignment restrictions when dealing with
1453 // std::function<void()> (see bug 1557617).
1454 typedef std::function<void()> AntiFootgunCallback;
1455 struct CompareStr {
1456 bool operator()(char const* a, char const* b) const {
1457 return std::strcmp(a, b) < 0;
1460 typedef std::map<const char*, AntiFootgunCallback, CompareStr> AntiFootgunMap;
1461 static StaticAutoPtr<AntiFootgunMap> gOnceStaticPrefsAntiFootgun;
1462 #endif
1464 // The callback list contains all the priority callbacks followed by the
1465 // non-priority callbacks. gLastPriorityNode records where the first part ends.
1466 static CallbackNode* gFirstCallback = nullptr;
1467 static CallbackNode* gLastPriorityNode = nullptr;
1469 #ifdef DEBUG
1470 # define ACCESS_COUNTS
1471 #endif
1473 #ifdef ACCESS_COUNTS
1474 using AccessCountsHashTable = nsTHashMap<nsCStringHashKey, uint32_t>;
1475 static StaticAutoPtr<AccessCountsHashTable> gAccessCounts;
1477 static void AddAccessCount(const nsACString& aPrefName) {
1478 // FIXME: Servo reads preferences from background threads in unsafe ways (bug
1479 // 1474789), and triggers assertions here if we try to add usage count entries
1480 // from background threads.
1481 if (NS_IsMainThread()) {
1482 JS::AutoSuppressGCAnalysis nogc; // Hash functions will not GC.
1483 uint32_t& count = gAccessCounts->LookupOrInsert(aPrefName);
1484 count++;
1488 static void AddAccessCount(const char* aPrefName) {
1489 AddAccessCount(nsDependentCString(aPrefName));
1491 #else
1492 static void MOZ_MAYBE_UNUSED AddAccessCount(const nsACString& aPrefName) {}
1494 static void AddAccessCount(const char* aPrefName) {}
1495 #endif
1497 // These are only used during the call to NotifyCallbacks().
1498 static bool gCallbacksInProgress = false;
1499 static bool gShouldCleanupDeadNodes = false;
1501 class PrefsHashIter {
1502 using Iterator = decltype(HashTable()->modIter());
1503 using ElemType = Pref*;
1505 Iterator mIter;
1507 public:
1508 explicit PrefsHashIter(PrefsHashTable* aTable) : mIter(aTable->modIter()) {}
1510 class Elem {
1511 friend class PrefsHashIter;
1513 PrefsHashIter& mParent;
1514 bool mDone;
1516 Elem(PrefsHashIter& aIter, bool aDone) : mParent(aIter), mDone(aDone) {}
1518 Iterator& Iter() { return mParent.mIter; }
1520 public:
1521 Elem& operator*() { return *this; }
1523 ElemType get() {
1524 if (mDone) {
1525 return nullptr;
1527 return Iter().get().get();
1529 ElemType get() const { return const_cast<Elem*>(this)->get(); }
1531 ElemType operator->() { return get(); }
1532 ElemType operator->() const { return get(); }
1534 operator ElemType() { return get(); }
1536 void Remove() { Iter().remove(); }
1538 Elem& operator++() {
1539 MOZ_ASSERT(!mDone);
1540 Iter().next();
1541 mDone = Iter().done();
1542 return *this;
1545 bool operator!=(Elem& other) {
1546 return mDone != other.mDone || this->get() != other.get();
1550 Elem begin() { return Elem(*this, mIter.done()); }
1552 Elem end() { return Elem(*this, true); }
1555 class PrefsIter {
1556 using Iterator = decltype(HashTable()->iter());
1557 using ElemType = PrefWrapper;
1559 using HashElem = PrefsHashIter::Elem;
1560 using SharedElem = SharedPrefMap::Pref;
1562 using ElemTypeVariant = Variant<HashElem, SharedElem>;
1564 SharedPrefMap* mSharedMap;
1565 PrefsHashTable* mHashTable;
1566 PrefsHashIter mIter;
1568 ElemTypeVariant mPos;
1569 ElemTypeVariant mEnd;
1571 Maybe<PrefWrapper> mEntry;
1573 public:
1574 PrefsIter(PrefsHashTable* aHashTable, SharedPrefMap* aSharedMap)
1575 : mSharedMap(aSharedMap),
1576 mHashTable(aHashTable),
1577 mIter(aHashTable),
1578 mPos(AsVariant(mIter.begin())),
1579 mEnd(AsVariant(mIter.end())) {
1580 if (Done()) {
1581 NextIterator();
1585 private:
1586 #define MATCH(type, ...) \
1587 do { \
1588 struct Matcher { \
1589 PrefsIter& mIter; \
1590 type operator()(HashElem& pos) { \
1591 HashElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<HashElem>(); \
1592 __VA_ARGS__; \
1594 type operator()(SharedElem& pos) { \
1595 SharedElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<SharedElem>(); \
1596 __VA_ARGS__; \
1598 }; \
1599 return mPos.match(Matcher{*this}); \
1600 } while (0);
1602 bool Done() { MATCH(bool, return pos == end); }
1604 PrefWrapper MakeEntry() { MATCH(PrefWrapper, return PrefWrapper(pos)); }
1606 void NextEntry() {
1607 mEntry.reset();
1608 MATCH(void, ++pos);
1610 #undef MATCH
1612 bool Next() {
1613 NextEntry();
1614 return !Done() || NextIterator();
1617 bool NextIterator() {
1618 if (mPos.is<HashElem>() && mSharedMap) {
1619 mPos = AsVariant(mSharedMap->begin());
1620 mEnd = AsVariant(mSharedMap->end());
1621 return !Done();
1623 return false;
1626 bool IteratingBase() { return mPos.is<SharedElem>(); }
1628 PrefWrapper& Entry() {
1629 MOZ_ASSERT(!Done());
1631 if (!mEntry.isSome()) {
1632 mEntry.emplace(MakeEntry());
1634 return mEntry.ref();
1637 public:
1638 class Elem {
1639 friend class PrefsIter;
1641 PrefsIter& mParent;
1642 bool mDone;
1644 Elem(PrefsIter& aIter, bool aDone) : mParent(aIter), mDone(aDone) {
1645 SkipDuplicates();
1648 void Next() { mDone = !mParent.Next(); }
1650 void SkipDuplicates() {
1651 while (!mDone &&
1652 (mParent.IteratingBase() ? mParent.mHashTable->has(ref().Name())
1653 : ref().IsTypeNone())) {
1654 Next();
1658 public:
1659 Elem& operator*() { return *this; }
1661 ElemType& ref() { return mParent.Entry(); }
1662 const ElemType& ref() const { return const_cast<Elem*>(this)->ref(); }
1664 ElemType* operator->() { return &ref(); }
1665 const ElemType* operator->() const { return &ref(); }
1667 operator ElemType() { return ref(); }
1669 Elem& operator++() {
1670 MOZ_ASSERT(!mDone);
1671 Next();
1672 SkipDuplicates();
1673 return *this;
1676 bool operator!=(Elem& other) {
1677 if (mDone != other.mDone) {
1678 return true;
1680 if (mDone) {
1681 return false;
1683 return &this->ref() != &other.ref();
1687 Elem begin() { return {*this, Done()}; }
1689 Elem end() { return {*this, true}; }
1692 static Pref* pref_HashTableLookup(const char* aPrefName);
1694 static void NotifyCallbacks(const nsCString& aPrefName,
1695 const PrefWrapper* aPref = nullptr);
1697 static void NotifyCallbacks(const nsCString& aPrefName,
1698 const PrefWrapper& aPref) {
1699 NotifyCallbacks(aPrefName, &aPref);
1702 // The approximate number of preferences in the dynamic hashtable for the parent
1703 // and content processes, respectively. These numbers are used to determine the
1704 // initial size of the dynamic preference hashtables, and should be chosen to
1705 // avoid rehashing during normal usage. The actual number of preferences will,
1706 // or course, change over time, but these numbers only need to be within a
1707 // binary order of magnitude of the actual values to remain effective.
1709 // The number for the parent process should reflect the total number of
1710 // preferences in the database, since the parent process needs to initially
1711 // build a dynamic hashtable of the entire preference database. The number for
1712 // the child process should reflect the number of preferences which are likely
1713 // to change after the startup of the first content process, since content
1714 // processes only store changed preferences on top of a snapshot of the database
1715 // created at startup.
1717 // Note: The capacity of a hashtable doubles when its length reaches an exact
1718 // power of two. A table with an initial length of 64 is twice as large as one
1719 // with an initial length of 63. This is important in content processes, where
1720 // lookup speed is less critical and we pay the price of the additional overhead
1721 // for each content process. So the initial content length should generally be
1722 // *under* the next power-of-two larger than its expected length.
1723 constexpr size_t kHashTableInitialLengthParent = 3000;
1724 constexpr size_t kHashTableInitialLengthContent = 64;
1726 static PrefSaveData pref_savePrefs() {
1727 MOZ_ASSERT(NS_IsMainThread());
1729 PrefSaveData savedPrefs(HashTable()->count());
1731 for (auto& pref : PrefsIter(HashTable(), gSharedMap)) {
1732 nsAutoCString prefValueStr;
1733 if (!pref->UserValueToStringForSaving(prefValueStr)) {
1734 continue;
1737 nsAutoCString prefNameStr;
1738 StrEscape(pref->Name(), prefNameStr);
1740 nsPrintfCString str("user_pref(%s, %s);", prefNameStr.get(),
1741 prefValueStr.get());
1743 savedPrefs.AppendElement(str);
1746 return savedPrefs;
1749 static Pref* pref_HashTableLookup(const char* aPrefName) {
1750 MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
1752 MOZ_ASSERT_IF(!XRE_IsParentProcess(), gContentProcessPrefsAreInited);
1754 // We use readonlyThreadsafeLookup() because we often have concurrent lookups
1755 // from multiple Stylo threads. This is safe because those threads cannot
1756 // modify sHashTable, and the main thread is blocked while Stylo threads are
1757 // doing these lookups.
1758 auto p = HashTable()->readonlyThreadsafeLookup(aPrefName);
1759 return p ? p->get() : nullptr;
1762 // While notifying preference callbacks, this holds the wrapper for the
1763 // preference being notified, in order to optimize lookups.
1765 // Note: Callbacks and lookups only happen on the main thread, so this is safe
1766 // to use without locking.
1767 static const PrefWrapper* gCallbackPref;
1769 Maybe<PrefWrapper> pref_SharedLookup(const char* aPrefName) {
1770 MOZ_DIAGNOSTIC_ASSERT(gSharedMap, "gSharedMap must be initialized");
1771 if (Maybe<SharedPrefMap::Pref> pref = gSharedMap->Get(aPrefName)) {
1772 return Some(*pref);
1774 return Nothing();
1777 Maybe<PrefWrapper> pref_Lookup(const char* aPrefName,
1778 bool aIncludeTypeNone = false) {
1779 MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
1781 AddAccessCount(aPrefName);
1783 if (gCallbackPref && strcmp(aPrefName, gCallbackPref->Name()) == 0) {
1784 return Some(*gCallbackPref);
1786 if (Pref* pref = pref_HashTableLookup(aPrefName)) {
1787 if (aIncludeTypeNone || !pref->IsTypeNone() || pref->IsSanitized()) {
1788 return Some(pref);
1790 } else if (gSharedMap) {
1791 return pref_SharedLookup(aPrefName);
1794 return Nothing();
1797 static Result<Pref*, nsresult> pref_LookupForModify(
1798 const nsCString& aPrefName,
1799 const std::function<bool(const PrefWrapper&)>& aCheckFn) {
1800 Maybe<PrefWrapper> wrapper =
1801 pref_Lookup(aPrefName.get(), /* includeTypeNone */ true);
1802 if (wrapper.isNothing()) {
1803 return Err(NS_ERROR_INVALID_ARG);
1805 if (!aCheckFn(*wrapper)) {
1806 return nullptr;
1808 if (wrapper->is<Pref*>()) {
1809 return wrapper->as<Pref*>();
1812 Pref* pref = new Pref(aPrefName);
1813 if (!HashTable()->putNew(aPrefName.get(), pref)) {
1814 delete pref;
1815 return Err(NS_ERROR_OUT_OF_MEMORY);
1817 pref->FromWrapper(*wrapper);
1818 return pref;
1821 static nsresult pref_SetPref(const nsCString& aPrefName, PrefType aType,
1822 PrefValueKind aKind, PrefValue aValue,
1823 bool aIsSticky, bool aIsLocked, bool aFromInit) {
1824 MOZ_ASSERT(XRE_IsParentProcess());
1825 MOZ_ASSERT(NS_IsMainThread());
1827 if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) {
1828 printf(
1829 "pref_SetPref: Attempt to write pref %s after XPCOMShutdownThreads "
1830 "started.\n",
1831 aPrefName.get());
1832 if (nsContentUtils::IsInitialized()) {
1833 xpc_DumpJSStack(true, true, false);
1835 MOZ_ASSERT(false, "Late preference writes should be avoided.");
1836 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
1839 if (!HashTable()) {
1840 return NS_ERROR_OUT_OF_MEMORY;
1843 Pref* pref = nullptr;
1844 if (gSharedMap) {
1845 auto result =
1846 pref_LookupForModify(aPrefName, [&](const PrefWrapper& aWrapper) {
1847 return !aWrapper.Matches(aType, aKind, aValue, aIsSticky, aIsLocked);
1849 if (result.isOk() && !(pref = result.unwrap())) {
1850 // No changes required.
1851 return NS_OK;
1855 if (!pref) {
1856 auto p = HashTable()->lookupForAdd(aPrefName.get());
1857 if (!p) {
1858 pref = new Pref(aPrefName);
1859 pref->SetType(aType);
1860 if (!HashTable()->add(p, pref)) {
1861 delete pref;
1862 return NS_ERROR_OUT_OF_MEMORY;
1864 } else {
1865 pref = p->get();
1869 bool valueChanged = false;
1870 nsresult rv;
1871 if (aKind == PrefValueKind::Default) {
1872 rv = pref->SetDefaultValue(aType, aValue, aIsSticky, aIsLocked,
1873 &valueChanged);
1874 } else {
1875 MOZ_ASSERT(!aIsLocked); // `locked` is disallowed in user pref files
1876 rv = pref->SetUserValue(aType, aValue, aFromInit, &valueChanged);
1878 if (NS_FAILED(rv)) {
1879 NS_WARNING(
1880 nsPrintfCString("Rejected attempt to change type of pref %s's %s value "
1881 "from %s to %s",
1882 aPrefName.get(),
1883 (aKind == PrefValueKind::Default) ? "default" : "user",
1884 PrefTypeToString(pref->Type()), PrefTypeToString(aType))
1885 .get());
1887 return rv;
1890 if (valueChanged) {
1891 if (!aFromInit && profiler_thread_is_being_profiled_for_markers()) {
1892 nsAutoCString value;
1893 aValue.ToString(aType, value);
1894 profiler_add_marker(
1895 "Preference Write", baseprofiler::category::OTHER_PreferenceRead, {},
1896 PreferenceMarker{}, aPrefName, Some(aKind), aType, value);
1899 if (aKind == PrefValueKind::User) {
1900 Preferences::HandleDirty();
1902 NotifyCallbacks(aPrefName, PrefWrapper(pref));
1905 return NS_OK;
1908 // Removes |node| from callback list. Returns the node after the deleted one.
1909 static CallbackNode* pref_RemoveCallbackNode(CallbackNode* aNode,
1910 CallbackNode* aPrevNode) {
1911 MOZ_ASSERT(!aPrevNode || aPrevNode->Next() == aNode);
1912 MOZ_ASSERT(aPrevNode || gFirstCallback == aNode);
1913 MOZ_ASSERT(!gCallbacksInProgress);
1915 CallbackNode* next_node = aNode->Next();
1916 if (aPrevNode) {
1917 aPrevNode->SetNext(next_node);
1918 } else {
1919 gFirstCallback = next_node;
1921 if (gLastPriorityNode == aNode) {
1922 gLastPriorityNode = aPrevNode;
1924 delete aNode;
1925 return next_node;
1928 static void NotifyCallbacks(const nsCString& aPrefName,
1929 const PrefWrapper* aPref) {
1930 bool reentered = gCallbacksInProgress;
1932 gCallbackPref = aPref;
1933 auto cleanup = MakeScopeExit([]() { gCallbackPref = nullptr; });
1935 // Nodes must not be deleted while gCallbacksInProgress is true.
1936 // Nodes that need to be deleted are marked for deletion by nulling
1937 // out the |func| pointer. We release them at the end of this function
1938 // if we haven't reentered.
1939 gCallbacksInProgress = true;
1941 for (CallbackNode* node = gFirstCallback; node; node = node->Next()) {
1942 if (node->Func()) {
1943 if (node->Matches(aPrefName)) {
1944 (node->Func())(aPrefName.get(), node->Data());
1949 gCallbacksInProgress = reentered;
1951 if (gShouldCleanupDeadNodes && !gCallbacksInProgress) {
1952 CallbackNode* prev_node = nullptr;
1953 CallbackNode* node = gFirstCallback;
1955 while (node) {
1956 if (!node->Func()) {
1957 node = pref_RemoveCallbackNode(node, prev_node);
1958 } else {
1959 prev_node = node;
1960 node = node->Next();
1963 gShouldCleanupDeadNodes = false;
1966 #ifdef DEBUG
1967 if (XRE_IsParentProcess() &&
1968 !StaticPrefs::preferences_force_disable_check_once_policy() &&
1969 (StaticPrefs::preferences_check_once_policy() || xpc::IsInAutomation())) {
1970 // Check that we aren't modifying a `once`-mirrored pref using that pref
1971 // name. We have about 100 `once`-mirrored prefs. std::map performs a
1972 // search in O(log n), so this is fast enough.
1973 MOZ_ASSERT(gOnceStaticPrefsAntiFootgun);
1974 auto search = gOnceStaticPrefsAntiFootgun->find(aPrefName.get());
1975 if (search != gOnceStaticPrefsAntiFootgun->end()) {
1976 // Run the callback.
1977 (search->second)();
1980 #endif
1983 //===========================================================================
1984 // Prefs parsing
1985 //===========================================================================
1987 extern "C" {
1989 // Keep this in sync with PrefFn in parser/src/lib.rs.
1990 typedef void (*PrefsParserPrefFn)(const char* aPrefName, PrefType aType,
1991 PrefValueKind aKind, PrefValue aValue,
1992 bool aIsSticky, bool aIsLocked);
1994 // Keep this in sync with ErrorFn in parser/src/lib.rs.
1996 // `aMsg` is just a borrow of the string, and must be copied if it is used
1997 // outside the lifetime of the prefs_parser_parse() call.
1998 typedef void (*PrefsParserErrorFn)(const char* aMsg);
2000 // Keep this in sync with prefs_parser_parse() in parser/src/lib.rs.
2001 bool prefs_parser_parse(const char* aPath, PrefValueKind aKind,
2002 const char* aBuf, size_t aLen,
2003 PrefsParserPrefFn aPrefFn, PrefsParserErrorFn aErrorFn);
2006 class Parser {
2007 public:
2008 Parser() = default;
2009 ~Parser() = default;
2011 bool Parse(PrefValueKind aKind, const char* aPath, const nsCString& aBuf) {
2012 MOZ_ASSERT(XRE_IsParentProcess());
2013 return prefs_parser_parse(aPath, aKind, aBuf.get(), aBuf.Length(),
2014 HandlePref, HandleError);
2017 private:
2018 static void HandlePref(const char* aPrefName, PrefType aType,
2019 PrefValueKind aKind, PrefValue aValue, bool aIsSticky,
2020 bool aIsLocked) {
2021 MOZ_ASSERT(XRE_IsParentProcess());
2022 pref_SetPref(nsDependentCString(aPrefName), aType, aKind, aValue, aIsSticky,
2023 aIsLocked,
2024 /* fromInit */ true);
2027 static void HandleError(const char* aMsg) {
2028 nsresult rv;
2029 nsCOMPtr<nsIConsoleService> console =
2030 do_GetService("@mozilla.org/consoleservice;1", &rv);
2031 if (NS_SUCCEEDED(rv)) {
2032 console->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg).get());
2034 #ifdef DEBUG
2035 NS_ERROR(aMsg);
2036 #else
2037 printf_stderr("%s\n", aMsg);
2038 #endif
2042 // The following code is test code for the gtest.
2044 static void TestParseErrorHandlePref(const char* aPrefName, PrefType aType,
2045 PrefValueKind aKind, PrefValue aValue,
2046 bool aIsSticky, bool aIsLocked) {}
2048 MOZ_CONSTINIT static nsCString gTestParseErrorMsgs;
2050 static void TestParseErrorHandleError(const char* aMsg) {
2051 gTestParseErrorMsgs.Append(aMsg);
2052 gTestParseErrorMsgs.Append('\n');
2055 // Keep this in sync with the declaration in test/gtest/Parser.cpp.
2056 void TestParseError(PrefValueKind aKind, const char* aText,
2057 nsCString& aErrorMsg) {
2058 prefs_parser_parse("test", aKind, aText, strlen(aText),
2059 TestParseErrorHandlePref, TestParseErrorHandleError);
2061 // Copy the error messages into the outparam, then clear them from
2062 // gTestParseErrorMsgs.
2063 aErrorMsg.Assign(gTestParseErrorMsgs);
2064 gTestParseErrorMsgs.Truncate();
2067 //===========================================================================
2068 // nsPrefBranch et al.
2069 //===========================================================================
2071 namespace mozilla {
2072 class PreferenceServiceReporter;
2073 } // namespace mozilla
2075 class PrefCallback : public PLDHashEntryHdr {
2076 friend class mozilla::PreferenceServiceReporter;
2078 public:
2079 typedef PrefCallback* KeyType;
2080 typedef const PrefCallback* KeyTypePointer;
2082 static const PrefCallback* KeyToPointer(PrefCallback* aKey) { return aKey; }
2084 static PLDHashNumber HashKey(const PrefCallback* aKey) {
2085 uint32_t hash = HashString(aKey->mDomain);
2086 return AddToHash(hash, aKey->mCanonical);
2089 public:
2090 // Create a PrefCallback with a strong reference to its observer.
2091 PrefCallback(const nsACString& aDomain, nsIObserver* aObserver,
2092 nsPrefBranch* aBranch)
2093 : mDomain(aDomain),
2094 mBranch(aBranch),
2095 mWeakRef(nullptr),
2096 mStrongRef(aObserver) {
2097 MOZ_COUNT_CTOR(PrefCallback);
2098 nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver);
2099 mCanonical = canonical;
2102 // Create a PrefCallback with a weak reference to its observer.
2103 PrefCallback(const nsACString& aDomain, nsISupportsWeakReference* aObserver,
2104 nsPrefBranch* aBranch)
2105 : mDomain(aDomain),
2106 mBranch(aBranch),
2107 mWeakRef(do_GetWeakReference(aObserver)),
2108 mStrongRef(nullptr) {
2109 MOZ_COUNT_CTOR(PrefCallback);
2110 nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver);
2111 mCanonical = canonical;
2114 // This is explicitly not a copy constructor.
2115 explicit PrefCallback(const PrefCallback*& aCopy)
2116 : mDomain(aCopy->mDomain),
2117 mBranch(aCopy->mBranch),
2118 mWeakRef(aCopy->mWeakRef),
2119 mStrongRef(aCopy->mStrongRef),
2120 mCanonical(aCopy->mCanonical) {
2121 MOZ_COUNT_CTOR(PrefCallback);
2124 PrefCallback(const PrefCallback&) = delete;
2125 PrefCallback(PrefCallback&&) = default;
2127 MOZ_COUNTED_DTOR(PrefCallback)
2129 bool KeyEquals(const PrefCallback* aKey) const {
2130 // We want to be able to look up a weakly-referencing PrefCallback after
2131 // its observer has died so we can remove it from the table. Once the
2132 // callback's observer dies, its canonical pointer is stale -- in
2133 // particular, we may have allocated a new observer in the same spot in
2134 // memory! So we can't just compare canonical pointers to determine whether
2135 // aKey refers to the same observer as this.
2137 // Our workaround is based on the way we use this hashtable: When we ask
2138 // the hashtable to remove a PrefCallback whose weak reference has expired,
2139 // we use as the key for removal the same object as was inserted into the
2140 // hashtable. Thus we can say that if one of the keys' weak references has
2141 // expired, the two keys are equal iff they're the same object.
2143 if (IsExpired() || aKey->IsExpired()) {
2144 return this == aKey;
2147 if (mCanonical != aKey->mCanonical) {
2148 return false;
2151 return mDomain.Equals(aKey->mDomain);
2154 PrefCallback* GetKey() const { return const_cast<PrefCallback*>(this); }
2156 // Get a reference to the callback's observer, or null if the observer was
2157 // weakly referenced and has been destroyed.
2158 already_AddRefed<nsIObserver> GetObserver() const {
2159 if (!IsWeak()) {
2160 nsCOMPtr<nsIObserver> copy = mStrongRef;
2161 return copy.forget();
2164 nsCOMPtr<nsIObserver> observer = do_QueryReferent(mWeakRef);
2165 return observer.forget();
2168 const nsCString& GetDomain() const { return mDomain; }
2170 nsPrefBranch* GetPrefBranch() const { return mBranch; }
2172 // Has this callback's weak reference died?
2173 bool IsExpired() const {
2174 if (!IsWeak()) return false;
2176 nsCOMPtr<nsIObserver> observer(do_QueryReferent(mWeakRef));
2177 return !observer;
2180 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
2181 size_t n = aMallocSizeOf(this);
2182 n += mDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
2184 // All the other fields are non-owning pointers, so we don't measure them.
2186 return n;
2189 enum { ALLOW_MEMMOVE = true };
2191 private:
2192 nsCString mDomain;
2193 nsPrefBranch* mBranch;
2195 // Exactly one of mWeakRef and mStrongRef should be non-null.
2196 nsWeakPtr mWeakRef;
2197 nsCOMPtr<nsIObserver> mStrongRef;
2199 // We need a canonical nsISupports pointer, per bug 578392.
2200 nsISupports* mCanonical;
2202 bool IsWeak() const { return !!mWeakRef; }
2205 class nsPrefBranch final : public nsIPrefBranch,
2206 public nsIObserver,
2207 public nsSupportsWeakReference {
2208 friend class mozilla::PreferenceServiceReporter;
2210 public:
2211 NS_DECL_ISUPPORTS
2212 NS_DECL_NSIPREFBRANCH
2213 NS_DECL_NSIOBSERVER
2215 nsPrefBranch(const char* aPrefRoot, PrefValueKind aKind);
2216 nsPrefBranch() = delete;
2218 static void NotifyObserver(const char* aNewpref, void* aData);
2220 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
2222 private:
2223 using PrefName = nsCString;
2225 virtual ~nsPrefBranch();
2227 int32_t GetRootLength() const { return mPrefRoot.Length(); }
2229 nsresult GetDefaultFromPropertiesFile(const char* aPrefName,
2230 nsAString& aReturn);
2232 // As SetCharPref, but without any check on the length of |aValue|.
2233 nsresult SetCharPrefNoLengthCheck(const char* aPrefName,
2234 const nsACString& aValue);
2236 // Reject strings that are more than 1Mb, warn if strings are more than 16kb.
2237 nsresult CheckSanityOfStringLength(const char* aPrefName,
2238 const nsAString& aValue);
2239 nsresult CheckSanityOfStringLength(const char* aPrefName,
2240 const nsACString& aValue);
2241 nsresult CheckSanityOfStringLength(const char* aPrefName,
2242 const uint32_t aLength);
2244 void RemoveExpiredCallback(PrefCallback* aCallback);
2246 PrefName GetPrefName(const char* aPrefName) const {
2247 return GetPrefName(nsDependentCString(aPrefName));
2250 PrefName GetPrefName(const nsACString& aPrefName) const;
2252 void FreeObserverList(void);
2254 const nsCString mPrefRoot;
2255 PrefValueKind mKind;
2257 bool mFreeingObserverList;
2258 nsClassHashtable<PrefCallback, PrefCallback> mObservers;
2261 class nsPrefLocalizedString final : public nsIPrefLocalizedString {
2262 public:
2263 nsPrefLocalizedString();
2265 NS_DECL_ISUPPORTS
2266 NS_FORWARD_NSISUPPORTSPRIMITIVE(mUnicodeString->)
2267 NS_FORWARD_NSISUPPORTSSTRING(mUnicodeString->)
2269 nsresult Init();
2271 private:
2272 virtual ~nsPrefLocalizedString();
2274 nsCOMPtr<nsISupportsString> mUnicodeString;
2277 //----------------------------------------------------------------------------
2278 // nsPrefBranch
2279 //----------------------------------------------------------------------------
2281 nsPrefBranch::nsPrefBranch(const char* aPrefRoot, PrefValueKind aKind)
2282 : mPrefRoot(aPrefRoot), mKind(aKind), mFreeingObserverList(false) {
2283 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
2284 if (observerService) {
2285 ++mRefCnt; // must be > 0 when we call this, or we'll get deleted!
2287 // Add weakly so we don't have to clean up at shutdown.
2288 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
2289 --mRefCnt;
2293 nsPrefBranch::~nsPrefBranch() { FreeObserverList(); }
2295 NS_IMPL_ISUPPORTS(nsPrefBranch, nsIPrefBranch, nsIObserver,
2296 nsISupportsWeakReference)
2298 NS_IMETHODIMP
2299 nsPrefBranch::GetRoot(nsACString& aRoot) {
2300 aRoot = mPrefRoot;
2301 return NS_OK;
2304 NS_IMETHODIMP
2305 nsPrefBranch::GetPrefType(const char* aPrefName, int32_t* aRetVal) {
2306 NS_ENSURE_ARG(aPrefName);
2308 const PrefName& prefName = GetPrefName(aPrefName);
2309 *aRetVal = Preferences::GetType(prefName.get());
2310 return NS_OK;
2313 NS_IMETHODIMP
2314 nsPrefBranch::GetBoolPrefWithDefault(const char* aPrefName, bool aDefaultValue,
2315 uint8_t aArgc, bool* aRetVal) {
2316 nsresult rv = GetBoolPref(aPrefName, aRetVal);
2317 if (NS_FAILED(rv) && aArgc == 1) {
2318 *aRetVal = aDefaultValue;
2319 return NS_OK;
2322 return rv;
2325 NS_IMETHODIMP
2326 nsPrefBranch::GetBoolPref(const char* aPrefName, bool* aRetVal) {
2327 NS_ENSURE_ARG(aPrefName);
2329 const PrefName& pref = GetPrefName(aPrefName);
2330 return Preferences::GetBool(pref.get(), aRetVal, mKind);
2333 NS_IMETHODIMP
2334 nsPrefBranch::SetBoolPref(const char* aPrefName, bool aValue) {
2335 NS_ENSURE_ARG(aPrefName);
2337 const PrefName& pref = GetPrefName(aPrefName);
2338 return Preferences::SetBool(pref.get(), aValue, mKind);
2341 NS_IMETHODIMP
2342 nsPrefBranch::GetFloatPrefWithDefault(const char* aPrefName,
2343 float aDefaultValue, uint8_t aArgc,
2344 float* aRetVal) {
2345 nsresult rv = GetFloatPref(aPrefName, aRetVal);
2347 if (NS_FAILED(rv) && aArgc == 1) {
2348 *aRetVal = aDefaultValue;
2349 return NS_OK;
2352 return rv;
2355 NS_IMETHODIMP
2356 nsPrefBranch::GetFloatPref(const char* aPrefName, float* aRetVal) {
2357 NS_ENSURE_ARG(aPrefName);
2359 nsAutoCString stringVal;
2360 nsresult rv = GetCharPref(aPrefName, stringVal);
2361 if (NS_SUCCEEDED(rv)) {
2362 // ParsePrefFloat() does a locale-independent conversion.
2363 *aRetVal = ParsePrefFloat(stringVal, &rv);
2366 return rv;
2369 NS_IMETHODIMP
2370 nsPrefBranch::GetCharPrefWithDefault(const char* aPrefName,
2371 const nsACString& aDefaultValue,
2372 uint8_t aArgc, nsACString& aRetVal) {
2373 nsresult rv = GetCharPref(aPrefName, aRetVal);
2375 if (NS_FAILED(rv) && aArgc == 1) {
2376 aRetVal = aDefaultValue;
2377 return NS_OK;
2380 return rv;
2383 NS_IMETHODIMP
2384 nsPrefBranch::GetCharPref(const char* aPrefName, nsACString& aRetVal) {
2385 NS_ENSURE_ARG(aPrefName);
2387 const PrefName& pref = GetPrefName(aPrefName);
2388 return Preferences::GetCString(pref.get(), aRetVal, mKind);
2391 NS_IMETHODIMP
2392 nsPrefBranch::SetCharPref(const char* aPrefName, const nsACString& aValue) {
2393 nsresult rv = CheckSanityOfStringLength(aPrefName, aValue);
2394 if (NS_FAILED(rv)) {
2395 return rv;
2397 return SetCharPrefNoLengthCheck(aPrefName, aValue);
2400 nsresult nsPrefBranch::SetCharPrefNoLengthCheck(const char* aPrefName,
2401 const nsACString& aValue) {
2402 NS_ENSURE_ARG(aPrefName);
2404 const PrefName& pref = GetPrefName(aPrefName);
2405 return Preferences::SetCString(pref.get(), aValue, mKind);
2408 NS_IMETHODIMP
2409 nsPrefBranch::GetStringPref(const char* aPrefName,
2410 const nsACString& aDefaultValue, uint8_t aArgc,
2411 nsACString& aRetVal) {
2412 nsCString utf8String;
2413 nsresult rv = GetCharPref(aPrefName, utf8String);
2414 if (NS_SUCCEEDED(rv)) {
2415 aRetVal = utf8String;
2416 return rv;
2419 if (aArgc == 1) {
2420 aRetVal = aDefaultValue;
2421 return NS_OK;
2424 return rv;
2427 NS_IMETHODIMP
2428 nsPrefBranch::SetStringPref(const char* aPrefName, const nsACString& aValue) {
2429 nsresult rv = CheckSanityOfStringLength(aPrefName, aValue);
2430 if (NS_FAILED(rv)) {
2431 return rv;
2434 return SetCharPrefNoLengthCheck(aPrefName, aValue);
2437 NS_IMETHODIMP
2438 nsPrefBranch::GetIntPrefWithDefault(const char* aPrefName,
2439 int32_t aDefaultValue, uint8_t aArgc,
2440 int32_t* aRetVal) {
2441 nsresult rv = GetIntPref(aPrefName, aRetVal);
2443 if (NS_FAILED(rv) && aArgc == 1) {
2444 *aRetVal = aDefaultValue;
2445 return NS_OK;
2448 return rv;
2451 NS_IMETHODIMP
2452 nsPrefBranch::GetIntPref(const char* aPrefName, int32_t* aRetVal) {
2453 NS_ENSURE_ARG(aPrefName);
2454 const PrefName& pref = GetPrefName(aPrefName);
2455 return Preferences::GetInt(pref.get(), aRetVal, mKind);
2458 NS_IMETHODIMP
2459 nsPrefBranch::SetIntPref(const char* aPrefName, int32_t aValue) {
2460 NS_ENSURE_ARG(aPrefName);
2462 const PrefName& pref = GetPrefName(aPrefName);
2463 return Preferences::SetInt(pref.get(), aValue, mKind);
2466 NS_IMETHODIMP
2467 nsPrefBranch::GetComplexValue(const char* aPrefName, const nsIID& aType,
2468 void** aRetVal) {
2469 NS_ENSURE_ARG(aPrefName);
2471 nsresult rv;
2472 nsAutoCString utf8String;
2474 // We have to do this one first because it's different to all the rest.
2475 if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) {
2476 nsCOMPtr<nsIPrefLocalizedString> theString(
2477 do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
2478 if (NS_FAILED(rv)) {
2479 return rv;
2482 const PrefName& pref = GetPrefName(aPrefName);
2483 bool bNeedDefault = false;
2485 if (mKind == PrefValueKind::Default) {
2486 bNeedDefault = true;
2487 } else {
2488 // if there is no user (or locked) value
2489 if (!Preferences::HasUserValue(pref.get()) &&
2490 !Preferences::IsLocked(pref.get())) {
2491 bNeedDefault = true;
2495 // if we need to fetch the default value, do that instead, otherwise use the
2496 // value we pulled in at the top of this function
2497 if (bNeedDefault) {
2498 nsAutoString utf16String;
2499 rv = GetDefaultFromPropertiesFile(pref.get(), utf16String);
2500 if (NS_SUCCEEDED(rv)) {
2501 theString->SetData(utf16String);
2503 } else {
2504 rv = GetCharPref(aPrefName, utf8String);
2505 if (NS_SUCCEEDED(rv)) {
2506 theString->SetData(NS_ConvertUTF8toUTF16(utf8String));
2510 if (NS_SUCCEEDED(rv)) {
2511 theString.forget(reinterpret_cast<nsIPrefLocalizedString**>(aRetVal));
2514 return rv;
2517 // if we can't get the pref, there's no point in being here
2518 rv = GetCharPref(aPrefName, utf8String);
2519 if (NS_FAILED(rv)) {
2520 return rv;
2523 if (aType.Equals(NS_GET_IID(nsIFile))) {
2524 ENSURE_PARENT_PROCESS("GetComplexValue(nsIFile)", aPrefName);
2525 MOZ_TRY(NS_NewLocalFileWithPersistentDescriptor(
2526 utf8String, reinterpret_cast<nsIFile**>(aRetVal)));
2527 return NS_OK;
2530 if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) {
2531 ENSURE_PARENT_PROCESS("GetComplexValue(nsIRelativeFilePref)", aPrefName);
2533 nsACString::const_iterator keyBegin, strEnd;
2534 utf8String.BeginReading(keyBegin);
2535 utf8String.EndReading(strEnd);
2537 // The pref has the format: [fromKey]a/b/c
2538 if (*keyBegin++ != '[') {
2539 return NS_ERROR_FAILURE;
2542 nsACString::const_iterator keyEnd(keyBegin);
2543 if (!FindCharInReadable(']', keyEnd, strEnd)) {
2544 return NS_ERROR_FAILURE;
2547 nsAutoCString key(Substring(keyBegin, keyEnd));
2549 nsCOMPtr<nsIFile> fromFile;
2550 nsCOMPtr<nsIProperties> directoryService(
2551 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
2552 if (NS_FAILED(rv)) {
2553 return rv;
2556 rv = directoryService->Get(key.get(), NS_GET_IID(nsIFile),
2557 getter_AddRefs(fromFile));
2558 if (NS_FAILED(rv)) {
2559 return rv;
2562 nsCOMPtr<nsIFile> theFile;
2563 MOZ_TRY(NS_NewLocalFileWithRelativeDescriptor(
2564 fromFile, Substring(++keyEnd, strEnd), getter_AddRefs(theFile)));
2566 nsCOMPtr<nsIRelativeFilePref> relativePref = new nsRelativeFilePref();
2567 Unused << relativePref->SetFile(theFile);
2568 Unused << relativePref->SetRelativeToKey(key);
2570 relativePref.forget(reinterpret_cast<nsIRelativeFilePref**>(aRetVal));
2571 return NS_OK;
2574 NS_WARNING("nsPrefBranch::GetComplexValue - Unsupported interface type");
2575 return NS_NOINTERFACE;
2578 nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName,
2579 const nsAString& aValue) {
2580 return CheckSanityOfStringLength(aPrefName, aValue.Length());
2583 nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName,
2584 const nsACString& aValue) {
2585 return CheckSanityOfStringLength(aPrefName, aValue.Length());
2588 nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName,
2589 const uint32_t aLength) {
2590 if (aLength > MAX_PREF_LENGTH) {
2591 return NS_ERROR_ILLEGAL_VALUE;
2593 if (aLength <= MAX_ADVISABLE_PREF_LENGTH) {
2594 return NS_OK;
2597 nsresult rv;
2598 nsCOMPtr<nsIConsoleService> console =
2599 do_GetService("@mozilla.org/consoleservice;1", &rv);
2600 if (NS_FAILED(rv)) {
2601 return rv;
2604 nsAutoCString message(nsPrintfCString(
2605 "Warning: attempting to write %d bytes to preference %s. This is bad "
2606 "for general performance and memory usage. Such an amount of data "
2607 "should rather be written to an external file.",
2608 aLength, GetPrefName(aPrefName).get()));
2610 rv = console->LogStringMessage(NS_ConvertUTF8toUTF16(message).get());
2611 if (NS_FAILED(rv)) {
2612 return rv;
2614 return NS_OK;
2617 NS_IMETHODIMP
2618 nsPrefBranch::SetComplexValue(const char* aPrefName, const nsIID& aType,
2619 nsISupports* aValue) {
2620 ENSURE_PARENT_PROCESS("SetComplexValue", aPrefName);
2621 NS_ENSURE_ARG(aPrefName);
2623 nsresult rv = NS_NOINTERFACE;
2625 if (aType.Equals(NS_GET_IID(nsIFile))) {
2626 nsCOMPtr<nsIFile> file = do_QueryInterface(aValue);
2627 if (!file) {
2628 return NS_NOINTERFACE;
2631 nsAutoCString descriptorString;
2632 rv = file->GetPersistentDescriptor(descriptorString);
2633 if (NS_SUCCEEDED(rv)) {
2634 rv = SetCharPrefNoLengthCheck(aPrefName, descriptorString);
2636 return rv;
2639 if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) {
2640 nsCOMPtr<nsIRelativeFilePref> relFilePref = do_QueryInterface(aValue);
2641 if (!relFilePref) {
2642 return NS_NOINTERFACE;
2645 nsCOMPtr<nsIFile> file;
2646 relFilePref->GetFile(getter_AddRefs(file));
2647 if (!file) {
2648 return NS_NOINTERFACE;
2651 nsAutoCString relativeToKey;
2652 (void)relFilePref->GetRelativeToKey(relativeToKey);
2654 nsCOMPtr<nsIFile> relativeToFile;
2655 nsCOMPtr<nsIProperties> directoryService(
2656 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
2657 if (NS_FAILED(rv)) {
2658 return rv;
2661 rv = directoryService->Get(relativeToKey.get(), NS_GET_IID(nsIFile),
2662 getter_AddRefs(relativeToFile));
2663 if (NS_FAILED(rv)) {
2664 return rv;
2667 nsAutoCString relDescriptor;
2668 rv = file->GetRelativeDescriptor(relativeToFile, relDescriptor);
2669 if (NS_FAILED(rv)) {
2670 return rv;
2673 nsAutoCString descriptorString;
2674 descriptorString.Append('[');
2675 descriptorString.Append(relativeToKey);
2676 descriptorString.Append(']');
2677 descriptorString.Append(relDescriptor);
2678 return SetCharPrefNoLengthCheck(aPrefName, descriptorString);
2681 if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) {
2682 nsCOMPtr<nsISupportsString> theString = do_QueryInterface(aValue);
2684 if (theString) {
2685 nsString wideString;
2687 rv = theString->GetData(wideString);
2688 if (NS_SUCCEEDED(rv)) {
2689 // Check sanity of string length before any lengthy conversion
2690 rv = CheckSanityOfStringLength(aPrefName, wideString);
2691 if (NS_FAILED(rv)) {
2692 return rv;
2694 rv = SetCharPrefNoLengthCheck(aPrefName,
2695 NS_ConvertUTF16toUTF8(wideString));
2698 return rv;
2701 NS_WARNING("nsPrefBranch::SetComplexValue - Unsupported interface type");
2702 return NS_NOINTERFACE;
2705 NS_IMETHODIMP
2706 nsPrefBranch::ClearUserPref(const char* aPrefName) {
2707 NS_ENSURE_ARG(aPrefName);
2709 const PrefName& pref = GetPrefName(aPrefName);
2710 return Preferences::ClearUser(pref.get());
2713 NS_IMETHODIMP
2714 nsPrefBranch::PrefHasUserValue(const char* aPrefName, bool* aRetVal) {
2715 NS_ENSURE_ARG_POINTER(aRetVal);
2716 NS_ENSURE_ARG(aPrefName);
2718 const PrefName& pref = GetPrefName(aPrefName);
2719 *aRetVal = Preferences::HasUserValue(pref.get());
2720 return NS_OK;
2723 NS_IMETHODIMP
2724 nsPrefBranch::PrefHasDefaultValue(const char* aPrefName, bool* aRetVal) {
2725 NS_ENSURE_ARG_POINTER(aRetVal);
2726 NS_ENSURE_ARG(aPrefName);
2728 const PrefName& pref = GetPrefName(aPrefName);
2729 *aRetVal = Preferences::HasDefaultValue(pref.get());
2730 return NS_OK;
2733 NS_IMETHODIMP
2734 nsPrefBranch::LockPref(const char* aPrefName) {
2735 NS_ENSURE_ARG(aPrefName);
2737 const PrefName& pref = GetPrefName(aPrefName);
2738 return Preferences::Lock(pref.get());
2741 NS_IMETHODIMP
2742 nsPrefBranch::PrefIsLocked(const char* aPrefName, bool* aRetVal) {
2743 NS_ENSURE_ARG_POINTER(aRetVal);
2744 NS_ENSURE_ARG(aPrefName);
2746 const PrefName& pref = GetPrefName(aPrefName);
2747 *aRetVal = Preferences::IsLocked(pref.get());
2748 return NS_OK;
2751 NS_IMETHODIMP
2752 nsPrefBranch::PrefIsSanitized(const char* aPrefName, bool* aRetVal) {
2753 NS_ENSURE_ARG_POINTER(aRetVal);
2754 NS_ENSURE_ARG(aPrefName);
2756 const PrefName& pref = GetPrefName(aPrefName);
2757 *aRetVal = Preferences::IsSanitized(pref.get());
2758 return NS_OK;
2761 NS_IMETHODIMP
2762 nsPrefBranch::UnlockPref(const char* aPrefName) {
2763 NS_ENSURE_ARG(aPrefName);
2765 const PrefName& pref = GetPrefName(aPrefName);
2766 return Preferences::Unlock(pref.get());
2769 NS_IMETHODIMP
2770 nsPrefBranch::DeleteBranch(const char* aStartingAt) {
2771 ENSURE_PARENT_PROCESS("DeleteBranch", aStartingAt);
2772 NS_ENSURE_ARG(aStartingAt);
2774 MOZ_ASSERT(NS_IsMainThread());
2776 if (!HashTable()) {
2777 return NS_ERROR_NOT_INITIALIZED;
2780 const PrefName& pref = GetPrefName(aStartingAt);
2781 nsAutoCString branchName(pref.get());
2783 // Add a trailing '.' if it doesn't already have one.
2784 if (branchName.Length() > 1 && !StringEndsWith(branchName, "."_ns)) {
2785 branchName += '.';
2788 const nsACString& branchNameNoDot =
2789 Substring(branchName, 0, branchName.Length() - 1);
2791 for (auto iter = HashTable()->modIter(); !iter.done(); iter.next()) {
2792 // The first disjunct matches branches: e.g. a branch name "foo.bar."
2793 // matches a name "foo.bar.baz" (but it won't match "foo.barrel.baz").
2794 // The second disjunct matches leaf nodes: e.g. a branch name "foo.bar."
2795 // matches a name "foo.bar" (by ignoring the trailing '.').
2796 nsDependentCString name(iter.get()->Name());
2797 if (StringBeginsWith(name, branchName) || name.Equals(branchNameNoDot)) {
2798 iter.remove();
2799 // The saved callback pref may be invalid now.
2800 gCallbackPref = nullptr;
2804 Preferences::HandleDirty();
2805 return NS_OK;
2808 NS_IMETHODIMP
2809 nsPrefBranch::GetChildList(const char* aStartingAt,
2810 nsTArray<nsCString>& aChildArray) {
2811 NS_ENSURE_ARG(aStartingAt);
2813 MOZ_ASSERT(NS_IsMainThread());
2815 // This will contain a list of all the pref name strings. Allocated on the
2816 // stack for speed.
2817 AutoTArray<nsCString, 32> prefArray;
2819 const PrefName& parent = GetPrefName(aStartingAt);
2820 size_t parentLen = parent.Length();
2821 for (auto& pref : PrefsIter(HashTable(), gSharedMap)) {
2822 if (strncmp(pref->Name(), parent.get(), parentLen) == 0) {
2823 prefArray.AppendElement(pref->NameString());
2827 // Now that we've built up the list, run the callback on all the matching
2828 // elements.
2829 aChildArray.SetCapacity(prefArray.Length());
2830 for (auto& element : prefArray) {
2831 // we need to lop off mPrefRoot in case the user is planning to pass this
2832 // back to us because if they do we are going to add mPrefRoot again.
2833 aChildArray.AppendElement(Substring(element, mPrefRoot.Length()));
2836 return NS_OK;
2839 NS_IMETHODIMP
2840 nsPrefBranch::AddObserverImpl(const nsACString& aDomain, nsIObserver* aObserver,
2841 bool aHoldWeak) {
2842 UniquePtr<PrefCallback> pCallback;
2844 NS_ENSURE_ARG(aObserver);
2846 const nsCString& prefName = GetPrefName(aDomain);
2848 // Hold a weak reference to the observer if so requested.
2849 if (aHoldWeak) {
2850 nsCOMPtr<nsISupportsWeakReference> weakRefFactory =
2851 do_QueryInterface(aObserver);
2852 if (!weakRefFactory) {
2853 // The caller didn't give us a object that supports weak reference...
2854 // tell them.
2855 return NS_ERROR_INVALID_ARG;
2858 // Construct a PrefCallback with a weak reference to the observer.
2859 pCallback = MakeUnique<PrefCallback>(prefName, weakRefFactory, this);
2861 } else {
2862 // Construct a PrefCallback with a strong reference to the observer.
2863 pCallback = MakeUnique<PrefCallback>(prefName, aObserver, this);
2866 mObservers.WithEntryHandle(pCallback.get(), [&](auto&& p) {
2867 if (p) {
2868 NS_WARNING(
2869 nsPrintfCString("Ignoring duplicate observer: %s", prefName.get())
2870 .get());
2871 } else {
2872 // We must pass a fully qualified preference name to the callback
2873 // aDomain == nullptr is the only possible failure, and we trapped it with
2874 // NS_ENSURE_ARG above.
2875 Preferences::RegisterCallback(NotifyObserver, prefName, pCallback.get(),
2876 Preferences::PrefixMatch,
2877 /* isPriority */ false);
2879 p.Insert(std::move(pCallback));
2883 return NS_OK;
2886 NS_IMETHODIMP
2887 nsPrefBranch::RemoveObserverImpl(const nsACString& aDomain,
2888 nsIObserver* aObserver) {
2889 NS_ENSURE_ARG(aObserver);
2891 nsresult rv = NS_OK;
2893 // If we're in the middle of a call to FreeObserverList, don't process this
2894 // RemoveObserver call -- the observer in question will be removed soon, if
2895 // it hasn't been already.
2897 // It's important that we don't touch mObservers in any way -- even a Get()
2898 // which returns null might cause the hashtable to resize itself, which will
2899 // break the iteration in FreeObserverList.
2900 if (mFreeingObserverList) {
2901 return NS_OK;
2904 // Remove the relevant PrefCallback from mObservers and get an owning pointer
2905 // to it. Unregister the callback first, and then let the owning pointer go
2906 // out of scope and destroy the callback.
2907 const nsCString& prefName = GetPrefName(aDomain);
2908 PrefCallback key(prefName, aObserver, this);
2909 mozilla::UniquePtr<PrefCallback> pCallback;
2910 mObservers.Remove(&key, &pCallback);
2911 if (pCallback) {
2912 rv = Preferences::UnregisterCallback(
2913 NotifyObserver, prefName, pCallback.get(), Preferences::PrefixMatch);
2916 return rv;
2919 NS_IMETHODIMP
2920 nsPrefBranch::Observe(nsISupports* aSubject, const char* aTopic,
2921 const char16_t* aData) {
2922 // Watch for xpcom shutdown and free our observers to eliminate any cyclic
2923 // references.
2924 if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
2925 FreeObserverList();
2927 return NS_OK;
2930 /* static */
2931 void nsPrefBranch::NotifyObserver(const char* aNewPref, void* aData) {
2932 PrefCallback* pCallback = (PrefCallback*)aData;
2934 nsCOMPtr<nsIObserver> observer = pCallback->GetObserver();
2935 if (!observer) {
2936 // The observer has expired. Let's remove this callback.
2937 pCallback->GetPrefBranch()->RemoveExpiredCallback(pCallback);
2938 return;
2941 // Remove any root this string may contain so as to not confuse the observer
2942 // by passing them something other than what they passed us as a topic.
2943 uint32_t len = pCallback->GetPrefBranch()->GetRootLength();
2944 nsDependentCString suffix(aNewPref + len);
2946 observer->Observe(static_cast<nsIPrefBranch*>(pCallback->GetPrefBranch()),
2947 NS_PREFBRANCH_PREFCHANGE_TOPIC_ID,
2948 NS_ConvertASCIItoUTF16(suffix).get());
2951 size_t nsPrefBranch::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
2952 size_t n = aMallocSizeOf(this);
2954 n += mPrefRoot.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
2956 n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
2957 for (const auto& entry : mObservers) {
2958 const PrefCallback* data = entry.GetWeak();
2959 n += data->SizeOfIncludingThis(aMallocSizeOf);
2962 return n;
2965 void nsPrefBranch::FreeObserverList() {
2966 // We need to prevent anyone from modifying mObservers while we're iterating
2967 // over it. In particular, some clients will call RemoveObserver() when
2968 // they're removed and destructed via the iterator; we set
2969 // mFreeingObserverList to keep those calls from touching mObservers.
2970 mFreeingObserverList = true;
2971 for (auto iter = mObservers.Iter(); !iter.Done(); iter.Next()) {
2972 auto callback = iter.UserData();
2973 Preferences::UnregisterCallback(nsPrefBranch::NotifyObserver,
2974 callback->GetDomain(), callback,
2975 Preferences::PrefixMatch);
2976 iter.Remove();
2979 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
2980 if (observerService) {
2981 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
2984 mFreeingObserverList = false;
2987 void nsPrefBranch::RemoveExpiredCallback(PrefCallback* aCallback) {
2988 MOZ_ASSERT(aCallback->IsExpired());
2989 mObservers.Remove(aCallback);
2992 nsresult nsPrefBranch::GetDefaultFromPropertiesFile(const char* aPrefName,
2993 nsAString& aReturn) {
2994 // The default value contains a URL to a .properties file.
2996 nsAutoCString propertyFileURL;
2997 nsresult rv = Preferences::GetCString(aPrefName, propertyFileURL,
2998 PrefValueKind::Default);
2999 if (NS_FAILED(rv)) {
3000 return rv;
3003 nsCOMPtr<nsIStringBundleService> bundleService =
3004 components::StringBundle::Service();
3005 if (!bundleService) {
3006 return NS_ERROR_FAILURE;
3009 nsCOMPtr<nsIStringBundle> bundle;
3010 rv = bundleService->CreateBundle(propertyFileURL.get(),
3011 getter_AddRefs(bundle));
3012 if (NS_FAILED(rv)) {
3013 return rv;
3016 return bundle->GetStringFromName(aPrefName, aReturn);
3019 nsPrefBranch::PrefName nsPrefBranch::GetPrefName(
3020 const nsACString& aPrefName) const {
3021 if (mPrefRoot.IsEmpty()) {
3022 return PrefName(PromiseFlatCString(aPrefName));
3025 return PrefName(mPrefRoot + aPrefName);
3028 //----------------------------------------------------------------------------
3029 // nsPrefLocalizedString
3030 //----------------------------------------------------------------------------
3032 nsPrefLocalizedString::nsPrefLocalizedString() = default;
3034 nsPrefLocalizedString::~nsPrefLocalizedString() = default;
3036 NS_IMPL_ISUPPORTS(nsPrefLocalizedString, nsIPrefLocalizedString,
3037 nsISupportsString)
3039 nsresult nsPrefLocalizedString::Init() {
3040 nsresult rv;
3041 mUnicodeString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
3043 return rv;
3046 //----------------------------------------------------------------------------
3047 // nsRelativeFilePref
3048 //----------------------------------------------------------------------------
3050 NS_IMPL_ISUPPORTS(nsRelativeFilePref, nsIRelativeFilePref)
3052 nsRelativeFilePref::nsRelativeFilePref() = default;
3054 nsRelativeFilePref::~nsRelativeFilePref() = default;
3056 NS_IMETHODIMP
3057 nsRelativeFilePref::GetFile(nsIFile** aFile) {
3058 NS_ENSURE_ARG_POINTER(aFile);
3059 *aFile = mFile;
3060 NS_IF_ADDREF(*aFile);
3061 return NS_OK;
3064 NS_IMETHODIMP
3065 nsRelativeFilePref::SetFile(nsIFile* aFile) {
3066 mFile = aFile;
3067 return NS_OK;
3070 NS_IMETHODIMP
3071 nsRelativeFilePref::GetRelativeToKey(nsACString& aRelativeToKey) {
3072 aRelativeToKey.Assign(mRelativeToKey);
3073 return NS_OK;
3076 NS_IMETHODIMP
3077 nsRelativeFilePref::SetRelativeToKey(const nsACString& aRelativeToKey) {
3078 mRelativeToKey.Assign(aRelativeToKey);
3079 return NS_OK;
3082 //===========================================================================
3083 // class Preferences and related things
3084 //===========================================================================
3086 namespace mozilla {
3088 #define INITIAL_PREF_FILES 10
3090 void Preferences::HandleDirty() {
3091 MOZ_ASSERT(XRE_IsParentProcess());
3093 if (!HashTable() || !sPreferences) {
3094 return;
3097 if (sPreferences->mProfileShutdown) {
3098 NS_WARNING("Setting user pref after profile shutdown.");
3099 return;
3102 if (!sPreferences->mDirty) {
3103 sPreferences->mDirty = true;
3105 if (sPreferences->mCurrentFile && sPreferences->AllowOffMainThreadSave() &&
3106 !sPreferences->mSavePending) {
3107 sPreferences->mSavePending = true;
3108 static const int PREF_DELAY_MS = 500;
3109 NS_DelayedDispatchToCurrentThread(
3110 NewRunnableMethod("Preferences::SavePrefFileAsynchronous",
3111 sPreferences.get(),
3112 &Preferences::SavePrefFileAsynchronous),
3113 PREF_DELAY_MS);
3118 static nsresult openPrefFile(nsIFile* aFile, PrefValueKind aKind);
3120 static nsresult parsePrefData(const nsCString& aData, PrefValueKind aKind);
3122 // clang-format off
3123 static const char kPrefFileHeader[] =
3124 "// Mozilla User Preferences"
3125 NS_LINEBREAK
3126 NS_LINEBREAK
3127 "// DO NOT EDIT THIS FILE."
3128 NS_LINEBREAK
3129 "//"
3130 NS_LINEBREAK
3131 "// If you make changes to this file while the application is running,"
3132 NS_LINEBREAK
3133 "// the changes will be overwritten when the application exits."
3134 NS_LINEBREAK
3135 "//"
3136 NS_LINEBREAK
3137 "// To change a preference value, you can either:"
3138 NS_LINEBREAK
3139 "// - modify it via the UI (e.g. via about:config in the browser); or"
3140 NS_LINEBREAK
3141 "// - set it within a user.js file in your profile."
3142 NS_LINEBREAK
3143 NS_LINEBREAK;
3144 // clang-format on
3146 // Note: if sShutdown is true, sPreferences will be nullptr.
3147 StaticRefPtr<Preferences> Preferences::sPreferences;
3148 bool Preferences::sShutdown = false;
3150 // This globally enables or disables OMT pref writing, both sync and async.
3151 static int32_t sAllowOMTPrefWrite = -1;
3153 // Write the preference data to a file.
3154 class PreferencesWriter final {
3155 public:
3156 PreferencesWriter() = default;
3158 static nsresult Write(nsIFile* aFile, PrefSaveData& aPrefs) {
3159 nsCOMPtr<nsIOutputStream> outStreamSink;
3160 nsCOMPtr<nsIOutputStream> outStream;
3161 uint32_t writeAmount;
3162 nsresult rv;
3164 // Execute a "safe" save by saving through a tempfile.
3165 rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink), aFile,
3166 -1, 0600);
3167 if (NS_FAILED(rv)) {
3168 return rv;
3171 rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream),
3172 outStreamSink.forget(), 4096);
3173 if (NS_FAILED(rv)) {
3174 return rv;
3177 struct CharComparator {
3178 bool LessThan(const nsCString& aA, const nsCString& aB) const {
3179 return aA < aB;
3182 bool Equals(const nsCString& aA, const nsCString& aB) const {
3183 return aA == aB;
3187 // Sort the preferences to make a readable file on disk.
3188 aPrefs.Sort(CharComparator());
3190 // Write out the file header.
3191 outStream->Write(kPrefFileHeader, sizeof(kPrefFileHeader) - 1,
3192 &writeAmount);
3194 for (nsCString& pref : aPrefs) {
3195 outStream->Write(pref.get(), pref.Length(), &writeAmount);
3196 outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount);
3199 // Tell the safe output stream to overwrite the real prefs file.
3200 // (It'll abort if there were any errors during writing.)
3201 nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream);
3202 MOZ_ASSERT(safeStream, "expected a safe output stream!");
3203 if (safeStream) {
3204 rv = safeStream->Finish();
3207 #ifdef DEBUG
3208 if (NS_FAILED(rv)) {
3209 NS_WARNING("failed to save prefs file! possible data loss");
3211 #endif
3213 return rv;
3216 static void Flush() {
3217 MOZ_DIAGNOSTIC_ASSERT(sPendingWriteCount >= 0);
3218 // SpinEventLoopUntil is unfortunate, but ultimately it's the best thing
3219 // we can do here given the constraint that we need to ensure that
3220 // the preferences on disk match what we have in memory. We could
3221 // easily perform the write here ourselves by doing exactly what
3222 // happens in PWRunnable::Run. This would be the right thing to do
3223 // if we're stuck here because other unrelated runnables are taking
3224 // a long time, and the wrong thing to do if PreferencesWriter::Write
3225 // is what takes a long time, as we would be trading a SpinEventLoopUntil
3226 // for a synchronous disk write, wherein we could not even spin the
3227 // event loop. Given that PWRunnable generally runs on a thread pool,
3228 // if we're stuck here, it's likely because of PreferencesWriter::Write
3229 // and not some other runnable. Thus, spin away.
3230 mozilla::SpinEventLoopUntil("PreferencesWriter::Flush"_ns,
3231 []() { return sPendingWriteCount <= 0; });
3234 // This is the data that all of the runnables (see below) will attempt
3235 // to write. It will always have the most up to date version, or be
3236 // null, if the up to date information has already been written out.
3237 static Atomic<PrefSaveData*> sPendingWriteData;
3239 // This is the number of writes via PWRunnables which have been dispatched
3240 // but not yet completed. This is intended to be used by Flush to ensure
3241 // that there are no outstanding writes left incomplete, and thus our prefs
3242 // on disk are in sync with what we have in memory.
3243 static Atomic<int> sPendingWriteCount;
3245 // See PWRunnable::Run for details on why we need this lock.
3246 static StaticMutex sWritingToFile MOZ_UNANNOTATED;
3249 Atomic<PrefSaveData*> PreferencesWriter::sPendingWriteData(nullptr);
3250 Atomic<int> PreferencesWriter::sPendingWriteCount(0);
3251 StaticMutex PreferencesWriter::sWritingToFile;
3253 class PWRunnable : public Runnable {
3254 public:
3255 explicit PWRunnable(
3256 nsIFile* aFile,
3257 UniquePtr<MozPromiseHolder<Preferences::WritePrefFilePromise>>
3258 aPromiseHolder)
3259 : Runnable("PWRunnable"),
3260 mFile(aFile),
3261 mPromiseHolder(std::move(aPromiseHolder)) {}
3263 NS_IMETHOD Run() override {
3264 // Preference writes are handled a bit strangely, in that a "newer"
3265 // write is generally regarded as always better. For this reason,
3266 // sPendingWriteData can be overwritten multiple times before anyone
3267 // gets around to actually using it, minimizing writes. However,
3268 // once we've acquired sPendingWriteData we've reached a
3269 // "point of no return" and have to complete the write.
3271 // Unfortunately, this design allows the following behaviour:
3273 // 1. write1 is queued up
3274 // 2. thread1 acquires write1
3275 // 3. write2 is queued up
3276 // 4. thread2 acquires write2
3277 // 5. thread1 and thread2 concurrently clobber each other
3279 // To avoid this, we use this lock to ensure that only one thread
3280 // at a time is trying to acquire the write, and when it does,
3281 // all other threads are prevented from acquiring writes until it
3282 // completes the write. New writes are still allowed to be queued
3283 // up in this time.
3285 // Although it's atomic, the acquire needs to be guarded by the mutex
3286 // to avoid reordering of writes -- we don't want an older write to
3287 // run after a newer one. To avoid this causing too much waiting, we check
3288 // if sPendingWriteData is already null before acquiring the mutex. If it
3289 // is, then there's definitely no work to be done (or someone is in the
3290 // middle of doing it for us).
3292 // Note that every time a new write is queued up, a new write task is
3293 // is also queued up, so there will always be a task that can see the newest
3294 // write.
3296 // Ideally this lock wouldn't be necessary, and the PreferencesWriter
3297 // would be used more carefully, but it's hard to untangle all that.
3298 nsresult rv = NS_OK;
3299 if (PreferencesWriter::sPendingWriteData) {
3300 StaticMutexAutoLock lock(PreferencesWriter::sWritingToFile);
3301 // If we get a nullptr on the exchange, it means that somebody
3302 // else has already processed the request, and we can just return.
3303 UniquePtr<PrefSaveData> prefs(
3304 PreferencesWriter::sPendingWriteData.exchange(nullptr));
3305 if (prefs) {
3306 rv = PreferencesWriter::Write(mFile, *prefs);
3307 // Make a copy of these so we can have them in runnable lambda.
3308 // nsIFile is only there so that we would never release the
3309 // ref counted pointer off main thread.
3310 nsresult rvCopy = rv;
3311 nsCOMPtr<nsIFile> fileCopy(mFile);
3312 SchedulerGroup::Dispatch(NS_NewRunnableFunction(
3313 "Preferences::WriterRunnable",
3314 [fileCopy, rvCopy, promiseHolder = std::move(mPromiseHolder)] {
3315 MOZ_RELEASE_ASSERT(NS_IsMainThread());
3316 if (NS_FAILED(rvCopy)) {
3317 Preferences::HandleDirty();
3319 if (promiseHolder) {
3320 promiseHolder->ResolveIfExists(true, __func__);
3322 }));
3325 // We've completed the write to the best of our abilities, whether
3326 // we had prefs to write or another runnable got to them first. If
3327 // PreferencesWriter::Write failed, this is still correct as the
3328 // write is no longer outstanding, and the above HandleDirty call
3329 // will just start the cycle again.
3330 PreferencesWriter::sPendingWriteCount--;
3331 return rv;
3334 private:
3335 ~PWRunnable() {
3336 if (mPromiseHolder) {
3337 mPromiseHolder->RejectIfExists(NS_ERROR_ABORT, __func__);
3341 protected:
3342 nsCOMPtr<nsIFile> mFile;
3343 UniquePtr<MozPromiseHolder<Preferences::WritePrefFilePromise>> mPromiseHolder;
3346 // Although this is a member of Preferences, it measures sPreferences and
3347 // several other global structures.
3348 /* static */
3349 void Preferences::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
3350 PrefsSizes& aSizes) {
3351 if (!sPreferences) {
3352 return;
3355 aSizes.mMisc += aMallocSizeOf(sPreferences.get());
3357 aSizes.mRootBranches +=
3358 static_cast<nsPrefBranch*>(sPreferences->mRootBranch.get())
3359 ->SizeOfIncludingThis(aMallocSizeOf) +
3360 static_cast<nsPrefBranch*>(sPreferences->mDefaultRootBranch.get())
3361 ->SizeOfIncludingThis(aMallocSizeOf);
3364 class PreferenceServiceReporter final : public nsIMemoryReporter {
3365 ~PreferenceServiceReporter() = default;
3367 public:
3368 NS_DECL_ISUPPORTS
3369 NS_DECL_NSIMEMORYREPORTER
3371 protected:
3372 static const uint32_t kSuspectReferentCount = 1000;
3375 NS_IMPL_ISUPPORTS(PreferenceServiceReporter, nsIMemoryReporter)
3377 MOZ_DEFINE_MALLOC_SIZE_OF(PreferenceServiceMallocSizeOf)
3379 NS_IMETHODIMP
3380 PreferenceServiceReporter::CollectReports(
3381 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
3382 bool aAnonymize) {
3383 MOZ_ASSERT(NS_IsMainThread());
3385 MallocSizeOf mallocSizeOf = PreferenceServiceMallocSizeOf;
3386 PrefsSizes sizes;
3388 Preferences::AddSizeOfIncludingThis(mallocSizeOf, sizes);
3390 if (HashTable()) {
3391 sizes.mHashTable += HashTable()->shallowSizeOfIncludingThis(mallocSizeOf);
3392 for (auto iter = HashTable()->iter(); !iter.done(); iter.next()) {
3393 iter.get()->AddSizeOfIncludingThis(mallocSizeOf, sizes);
3397 sizes.mPrefNameArena += PrefNameArena().SizeOfExcludingThis(mallocSizeOf);
3399 for (CallbackNode* node = gFirstCallback; node; node = node->Next()) {
3400 node->AddSizeOfIncludingThis(mallocSizeOf, sizes);
3403 if (gSharedMap) {
3404 sizes.mMisc += mallocSizeOf(gSharedMap);
3407 #ifdef ACCESS_COUNTS
3408 if (gAccessCounts) {
3409 sizes.mMisc += gAccessCounts->ShallowSizeOfIncludingThis(mallocSizeOf);
3411 #endif
3413 MOZ_COLLECT_REPORT("explicit/preferences/hash-table", KIND_HEAP, UNITS_BYTES,
3414 sizes.mHashTable, "Memory used by libpref's hash table.");
3416 MOZ_COLLECT_REPORT("explicit/preferences/pref-values", KIND_HEAP, UNITS_BYTES,
3417 sizes.mPrefValues,
3418 "Memory used by PrefValues hanging off the hash table.");
3420 MOZ_COLLECT_REPORT("explicit/preferences/string-values", KIND_HEAP,
3421 UNITS_BYTES, sizes.mStringValues,
3422 "Memory used by libpref's string pref values.");
3424 MOZ_COLLECT_REPORT("explicit/preferences/root-branches", KIND_HEAP,
3425 UNITS_BYTES, sizes.mRootBranches,
3426 "Memory used by libpref's root branches.");
3428 MOZ_COLLECT_REPORT("explicit/preferences/pref-name-arena", KIND_HEAP,
3429 UNITS_BYTES, sizes.mPrefNameArena,
3430 "Memory used by libpref's arena for pref names.");
3432 MOZ_COLLECT_REPORT("explicit/preferences/callbacks/objects", KIND_HEAP,
3433 UNITS_BYTES, sizes.mCallbacksObjects,
3434 "Memory used by pref callback objects.");
3436 MOZ_COLLECT_REPORT("explicit/preferences/callbacks/domains", KIND_HEAP,
3437 UNITS_BYTES, sizes.mCallbacksDomains,
3438 "Memory used by pref callback domains (pref names and "
3439 "prefixes).");
3441 MOZ_COLLECT_REPORT("explicit/preferences/misc", KIND_HEAP, UNITS_BYTES,
3442 sizes.mMisc, "Miscellaneous memory used by libpref.");
3444 if (gSharedMap) {
3445 if (XRE_IsParentProcess()) {
3446 MOZ_COLLECT_REPORT("explicit/preferences/shared-memory-map", KIND_NONHEAP,
3447 UNITS_BYTES, gSharedMap->MapSize(),
3448 "The shared memory mapping used to share a "
3449 "snapshot of preference values across processes.");
3453 nsPrefBranch* rootBranch =
3454 static_cast<nsPrefBranch*>(Preferences::GetRootBranch());
3455 if (!rootBranch) {
3456 return NS_OK;
3459 size_t numStrong = 0;
3460 size_t numWeakAlive = 0;
3461 size_t numWeakDead = 0;
3462 nsTArray<nsCString> suspectPreferences;
3463 // Count of the number of referents for each preference.
3464 nsTHashMap<nsCStringHashKey, uint32_t> prefCounter;
3466 for (const auto& entry : rootBranch->mObservers) {
3467 auto* callback = entry.GetWeak();
3469 if (callback->IsWeak()) {
3470 nsCOMPtr<nsIObserver> callbackRef = do_QueryReferent(callback->mWeakRef);
3471 if (callbackRef) {
3472 numWeakAlive++;
3473 } else {
3474 numWeakDead++;
3476 } else {
3477 numStrong++;
3480 const uint32_t currentCount = prefCounter.Get(callback->GetDomain()) + 1;
3481 prefCounter.InsertOrUpdate(callback->GetDomain(), currentCount);
3483 // Keep track of preferences that have a suspiciously large number of
3484 // referents (a symptom of a leak).
3485 if (currentCount == kSuspectReferentCount) {
3486 suspectPreferences.AppendElement(callback->GetDomain());
3490 for (uint32_t i = 0; i < suspectPreferences.Length(); i++) {
3491 nsCString& suspect = suspectPreferences[i];
3492 const uint32_t totalReferentCount = prefCounter.Get(suspect);
3494 nsPrintfCString suspectPath(
3495 "preference-service-suspect/"
3496 "referent(pref=%s)",
3497 suspect.get());
3499 aHandleReport->Callback(
3500 /* process = */ ""_ns, suspectPath, KIND_OTHER, UNITS_COUNT,
3501 totalReferentCount,
3502 "A preference with a suspiciously large number "
3503 "referents (symptom of a leak)."_ns,
3504 aData);
3507 MOZ_COLLECT_REPORT(
3508 "preference-service/referent/strong", KIND_OTHER, UNITS_COUNT, numStrong,
3509 "The number of strong referents held by the preference service.");
3511 MOZ_COLLECT_REPORT(
3512 "preference-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT,
3513 numWeakAlive,
3514 "The number of weak referents held by the preference service that are "
3515 "still alive.");
3517 MOZ_COLLECT_REPORT(
3518 "preference-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT,
3519 numWeakDead,
3520 "The number of weak referents held by the preference service that are "
3521 "dead.");
3523 return NS_OK;
3526 namespace {
3528 class AddPreferencesMemoryReporterRunnable : public Runnable {
3529 public:
3530 AddPreferencesMemoryReporterRunnable()
3531 : Runnable("AddPreferencesMemoryReporterRunnable") {}
3533 NS_IMETHOD Run() override {
3534 return RegisterStrongMemoryReporter(new PreferenceServiceReporter());
3538 } // namespace
3540 // A list of changed prefs sent from the parent via shared memory.
3541 static StaticAutoPtr<nsTArray<dom::Pref>> gChangedDomPrefs;
3543 static const char kTelemetryPref[] = "toolkit.telemetry.enabled";
3544 static const char kChannelPref[] = "app.update.channel";
3546 #ifdef MOZ_WIDGET_ANDROID
3548 static Maybe<bool> TelemetryPrefValue() {
3549 // Leave it unchanged if it's already set.
3550 // XXX: how could it already be set?
3551 if (Preferences::GetType(kTelemetryPref) != nsIPrefBranch::PREF_INVALID) {
3552 return Nothing();
3555 // Determine the correct default for toolkit.telemetry.enabled. If this
3556 // build has MOZ_TELEMETRY_ON_BY_DEFAULT *or* we're on the beta channel,
3557 // telemetry is on by default, otherwise not. This is necessary so that
3558 // beta users who are testing final release builds don't flipflop defaults.
3559 # ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
3560 return Some(true);
3561 # else
3562 nsAutoCString channelPrefValue;
3563 Unused << Preferences::GetCString(kChannelPref, channelPrefValue,
3564 PrefValueKind::Default);
3565 return Some(channelPrefValue.EqualsLiteral("beta"));
3566 # endif
3569 /* static */
3570 void Preferences::SetupTelemetryPref() {
3571 MOZ_ASSERT(XRE_IsParentProcess());
3573 Maybe<bool> telemetryPrefValue = TelemetryPrefValue();
3574 if (telemetryPrefValue.isSome()) {
3575 Preferences::SetBool(kTelemetryPref, *telemetryPrefValue,
3576 PrefValueKind::Default);
3580 #else // !MOZ_WIDGET_ANDROID
3582 static bool TelemetryPrefValue() {
3583 // For platforms with Unified Telemetry (here meaning not-Android),
3584 // toolkit.telemetry.enabled determines whether we send "extended" data.
3585 // We only want extended data from pre-release channels due to size.
3587 constexpr auto channel = MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL) ""_ns;
3589 // Easy cases: Nightly, Aurora, Beta.
3590 if (channel.EqualsLiteral("nightly") || channel.EqualsLiteral("aurora") ||
3591 channel.EqualsLiteral("beta")) {
3592 return true;
3595 # ifndef MOZILLA_OFFICIAL
3596 // Local developer builds: non-official builds on the "default" channel.
3597 if (channel.EqualsLiteral("default")) {
3598 return true;
3600 # endif
3602 // Release Candidate builds: builds that think they are release builds, but
3603 // are shipped to beta users.
3604 if (channel.EqualsLiteral("release")) {
3605 nsAutoCString channelPrefValue;
3606 Unused << Preferences::GetCString(kChannelPref, channelPrefValue,
3607 PrefValueKind::Default);
3608 if (channelPrefValue.EqualsLiteral("beta")) {
3609 return true;
3613 return false;
3616 /* static */
3617 void Preferences::SetupTelemetryPref() {
3618 MOZ_ASSERT(XRE_IsParentProcess());
3620 Preferences::SetBool(kTelemetryPref, TelemetryPrefValue(),
3621 PrefValueKind::Default);
3622 Preferences::Lock(kTelemetryPref);
3625 #endif // MOZ_WIDGET_ANDROID
3627 /* static */
3628 already_AddRefed<Preferences> Preferences::GetInstanceForService() {
3629 if (sPreferences) {
3630 return do_AddRef(sPreferences);
3633 if (sShutdown) {
3634 return nullptr;
3637 sPreferences = new Preferences();
3639 MOZ_ASSERT(!HashTable());
3640 HashTable() = new PrefsHashTable(XRE_IsParentProcess()
3641 ? kHashTableInitialLengthParent
3642 : kHashTableInitialLengthContent);
3644 #ifdef DEBUG
3645 gOnceStaticPrefsAntiFootgun = new AntiFootgunMap();
3646 #endif
3648 #ifdef ACCESS_COUNTS
3649 MOZ_ASSERT(!gAccessCounts);
3650 gAccessCounts = new AccessCountsHashTable();
3651 #endif
3653 nsresult rv = InitInitialObjects(/* isStartup */ true);
3654 if (NS_FAILED(rv)) {
3655 sPreferences = nullptr;
3656 return nullptr;
3659 if (!XRE_IsParentProcess()) {
3660 MOZ_ASSERT(gChangedDomPrefs);
3661 for (unsigned int i = 0; i < gChangedDomPrefs->Length(); i++) {
3662 Preferences::SetPreference(gChangedDomPrefs->ElementAt(i));
3664 gChangedDomPrefs = nullptr;
3665 } else {
3666 // Check if there is a deployment configuration file. If so, set up the
3667 // pref config machinery, which will actually read the file.
3668 nsAutoCString lockFileName;
3669 nsresult rv = Preferences::GetCString("general.config.filename",
3670 lockFileName, PrefValueKind::User);
3671 if (NS_SUCCEEDED(rv)) {
3672 NS_CreateServicesFromCategory(
3673 "pref-config-startup",
3674 static_cast<nsISupports*>(static_cast<void*>(sPreferences)),
3675 "pref-config-startup");
3678 nsCOMPtr<nsIObserverService> observerService =
3679 services::GetObserverService();
3680 if (!observerService) {
3681 sPreferences = nullptr;
3682 return nullptr;
3685 observerService->AddObserver(sPreferences,
3686 "profile-before-change-telemetry", true);
3687 rv = observerService->AddObserver(sPreferences, "profile-before-change",
3688 true);
3690 observerService->AddObserver(sPreferences, "suspend_process_notification",
3691 true);
3693 if (NS_FAILED(rv)) {
3694 sPreferences = nullptr;
3695 return nullptr;
3699 const char* defaultPrefs = getenv("MOZ_DEFAULT_PREFS");
3700 if (defaultPrefs) {
3701 parsePrefData(nsCString(defaultPrefs), PrefValueKind::Default);
3704 // Preferences::GetInstanceForService() can be called from GetService(), and
3705 // RegisterStrongMemoryReporter calls GetService(nsIMemoryReporter). To
3706 // avoid a potential recursive GetService() call, we can't register the
3707 // memory reporter here; instead, do it off a runnable.
3708 RefPtr<AddPreferencesMemoryReporterRunnable> runnable =
3709 new AddPreferencesMemoryReporterRunnable();
3710 NS_DispatchToMainThread(runnable);
3712 return do_AddRef(sPreferences);
3715 /* static */
3716 bool Preferences::IsServiceAvailable() { return !!sPreferences; }
3718 /* static */
3719 bool Preferences::InitStaticMembers() {
3720 MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
3722 if (MOZ_LIKELY(sPreferences)) {
3723 return true;
3726 if (!sShutdown) {
3727 MOZ_ASSERT(NS_IsMainThread());
3728 nsCOMPtr<nsIPrefService> prefService =
3729 do_GetService(NS_PREFSERVICE_CONTRACTID);
3732 return sPreferences != nullptr;
3735 /* static */
3736 void Preferences::Shutdown() {
3737 if (!sShutdown) {
3738 sShutdown = true; // Don't create the singleton instance after here.
3739 sPreferences = nullptr;
3740 StaticPrefs::ShutdownAlwaysPrefs();
3744 Preferences::Preferences()
3745 : mRootBranch(new nsPrefBranch("", PrefValueKind::User)),
3746 mDefaultRootBranch(new nsPrefBranch("", PrefValueKind::Default)) {}
3748 Preferences::~Preferences() {
3749 MOZ_ASSERT(!sPreferences);
3751 MOZ_ASSERT(!gCallbacksInProgress);
3753 CallbackNode* node = gFirstCallback;
3754 while (node) {
3755 CallbackNode* next_node = node->Next();
3756 delete node;
3757 node = next_node;
3759 gLastPriorityNode = gFirstCallback = nullptr;
3761 delete HashTable();
3762 HashTable() = nullptr;
3764 #ifdef DEBUG
3765 gOnceStaticPrefsAntiFootgun = nullptr;
3766 #endif
3768 #ifdef ACCESS_COUNTS
3769 gAccessCounts = nullptr;
3770 #endif
3772 gSharedMap = nullptr;
3774 PrefNameArena().Clear();
3777 NS_IMPL_ISUPPORTS(Preferences, nsIPrefService, nsIObserver, nsIPrefBranch,
3778 nsISupportsWeakReference)
3780 /* static */
3781 void Preferences::SerializePreferences(nsCString& aStr,
3782 bool aIsDestinationWebContentProcess) {
3783 MOZ_RELEASE_ASSERT(InitStaticMembers());
3785 aStr.Truncate();
3787 for (auto iter = HashTable()->iter(); !iter.done(); iter.next()) {
3788 Pref* pref = iter.get().get();
3789 if (!pref->IsTypeNone() && pref->HasAdvisablySizedValues()) {
3790 pref->SerializeAndAppend(aStr, aIsDestinationWebContentProcess &&
3791 ShouldSanitizePreference(pref));
3795 aStr.Append('\0');
3798 /* static */
3799 void Preferences::DeserializePreferences(char* aStr, size_t aPrefsLen) {
3800 MOZ_ASSERT(!XRE_IsParentProcess());
3802 MOZ_ASSERT(!gChangedDomPrefs);
3803 gChangedDomPrefs = new nsTArray<dom::Pref>();
3805 char* p = aStr;
3806 while (*p != '\0') {
3807 dom::Pref pref;
3808 p = Pref::Deserialize(p, &pref);
3809 gChangedDomPrefs->AppendElement(pref);
3812 // We finished parsing on a '\0'. That should be the last char in the shared
3813 // memory. (aPrefsLen includes the '\0'.)
3814 MOZ_ASSERT(p == aStr + aPrefsLen - 1);
3816 MOZ_ASSERT(!gContentProcessPrefsAreInited);
3817 gContentProcessPrefsAreInited = true;
3820 /* static */
3821 mozilla::ipc::SharedMemoryHandle Preferences::EnsureSnapshot(size_t* aSize) {
3822 MOZ_ASSERT(XRE_IsParentProcess());
3823 MOZ_ASSERT(NS_IsMainThread());
3825 if (!gSharedMap) {
3826 SharedPrefMapBuilder builder;
3828 nsTArray<Pref*> toRepopulate;
3829 NameArena* newPrefNameArena = new NameArena();
3830 for (auto iter = HashTable()->modIter(); !iter.done(); iter.next()) {
3831 if (!ShouldSanitizePreference(iter.get().get())) {
3832 iter.get()->AddToMap(builder);
3833 } else {
3834 Pref* pref = iter.getMutable().release();
3835 pref->RelocateName(newPrefNameArena);
3836 toRepopulate.AppendElement(pref);
3840 // Store the current value of `once`-mirrored prefs. After this point they
3841 // will be immutable.
3842 StaticPrefs::RegisterOncePrefs(builder);
3844 gSharedMap = new SharedPrefMap(std::move(builder));
3846 // Once we've built a snapshot of the database, there's no need to continue
3847 // storing dynamic copies of the preferences it contains. Once we reset the
3848 // hashtable, preference lookups will fall back to the snapshot for any
3849 // preferences not in the dynamic hashtable.
3851 // And since the majority of the database is now contained in the snapshot,
3852 // we can initialize the hashtable with the expected number of per-session
3853 // changed preferences, rather than the expected total number of
3854 // preferences.
3855 HashTable()->clearAndCompact();
3856 Unused << HashTable()->reserve(kHashTableInitialLengthContent);
3858 delete sPrefNameArena;
3859 sPrefNameArena = newPrefNameArena;
3860 gCallbackPref = nullptr;
3862 for (uint32_t i = 0; i < toRepopulate.Length(); i++) {
3863 auto pref = toRepopulate[i];
3864 auto p = HashTable()->lookupForAdd(pref->Name());
3865 MOZ_ASSERT(!p.found());
3866 Unused << HashTable()->add(p, pref);
3870 *aSize = gSharedMap->MapSize();
3871 return gSharedMap->CloneHandle();
3874 /* static */
3875 void Preferences::InitSnapshot(const mozilla::ipc::SharedMemoryHandle& aHandle,
3876 size_t aSize) {
3877 MOZ_ASSERT(!XRE_IsParentProcess());
3878 MOZ_ASSERT(!gSharedMap);
3880 gSharedMap = new SharedPrefMap(aHandle, aSize);
3882 StaticPrefs::InitStaticPrefsFromShared();
3885 /* static */
3886 void Preferences::InitializeUserPrefs() {
3887 MOZ_ASSERT(XRE_IsParentProcess());
3888 MOZ_ASSERT(!sPreferences->mCurrentFile, "Should only initialize prefs once");
3890 // Prefs which are set before we initialize the profile are silently
3891 // discarded. This is stupid, but there are various tests which depend on
3892 // this behavior.
3893 sPreferences->ResetUserPrefs();
3895 nsCOMPtr<nsIFile> prefsFile = sPreferences->ReadSavedPrefs();
3896 sPreferences->ReadUserOverridePrefs();
3898 sPreferences->mDirty = false;
3900 // Don't set mCurrentFile until we're done so that dirty flags work properly.
3901 sPreferences->mCurrentFile = std::move(prefsFile);
3904 /* static */
3905 void Preferences::FinishInitializingUserPrefs() {
3906 sPreferences->NotifyServiceObservers(NS_PREFSERVICE_READ_TOPIC_ID);
3909 NS_IMETHODIMP
3910 Preferences::Observe(nsISupports* aSubject, const char* aTopic,
3911 const char16_t* someData) {
3912 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
3913 return NS_ERROR_NOT_AVAILABLE;
3916 nsresult rv = NS_OK;
3918 if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
3919 // Normally prefs aren't written after this point, and so we kick off
3920 // an asynchronous pref save so that I/O can be done in parallel with
3921 // other shutdown.
3922 if (AllowOffMainThreadSave()) {
3923 SavePrefFile(nullptr);
3926 } else if (!nsCRT::strcmp(aTopic, "profile-before-change-telemetry")) {
3927 // It's possible that a profile-before-change observer after ours
3928 // set a pref. A blocking save here re-saves if necessary and also waits
3929 // for any pending saves to complete.
3930 SavePrefFileBlocking();
3931 MOZ_ASSERT(!mDirty, "Preferences should not be dirty");
3932 mProfileShutdown = true;
3934 } else if (!nsCRT::strcmp(aTopic, "suspend_process_notification")) {
3935 // Our process is being suspended. The OS may wake our process later,
3936 // or it may kill the process. In case our process is going to be killed
3937 // from the suspended state, we save preferences before suspending.
3938 rv = SavePrefFileBlocking();
3941 return rv;
3944 NS_IMETHODIMP
3945 Preferences::ReadDefaultPrefsFromFile(nsIFile* aFile) {
3946 ENSURE_PARENT_PROCESS("Preferences::ReadDefaultPrefsFromFile", "all prefs");
3948 if (!aFile) {
3949 NS_ERROR("ReadDefaultPrefsFromFile requires a parameter");
3950 return NS_ERROR_INVALID_ARG;
3953 return openPrefFile(aFile, PrefValueKind::Default);
3956 NS_IMETHODIMP
3957 Preferences::ReadUserPrefsFromFile(nsIFile* aFile) {
3958 ENSURE_PARENT_PROCESS("Preferences::ReadUserPrefsFromFile", "all prefs");
3960 if (!aFile) {
3961 NS_ERROR("ReadUserPrefsFromFile requires a parameter");
3962 return NS_ERROR_INVALID_ARG;
3965 return openPrefFile(aFile, PrefValueKind::User);
3968 NS_IMETHODIMP
3969 Preferences::ResetPrefs() {
3970 ENSURE_PARENT_PROCESS("Preferences::ResetPrefs", "all prefs");
3972 if (gSharedMap) {
3973 return NS_ERROR_NOT_AVAILABLE;
3976 HashTable()->clearAndCompact();
3977 Unused << HashTable()->reserve(kHashTableInitialLengthParent);
3979 PrefNameArena().Clear();
3981 return InitInitialObjects(/* isStartup */ false);
3984 nsresult Preferences::ResetUserPrefs() {
3985 ENSURE_PARENT_PROCESS("Preferences::ResetUserPrefs", "all prefs");
3986 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
3987 MOZ_ASSERT(NS_IsMainThread());
3989 Vector<const char*> prefNames;
3990 for (auto iter = HashTable()->modIter(); !iter.done(); iter.next()) {
3991 Pref* pref = iter.get().get();
3993 if (pref->HasUserValue()) {
3994 if (!prefNames.append(pref->Name())) {
3995 return NS_ERROR_OUT_OF_MEMORY;
3998 pref->ClearUserValue();
3999 if (!pref->HasDefaultValue()) {
4000 iter.remove();
4005 for (const char* prefName : prefNames) {
4006 NotifyCallbacks(nsDependentCString(prefName));
4009 Preferences::HandleDirty();
4010 return NS_OK;
4013 bool Preferences::AllowOffMainThreadSave() {
4014 // Put in a preference that allows us to disable off main thread preference
4015 // file save.
4016 if (sAllowOMTPrefWrite < 0) {
4017 bool value = false;
4018 Preferences::GetBool("preferences.allow.omt-write", &value);
4019 sAllowOMTPrefWrite = value ? 1 : 0;
4022 return !!sAllowOMTPrefWrite;
4025 nsresult Preferences::SavePrefFileBlocking() {
4026 if (mDirty) {
4027 return SavePrefFileInternal(nullptr, SaveMethod::Blocking);
4030 // If we weren't dirty to start, SavePrefFileInternal will early exit so
4031 // there is no guarantee that we don't have oustanding async saves in the
4032 // pipe. Since the contract of SavePrefFileOnMainThread is that the file on
4033 // disk matches the preferences, we have to make sure those requests are
4034 // completed.
4036 if (AllowOffMainThreadSave()) {
4037 PreferencesWriter::Flush();
4040 return NS_OK;
4043 nsresult Preferences::SavePrefFileAsynchronous() {
4044 return SavePrefFileInternal(nullptr, SaveMethod::Asynchronous);
4047 NS_IMETHODIMP
4048 Preferences::SavePrefFile(nsIFile* aFile) {
4049 // This is the method accessible from service API. Make it off main thread.
4050 return SavePrefFileInternal(aFile, SaveMethod::Asynchronous);
4053 NS_IMETHODIMP
4054 Preferences::BackupPrefFile(nsIFile* aFile, JSContext* aCx,
4055 Promise** aPromise) {
4056 MOZ_ASSERT(NS_IsMainThread());
4058 if (!aFile) {
4059 return NS_ERROR_INVALID_ARG;
4062 if (mCurrentFile) {
4063 bool equalsCurrent = false;
4064 nsresult rv = aFile->Equals(mCurrentFile, &equalsCurrent);
4066 if (NS_FAILED(rv)) {
4067 return rv;
4070 if (equalsCurrent) {
4071 return NS_ERROR_INVALID_ARG;
4075 ErrorResult result;
4076 RefPtr<Promise> promise =
4077 Promise::Create(xpc::CurrentNativeGlobal(aCx), result);
4079 if (MOZ_UNLIKELY(result.Failed())) {
4080 return result.StealNSResult();
4083 nsMainThreadPtrHandle<Promise> domPromiseHolder(
4084 new nsMainThreadPtrHolder<Promise>("Preferences::BackupPrefFile promise",
4085 promise));
4087 auto mozPromiseHolder = MakeUnique<MozPromiseHolder<WritePrefFilePromise>>();
4088 RefPtr<WritePrefFilePromise> writePrefPromise =
4089 mozPromiseHolder->Ensure(__func__);
4091 nsresult rv = WritePrefFile(aFile, SaveMethod::Asynchronous,
4092 std::move(mozPromiseHolder));
4093 if (NS_FAILED(rv)) {
4094 // WritePrefFile is responsible for rejecting the underlying MozPromise in
4095 // the event that it the method failed somewhere.
4096 return rv;
4099 writePrefPromise->Then(
4100 GetMainThreadSerialEventTarget(), __func__,
4101 [domPromiseHolder](bool) {
4102 MOZ_ASSERT(NS_IsMainThread());
4103 domPromiseHolder.get()->MaybeResolveWithUndefined();
4105 [domPromiseHolder](nsresult rv) {
4106 MOZ_ASSERT(NS_IsMainThread());
4107 domPromiseHolder.get()->MaybeReject(rv);
4110 promise.forget(aPromise);
4111 return NS_OK;
4114 /* static */
4115 void Preferences::SetPreference(const dom::Pref& aDomPref) {
4116 MOZ_ASSERT(!XRE_IsParentProcess());
4117 NS_ENSURE_TRUE(InitStaticMembers(), (void)0);
4119 const nsCString& prefName = aDomPref.name();
4121 Pref* pref;
4122 auto p = HashTable()->lookupForAdd(prefName.get());
4123 if (!p) {
4124 pref = new Pref(prefName);
4125 if (!HashTable()->add(p, pref)) {
4126 delete pref;
4127 return;
4129 } else {
4130 pref = p->get();
4133 bool valueChanged = false;
4134 pref->FromDomPref(aDomPref, &valueChanged);
4136 // When the parent process clears a pref's user value we get a DomPref here
4137 // with no default value and no user value. There are two possibilities.
4139 // - There was an existing pref with only a user value. FromDomPref() will
4140 // have just cleared that user value, so the pref can be removed.
4142 // - There was no existing pref. FromDomPref() will have done nothing, and
4143 // `pref` will be valueless. We will end up adding and removing the value
4144 // needlessly, but that's ok because this case is rare.
4146 if (!pref->HasDefaultValue() && !pref->HasUserValue() &&
4147 !pref->IsSanitized()) {
4148 // If the preference exists in the shared map, we need to keep the dynamic
4149 // entry around to mask it.
4150 if (gSharedMap->Has(pref->Name())) {
4151 pref->SetType(PrefType::None);
4152 } else {
4153 HashTable()->remove(prefName.get());
4155 pref = nullptr;
4158 // Note: we don't have to worry about HandleDirty() because we are setting
4159 // prefs in the content process that have come from the parent process.
4161 if (valueChanged) {
4162 if (pref) {
4163 NotifyCallbacks(prefName, PrefWrapper(pref));
4164 } else {
4165 NotifyCallbacks(prefName);
4170 /* static */
4171 void Preferences::GetPreference(dom::Pref* aDomPref,
4172 const GeckoProcessType aDestinationProcessType,
4173 const nsACString& aDestinationRemoteType) {
4174 MOZ_ASSERT(XRE_IsParentProcess());
4175 bool destIsWebContent =
4176 aDestinationProcessType == GeckoProcessType_Content &&
4177 (StringBeginsWith(aDestinationRemoteType, WEB_REMOTE_TYPE) ||
4178 StringBeginsWith(aDestinationRemoteType, PREALLOC_REMOTE_TYPE) ||
4179 StringBeginsWith(aDestinationRemoteType, PRIVILEGEDMOZILLA_REMOTE_TYPE));
4181 Pref* pref = pref_HashTableLookup(aDomPref->name().get());
4182 if (pref && pref->HasAdvisablySizedValues()) {
4183 pref->ToDomPref(aDomPref, destIsWebContent);
4187 #ifdef DEBUG
4188 bool Preferences::ArePrefsInitedInContentProcess() {
4189 MOZ_ASSERT(!XRE_IsParentProcess());
4190 return gContentProcessPrefsAreInited;
4192 #endif
4194 NS_IMETHODIMP
4195 Preferences::GetBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal) {
4196 if ((nullptr != aPrefRoot) && (*aPrefRoot != '\0')) {
4197 // TODO: Cache this stuff and allow consumers to share branches (hold weak
4198 // references, I think).
4199 RefPtr<nsPrefBranch> prefBranch =
4200 new nsPrefBranch(aPrefRoot, PrefValueKind::User);
4201 prefBranch.forget(aRetVal);
4202 } else {
4203 // Special case: caching the default root.
4204 nsCOMPtr<nsIPrefBranch> root(sPreferences->mRootBranch);
4205 root.forget(aRetVal);
4208 return NS_OK;
4211 NS_IMETHODIMP
4212 Preferences::GetDefaultBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal) {
4213 if (!aPrefRoot || !aPrefRoot[0]) {
4214 nsCOMPtr<nsIPrefBranch> root(sPreferences->mDefaultRootBranch);
4215 root.forget(aRetVal);
4216 return NS_OK;
4219 // TODO: Cache this stuff and allow consumers to share branches (hold weak
4220 // references, I think).
4221 RefPtr<nsPrefBranch> prefBranch =
4222 new nsPrefBranch(aPrefRoot, PrefValueKind::Default);
4223 if (!prefBranch) {
4224 return NS_ERROR_OUT_OF_MEMORY;
4227 prefBranch.forget(aRetVal);
4228 return NS_OK;
4231 NS_IMETHODIMP
4232 Preferences::ReadStats(nsIPrefStatsCallback* aCallback) {
4233 #ifdef ACCESS_COUNTS
4234 for (const auto& entry : *gAccessCounts) {
4235 aCallback->Visit(entry.GetKey(), entry.GetData());
4238 return NS_OK;
4239 #else
4240 return NS_ERROR_NOT_IMPLEMENTED;
4241 #endif
4244 NS_IMETHODIMP
4245 Preferences::ResetStats() {
4246 #ifdef ACCESS_COUNTS
4247 gAccessCounts->Clear();
4248 return NS_OK;
4249 #else
4250 return NS_ERROR_NOT_IMPLEMENTED;
4251 #endif
4254 // We would much prefer to use C++ lambdas, but we cannot convert
4255 // lambdas that capture (here, the underlying observer) to C pointer
4256 // to functions. So, here we are, with icky C callbacks. Be aware
4257 // that nothing is thread-safe here because there's a single global
4258 // `nsIPrefObserver` instance. Use this from the main thread only.
4259 nsIPrefObserver* PrefObserver = nullptr;
4261 void HandlePref(const char* aPrefName, PrefType aType, PrefValueKind aKind,
4262 PrefValue aValue, bool aIsSticky, bool aIsLocked) {
4263 MOZ_ASSERT(NS_IsMainThread());
4265 if (!PrefObserver) {
4266 return;
4269 const char* kind = aKind == PrefValueKind::Default ? "Default" : "User";
4271 switch (aType) {
4272 case PrefType::String:
4273 PrefObserver->OnStringPref(kind, aPrefName, aValue.mStringVal, aIsSticky,
4274 aIsLocked);
4275 break;
4276 case PrefType::Int:
4277 PrefObserver->OnIntPref(kind, aPrefName, aValue.mIntVal, aIsSticky,
4278 aIsLocked);
4279 break;
4280 case PrefType::Bool:
4281 PrefObserver->OnBoolPref(kind, aPrefName, aValue.mBoolVal, aIsSticky,
4282 aIsLocked);
4283 break;
4284 default:
4285 PrefObserver->OnError("Unexpected pref type.");
4289 void HandleError(const char* aMsg) {
4290 MOZ_ASSERT(NS_IsMainThread());
4292 if (!PrefObserver) {
4293 return;
4296 PrefObserver->OnError(aMsg);
4299 NS_IMETHODIMP
4300 Preferences::ParsePrefsFromBuffer(const nsTArray<uint8_t>& aBytes,
4301 nsIPrefObserver* aObserver,
4302 const char* aPathLabel) {
4303 MOZ_ASSERT(NS_IsMainThread());
4305 // We need a null-terminated buffer.
4306 nsTArray<uint8_t> data = aBytes.Clone();
4307 data.AppendElement(0);
4309 // Parsing as default handles both `pref` and `user_pref`.
4310 PrefObserver = aObserver;
4311 prefs_parser_parse(aPathLabel ? aPathLabel : "<ParsePrefsFromBuffer data>",
4312 PrefValueKind::Default, (const char*)data.Elements(),
4313 data.Length() - 1, HandlePref, HandleError);
4314 PrefObserver = nullptr;
4316 return NS_OK;
4319 NS_IMETHODIMP
4320 Preferences::GetUserPrefsFileLastModifiedAtStartup(PRTime* aLastModified) {
4321 *aLastModified = mUserPrefsFileLastModifiedAtStartup;
4322 return NS_OK;
4325 NS_IMETHODIMP
4326 Preferences::GetDirty(bool* aRetVal) {
4327 *aRetVal = mDirty;
4328 return NS_OK;
4331 nsresult Preferences::NotifyServiceObservers(const char* aTopic) {
4332 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
4333 if (!observerService) {
4334 return NS_ERROR_FAILURE;
4337 auto subject = static_cast<nsIPrefService*>(this);
4338 observerService->NotifyObservers(subject, aTopic, nullptr);
4340 return NS_OK;
4343 already_AddRefed<nsIFile> Preferences::ReadSavedPrefs() {
4344 nsCOMPtr<nsIFile> file;
4345 nsresult rv =
4346 NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE, getter_AddRefs(file));
4347 if (NS_WARN_IF(NS_FAILED(rv))) {
4348 return nullptr;
4351 rv = openPrefFile(file, PrefValueKind::User);
4352 if (rv == NS_ERROR_FILE_NOT_FOUND) {
4353 // This is a normal case for new users.
4354 rv = NS_OK;
4355 } else {
4356 // Store the last modified time of the file while we've got it.
4357 // We don't really care if this fails.
4358 Unused << file->GetLastModifiedTime(&mUserPrefsFileLastModifiedAtStartup);
4360 if (NS_FAILED(rv)) {
4361 // Save a backup copy of the current (invalid) prefs file, since all prefs
4362 // from the error line to the end of the file will be lost (bug 361102).
4363 // TODO we should notify the user about it (bug 523725).
4364 glean::preferences::prefs_file_was_invalid.Set(true);
4365 MakeBackupPrefFile(file);
4369 return file.forget();
4372 void Preferences::ReadUserOverridePrefs() {
4373 nsCOMPtr<nsIFile> aFile;
4374 nsresult rv =
4375 NS_GetSpecialDirectory(NS_APP_PREFS_50_DIR, getter_AddRefs(aFile));
4376 if (NS_WARN_IF(NS_FAILED(rv))) {
4377 return;
4380 aFile->AppendNative("user.js"_ns);
4381 rv = openPrefFile(aFile, PrefValueKind::User);
4384 nsresult Preferences::MakeBackupPrefFile(nsIFile* aFile) {
4385 // Example: this copies "prefs.js" to "Invalidprefs.js" in the same directory.
4386 // "Invalidprefs.js" is removed if it exists, prior to making the copy.
4387 nsAutoString newFilename;
4388 nsresult rv = aFile->GetLeafName(newFilename);
4389 NS_ENSURE_SUCCESS(rv, rv);
4391 newFilename.InsertLiteral(u"Invalid", 0);
4392 nsCOMPtr<nsIFile> newFile;
4393 rv = aFile->GetParent(getter_AddRefs(newFile));
4394 NS_ENSURE_SUCCESS(rv, rv);
4396 rv = newFile->Append(newFilename);
4397 NS_ENSURE_SUCCESS(rv, rv);
4399 bool exists = false;
4400 newFile->Exists(&exists);
4401 if (exists) {
4402 rv = newFile->Remove(false);
4403 NS_ENSURE_SUCCESS(rv, rv);
4406 rv = aFile->CopyTo(nullptr, newFilename);
4407 NS_ENSURE_SUCCESS(rv, rv);
4409 return rv;
4412 nsresult Preferences::SavePrefFileInternal(nsIFile* aFile,
4413 SaveMethod aSaveMethod) {
4414 ENSURE_PARENT_PROCESS("Preferences::SavePrefFileInternal", "all prefs");
4416 // We allow different behavior here when aFile argument is not null, but it
4417 // happens to be the same as the current file. It is not clear that we
4418 // should, but it does give us a "force" save on the unmodified pref file
4419 // (see the original bug 160377 when we added this.)
4421 if (nullptr == aFile) {
4422 mSavePending = false;
4424 // Off main thread writing only if allowed.
4425 if (!AllowOffMainThreadSave()) {
4426 aSaveMethod = SaveMethod::Blocking;
4429 // The mDirty flag tells us if we should write to mCurrentFile. We only
4430 // check this flag when the caller wants to write to the default.
4431 if (!mDirty) {
4432 return NS_OK;
4435 // Check for profile shutdown after mDirty because the runnables from
4436 // HandleDirty() can still be pending.
4437 if (mProfileShutdown) {
4438 NS_WARNING("Cannot save pref file after profile shutdown.");
4439 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
4442 // It's possible that we never got a prefs file.
4443 nsresult rv = NS_OK;
4444 if (mCurrentFile) {
4445 rv = WritePrefFile(mCurrentFile, aSaveMethod);
4448 // If we succeeded writing to mCurrentFile, reset the dirty flag.
4449 if (NS_SUCCEEDED(rv)) {
4450 mDirty = false;
4452 return rv;
4454 } else {
4455 // We only allow off main thread writes on mCurrentFile using this method.
4456 // If you want to write asynchronously, use BackupPrefFile instead.
4457 return WritePrefFile(aFile, SaveMethod::Blocking);
4461 nsresult Preferences::WritePrefFile(
4462 nsIFile* aFile, SaveMethod aSaveMethod,
4463 UniquePtr<MozPromiseHolder<WritePrefFilePromise>>
4464 aPromiseHolder /* = nullptr */) {
4465 MOZ_ASSERT(XRE_IsParentProcess());
4467 #define REJECT_IF_PROMISE_HOLDER_EXISTS(rv) \
4468 if (aPromiseHolder) { \
4469 aPromiseHolder->RejectIfExists(rv, __func__); \
4471 return rv;
4473 if (!HashTable()) {
4474 REJECT_IF_PROMISE_HOLDER_EXISTS(NS_ERROR_NOT_INITIALIZED);
4477 AUTO_PROFILER_LABEL("Preferences::WritePrefFile", OTHER);
4479 if (AllowOffMainThreadSave()) {
4480 UniquePtr<PrefSaveData> prefs = MakeUnique<PrefSaveData>(pref_savePrefs());
4482 nsresult rv = NS_OK;
4483 bool writingToCurrent = false;
4485 if (mCurrentFile) {
4486 rv = mCurrentFile->Equals(aFile, &writingToCurrent);
4487 if (NS_FAILED(rv)) {
4488 REJECT_IF_PROMISE_HOLDER_EXISTS(rv);
4492 // Put the newly constructed preference data into sPendingWriteData
4493 // for the next request to pick up
4494 prefs.reset(PreferencesWriter::sPendingWriteData.exchange(prefs.release()));
4495 if (prefs && !writingToCurrent) {
4496 MOZ_ASSERT(!aPromiseHolder,
4497 "Shouldn't be able to enter here if aPromiseHolder is set");
4498 // There was a previous request writing to the default location that
4499 // hasn't been processed. It will do the work of eventually writing this
4500 // latest batch of data to disk.
4501 return NS_OK;
4504 // There were no previous requests. Dispatch one since sPendingWriteData has
4505 // the up to date information.
4506 nsCOMPtr<nsIEventTarget> target =
4507 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
4508 if (NS_SUCCEEDED(rv)) {
4509 bool async = aSaveMethod == SaveMethod::Asynchronous;
4511 // Increment sPendingWriteCount, even though it's redundant to track this
4512 // in the case of a sync runnable; it just makes it easier to simply
4513 // decrement this inside PWRunnable. We cannot use the constructor /
4514 // destructor for increment/decrement, as on dispatch failure we might
4515 // leak the runnable in order to not destroy it on the wrong thread, which
4516 // would make us get stuck in an infinite SpinEventLoopUntil inside
4517 // PreferencesWriter::Flush. Better that in future code we miss an
4518 // increment of sPendingWriteCount and cause a simple crash due to it
4519 // ending up negative.
4521 // If aPromiseHolder is not null, ownership is transferred to PWRunnable.
4522 // The PWRunnable will automatically reject the MozPromise if it is
4523 // destroyed before being resolved or rejected by the Run method.
4524 PreferencesWriter::sPendingWriteCount++;
4525 if (async) {
4526 rv = target->Dispatch(new PWRunnable(aFile, std::move(aPromiseHolder)),
4527 nsIEventTarget::DISPATCH_NORMAL);
4528 } else {
4529 rv = SyncRunnable::DispatchToThread(
4530 target, new PWRunnable(aFile, std::move(aPromiseHolder)), true);
4532 if (NS_FAILED(rv)) {
4533 // If our dispatch failed, we should correct our bookkeeping to
4534 // avoid shutdown hangs.
4535 PreferencesWriter::sPendingWriteCount--;
4536 // No need to reject the aPromiseHolder here, as the PWRunnable will
4537 // have already done so.
4538 return rv;
4540 return NS_OK;
4543 // If we can't get the thread for writing, for whatever reason, do the main
4544 // thread write after making some noise.
4545 MOZ_ASSERT(false, "failed to get the target thread for OMT pref write");
4548 // This will do a main thread write. It is safe to do it this way because
4549 // AllowOffMainThreadSave() returns a consistent value for the lifetime of
4550 // the parent process.
4551 PrefSaveData prefsData = pref_savePrefs();
4553 // If we were given a MozPromiseHolder, this means the caller is attempting
4554 // to write prefs asynchronously to the disk - but if we get here, it means
4555 // that AllowOffMainThreadSave() return false, and that we will be forced
4556 // to write on the main thread instead. We still have to resolve or reject
4557 // that MozPromise regardless.
4558 nsresult rv = PreferencesWriter::Write(aFile, prefsData);
4559 if (aPromiseHolder) {
4560 NS_WARNING(
4561 "Cannot write to prefs asynchronously, as AllowOffMainThreadSave() "
4562 "returned false.");
4563 if (NS_SUCCEEDED(rv)) {
4564 aPromiseHolder->ResolveIfExists(true, __func__);
4565 } else {
4566 aPromiseHolder->RejectIfExists(rv, __func__);
4569 return rv;
4571 #undef REJECT_IF_PROMISE_HOLDER_EXISTS
4574 static nsresult openPrefFile(nsIFile* aFile, PrefValueKind aKind) {
4575 MOZ_ASSERT(XRE_IsParentProcess());
4577 nsCString data;
4578 MOZ_TRY_VAR(data, URLPreloader::ReadFile(aFile));
4580 nsAutoString filenameUtf16;
4581 aFile->GetLeafName(filenameUtf16);
4582 NS_ConvertUTF16toUTF8 filename(filenameUtf16);
4584 nsAutoString path;
4585 aFile->GetPath(path);
4587 Parser parser;
4588 if (!parser.Parse(aKind, NS_ConvertUTF16toUTF8(path).get(), data)) {
4589 return NS_ERROR_FILE_CORRUPTED;
4592 return NS_OK;
4595 static nsresult parsePrefData(const nsCString& aData, PrefValueKind aKind) {
4596 const nsCString path = "$MOZ_DEFAULT_PREFS"_ns;
4598 Parser parser;
4599 if (!parser.Parse(aKind, path.get(), aData)) {
4600 return NS_ERROR_FILE_CORRUPTED;
4603 return NS_OK;
4606 static int pref_CompareFileNames(nsIFile* aFile1, nsIFile* aFile2) {
4607 nsAutoCString filename1, filename2;
4608 aFile1->GetNativeLeafName(filename1);
4609 aFile2->GetNativeLeafName(filename2);
4611 return Compare(filename2, filename1);
4614 // Load default pref files from a directory. The files in the directory are
4615 // sorted reverse-alphabetically.
4616 static nsresult pref_LoadPrefsInDir(nsIFile* aDir) {
4617 MOZ_ASSERT(XRE_IsParentProcess());
4619 nsresult rv, rv2;
4621 nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
4623 // This may fail in some normal cases, such as embedders who do not use a
4624 // GRE.
4625 rv = aDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
4626 if (NS_FAILED(rv)) {
4627 // If the directory doesn't exist, then we have no reason to complain. We
4628 // loaded everything (and nothing) successfully.
4629 if (rv == NS_ERROR_FILE_NOT_FOUND) {
4630 rv = NS_OK;
4632 return rv;
4635 nsCOMArray<nsIFile> prefFiles(INITIAL_PREF_FILES);
4636 nsCOMPtr<nsIFile> prefFile;
4638 while (NS_SUCCEEDED(dirIterator->GetNextFile(getter_AddRefs(prefFile))) &&
4639 prefFile) {
4640 nsAutoCString leafName;
4641 prefFile->GetNativeLeafName(leafName);
4642 MOZ_ASSERT(
4643 !leafName.IsEmpty(),
4644 "Failure in default prefs: directory enumerator returned empty file?");
4646 // Skip non-js files.
4647 if (StringEndsWith(leafName, ".js"_ns,
4648 nsCaseInsensitiveCStringComparator)) {
4649 prefFiles.AppendObject(prefFile);
4653 if (prefFiles.Count() == 0) {
4654 NS_WARNING("No default pref files found.");
4655 if (NS_SUCCEEDED(rv)) {
4656 rv = NS_SUCCESS_FILE_DIRECTORY_EMPTY;
4658 return rv;
4661 prefFiles.Sort(pref_CompareFileNames);
4663 uint32_t arrayCount = prefFiles.Count();
4664 uint32_t i;
4665 for (i = 0; i < arrayCount; ++i) {
4666 rv2 = openPrefFile(prefFiles[i], PrefValueKind::Default);
4667 if (NS_FAILED(rv2)) {
4668 NS_ERROR("Default pref file not parsed successfully.");
4669 rv = rv2;
4673 return rv;
4676 static nsresult pref_ReadPrefFromJar(nsZipArchive* aJarReader,
4677 const char* aName) {
4678 nsCString manifest;
4679 MOZ_TRY_VAR(manifest,
4680 URLPreloader::ReadZip(aJarReader, nsDependentCString(aName)));
4682 Parser parser;
4683 if (!parser.Parse(PrefValueKind::Default, aName, manifest)) {
4684 return NS_ERROR_FILE_CORRUPTED;
4687 return NS_OK;
4690 static nsresult pref_ReadDefaultPrefs(const RefPtr<nsZipArchive> jarReader,
4691 const char* path) {
4692 UniquePtr<nsZipFind> find;
4693 nsTArray<nsCString> prefEntries;
4694 const char* entryName;
4695 uint16_t entryNameLen;
4697 nsresult rv = jarReader->FindInit(path, getter_Transfers(find));
4698 NS_ENSURE_SUCCESS(rv, rv);
4700 while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
4701 prefEntries.AppendElement(Substring(entryName, entryNameLen));
4704 prefEntries.Sort();
4705 for (uint32_t i = prefEntries.Length(); i--;) {
4706 rv = pref_ReadPrefFromJar(jarReader, prefEntries[i].get());
4707 if (NS_FAILED(rv)) {
4708 NS_WARNING("Error parsing preferences.");
4712 return NS_OK;
4715 static nsCString PrefValueToString(const bool* b) {
4716 return nsCString(*b ? "true" : "false");
4718 static nsCString PrefValueToString(const int* i) {
4719 return nsPrintfCString("%d", *i);
4721 static nsCString PrefValueToString(const uint32_t* u) {
4722 return nsPrintfCString("%d", *u);
4724 static nsCString PrefValueToString(const float* f) {
4725 return nsPrintfCString("%f", *f);
4727 static nsCString PrefValueToString(const nsACString* s) {
4728 return nsCString(*s);
4730 static nsCString PrefValueToString(const nsACString& s) { return nsCString(s); }
4732 // These preference getter wrappers allow us to look up the value for static
4733 // preferences based on their native types, rather than manually mapping them to
4734 // the appropriate Preferences::Get* functions.
4735 // We define these methods in a struct which is made friend of Preferences in
4736 // order to access private members.
4737 struct Internals {
4738 template <typename T>
4739 static nsresult GetPrefValue(const char* aPrefName, T&& aResult,
4740 PrefValueKind aKind) {
4741 nsresult rv = NS_ERROR_UNEXPECTED;
4742 NS_ENSURE_TRUE(Preferences::InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4744 if (Maybe<PrefWrapper> pref = pref_Lookup(aPrefName)) {
4745 rv = pref->GetValue(aKind, std::forward<T>(aResult));
4747 if (profiler_thread_is_being_profiled_for_markers()) {
4748 profiler_add_marker(
4749 "Preference Read", baseprofiler::category::OTHER_PreferenceRead, {},
4750 PreferenceMarker{},
4751 ProfilerString8View::WrapNullTerminatedString(aPrefName),
4752 Some(aKind), pref->Type(), PrefValueToString(aResult));
4756 return rv;
4759 template <typename T>
4760 static nsresult GetSharedPrefValue(const char* aName, T* aResult) {
4761 nsresult rv = NS_ERROR_UNEXPECTED;
4763 if (Maybe<PrefWrapper> pref = pref_SharedLookup(aName)) {
4764 rv = pref->GetValue(PrefValueKind::User, aResult);
4766 if (profiler_thread_is_being_profiled_for_markers()) {
4767 profiler_add_marker(
4768 "Preference Read", baseprofiler::category::OTHER_PreferenceRead, {},
4769 PreferenceMarker{},
4770 ProfilerString8View::WrapNullTerminatedString(aName),
4771 Nothing() /* indicates Shared */, pref->Type(),
4772 PrefValueToString(aResult));
4776 return rv;
4779 template <typename T>
4780 static T GetPref(const char* aPrefName, T aFallback,
4781 PrefValueKind aKind = PrefValueKind::User) {
4782 T result = aFallback;
4783 GetPrefValue(aPrefName, &result, aKind);
4784 return result;
4787 template <typename T, typename V>
4788 static void MOZ_NEVER_INLINE AssignMirror(T& aMirror, V aValue) {
4789 aMirror = aValue;
4792 static void MOZ_NEVER_INLINE AssignMirror(DataMutexString& aMirror,
4793 nsCString&& aValue) {
4794 auto lock = aMirror.Lock();
4795 lock->Assign(std::move(aValue));
4798 static void MOZ_NEVER_INLINE AssignMirror(DataMutexString& aMirror,
4799 const nsLiteralCString& aValue) {
4800 auto lock = aMirror.Lock();
4801 lock->Assign(aValue);
4804 static void ClearMirror(DataMutexString& aMirror) {
4805 auto lock = aMirror.Lock();
4806 lock->Assign(nsCString());
4809 template <typename T>
4810 static void UpdateMirror(const char* aPref, void* aMirror) {
4811 StripAtomic<T> value;
4813 nsresult rv = GetPrefValue(aPref, &value, PrefValueKind::User);
4814 if (NS_SUCCEEDED(rv)) {
4815 AssignMirror(*static_cast<T*>(aMirror),
4816 std::forward<StripAtomic<T>>(value));
4817 } else {
4818 // GetPrefValue() can fail if the update is caused by the pref being
4819 // deleted or if it fails to make a cast. This assertion is the only place
4820 // where we safeguard these. In this case the mirror variable will be
4821 // untouched, thus keeping the value it had prior to the change.
4822 // (Note that this case won't happen for a deletion via DeleteBranch()
4823 // unless bug 343600 is fixed, but it will happen for a deletion via
4824 // ClearUserPref().)
4825 NS_WARNING(nsPrintfCString("Pref changed failure: %s\n", aPref).get());
4826 MOZ_ASSERT(false);
4830 template <typename T>
4831 static nsresult RegisterCallback(void* aMirror, const nsACString& aPref) {
4832 return Preferences::RegisterCallback(UpdateMirror<T>, aPref, aMirror,
4833 Preferences::ExactMatch,
4834 /* isPriority */ true);
4838 // Initialize default preference JavaScript buffers from appropriate TEXT
4839 // resources.
4840 /* static */
4841 nsresult Preferences::InitInitialObjects(bool aIsStartup) {
4842 MOZ_ASSERT(NS_IsMainThread());
4844 if (!XRE_IsParentProcess()) {
4845 MOZ_DIAGNOSTIC_ASSERT(gSharedMap);
4846 if (aIsStartup) {
4847 StaticPrefs::StartObservingAlwaysPrefs();
4849 return NS_OK;
4852 // Initialize static prefs before prefs from data files so that the latter
4853 // will override the former.
4854 StaticPrefs::InitAll();
4856 // In the omni.jar case, we load the following prefs:
4857 // - jar:$gre/omni.jar!/greprefs.js
4858 // - jar:$gre/omni.jar!/defaults/pref/*.js
4860 // In the non-omni.jar case, we load:
4861 // - $gre/greprefs.js
4863 // In both cases, we also load:
4864 // - $gre/defaults/pref/*.js
4866 // This is kept for bug 591866 (channel-prefs.js should not be in omni.jar)
4867 // in the `$app == $gre` case; we load all files instead of channel-prefs.js
4868 // only to have the same behaviour as `$app != $gre`, where this is required
4869 // as a supported location for GRE preferences.
4871 // When `$app != $gre`, we additionally load, in the omni.jar case:
4872 // - jar:$app/omni.jar!/defaults/preferences/*.js
4873 // - $app/defaults/preferences/*.js
4875 // and in the non-omni.jar case:
4876 // - $app/defaults/preferences/*.js
4878 // When `$app == $gre`, we additionally load, in the omni.jar case:
4879 // - jar:$gre/omni.jar!/defaults/preferences/*.js
4881 // Thus, in the omni.jar case, we always load app-specific default
4882 // preferences from omni.jar, whether or not `$app == $gre`.
4884 nsresult rv = NS_ERROR_FAILURE;
4885 UniquePtr<nsZipFind> find;
4886 nsTArray<nsCString> prefEntries;
4887 const char* entryName;
4888 uint16_t entryNameLen;
4890 RefPtr<nsZipArchive> jarReader = Omnijar::GetReader(Omnijar::GRE);
4891 if (jarReader) {
4892 #ifdef MOZ_WIDGET_ANDROID
4893 // Try to load an architecture-specific greprefs.js first. This will be
4894 // present in FAT AAR builds of GeckoView on Android.
4895 const char* abi = getenv("MOZ_ANDROID_CPU_ABI");
4896 if (abi) {
4897 nsAutoCString path;
4898 path.AppendPrintf("%s/greprefs.js", abi);
4899 rv = pref_ReadPrefFromJar(jarReader, path.get());
4902 if (NS_FAILED(rv)) {
4903 // Fallback to toplevel greprefs.js if arch-specific load fails.
4904 rv = pref_ReadPrefFromJar(jarReader, "greprefs.js");
4906 #else
4907 // Load jar:$gre/omni.jar!/greprefs.js.
4908 rv = pref_ReadPrefFromJar(jarReader, "greprefs.js");
4909 #endif
4910 NS_ENSURE_SUCCESS(rv, rv);
4912 // Load jar:$gre/omni.jar!/defaults/pref/*.js.
4913 rv = pref_ReadDefaultPrefs(jarReader, "defaults/pref/*.js$");
4914 NS_ENSURE_SUCCESS(rv, rv);
4916 #ifdef MOZ_BACKGROUNDTASKS
4917 if (BackgroundTasks::IsBackgroundTaskMode()) {
4918 rv = pref_ReadDefaultPrefs(jarReader, "defaults/backgroundtasks/*.js$");
4919 NS_ENSURE_SUCCESS(rv, rv);
4921 #endif
4923 #ifdef MOZ_WIDGET_ANDROID
4924 // Load jar:$gre/omni.jar!/defaults/pref/$MOZ_ANDROID_CPU_ABI/*.js.
4925 nsAutoCString path;
4926 path.AppendPrintf("jar:$gre/omni.jar!/defaults/pref/%s/*.js$", abi);
4927 pref_ReadDefaultPrefs(jarReader, path.get());
4928 NS_ENSURE_SUCCESS(rv, rv);
4929 #endif
4930 } else {
4931 // Load $gre/greprefs.js.
4932 nsCOMPtr<nsIFile> greprefsFile;
4933 rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greprefsFile));
4934 NS_ENSURE_SUCCESS(rv, rv);
4936 rv = greprefsFile->AppendNative("greprefs.js"_ns);
4937 NS_ENSURE_SUCCESS(rv, rv);
4939 rv = openPrefFile(greprefsFile, PrefValueKind::Default);
4940 if (NS_FAILED(rv)) {
4941 NS_WARNING(
4942 "Error parsing GRE default preferences. Is this an old-style "
4943 "embedding app?");
4947 // Load $gre/defaults/pref/*.js.
4948 nsCOMPtr<nsIFile> defaultPrefDir;
4949 rv = NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR,
4950 getter_AddRefs(defaultPrefDir));
4951 NS_ENSURE_SUCCESS(rv, rv);
4953 rv = pref_LoadPrefsInDir(defaultPrefDir);
4954 if (NS_FAILED(rv)) {
4955 NS_WARNING("Error parsing application default preferences.");
4958 #ifdef MOZ_WIDGET_COCOA
4959 // On macOS, channel-prefs.js is no longer bundled with the application and
4960 // the "app.update.channel" pref is now read from a Framework instead.
4961 // Previously, channel-prefs.js was read as one of the files in
4962 // NS_APP_PREF_DEFAULTS_50_DIR (see just above). See bug 1799332 for more
4963 // info.
4964 nsAutoCString appUpdatePrefKey;
4965 appUpdatePrefKey.Assign(kChannelPref);
4966 nsAutoCString appUpdatePrefValue;
4967 PrefValue channelPrefValue;
4968 channelPrefValue.mStringVal = MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL);
4969 if (ChannelPrefsUtil::GetChannelPrefValue(appUpdatePrefValue)) {
4970 channelPrefValue.mStringVal = appUpdatePrefValue.get();
4972 pref_SetPref(appUpdatePrefKey, PrefType::String, PrefValueKind::Default,
4973 channelPrefValue,
4974 /* isSticky */ false,
4975 /* isLocked */ true,
4976 /* fromInit */ true);
4977 #endif
4979 // Load jar:$app/omni.jar!/defaults/preferences/*.js
4980 // or jar:$gre/omni.jar!/defaults/preferences/*.js.
4981 RefPtr<nsZipArchive> appJarReader = Omnijar::GetReader(Omnijar::APP);
4983 // GetReader(Omnijar::APP) returns null when `$app == $gre`, in
4984 // which case we look for app-specific default preferences in $gre.
4985 if (!appJarReader) {
4986 appJarReader = Omnijar::GetReader(Omnijar::GRE);
4989 if (appJarReader) {
4990 rv = appJarReader->FindInit("defaults/preferences/*.js$",
4991 getter_Transfers(find));
4992 NS_ENSURE_SUCCESS(rv, rv);
4993 prefEntries.Clear();
4994 while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
4995 prefEntries.AppendElement(Substring(entryName, entryNameLen));
4997 prefEntries.Sort();
4998 for (uint32_t i = prefEntries.Length(); i--;) {
4999 rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get());
5000 if (NS_FAILED(rv)) {
5001 NS_WARNING("Error parsing preferences.");
5005 #ifdef MOZ_BACKGROUNDTASKS
5006 if (BackgroundTasks::IsBackgroundTaskMode()) {
5007 rv = appJarReader->FindInit("defaults/backgroundtasks/*.js$",
5008 getter_Transfers(find));
5009 NS_ENSURE_SUCCESS(rv, rv);
5010 prefEntries.Clear();
5011 while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
5012 prefEntries.AppendElement(Substring(entryName, entryNameLen));
5014 prefEntries.Sort();
5015 for (uint32_t i = prefEntries.Length(); i--;) {
5016 rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get());
5017 if (NS_FAILED(rv)) {
5018 NS_WARNING("Error parsing preferences.");
5022 #endif
5025 nsCOMPtr<nsIProperties> dirSvc(
5026 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
5027 NS_ENSURE_SUCCESS(rv, rv);
5029 nsCOMPtr<nsISimpleEnumerator> list;
5030 dirSvc->Get(NS_APP_PREFS_DEFAULTS_DIR_LIST, NS_GET_IID(nsISimpleEnumerator),
5031 getter_AddRefs(list));
5032 if (list) {
5033 bool hasMore;
5034 while (NS_SUCCEEDED(list->HasMoreElements(&hasMore)) && hasMore) {
5035 nsCOMPtr<nsISupports> elem;
5036 list->GetNext(getter_AddRefs(elem));
5037 if (!elem) {
5038 continue;
5041 nsCOMPtr<nsIFile> path = do_QueryInterface(elem);
5042 if (!path) {
5043 continue;
5046 // Do we care if a file provided by this process fails to load?
5047 pref_LoadPrefsInDir(path);
5051 #if defined(MOZ_WIDGET_GTK)
5052 // To ensure the system-wide preferences are not overwritten by
5053 // firefox/browser/defauts/preferences/*.js we need to load
5054 // the /etc/firefox/defaults/pref/*.js settings as last.
5055 // Under Flatpak, the NS_OS_SYSTEM_CONFIG_DIR points to /app/etc/firefox
5056 nsCOMPtr<nsIFile> defaultSystemPrefDir;
5057 rv = NS_GetSpecialDirectory(NS_OS_SYSTEM_CONFIG_DIR,
5058 getter_AddRefs(defaultSystemPrefDir));
5059 NS_ENSURE_SUCCESS(rv, rv);
5060 defaultSystemPrefDir->AppendNative("defaults"_ns);
5061 defaultSystemPrefDir->AppendNative("pref"_ns);
5063 rv = pref_LoadPrefsInDir(defaultSystemPrefDir);
5064 if (NS_FAILED(rv)) {
5065 NS_WARNING("Error parsing application default preferences.");
5067 #endif
5069 if (XRE_IsParentProcess()) {
5070 SetupTelemetryPref();
5073 if (aIsStartup) {
5074 // Now that all prefs have their initial values, install the callbacks for
5075 // `always`-mirrored static prefs. We do this now rather than in
5076 // StaticPrefs::InitAll() so that the callbacks don't need to be traversed
5077 // while we load prefs from data files.
5078 StaticPrefs::StartObservingAlwaysPrefs();
5081 NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, nullptr,
5082 NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID);
5084 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
5085 if (NS_WARN_IF(!observerService)) {
5086 return NS_ERROR_FAILURE;
5089 observerService->NotifyObservers(nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID,
5090 nullptr);
5092 return NS_OK;
5095 /* static */
5096 nsresult Preferences::GetBool(const char* aPrefName, bool* aResult,
5097 PrefValueKind aKind) {
5098 MOZ_ASSERT(aResult);
5099 return Internals::GetPrefValue(aPrefName, aResult, aKind);
5102 /* static */
5103 nsresult Preferences::GetInt(const char* aPrefName, int32_t* aResult,
5104 PrefValueKind aKind) {
5105 MOZ_ASSERT(aResult);
5106 return Internals::GetPrefValue(aPrefName, aResult, aKind);
5109 /* static */
5110 nsresult Preferences::GetFloat(const char* aPrefName, float* aResult,
5111 PrefValueKind aKind) {
5112 MOZ_ASSERT(aResult);
5113 return Internals::GetPrefValue(aPrefName, aResult, aKind);
5116 /* static */
5117 nsresult Preferences::GetCString(const char* aPrefName, nsACString& aResult,
5118 PrefValueKind aKind) {
5119 aResult.SetIsVoid(true);
5120 return Internals::GetPrefValue(aPrefName, aResult, aKind);
5123 /* static */
5124 nsresult Preferences::GetString(const char* aPrefName, nsAString& aResult,
5125 PrefValueKind aKind) {
5126 nsAutoCString result;
5127 nsresult rv = Preferences::GetCString(aPrefName, result, aKind);
5128 if (NS_SUCCEEDED(rv)) {
5129 CopyUTF8toUTF16(result, aResult);
5131 return rv;
5134 /* static */
5135 nsresult Preferences::GetLocalizedCString(const char* aPrefName,
5136 nsACString& aResult,
5137 PrefValueKind aKind) {
5138 nsAutoString result;
5139 nsresult rv = GetLocalizedString(aPrefName, result, aKind);
5140 if (NS_SUCCEEDED(rv)) {
5141 CopyUTF16toUTF8(result, aResult);
5143 return rv;
5146 /* static */
5147 nsresult Preferences::GetLocalizedString(const char* aPrefName,
5148 nsAString& aResult,
5149 PrefValueKind aKind) {
5150 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5151 nsCOMPtr<nsIPrefLocalizedString> prefLocalString;
5152 nsresult rv = GetRootBranch(aKind)->GetComplexValue(
5153 aPrefName, NS_GET_IID(nsIPrefLocalizedString),
5154 getter_AddRefs(prefLocalString));
5155 if (NS_SUCCEEDED(rv)) {
5156 MOZ_ASSERT(prefLocalString, "Succeeded but the result is NULL");
5157 prefLocalString->GetData(aResult);
5159 return rv;
5162 /* static */
5163 nsresult Preferences::GetComplex(const char* aPrefName, const nsIID& aType,
5164 void** aResult, PrefValueKind aKind) {
5165 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5166 return GetRootBranch(aKind)->GetComplexValue(aPrefName, aType, aResult);
5169 /* static */
5170 bool Preferences::GetBool(const char* aPrefName, bool aFallback,
5171 PrefValueKind aKind) {
5172 return Internals::GetPref(aPrefName, aFallback, aKind);
5175 /* static */
5176 int32_t Preferences::GetInt(const char* aPrefName, int32_t aFallback,
5177 PrefValueKind aKind) {
5178 return Internals::GetPref(aPrefName, aFallback, aKind);
5181 /* static */
5182 uint32_t Preferences::GetUint(const char* aPrefName, uint32_t aFallback,
5183 PrefValueKind aKind) {
5184 return Internals::GetPref(aPrefName, aFallback, aKind);
5187 /* static */
5188 float Preferences::GetFloat(const char* aPrefName, float aFallback,
5189 PrefValueKind aKind) {
5190 return Internals::GetPref(aPrefName, aFallback, aKind);
5193 /* static */
5194 nsresult Preferences::SetCString(const char* aPrefName,
5195 const nsACString& aValue,
5196 PrefValueKind aKind) {
5197 ENSURE_PARENT_PROCESS("SetCString", aPrefName);
5198 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5200 if (aValue.Length() > MAX_PREF_LENGTH) {
5201 return NS_ERROR_ILLEGAL_VALUE;
5204 // It's ok to stash a pointer to the temporary PromiseFlatCString's chars in
5205 // pref because pref_SetPref() duplicates those chars.
5206 PrefValue prefValue;
5207 const nsCString& flat = PromiseFlatCString(aValue);
5208 prefValue.mStringVal = flat.get();
5209 return pref_SetPref(nsDependentCString(aPrefName), PrefType::String, aKind,
5210 prefValue,
5211 /* isSticky */ false,
5212 /* isLocked */ false,
5213 /* fromInit */ false);
5216 /* static */
5217 nsresult Preferences::SetBool(const char* aPrefName, bool aValue,
5218 PrefValueKind aKind) {
5219 ENSURE_PARENT_PROCESS("SetBool", aPrefName);
5220 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5222 PrefValue prefValue;
5223 prefValue.mBoolVal = aValue;
5224 return pref_SetPref(nsDependentCString(aPrefName), PrefType::Bool, aKind,
5225 prefValue,
5226 /* isSticky */ false,
5227 /* isLocked */ false,
5228 /* fromInit */ false);
5231 /* static */
5232 nsresult Preferences::SetInt(const char* aPrefName, int32_t aValue,
5233 PrefValueKind aKind) {
5234 ENSURE_PARENT_PROCESS("SetInt", aPrefName);
5235 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5237 PrefValue prefValue;
5238 prefValue.mIntVal = aValue;
5239 return pref_SetPref(nsDependentCString(aPrefName), PrefType::Int, aKind,
5240 prefValue,
5241 /* isSticky */ false,
5242 /* isLocked */ false,
5243 /* fromInit */ false);
5246 /* static */
5247 nsresult Preferences::SetComplex(const char* aPrefName, const nsIID& aType,
5248 nsISupports* aValue, PrefValueKind aKind) {
5249 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5250 return GetRootBranch(aKind)->SetComplexValue(aPrefName, aType, aValue);
5253 /* static */
5254 nsresult Preferences::Lock(const char* aPrefName) {
5255 ENSURE_PARENT_PROCESS("Lock", aPrefName);
5256 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5258 const auto& prefName = nsDependentCString(aPrefName);
5260 Pref* pref;
5261 MOZ_TRY_VAR(pref,
5262 pref_LookupForModify(prefName, [](const PrefWrapper& aPref) {
5263 return !aPref.IsLocked();
5264 }));
5266 if (pref) {
5267 pref->SetIsLocked(true);
5268 NotifyCallbacks(prefName, PrefWrapper(pref));
5271 return NS_OK;
5274 /* static */
5275 nsresult Preferences::Unlock(const char* aPrefName) {
5276 ENSURE_PARENT_PROCESS("Unlock", aPrefName);
5277 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5279 const auto& prefName = nsDependentCString(aPrefName);
5281 Pref* pref;
5282 MOZ_TRY_VAR(pref,
5283 pref_LookupForModify(prefName, [](const PrefWrapper& aPref) {
5284 return aPref.IsLocked();
5285 }));
5287 if (pref) {
5288 pref->SetIsLocked(false);
5289 NotifyCallbacks(prefName, PrefWrapper(pref));
5292 return NS_OK;
5295 /* static */
5296 bool Preferences::IsLocked(const char* aPrefName) {
5297 NS_ENSURE_TRUE(InitStaticMembers(), false);
5299 Maybe<PrefWrapper> pref = pref_Lookup(aPrefName);
5300 return pref.isSome() && pref->IsLocked();
5303 /* static */
5304 bool Preferences::IsSanitized(const char* aPrefName) {
5305 NS_ENSURE_TRUE(InitStaticMembers(), false);
5307 Maybe<PrefWrapper> pref = pref_Lookup(aPrefName);
5308 return pref.isSome() && pref->IsSanitized();
5311 /* static */
5312 nsresult Preferences::ClearUser(const char* aPrefName) {
5313 ENSURE_PARENT_PROCESS("ClearUser", aPrefName);
5314 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5316 const auto& prefName = nsDependentCString{aPrefName};
5317 auto result = pref_LookupForModify(
5318 prefName, [](const PrefWrapper& aPref) { return aPref.HasUserValue(); });
5319 if (result.isErr()) {
5320 return NS_OK;
5323 if (Pref* pref = result.unwrap()) {
5324 pref->ClearUserValue();
5326 if (!pref->HasDefaultValue()) {
5327 MOZ_ASSERT(
5328 !gSharedMap || !pref->IsSanitized() || !gSharedMap->Has(pref->Name()),
5329 "A sanitized pref should never be in the shared pref map.");
5330 if (!pref->IsSanitized() &&
5331 (!gSharedMap || !gSharedMap->Has(pref->Name()))) {
5332 HashTable()->remove(aPrefName);
5333 } else {
5334 pref->SetType(PrefType::None);
5337 NotifyCallbacks(prefName);
5338 } else {
5339 NotifyCallbacks(prefName, PrefWrapper(pref));
5342 Preferences::HandleDirty();
5344 return NS_OK;
5347 /* static */
5348 bool Preferences::HasUserValue(const char* aPrefName) {
5349 NS_ENSURE_TRUE(InitStaticMembers(), false);
5351 Maybe<PrefWrapper> pref = pref_Lookup(aPrefName);
5352 return pref.isSome() && pref->HasUserValue();
5355 /* static */
5356 bool Preferences::HasDefaultValue(const char* aPrefName) {
5357 NS_ENSURE_TRUE(InitStaticMembers(), false);
5359 Maybe<PrefWrapper> pref = pref_Lookup(aPrefName);
5360 return pref.isSome() && pref->HasDefaultValue();
5363 /* static */
5364 int32_t Preferences::GetType(const char* aPrefName) {
5365 NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
5367 if (!HashTable()) {
5368 return PREF_INVALID;
5371 Maybe<PrefWrapper> pref = pref_Lookup(aPrefName);
5372 if (!pref.isSome()) {
5373 return PREF_INVALID;
5376 switch (pref->Type()) {
5377 case PrefType::String:
5378 return PREF_STRING;
5380 case PrefType::Int:
5381 return PREF_INT;
5383 case PrefType::Bool:
5384 return PREF_BOOL;
5386 case PrefType::None:
5387 if (IsPreferenceSanitized(aPrefName)) {
5388 glean::security::pref_usage_content_process.Record(Some(
5389 glean::security::PrefUsageContentProcessExtra{Some(aPrefName)}));
5391 if (sCrashOnBlocklistedPref) {
5392 MOZ_CRASH_UNSAFE_PRINTF(
5393 "Should not access the preference '%s' in the Content Processes",
5394 aPrefName);
5395 } else {
5396 return PREF_INVALID;
5399 [[fallthrough]];
5401 default:
5402 MOZ_CRASH();
5406 /* static */
5407 nsresult Preferences::AddStrongObserver(nsIObserver* aObserver,
5408 const nsACString& aPref) {
5409 MOZ_ASSERT(aObserver);
5410 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5411 return sPreferences->mRootBranch->AddObserver(aPref, aObserver, false);
5414 /* static */
5415 nsresult Preferences::AddWeakObserver(nsIObserver* aObserver,
5416 const nsACString& aPref) {
5417 MOZ_ASSERT(aObserver);
5418 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5419 return sPreferences->mRootBranch->AddObserver(aPref, aObserver, true);
5422 /* static */
5423 nsresult Preferences::RemoveObserver(nsIObserver* aObserver,
5424 const nsACString& aPref) {
5425 MOZ_ASSERT(aObserver);
5426 if (sShutdown) {
5427 MOZ_ASSERT(!sPreferences);
5428 return NS_OK; // Observers have been released automatically.
5430 NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
5431 return sPreferences->mRootBranch->RemoveObserver(aPref, aObserver);
5434 template <typename T>
5435 static void AssertNotMallocAllocated(T* aPtr) {
5436 #if defined(DEBUG) && defined(MOZ_MEMORY)
5437 jemalloc_ptr_info_t info;
5438 jemalloc_ptr_info((void*)aPtr, &info);
5439 MOZ_ASSERT(info.tag == TagUnknown);
5440 #endif
5443 /* static */
5444 nsresult Preferences::AddStrongObservers(nsIObserver* aObserver,
5445 const char* const* aPrefs) {
5446 MOZ_ASSERT(aObserver);
5447 for (uint32_t i = 0; aPrefs[i]; i++) {
5448 AssertNotMallocAllocated(aPrefs[i]);
5450 nsCString pref;
5451 pref.AssignLiteral(aPrefs[i], strlen(aPrefs[i]));
5452 nsresult rv = AddStrongObserver(aObserver, pref);
5453 NS_ENSURE_SUCCESS(rv, rv);
5455 return NS_OK;
5458 /* static */
5459 nsresult Preferences::AddWeakObservers(nsIObserver* aObserver,
5460 const char* const* aPrefs) {
5461 MOZ_ASSERT(aObserver);
5462 for (uint32_t i = 0; aPrefs[i]; i++) {
5463 AssertNotMallocAllocated(aPrefs[i]);
5465 nsCString pref;
5466 pref.AssignLiteral(aPrefs[i], strlen(aPrefs[i]));
5467 nsresult rv = AddWeakObserver(aObserver, pref);
5468 NS_ENSURE_SUCCESS(rv, rv);
5470 return NS_OK;
5473 /* static */
5474 nsresult Preferences::RemoveObservers(nsIObserver* aObserver,
5475 const char* const* aPrefs) {
5476 MOZ_ASSERT(aObserver);
5477 if (sShutdown) {
5478 MOZ_ASSERT(!sPreferences);
5479 return NS_OK; // Observers have been released automatically.
5481 NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
5483 for (uint32_t i = 0; aPrefs[i]; i++) {
5484 nsresult rv = RemoveObserver(aObserver, nsDependentCString(aPrefs[i]));
5485 NS_ENSURE_SUCCESS(rv, rv);
5487 return NS_OK;
5490 template <typename T>
5491 /* static */
5492 nsresult Preferences::RegisterCallbackImpl(PrefChangedFunc aCallback,
5493 T& aPrefNode, void* aData,
5494 MatchKind aMatchKind,
5495 bool aIsPriority) {
5496 NS_ENSURE_ARG(aCallback);
5498 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5500 auto node = new CallbackNode(aPrefNode, aCallback, aData, aMatchKind);
5502 if (aIsPriority) {
5503 // Add to the start of the list.
5504 node->SetNext(gFirstCallback);
5505 gFirstCallback = node;
5506 if (!gLastPriorityNode) {
5507 gLastPriorityNode = node;
5509 } else {
5510 // Add to the start of the non-priority part of the list.
5511 if (gLastPriorityNode) {
5512 node->SetNext(gLastPriorityNode->Next());
5513 gLastPriorityNode->SetNext(node);
5514 } else {
5515 node->SetNext(gFirstCallback);
5516 gFirstCallback = node;
5520 return NS_OK;
5523 /* static */
5524 nsresult Preferences::RegisterCallback(PrefChangedFunc aCallback,
5525 const nsACString& aPrefNode, void* aData,
5526 MatchKind aMatchKind, bool aIsPriority) {
5527 return RegisterCallbackImpl(aCallback, aPrefNode, aData, aMatchKind,
5528 aIsPriority);
5531 /* static */
5532 nsresult Preferences::RegisterCallbacks(PrefChangedFunc aCallback,
5533 const char* const* aPrefs, void* aData,
5534 MatchKind aMatchKind) {
5535 return RegisterCallbackImpl(aCallback, aPrefs, aData, aMatchKind);
5538 /* static */
5539 nsresult Preferences::RegisterCallbackAndCall(PrefChangedFunc aCallback,
5540 const nsACString& aPref,
5541 void* aClosure,
5542 MatchKind aMatchKind) {
5543 MOZ_ASSERT(aCallback);
5544 nsresult rv = RegisterCallback(aCallback, aPref, aClosure, aMatchKind);
5545 if (NS_SUCCEEDED(rv)) {
5546 (*aCallback)(PromiseFlatCString(aPref).get(), aClosure);
5548 return rv;
5551 /* static */
5552 nsresult Preferences::RegisterCallbacksAndCall(PrefChangedFunc aCallback,
5553 const char* const* aPrefs,
5554 void* aClosure) {
5555 MOZ_ASSERT(aCallback);
5557 nsresult rv =
5558 RegisterCallbacks(aCallback, aPrefs, aClosure, MatchKind::ExactMatch);
5559 if (NS_SUCCEEDED(rv)) {
5560 for (const char* const* ptr = aPrefs; *ptr; ptr++) {
5561 (*aCallback)(*ptr, aClosure);
5564 return rv;
5567 template <typename T>
5568 /* static */
5569 nsresult Preferences::UnregisterCallbackImpl(PrefChangedFunc aCallback,
5570 T& aPrefNode, void* aData,
5571 MatchKind aMatchKind) {
5572 MOZ_ASSERT(aCallback);
5573 if (sShutdown) {
5574 MOZ_ASSERT(!sPreferences);
5575 return NS_OK; // Observers have been released automatically.
5577 NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
5579 nsresult rv = NS_ERROR_FAILURE;
5580 CallbackNode* node = gFirstCallback;
5581 CallbackNode* prev_node = nullptr;
5583 while (node) {
5584 if (node->Func() == aCallback && node->Data() == aData &&
5585 node->MatchKind() == aMatchKind && node->DomainIs(aPrefNode)) {
5586 if (gCallbacksInProgress) {
5587 // Postpone the node removal until after callbacks enumeration is
5588 // finished.
5589 node->ClearFunc();
5590 gShouldCleanupDeadNodes = true;
5591 prev_node = node;
5592 node = node->Next();
5593 } else {
5594 node = pref_RemoveCallbackNode(node, prev_node);
5596 rv = NS_OK;
5597 } else {
5598 prev_node = node;
5599 node = node->Next();
5602 return rv;
5605 /* static */
5606 nsresult Preferences::UnregisterCallback(PrefChangedFunc aCallback,
5607 const nsACString& aPrefNode,
5608 void* aData, MatchKind aMatchKind) {
5609 return UnregisterCallbackImpl<const nsACString&>(aCallback, aPrefNode, aData,
5610 aMatchKind);
5613 /* static */
5614 nsresult Preferences::UnregisterCallbacks(PrefChangedFunc aCallback,
5615 const char* const* aPrefs,
5616 void* aData, MatchKind aMatchKind) {
5617 return UnregisterCallbackImpl(aCallback, aPrefs, aData, aMatchKind);
5620 template <typename T>
5621 static void AddMirrorCallback(T* aMirror, const nsACString& aPref) {
5622 MOZ_ASSERT(NS_IsMainThread());
5624 Internals::RegisterCallback<T>(aMirror, aPref);
5627 // Don't inline because it explodes compile times.
5628 template <typename T>
5629 static MOZ_NEVER_INLINE void AddMirror(T* aMirror, const nsACString& aPref,
5630 StripAtomic<T> aDefault) {
5631 *aMirror = Internals::GetPref(PromiseFlatCString(aPref).get(), aDefault);
5632 AddMirrorCallback(aMirror, aPref);
5635 static MOZ_NEVER_INLINE void AddMirror(DataMutexString& aMirror,
5636 const nsACString& aPref) {
5637 auto lock = aMirror.Lock();
5638 nsCString result(*lock);
5639 Internals::GetPrefValue(PromiseFlatCString(aPref).get(), result,
5640 PrefValueKind::User);
5641 lock->Assign(std::move(result));
5642 AddMirrorCallback(&aMirror, aPref);
5645 // The InitPref_*() functions below end in a `_<type>` suffix because they are
5646 // used by the PREF macro definition in InitAll() below.
5648 static void InitPref_bool(const nsCString& aName, bool aDefaultValue) {
5649 MOZ_ASSERT(XRE_IsParentProcess());
5650 PrefValue value;
5651 value.mBoolVal = aDefaultValue;
5652 pref_SetPref(aName, PrefType::Bool, PrefValueKind::Default, value,
5653 /* isSticky */ false,
5654 /* isLocked */ false,
5655 /* fromInit */ true);
5658 static void InitPref_int32_t(const nsCString& aName, int32_t aDefaultValue) {
5659 MOZ_ASSERT(XRE_IsParentProcess());
5660 PrefValue value;
5661 value.mIntVal = aDefaultValue;
5662 pref_SetPref(aName, PrefType::Int, PrefValueKind::Default, value,
5663 /* isSticky */ false,
5664 /* isLocked */ false,
5665 /* fromInit */ true);
5668 static void InitPref_uint32_t(const nsCString& aName, uint32_t aDefaultValue) {
5669 InitPref_int32_t(aName, int32_t(aDefaultValue));
5672 static void InitPref_float(const nsCString& aName, float aDefaultValue) {
5673 MOZ_ASSERT(XRE_IsParentProcess());
5674 PrefValue value;
5675 // Convert the value in a locale-independent way, including a trailing ".0"
5676 // if necessary to distinguish floating-point from integer prefs when viewing
5677 // them in about:config.
5678 nsAutoCString defaultValue;
5679 defaultValue.AppendFloat(aDefaultValue);
5680 if (!defaultValue.Contains('.') && !defaultValue.Contains('e')) {
5681 defaultValue.AppendLiteral(".0");
5683 value.mStringVal = defaultValue.get();
5684 pref_SetPref(aName, PrefType::String, PrefValueKind::Default, value,
5685 /* isSticky */ false,
5686 /* isLocked */ false,
5687 /* fromInit */ true);
5690 static void InitPref_String(const nsCString& aName, const char* aDefaultValue) {
5691 MOZ_ASSERT(XRE_IsParentProcess());
5692 PrefValue value;
5693 value.mStringVal = aDefaultValue;
5694 pref_SetPref(aName, PrefType::String, PrefValueKind::Default, value,
5695 /* isSticky */ false,
5696 /* isLocked */ false,
5697 /* fromInit */ true);
5700 static void InitPref(const nsCString& aName, bool aDefaultValue) {
5701 InitPref_bool(aName, aDefaultValue);
5703 static void InitPref(const nsCString& aName, int32_t aDefaultValue) {
5704 InitPref_int32_t(aName, aDefaultValue);
5706 static void InitPref(const nsCString& aName, uint32_t aDefaultValue) {
5707 InitPref_uint32_t(aName, aDefaultValue);
5709 static void InitPref(const nsCString& aName, float aDefaultValue) {
5710 InitPref_float(aName, aDefaultValue);
5713 template <typename T>
5714 static void InitAlwaysPref(const nsCString& aName, T* aCache,
5715 StripAtomic<T> aDefaultValue) {
5716 // Only called in the parent process. Set/reset the pref value and the
5717 // `always` mirror to the default value.
5718 // `once` mirrors will be initialized lazily in InitOncePrefs().
5719 InitPref(aName, aDefaultValue);
5720 *aCache = aDefaultValue;
5723 static void InitAlwaysPref(const nsCString& aName, DataMutexString& aCache,
5724 const nsLiteralCString& aDefaultValue) {
5725 // Only called in the parent process. Set/reset the pref value and the
5726 // `always` mirror to the default value.
5727 // `once` mirrors will be initialized lazily in InitOncePrefs().
5728 InitPref_String(aName, aDefaultValue.get());
5729 Internals::AssignMirror(aCache, aDefaultValue);
5732 static Atomic<bool> sOncePrefRead(false);
5733 static StaticMutex sOncePrefMutex MOZ_UNANNOTATED;
5735 namespace StaticPrefs {
5737 void MaybeInitOncePrefs() {
5738 if (MOZ_LIKELY(sOncePrefRead)) {
5739 // `once`-mirrored prefs have already been initialized to their default
5740 // value.
5741 return;
5743 StaticMutexAutoLock lock(sOncePrefMutex);
5744 if (NS_IsMainThread()) {
5745 InitOncePrefs();
5746 } else {
5747 RefPtr<Runnable> runnable = NS_NewRunnableFunction(
5748 "Preferences::MaybeInitOncePrefs", [&]() { InitOncePrefs(); });
5749 // This logic needs to run on the main thread
5750 SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), runnable);
5752 sOncePrefRead = true;
5755 // For mirrored prefs we generate a variable definition.
5756 #define NEVER_PREF(name, cpp_type, value)
5757 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, default_value) \
5758 cpp_type sMirror_##full_id(default_value);
5759 #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, default_value) \
5760 MOZ_RUNINIT cpp_type sMirror_##full_id("DataMutexString");
5761 #define ONCE_PREF(name, base_id, full_id, cpp_type, default_value) \
5762 cpp_type sMirror_##full_id(default_value);
5763 #include "mozilla/StaticPrefListAll.h"
5764 #undef NEVER_PREF
5765 #undef ALWAYS_PREF
5766 #undef ALWAYS_DATAMUTEX_PREF
5767 #undef ONCE_PREF
5769 static void InitAll() {
5770 MOZ_ASSERT(NS_IsMainThread());
5771 MOZ_ASSERT(XRE_IsParentProcess());
5773 // For all prefs we generate some initialization code.
5775 // The InitPref_*() functions have a type suffix to avoid ambiguity between
5776 // prefs having int32_t and float default values. That suffix is not needed
5777 // for the InitAlwaysPref() functions because they take a pointer parameter,
5778 // which prevents automatic int-to-float coercion.
5779 #define NEVER_PREF(name, cpp_type, value) \
5780 InitPref_##cpp_type(name ""_ns, value);
5781 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) \
5782 InitAlwaysPref(name ""_ns, &sMirror_##full_id, value);
5783 #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value) \
5784 InitAlwaysPref(name ""_ns, sMirror_##full_id, value);
5785 #define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
5786 InitPref_##cpp_type(name ""_ns, value);
5787 #include "mozilla/StaticPrefListAll.h"
5788 #undef NEVER_PREF
5789 #undef ALWAYS_PREF
5790 #undef ALWAYS_DATAMUTEX_PREF
5791 #undef ONCE_PREF
5794 static void StartObservingAlwaysPrefs() {
5795 MOZ_ASSERT(NS_IsMainThread());
5797 // Call AddMirror so that our mirrors for `always` prefs will stay updated.
5798 // The call to AddMirror re-reads the current pref value into the mirror, so
5799 // our mirror will now be up-to-date even if some of the prefs have changed
5800 // since the call to InitAll().
5801 #define NEVER_PREF(name, cpp_type, value)
5802 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) \
5803 AddMirror(&sMirror_##full_id, name ""_ns, sMirror_##full_id);
5804 #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value) \
5805 AddMirror(sMirror_##full_id, name ""_ns);
5806 #define ONCE_PREF(name, base_id, full_id, cpp_type, value)
5807 #include "mozilla/StaticPrefListAll.h"
5808 #undef NEVER_PREF
5809 #undef ALWAYS_PREF
5810 #undef ALWAYS_DATAMUTEX_PREF
5811 #undef ONCE_PREF
5814 static void InitOncePrefs() {
5815 // For `once`-mirrored prefs we generate some initialization code. This is
5816 // done in case the pref value was updated when reading pref data files. It's
5817 // necessary because we don't have callbacks registered for `once`-mirrored
5818 // prefs.
5820 // In debug builds, we also install a mechanism that can check if the
5821 // preference value is modified after `once`-mirrored prefs are initialized.
5822 // In tests this would indicate a likely misuse of a `once`-mirrored pref and
5823 // suggest that it should instead be `always`-mirrored.
5824 #define NEVER_PREF(name, cpp_type, value)
5825 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value)
5826 #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value)
5827 #ifdef DEBUG
5828 # define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
5830 MOZ_ASSERT(gOnceStaticPrefsAntiFootgun); \
5831 sMirror_##full_id = Internals::GetPref(name, cpp_type(value)); \
5832 auto checkPref = [&]() { \
5833 MOZ_ASSERT(sOncePrefRead); \
5834 cpp_type staticPrefValue = full_id(); \
5835 cpp_type preferenceValue = \
5836 Internals::GetPref(GetPrefName_##base_id(), cpp_type(value)); \
5837 MOZ_ASSERT(staticPrefValue == preferenceValue, \
5838 "Preference '" name \
5839 "' got modified since StaticPrefs::" #full_id \
5840 " was initialized. Consider using an `always` mirror kind " \
5841 "instead"); \
5842 }; \
5843 gOnceStaticPrefsAntiFootgun->insert( \
5844 std::pair<const char*, AntiFootgunCallback>(GetPrefName_##base_id(), \
5845 std::move(checkPref))); \
5847 #else
5848 # define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
5849 sMirror_##full_id = Internals::GetPref(name, cpp_type(value));
5850 #endif
5852 #include "mozilla/StaticPrefListAll.h"
5853 #undef NEVER_PREF
5854 #undef ALWAYS_PREF
5855 #undef ALWAYS_DATAMUTEX_PREF
5856 #undef ONCE_PREF
5859 static void ShutdownAlwaysPrefs() {
5860 MOZ_ASSERT(NS_IsMainThread());
5862 // We may need to do clean up for leak detection for some StaticPrefs.
5863 #define NEVER_PREF(name, cpp_type, value)
5864 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value)
5865 #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value) \
5866 Internals::ClearMirror(sMirror_##full_id);
5867 #define ONCE_PREF(name, base_id, full_id, cpp_type, value)
5868 #include "mozilla/StaticPrefListAll.h"
5869 #undef NEVER_PREF
5870 #undef ALWAYS_PREF
5871 #undef ALWAYS_DATAMUTEX_PREF
5872 #undef ONCE_PREF
5875 } // namespace StaticPrefs
5877 static MOZ_MAYBE_UNUSED void SaveOncePrefToSharedMap(
5878 SharedPrefMapBuilder& aBuilder, const nsACString& aName, bool aValue) {
5879 auto oncePref = MakeUnique<Pref>(aName);
5880 oncePref->SetType(PrefType::Bool);
5881 oncePref->SetIsSkippedByIteration(true);
5882 bool valueChanged = false;
5883 MOZ_ALWAYS_SUCCEEDS(
5884 oncePref->SetDefaultValue(PrefType::Bool, PrefValue(aValue),
5885 /* isSticky */ true,
5886 /* isLocked */ true, &valueChanged));
5887 oncePref->AddToMap(aBuilder);
5890 static MOZ_MAYBE_UNUSED void SaveOncePrefToSharedMap(
5891 SharedPrefMapBuilder& aBuilder, const nsACString& aName, int32_t aValue) {
5892 auto oncePref = MakeUnique<Pref>(aName);
5893 oncePref->SetType(PrefType::Int);
5894 oncePref->SetIsSkippedByIteration(true);
5895 bool valueChanged = false;
5896 MOZ_ALWAYS_SUCCEEDS(
5897 oncePref->SetDefaultValue(PrefType::Int, PrefValue(aValue),
5898 /* isSticky */ true,
5899 /* isLocked */ true, &valueChanged));
5900 oncePref->AddToMap(aBuilder);
5903 static MOZ_MAYBE_UNUSED void SaveOncePrefToSharedMap(
5904 SharedPrefMapBuilder& aBuilder, const nsACString& aName, uint32_t aValue) {
5905 SaveOncePrefToSharedMap(aBuilder, aName, int32_t(aValue));
5908 static MOZ_MAYBE_UNUSED void SaveOncePrefToSharedMap(
5909 SharedPrefMapBuilder& aBuilder, const nsACString& aName, float aValue) {
5910 auto oncePref = MakeUnique<Pref>(aName);
5911 oncePref->SetType(PrefType::String);
5912 oncePref->SetIsSkippedByIteration(true);
5913 nsAutoCString value;
5914 value.AppendFloat(aValue);
5915 bool valueChanged = false;
5916 // It's ok to stash a pointer to the temporary PromiseFlatCString's chars in
5917 // pref because pref_SetPref() duplicates those chars.
5918 const nsCString& flat = PromiseFlatCString(value);
5919 MOZ_ALWAYS_SUCCEEDS(
5920 oncePref->SetDefaultValue(PrefType::String, PrefValue(flat.get()),
5921 /* isSticky */ true,
5922 /* isLocked */ true, &valueChanged));
5923 oncePref->AddToMap(aBuilder);
5926 #define ONCE_PREF_NAME(name) "$$$" name "$$$"
5928 namespace StaticPrefs {
5930 static void RegisterOncePrefs(SharedPrefMapBuilder& aBuilder) {
5931 MOZ_ASSERT(XRE_IsParentProcess());
5932 MOZ_DIAGNOSTIC_ASSERT(!gSharedMap,
5933 "Must be called before gSharedMap has been created");
5934 MaybeInitOncePrefs();
5936 // For `once`-mirrored prefs we generate a save call, which saves the value
5937 // as it was at parent startup. It is stored in a special (hidden and locked)
5938 // entry in the global SharedPreferenceMap. In order for the entry to be
5939 // hidden and not appear in about:config nor ever be stored to disk, we set
5940 // its IsSkippedByIteration flag to true. We also distinguish it by adding a
5941 // "$$$" prefix and suffix to the preference name.
5942 #define NEVER_PREF(name, cpp_type, value)
5943 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value)
5944 #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value)
5945 #define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
5946 SaveOncePrefToSharedMap(aBuilder, ONCE_PREF_NAME(name) ""_ns, \
5947 cpp_type(sMirror_##full_id));
5948 #include "mozilla/StaticPrefListAll.h"
5949 #undef NEVER_PREF
5950 #undef ALWAYS_PREF
5951 #undef ALWAYS_DATAMUTEX_PREF
5952 #undef ONCE_PREF
5955 // Disable thread safety analysis on this function, because it explodes build
5956 // times and memory usage.
5957 MOZ_NO_THREAD_SAFETY_ANALYSIS
5958 static void InitStaticPrefsFromShared() {
5959 MOZ_ASSERT(!XRE_IsParentProcess());
5960 MOZ_DIAGNOSTIC_ASSERT(gSharedMap,
5961 "Must be called once gSharedMap has been created");
5963 #ifdef DEBUG
5964 # define ASSERT_PREF_NOT_SANITIZED(name, cpp_type) \
5965 if (IsString<cpp_type>::value && IsPreferenceSanitized(name)) { \
5966 MOZ_CRASH("Unexpected sanitized string preference '" name \
5967 "'. " \
5968 "Static Preferences cannot be sanitized currently, because " \
5969 "they expect to be initialized from the Static Map, and " \
5970 "sanitized preferences are not present there."); \
5972 #else
5973 # define ASSERT_PREF_NOT_SANITIZED(name, cpp_type)
5974 #endif
5976 // For mirrored static prefs we generate some initialization code. Each
5977 // mirror variable is already initialized in the binary with the default
5978 // value. If the pref value hasn't changed from the default in the main
5979 // process (the common case) then the overwriting here won't change the
5980 // mirror variable's value.
5982 // Note that the MOZ_ASSERT calls below can fail in one obscure case: when a
5983 // Firefox update occurs and we get a main process from the old binary (with
5984 // static prefs {A,B,C,D}) plus a new content process from the new binary
5985 // (with static prefs {A,B,C,D,E}). The content process' call to
5986 // GetSharedPrefValue() for pref E will fail because the shared pref map was
5987 // created by the main process, which doesn't have pref E.
5989 // This silent failure is safe. The mirror variable for pref E is already
5990 // initialized to the default value in the content process, and the main
5991 // process cannot have changed pref E because it doesn't know about it!
5993 // Nonetheless, it's useful to have the MOZ_ASSERT here for testing of debug
5994 // builds, where this scenario involving inconsistent binaries should not
5995 // occur.
5996 #define NEVER_PREF(name, cpp_type, default_value)
5997 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, default_value) \
5999 StripAtomic<cpp_type> val; \
6000 ASSERT_PREF_NOT_SANITIZED(name, cpp_type); \
6001 DebugOnly<nsresult> rv = Internals::GetSharedPrefValue(name, &val); \
6002 MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \
6003 StaticPrefs::sMirror_##full_id = val; \
6005 #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, default_value) \
6007 StripAtomic<cpp_type> val; \
6008 ASSERT_PREF_NOT_SANITIZED(name, cpp_type); \
6009 DebugOnly<nsresult> rv = Internals::GetSharedPrefValue(name, &val); \
6010 MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \
6011 Internals::AssignMirror(StaticPrefs::sMirror_##full_id, \
6012 std::forward<StripAtomic<cpp_type>>(val)); \
6014 #define ONCE_PREF(name, base_id, full_id, cpp_type, default_value) \
6016 cpp_type val; \
6017 ASSERT_PREF_NOT_SANITIZED(name, cpp_type); \
6018 DebugOnly<nsresult> rv = \
6019 Internals::GetSharedPrefValue(ONCE_PREF_NAME(name), &val); \
6020 MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \
6021 StaticPrefs::sMirror_##full_id = val; \
6023 #include "mozilla/StaticPrefListAll.h"
6024 #undef NEVER_PREF
6025 #undef ALWAYS_PREF
6026 #undef ALWAYS_DATAMUTEX_PREF
6027 #undef ONCE_PREF
6028 #undef ASSERT_PREF_NOT_SANITIZED
6030 // `once`-mirrored prefs have been set to their value in the step above and
6031 // outside the parent process they are immutable. We set sOncePrefRead so
6032 // that we can directly skip any lazy initializations.
6033 sOncePrefRead = true;
6036 } // namespace StaticPrefs
6038 } // namespace mozilla
6040 #undef ENSURE_PARENT_PROCESS
6042 //===========================================================================
6043 // Module and factory stuff
6044 //===========================================================================
6046 NS_IMPL_COMPONENT_FACTORY(nsPrefLocalizedString) {
6047 auto str = MakeRefPtr<nsPrefLocalizedString>();
6048 if (NS_SUCCEEDED(str->Init())) {
6049 return str.forget().downcast<nsISupports>();
6051 return nullptr;
6054 namespace mozilla {
6056 void UnloadPrefsModule() { Preferences::Shutdown(); }
6058 } // namespace mozilla
6060 // Preference Sanitization Related Code ---------------------------------------
6062 #define PREF_LIST_ENTRY(s) {s, (sizeof(s) / sizeof(char)) - 1}
6063 struct PrefListEntry {
6064 const char* mPrefBranch;
6065 size_t mLen;
6068 // A preference is 'sanitized' (i.e. not sent to web content processes) if
6069 // one of two criteria are met:
6070 // 1. The pref name matches one of the prefixes in the following list
6071 // 2. The pref is dynamically named (i.e. not specified in all.js or
6072 // StaticPrefList.yml), a string pref, and it is NOT exempted in
6073 // sDynamicPrefOverrideList
6075 // This behavior is codified in ShouldSanitizePreference() below.
6076 // Exclusions of preferences can be defined in sOverrideRestrictionsList[].
6077 static const PrefListEntry sRestrictFromWebContentProcesses[] = {
6078 // Remove prefs with user data
6079 PREF_LIST_ENTRY("datareporting.policy."),
6080 PREF_LIST_ENTRY("browser.download.lastDir"),
6081 PREF_LIST_ENTRY("browser.newtabpage.pinned"),
6082 PREF_LIST_ENTRY("browser.uiCustomization.state"),
6083 PREF_LIST_ENTRY("browser.urlbar"),
6084 PREF_LIST_ENTRY("devtools.debugger.pending-selected-location"),
6085 PREF_LIST_ENTRY("identity.fxaccounts.account.device.name"),
6086 PREF_LIST_ENTRY("identity.fxaccounts.account.telemetry.sanitized_uid"),
6087 PREF_LIST_ENTRY("identity.fxaccounts.lastSignedInUserHash"),
6088 PREF_LIST_ENTRY("print_printer"),
6089 PREF_LIST_ENTRY("services."),
6091 // Remove UUIDs
6092 PREF_LIST_ENTRY("app.normandy.user_id"),
6093 PREF_LIST_ENTRY("browser.newtabpage.activity-stream.impressionId"),
6094 PREF_LIST_ENTRY("browser.pageActions.persistedActions"),
6095 PREF_LIST_ENTRY("browser.startup.lastColdStartupCheck"),
6096 PREF_LIST_ENTRY("dom.push.userAgentID"),
6097 PREF_LIST_ENTRY("extensions.webextensions.uuids"),
6098 PREF_LIST_ENTRY("privacy.userContext.extension"),
6099 PREF_LIST_ENTRY("toolkit.telemetry.cachedClientID"),
6100 PREF_LIST_ENTRY("toolkit.telemetry.cachedProfileGroupID"),
6102 // Remove IDs that could be used to correlate across origins
6103 PREF_LIST_ENTRY("app.update.lastUpdateTime."),
6104 PREF_LIST_ENTRY(
6105 "browser.contentblocking.cfr-milestone.milestone-shown-time"),
6106 PREF_LIST_ENTRY("browser.contextual-services.contextId"),
6107 PREF_LIST_ENTRY("browser.laterrun.bookkeeping.profileCreationTime"),
6108 PREF_LIST_ENTRY("browser.newtabpage.activity-stream.discoverystream."),
6109 PREF_LIST_ENTRY("browser.sessionstore.upgradeBackup.latestBuildID"),
6110 PREF_LIST_ENTRY("browser.shell.mostRecentDateSetAsDefault"),
6111 PREF_LIST_ENTRY("idle.lastDailyNotification"),
6112 PREF_LIST_ENTRY("media.gmp-gmpopenh264.lastUpdate"),
6113 PREF_LIST_ENTRY("media.gmp-manager.lastCheck"),
6114 PREF_LIST_ENTRY("places.database.lastMaintenance"),
6115 PREF_LIST_ENTRY("privacy.purge_trackers.last_purge"),
6116 PREF_LIST_ENTRY("storage.vacuum.last.places.sqlite"),
6117 PREF_LIST_ENTRY("toolkit.startup.last_success"),
6119 // Remove fingerprintable things
6120 PREF_LIST_ENTRY("browser.startup.homepage_override.buildID"),
6121 PREF_LIST_ENTRY("extensions.lastAppBuildId"),
6122 PREF_LIST_ENTRY("media.gmp-manager.buildID"),
6123 PREF_LIST_ENTRY("toolkit.telemetry.previousBuildID"),
6126 // Allowlist for prefs and branches blocklisted in
6127 // sRestrictFromWebContentProcesses[], including prefs from
6128 // StaticPrefList.yaml and *.js, to let them pass.
6129 static const PrefListEntry sOverrideRestrictionsList[]{
6130 PREF_LIST_ENTRY("services.settings.clock_skew_seconds"),
6131 PREF_LIST_ENTRY("services.settings.last_update_seconds"),
6132 PREF_LIST_ENTRY("services.settings.loglevel"),
6133 // This is really a boolean dynamic pref, but one Nightly user
6134 // has it set as a string...
6135 PREF_LIST_ENTRY("services.settings.preview_enabled"),
6136 PREF_LIST_ENTRY("services.settings.server"),
6139 // These prefs are dynamically-named (i.e. not specified in prefs.js or
6140 // StaticPrefList) and would normally by blocklisted but we allow them through
6141 // anyway, so this override list acts as an allowlist
6142 static const PrefListEntry sDynamicPrefOverrideList[]{
6143 PREF_LIST_ENTRY("accessibility.tabfocus"),
6144 PREF_LIST_ENTRY("app.update.channel"),
6145 PREF_LIST_ENTRY("apz.subtest"),
6146 PREF_LIST_ENTRY("browser.contentblocking.category"),
6147 PREF_LIST_ENTRY("browser.dom.window.dump.file"),
6148 PREF_LIST_ENTRY("browser.search.region"),
6149 PREF_LIST_ENTRY(
6150 "browser.tabs.remote.testOnly.failPBrowserCreation.browsingContext"),
6151 PREF_LIST_ENTRY("browser.uitour.testingOrigins"),
6152 PREF_LIST_ENTRY("browser.urlbar.loglevel"),
6153 PREF_LIST_ENTRY("browser.urlbar.opencompanionsearch.enabled"),
6154 PREF_LIST_ENTRY("capability.policy"),
6155 PREF_LIST_ENTRY("dom.securecontext.allowlist"),
6156 PREF_LIST_ENTRY("extensions.foobaz"),
6157 PREF_LIST_ENTRY(
6158 "extensions.formautofill.creditCards.heuristics.testConfidence"),
6159 PREF_LIST_ENTRY("general.appversion.override"),
6160 PREF_LIST_ENTRY("general.buildID.override"),
6161 PREF_LIST_ENTRY("general.oscpu.override"),
6162 PREF_LIST_ENTRY("general.useragent.override"),
6163 PREF_LIST_ENTRY("general.platform.override"),
6164 PREF_LIST_ENTRY("gfx.blacklist."),
6165 PREF_LIST_ENTRY("font.system.whitelist"),
6166 PREF_LIST_ENTRY("font.name."),
6167 PREF_LIST_ENTRY("intl.date_time.pattern_override."),
6168 PREF_LIST_ENTRY("intl.hyphenation-alias."),
6169 PREF_LIST_ENTRY("logging.config.LOG_FILE"),
6170 PREF_LIST_ENTRY("media.audio_loopback_dev"),
6171 PREF_LIST_ENTRY("media.decoder-doctor."),
6172 PREF_LIST_ENTRY("media.cubeb.backend"),
6173 PREF_LIST_ENTRY("media.cubeb.output_device"),
6174 PREF_LIST_ENTRY("media.getusermedia.fake-camera-name"),
6175 PREF_LIST_ENTRY("media.hls.server.url"),
6176 PREF_LIST_ENTRY("media.peerconnection.nat_simulator.filtering_type"),
6177 PREF_LIST_ENTRY("media.peerconnection.nat_simulator.mapping_type"),
6178 PREF_LIST_ENTRY("media.peerconnection.nat_simulator.redirect_address"),
6179 PREF_LIST_ENTRY("media.peerconnection.nat_simulator.redirect_targets"),
6180 PREF_LIST_ENTRY("media.peerconnection.nat_simulator.network_delay_ms"),
6181 PREF_LIST_ENTRY("media.video_loopback_dev"),
6182 PREF_LIST_ENTRY("media.webspeech.service.endpoint"),
6183 PREF_LIST_ENTRY("network.gio.supported-protocols"),
6184 PREF_LIST_ENTRY("network.protocol-handler.external."),
6185 PREF_LIST_ENTRY("network.security.ports.banned"),
6186 PREF_LIST_ENTRY("nimbus.syncdatastore."),
6187 PREF_LIST_ENTRY("pdfjs."),
6188 PREF_LIST_ENTRY("plugins.force.wmode"),
6189 PREF_LIST_ENTRY("print.printer_"),
6190 PREF_LIST_ENTRY("print_printer"),
6191 PREF_LIST_ENTRY("places.interactions.customBlocklist"),
6192 PREF_LIST_ENTRY("remote.log.level"),
6193 // services.* preferences should be added in sOverrideRestrictionsList[] -
6194 // the whole preference branch gets sanitized by default.
6195 PREF_LIST_ENTRY("spellchecker.dictionary"),
6196 PREF_LIST_ENTRY("test.char"),
6197 PREF_LIST_ENTRY("Test.IPC."),
6198 PREF_LIST_ENTRY("exists.thenDoesNot"),
6199 PREF_LIST_ENTRY("type.String."),
6200 PREF_LIST_ENTRY("toolkit.mozprotocol.url"),
6201 PREF_LIST_ENTRY("toolkit.telemetry.log.level"),
6202 PREF_LIST_ENTRY("ui."),
6205 #undef PREF_LIST_ENTRY
6207 static bool ShouldSanitizePreference(const Pref* const aPref) {
6208 // In the parent process, we use a heuristic to decide if a pref
6209 // value should be sanitized before sending to subprocesses.
6210 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
6212 const char* prefName = aPref->Name();
6214 // If a pref starts with this magic string, it is a Once-Initialized pref
6215 // from Static Prefs. It should* not be in the above list and while it looks
6216 // like a dnyamically named pref, it is not.
6217 // * nothing enforces this
6218 if (strncmp(prefName, "$$$", 3) == 0) {
6219 return false;
6222 // First check against the denylist.
6223 // The services pref is an annoying one - it's much easier to blocklist
6224 // the whole branch and then add this one check to let this one annoying
6225 // pref through.
6226 for (const auto& entry : sRestrictFromWebContentProcesses) {
6227 if (strncmp(entry.mPrefBranch, prefName, entry.mLen) == 0) {
6228 for (const auto& pasEnt : sOverrideRestrictionsList) {
6229 if (strncmp(pasEnt.mPrefBranch, prefName, pasEnt.mLen) == 0) {
6230 return false;
6233 return true;
6237 // Then check if it's a dynamically named string preference and not
6238 // in the override list
6239 if (aPref->Type() == PrefType::String && !aPref->HasDefaultValue()) {
6240 for (const auto& entry : sDynamicPrefOverrideList) {
6241 if (strncmp(entry.mPrefBranch, prefName, entry.mLen) == 0) {
6242 return false;
6245 return true;
6248 return false;
6251 // Forward Declaration - it's not defined in the .h, because we don't need to;
6252 // it's only used here.
6253 template <class T>
6254 static bool IsPreferenceSanitized_Impl(const T& aPref);
6256 static bool IsPreferenceSanitized(const Pref* const aPref) {
6257 return IsPreferenceSanitized_Impl(*aPref);
6260 static bool IsPreferenceSanitized(const PrefWrapper& aPref) {
6261 return IsPreferenceSanitized_Impl(aPref);
6264 template <class T>
6265 static bool IsPreferenceSanitized_Impl(const T& aPref) {
6266 if (aPref.IsSanitized()) {
6267 MOZ_DIAGNOSTIC_ASSERT(!XRE_IsParentProcess());
6268 MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
6269 return true;
6271 return false;
6274 namespace mozilla {
6276 // This is the only Check Sanitization function exposed outside of
6277 // Preferences.cpp, because this is the only one ever called from
6278 // outside this file.
6279 bool IsPreferenceSanitized(const char* aPrefName) {
6280 // Perform this comparison (see notes above) early to avoid a lookup
6281 // if we can avoid it.
6282 if (strncmp(aPrefName, "$$$", 3) == 0) {
6283 return false;
6286 if (!gContentProcessPrefsAreInited) {
6287 return false;
6290 if (Maybe<PrefWrapper> pref = pref_Lookup(aPrefName)) {
6291 if (pref.isNothing()) {
6292 return true;
6294 return IsPreferenceSanitized(pref.value());
6297 return true;
6300 Atomic<bool, Relaxed> sOmitBlocklistedPrefValues(false);
6301 Atomic<bool, Relaxed> sCrashOnBlocklistedPref(false);
6303 void OnFissionBlocklistPrefChange(const char* aPref, void* aData) {
6304 if (strcmp(aPref, kFissionEnforceBlockList) == 0) {
6305 sCrashOnBlocklistedPref =
6306 StaticPrefs::fission_enforceBlocklistedPrefsInSubprocesses();
6307 } else if (strcmp(aPref, kFissionOmitBlockListValues) == 0) {
6308 sOmitBlocklistedPrefValues =
6309 StaticPrefs::fission_omitBlocklistedPrefsInSubprocesses();
6310 } else {
6311 MOZ_CRASH("Unknown pref passed to callback");
6315 } // namespace mozilla
6317 // This file contains the C wrappers for the C++ static pref getters, as used
6318 // by Rust code.
6319 #include "init/StaticPrefsCGetters.cpp"