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.
13 #include "SharedPrefMap.h"
15 #include "base/basictypes.h"
16 #include "MainThreadUtils.h"
17 #include "mozilla/ArenaAllocatorExtensions.h"
18 #include "mozilla/ArenaAllocator.h"
19 #include "mozilla/ArrayUtils.h"
20 #include "mozilla/Attributes.h"
21 #include "mozilla/Components.h"
22 #include "mozilla/dom/PContent.h"
23 #include "mozilla/HashFunctions.h"
24 #include "mozilla/HashTable.h"
25 #include "mozilla/Logging.h"
26 #include "mozilla/Maybe.h"
27 #include "mozilla/MemoryReporting.h"
28 #include "mozilla/Omnijar.h"
29 #include "mozilla/Preferences.h"
30 #include "mozilla/ProfilerLabels.h"
31 #include "mozilla/ProfilerMarkers.h"
32 #include "mozilla/ResultExtensions.h"
33 #include "mozilla/SchedulerGroup.h"
34 #include "mozilla/ScopeExit.h"
35 #include "mozilla/ServoStyleSet.h"
36 #include "mozilla/SpinEventLoopUntil.h"
37 #include "mozilla/StaticMutex.h"
38 #include "mozilla/StaticPrefsAll.h"
39 #include "mozilla/SyncRunnable.h"
40 #include "mozilla/Telemetry.h"
41 #include "mozilla/UniquePtrExtensions.h"
42 #include "mozilla/URLPreloader.h"
43 #include "mozilla/Variant.h"
44 #include "mozilla/Vector.h"
45 #include "nsAppDirectoryServiceDefs.h"
46 #include "nsCategoryManagerUtils.h"
47 #include "nsClassHashtable.h"
48 #include "nsCOMArray.h"
50 #include "nsComponentManagerUtils.h"
52 #include "nsTHashMap.h"
53 #include "nsDirectoryServiceDefs.h"
54 #include "nsIConsoleService.h"
56 #include "nsIMemoryReporter.h"
57 #include "nsIObserver.h"
58 #include "nsIObserverService.h"
59 #include "nsIOutputStream.h"
60 #include "nsIPrefBranch.h"
61 #include "nsIPrefLocalizedString.h"
62 #include "nsIRelativeFilePref.h"
63 #include "nsISafeOutputStream.h"
64 #include "nsISimpleEnumerator.h"
65 #include "nsIStringBundle.h"
66 #include "nsISupportsImpl.h"
67 #include "nsISupportsPrimitives.h"
68 #include "nsIZipReader.h"
69 #include "nsNetUtil.h"
70 #include "nsPrintfCString.h"
71 #include "nsQuickSort.h"
72 #include "nsReadableUtils.h"
73 #include "nsRefPtrHashtable.h"
74 #include "nsRelativeFilePref.h"
77 #include "nsThreadUtils.h"
78 #include "nsUTF8Utils.h"
79 #include "nsWeakReference.h"
80 #include "nsXPCOMCID.h"
82 #include "nsXULAppAPI.h"
83 #include "nsZipArchive.h"
85 #include "PLDHashTable.h"
88 #include "xpcpublic.h"
89 #ifdef MOZ_BACKGROUNDTASKS
90 # include "mozilla/BackgroundTasks.h"
98 # include "mozmemory.h"
102 # include "windows.h"
105 using namespace mozilla
;
107 using ipc::FileDescriptor
;
111 # define ENSURE_PARENT_PROCESS(func, pref) \
113 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
114 nsPrintfCString msg( \
115 "ENSURE_PARENT_PROCESS: called %s on %s in a non-parent process", \
117 NS_ERROR(msg.get()); \
118 return NS_ERROR_NOT_AVAILABLE; \
124 # define ENSURE_PARENT_PROCESS(func, pref) \
125 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
126 return NS_ERROR_NOT_AVAILABLE; \
131 //===========================================================================
132 // Low-level types and operations
133 //===========================================================================
135 typedef nsTArray
<nsCString
> PrefSaveData
;
137 // 1 MB should be enough for everyone.
138 static const uint32_t MAX_PREF_LENGTH
= 1 * 1024 * 1024;
139 // Actually, 4kb should be enough for everyone.
140 static const uint32_t MAX_ADVISABLE_PREF_LENGTH
= 4 * 1024;
142 // This is used for pref names and string pref values. We encode the string
143 // length, then a '/', then the string chars. This encoding means there are no
144 // special chars that are forbidden or require escaping.
145 static void SerializeAndAppendString(const nsCString
& aChars
, nsCString
& aStr
) {
146 aStr
.AppendInt(aChars
.Length());
151 static char* DeserializeString(char* aChars
, nsCString
& aStr
) {
153 uint32_t length
= strtol(p
, &p
, 10);
154 MOZ_ASSERT(p
[0] == '/');
155 p
++; // move past the '/'
156 aStr
.Assign(p
, length
);
157 p
+= length
; // move past the string itself
161 // Keep this in sync with PrefValue in parser/src/lib.rs.
163 // PrefValues within Pref objects own their chars. PrefValues passed around
164 // as arguments don't own their chars.
165 const char* mStringVal
;
169 PrefValue() = default;
171 explicit PrefValue(bool aVal
) : mBoolVal(aVal
) {}
173 explicit PrefValue(int32_t aVal
) : mIntVal(aVal
) {}
175 explicit PrefValue(const char* aVal
) : mStringVal(aVal
) {}
177 bool Equals(PrefType aType
, PrefValue aValue
) {
179 case PrefType::String
: {
180 if (mStringVal
&& aValue
.mStringVal
) {
181 return strcmp(mStringVal
, aValue
.mStringVal
) == 0;
183 if (!mStringVal
&& !aValue
.mStringVal
) {
190 return mIntVal
== aValue
.mIntVal
;
193 return mBoolVal
== aValue
.mBoolVal
;
196 MOZ_CRASH("Unhandled enum value");
200 template <typename T
>
203 void Init(PrefType aNewType
, PrefValue aNewValue
) {
204 if (aNewType
== PrefType::String
) {
205 MOZ_ASSERT(aNewValue
.mStringVal
);
206 aNewValue
.mStringVal
= moz_xstrdup(aNewValue
.mStringVal
);
211 void Clear(PrefType aType
) {
212 if (aType
== PrefType::String
) {
213 free(const_cast<char*>(mStringVal
));
216 // Zero the entire value (regardless of type) via mStringVal.
217 mStringVal
= nullptr;
220 void Replace(bool aHasValue
, PrefType aOldType
, PrefType aNewType
,
221 PrefValue aNewValue
) {
225 Init(aNewType
, aNewValue
);
228 void ToDomPrefValue(PrefType aType
, dom::PrefValue
* aDomValue
) {
230 case PrefType::String
:
231 *aDomValue
= nsDependentCString(mStringVal
);
235 *aDomValue
= mIntVal
;
239 *aDomValue
= mBoolVal
;
247 PrefType
FromDomPrefValue(const dom::PrefValue
& aDomValue
) {
248 switch (aDomValue
.type()) {
249 case dom::PrefValue::TnsCString
:
250 mStringVal
= aDomValue
.get_nsCString().get();
251 return PrefType::String
;
253 case dom::PrefValue::Tint32_t
:
254 mIntVal
= aDomValue
.get_int32_t();
255 return PrefType::Int
;
257 case dom::PrefValue::Tbool
:
258 mBoolVal
= aDomValue
.get_bool();
259 return PrefType::Bool
;
266 void SerializeAndAppend(PrefType aType
, nsCString
& aStr
) {
269 aStr
.Append(mBoolVal
? 'T' : 'F');
273 aStr
.AppendInt(mIntVal
);
276 case PrefType::String
: {
277 SerializeAndAppendString(nsDependentCString(mStringVal
), aStr
);
287 static char* Deserialize(PrefType aType
, char* aStr
,
288 Maybe
<dom::PrefValue
>* aDomValue
) {
294 *aDomValue
= Some(true);
295 } else if (*p
== 'F') {
296 *aDomValue
= Some(false);
298 *aDomValue
= Some(false);
299 NS_ERROR("bad bool pref value");
304 case PrefType::Int
: {
305 *aDomValue
= Some(int32_t(strtol(p
, &p
, 10)));
309 case PrefType::String
: {
311 p
= DeserializeString(p
, str
);
312 *aDomValue
= Some(str
);
323 bool PrefValue::Get() const {
328 int32_t PrefValue::Get() const {
333 nsDependentCString
PrefValue::Get() const {
334 return nsDependentCString(mStringVal
);
338 const char* PrefTypeToString(PrefType aType
) {
342 case PrefType::String
:
349 MOZ_CRASH("Unhandled enum value");
354 // Assign to aResult a quoted, escaped copy of aOriginal.
355 static void StrEscape(const char* aOriginal
, nsCString
& aResult
) {
356 if (aOriginal
== nullptr) {
357 aResult
.AssignLiteral("\"\"");
361 // JavaScript does not allow quotes, slashes, or line terminators inside
362 // strings so we must escape them. ECMAScript defines four line terminators,
363 // but we're only worrying about \r and \n here. We currently feed our pref
364 // script to the JS interpreter as Latin-1 so we won't encounter \u2028
365 // (line separator) or \u2029 (paragraph separator).
367 // WARNING: There are hints that we may be moving to storing prefs as utf8.
368 // If we ever feed them to the JS compiler as UTF8 then we'll have to worry
369 // about the multibyte sequences that would be interpreted as \u2028 and
375 // Paranoid worst case all slashes will free quickly.
376 for (p
= aOriginal
; *p
; ++p
) {
379 aResult
.AppendLiteral("\\n");
383 aResult
.AppendLiteral("\\r");
387 aResult
.AppendLiteral("\\\\");
391 aResult
.AppendLiteral("\\\"");
411 mCallbacksObjects(0),
412 mCallbacksDomains(0),
417 size_t mStringValues
;
418 size_t mRootBranches
;
419 size_t mPrefNameArena
;
420 size_t mCallbacksObjects
;
421 size_t mCallbacksDomains
;
424 } // namespace mozilla
426 static StaticRefPtr
<SharedPrefMap
> gSharedMap
;
428 // Arena for Pref names. Inside a function so we can assert it's only accessed
429 // on the main thread.
430 static inline ArenaAllocator
<4096, 1>& PrefNameArena() {
431 MOZ_ASSERT(NS_IsMainThread());
433 static ArenaAllocator
<4096, 1> sPrefNameArena
;
434 return sPrefNameArena
;
441 explicit Pref(const nsACString
& aName
)
442 : mName(ArenaStrdup(aName
, PrefNameArena()), aName
.Length()),
443 mType(static_cast<uint32_t>(PrefType::None
)),
446 mHasDefaultValue(false),
447 mHasUserValue(false),
448 mIsSkippedByIteration(false),
453 // There's no need to free mName because it's allocated in memory owned by
456 mDefaultValue
.Clear(Type());
457 mUserValue
.Clear(Type());
460 const char* Name() const { return mName
.get(); }
461 const nsDependentCString
& NameString() const { return mName
; }
465 PrefType
Type() const { return static_cast<PrefType
>(mType
); }
466 void SetType(PrefType aType
) { mType
= static_cast<uint32_t>(aType
); }
468 bool IsType(PrefType aType
) const { return Type() == aType
; }
469 bool IsTypeNone() const { return IsType(PrefType::None
); }
470 bool IsTypeString() const { return IsType(PrefType::String
); }
471 bool IsTypeInt() const { return IsType(PrefType::Int
); }
472 bool IsTypeBool() const { return IsType(PrefType::Bool
); }
476 bool IsLocked() const { return mIsLocked
; }
477 void SetIsLocked(bool aValue
) { mIsLocked
= aValue
; }
478 bool IsSkippedByIteration() const { return mIsSkippedByIteration
; }
479 void SetIsSkippedByIteration(bool aValue
) { mIsSkippedByIteration
= aValue
; }
481 bool IsSticky() const { return mIsSticky
; }
483 bool HasDefaultValue() const { return mHasDefaultValue
; }
484 bool HasUserValue() const { return mHasUserValue
; }
486 template <typename T
>
487 void AddToMap(SharedPrefMapBuilder
& aMap
) {
488 aMap
.Add(NameString(),
489 {HasDefaultValue(), HasUserValue(), IsSticky(), IsLocked(),
490 IsSkippedByIteration()},
491 HasDefaultValue() ? mDefaultValue
.Get
<T
>() : T(),
492 HasUserValue() ? mUserValue
.Get
<T
>() : T());
495 void AddToMap(SharedPrefMapBuilder
& aMap
) {
497 AddToMap
<bool>(aMap
);
498 } else if (IsTypeInt()) {
499 AddToMap
<int32_t>(aMap
);
500 } else if (IsTypeString()) {
501 AddToMap
<nsDependentCString
>(aMap
);
503 MOZ_ASSERT_UNREACHABLE("Unexpected preference type");
509 bool GetBoolValue(PrefValueKind aKind
= PrefValueKind::User
) const {
510 MOZ_ASSERT(IsTypeBool());
511 MOZ_ASSERT(aKind
== PrefValueKind::Default
? HasDefaultValue()
514 return aKind
== PrefValueKind::Default
? mDefaultValue
.mBoolVal
515 : mUserValue
.mBoolVal
;
518 int32_t GetIntValue(PrefValueKind aKind
= PrefValueKind::User
) const {
519 MOZ_ASSERT(IsTypeInt());
520 MOZ_ASSERT(aKind
== PrefValueKind::Default
? HasDefaultValue()
523 return aKind
== PrefValueKind::Default
? mDefaultValue
.mIntVal
524 : mUserValue
.mIntVal
;
527 const char* GetBareStringValue(
528 PrefValueKind aKind
= PrefValueKind::User
) const {
529 MOZ_ASSERT(IsTypeString());
530 MOZ_ASSERT(aKind
== PrefValueKind::Default
? HasDefaultValue()
533 return aKind
== PrefValueKind::Default
? mDefaultValue
.mStringVal
534 : mUserValue
.mStringVal
;
537 nsDependentCString
GetStringValue(
538 PrefValueKind aKind
= PrefValueKind::User
) const {
539 return nsDependentCString(GetBareStringValue(aKind
));
542 void ToDomPref(dom::Pref
* aDomPref
) {
543 MOZ_ASSERT(XRE_IsParentProcess());
545 aDomPref
->name() = mName
;
547 aDomPref
->isLocked() = mIsLocked
;
549 if (mHasDefaultValue
) {
550 aDomPref
->defaultValue() = Some(dom::PrefValue());
551 mDefaultValue
.ToDomPrefValue(Type(), &aDomPref
->defaultValue().ref());
553 aDomPref
->defaultValue() = Nothing();
557 aDomPref
->userValue() = Some(dom::PrefValue());
558 mUserValue
.ToDomPrefValue(Type(), &aDomPref
->userValue().ref());
560 aDomPref
->userValue() = Nothing();
563 MOZ_ASSERT(aDomPref
->defaultValue().isNothing() ||
564 aDomPref
->userValue().isNothing() ||
565 (aDomPref
->defaultValue().ref().type() ==
566 aDomPref
->userValue().ref().type()));
569 void FromDomPref(const dom::Pref
& aDomPref
, bool* aValueChanged
) {
570 MOZ_ASSERT(!XRE_IsParentProcess());
571 MOZ_ASSERT(mName
== aDomPref
.name());
573 mIsLocked
= aDomPref
.isLocked();
575 const Maybe
<dom::PrefValue
>& defaultValue
= aDomPref
.defaultValue();
576 bool defaultValueChanged
= false;
577 if (defaultValue
.isSome()) {
579 PrefType type
= value
.FromDomPrefValue(defaultValue
.ref());
580 if (!ValueMatches(PrefValueKind::Default
, type
, value
)) {
581 // Type() is PrefType::None if it's a newly added pref. This is ok.
582 mDefaultValue
.Replace(mHasDefaultValue
, Type(), type
, value
);
584 mHasDefaultValue
= true;
585 defaultValueChanged
= true;
588 // Note: we never clear a default value.
590 const Maybe
<dom::PrefValue
>& userValue
= aDomPref
.userValue();
591 bool userValueChanged
= false;
592 if (userValue
.isSome()) {
594 PrefType type
= value
.FromDomPrefValue(userValue
.ref());
595 if (!ValueMatches(PrefValueKind::User
, type
, value
)) {
596 // Type() is PrefType::None if it's a newly added pref. This is ok.
597 mUserValue
.Replace(mHasUserValue
, Type(), type
, value
);
599 mHasUserValue
= true;
600 userValueChanged
= true;
602 } else if (mHasUserValue
) {
604 userValueChanged
= true;
607 if (userValueChanged
|| (defaultValueChanged
&& !mHasUserValue
)) {
608 *aValueChanged
= true;
612 void FromWrapper(PrefWrapper
& aWrapper
);
614 bool HasAdvisablySizedValues() {
615 MOZ_ASSERT(XRE_IsParentProcess());
617 if (!IsTypeString()) {
621 if (mHasDefaultValue
&&
622 strlen(mDefaultValue
.mStringVal
) > MAX_ADVISABLE_PREF_LENGTH
) {
627 strlen(mUserValue
.mStringVal
) > MAX_ADVISABLE_PREF_LENGTH
) {
635 bool ValueMatches(PrefValueKind aKind
, PrefType aType
, PrefValue aValue
) {
636 return IsType(aType
) &&
637 (aKind
== PrefValueKind::Default
638 ? mHasDefaultValue
&& mDefaultValue
.Equals(aType
, aValue
)
639 : mHasUserValue
&& mUserValue
.Equals(aType
, aValue
));
643 void ClearUserValue() {
644 mUserValue
.Clear(Type());
645 mHasUserValue
= false;
648 nsresult
SetDefaultValue(PrefType aType
, PrefValue aValue
, bool aIsSticky
,
649 bool aIsLocked
, bool* aValueChanged
) {
650 // Types must always match when setting the default value.
651 if (!IsType(aType
)) {
652 return NS_ERROR_UNEXPECTED
;
655 // Should we set the default value? Only if the pref is not locked, and
656 // doing so would change the default value.
661 if (!ValueMatches(PrefValueKind::Default
, aType
, aValue
)) {
662 mDefaultValue
.Replace(mHasDefaultValue
, Type(), aType
, aValue
);
663 mHasDefaultValue
= true;
667 if (!mHasUserValue
) {
668 *aValueChanged
= true;
670 // What if we change the default to be the same as the user value?
671 // Should we clear the user value? Currently we don't.
677 nsresult
SetUserValue(PrefType aType
, PrefValue aValue
, bool aFromInit
,
678 bool* aValueChanged
) {
679 // If we have a default value, types must match when setting the user
681 if (mHasDefaultValue
&& !IsType(aType
)) {
682 return NS_ERROR_UNEXPECTED
;
685 // Should we clear the user value, if present? Only if the new user value
686 // matches the default value, and the pref isn't sticky, and we aren't
687 // force-setting it during initialization.
688 if (ValueMatches(PrefValueKind::Default
, aType
, aValue
) && !mIsSticky
&&
693 *aValueChanged
= true;
697 // Otherwise, should we set the user value? Only if doing so would
698 // change the user value.
699 } else if (!ValueMatches(PrefValueKind::User
, aType
, aValue
)) {
700 mUserValue
.Replace(mHasUserValue
, Type(), aType
, aValue
);
701 SetType(aType
); // needed because we may have changed the type
702 mHasUserValue
= true;
704 *aValueChanged
= true;
710 // Prefs are serialized in a manner that mirrors dom::Pref. The two should be
711 // kept in sync. E.g. if something is added to one it should also be added to
712 // the other. (It would be nice to be able to use the code generated from
713 // IPDL for serializing dom::Pref here instead of writing by hand this
714 // serialization/deserialization. Unfortunately, that generated code is
715 // difficult to use directly, outside of the IPDL IPC code.)
717 // The grammar for the serialized prefs has the following form.
719 // <pref> = <type> <locked> ':' <name> ':' <value>? ':' <value>? '\n'
720 // <type> = 'B' | 'I' | 'S'
721 // <locked> = 'L' | '-'
722 // <name> = <string-value>
723 // <value> = <bool-value> | <int-value> | <string-value>
724 // <bool-value> = 'T' | 'F'
725 // <int-value> = an integer literal accepted by strtol()
726 // <string-value> = <int-value> '/' <chars>
727 // <chars> = any char sequence of length dictated by the preceding
730 // No whitespace is tolerated between tokens. <type> must match the types of
733 // The serialization is text-based, rather than binary, for the following
736 // - The size difference wouldn't be much different between text-based and
737 // binary. Most of the space is for strings (pref names and string pref
738 // values), which would be the same in both styles. And other differences
739 // would be minimal, e.g. small integers are shorter in text but long
740 // integers are longer in text.
742 // - Likewise, speed differences should be negligible.
744 // - It's much easier to debug a text-based serialization. E.g. you can
745 // print it and inspect it easily in a debugger.
747 // Examples of unlocked boolean prefs:
748 // - "B-:8/my.bool1:F:T\n"
749 // - "B-:8/my.bool2:F:\n"
750 // - "B-:8/my.bool3::T\n"
752 // Examples of locked integer prefs:
753 // - "IL:7/my.int1:0:1\n"
754 // - "IL:7/my.int2:123:\n"
755 // - "IL:7/my.int3::-99\n"
757 // Examples of unlocked string prefs:
758 // - "S-:10/my.string1:3/abc:4/wxyz\n"
759 // - "S-:10/my.string2:5/1.234:\n"
760 // - "S-:10/my.string3::7/string!\n"
762 void SerializeAndAppend(nsCString
& aStr
) {
772 case PrefType::String
: {
782 aStr
.Append(mIsLocked
? 'L' : '-');
785 SerializeAndAppendString(mName
, aStr
);
788 if (mHasDefaultValue
) {
789 mDefaultValue
.SerializeAndAppend(Type(), aStr
);
794 mUserValue
.SerializeAndAppend(Type(), aStr
);
799 static char* Deserialize(char* aStr
, dom::Pref
* aDomPref
) {
805 type
= PrefType::Bool
;
806 } else if (*p
== 'I') {
807 type
= PrefType::Int
;
808 } else if (*p
== 'S') {
809 type
= PrefType::String
;
811 NS_ERROR("bad pref type");
812 type
= PrefType::None
;
814 p
++; // move past the type char
820 } else if (*p
== '-') {
823 NS_ERROR("bad pref locked status");
826 p
++; // move past the isLocked char
828 MOZ_ASSERT(*p
== ':');
829 p
++; // move past the ':'
833 p
= DeserializeString(p
, name
);
835 MOZ_ASSERT(*p
== ':');
836 p
++; // move past the ':' preceding the default value
838 Maybe
<dom::PrefValue
> maybeDefaultValue
;
840 dom::PrefValue defaultValue
;
841 p
= PrefValue::Deserialize(type
, p
, &maybeDefaultValue
);
844 MOZ_ASSERT(*p
== ':');
845 p
++; // move past the ':' between the default and user values
847 Maybe
<dom::PrefValue
> maybeUserValue
;
849 dom::PrefValue userValue
;
850 p
= PrefValue::Deserialize(type
, p
, &maybeUserValue
);
853 MOZ_ASSERT(*p
== '\n');
854 p
++; // move past the '\n' following the user value
856 *aDomPref
= dom::Pref(name
, isLocked
, maybeDefaultValue
, maybeUserValue
);
861 void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf
, PrefsSizes
& aSizes
) {
862 // Note: mName is allocated in sPrefNameArena, measured elsewhere.
863 aSizes
.mPrefValues
+= aMallocSizeOf(this);
864 if (IsTypeString()) {
865 if (mHasDefaultValue
) {
866 aSizes
.mStringValues
+= aMallocSizeOf(mDefaultValue
.mStringVal
);
869 aSizes
.mStringValues
+= aMallocSizeOf(mUserValue
.mStringVal
);
875 const nsDependentCString mName
; // allocated in sPrefNameArena
878 uint32_t mIsSticky
: 1;
879 uint32_t mIsLocked
: 1;
880 uint32_t mHasDefaultValue
: 1;
881 uint32_t mHasUserValue
: 1;
882 uint32_t mIsSkippedByIteration
: 1;
884 PrefValue mDefaultValue
;
885 PrefValue mUserValue
;
889 using Key
= UniquePtr
<Pref
>;
890 using Lookup
= const char*;
892 static HashNumber
hash(const Lookup aLookup
) { return HashString(aLookup
); }
894 static bool match(const Key
& aKey
, const Lookup aLookup
) {
895 if (!aLookup
|| !aKey
->Name()) {
899 return strcmp(aLookup
, aKey
->Name()) == 0;
903 using PrefWrapperBase
= Variant
<Pref
*, SharedPrefMap::Pref
>;
904 class MOZ_STACK_CLASS PrefWrapper
: public PrefWrapperBase
{
905 using SharedPref
= const SharedPrefMap::Pref
;
908 MOZ_IMPLICIT
PrefWrapper(Pref
* aPref
) : PrefWrapperBase(AsVariant(aPref
)) {}
910 MOZ_IMPLICIT
PrefWrapper(const SharedPrefMap::Pref
& aPref
)
911 : PrefWrapperBase(AsVariant(aPref
)) {}
915 bool IsType(PrefType aType
) const { return Type() == aType
; }
916 bool IsTypeNone() const { return IsType(PrefType::None
); }
917 bool IsTypeString() const { return IsType(PrefType::String
); }
918 bool IsTypeInt() const { return IsType(PrefType::Int
); }
919 bool IsTypeBool() const { return IsType(PrefType::Bool
); }
921 #define FORWARD(retType, method) \
922 retType method() const { \
924 retType operator()(const Pref* aPref) { return aPref->method(); } \
925 retType operator()(SharedPref& aPref) { return aPref.method(); } \
927 return match(Matcher()); \
930 FORWARD(bool, IsLocked
)
931 FORWARD(bool, IsSticky
)
932 FORWARD(bool, HasDefaultValue
)
933 FORWARD(bool, HasUserValue
)
934 FORWARD(const char*, Name
)
935 FORWARD(nsCString
, NameString
)
936 FORWARD(PrefType
, Type
)
939 #define FORWARD(retType, method) \
940 retType method(PrefValueKind aKind = PrefValueKind::User) const { \
942 PrefValueKind mKind; \
944 retType operator()(const Pref* aPref) { return aPref->method(mKind); } \
945 retType operator()(SharedPref& aPref) { return aPref.method(mKind); } \
947 return match(Matcher{aKind}); \
950 FORWARD(bool, GetBoolValue
)
951 FORWARD(int32_t, GetIntValue
)
952 FORWARD(nsCString
, GetStringValue
)
953 FORWARD(const char*, GetBareStringValue
)
956 PrefValue
GetValue(PrefValueKind aKind
= PrefValueKind::User
) const {
959 return PrefValue
{GetBoolValue(aKind
)};
961 return PrefValue
{GetIntValue(aKind
)};
962 case PrefType::String
:
963 return PrefValue
{GetBareStringValue(aKind
)};
965 MOZ_ASSERT_UNREACHABLE("Unexpected pref type");
970 Result
<PrefValueKind
, nsresult
> WantValueKind(PrefType aType
,
971 PrefValueKind aKind
) const {
972 if (Type() != aType
) {
973 return Err(NS_ERROR_UNEXPECTED
);
976 if (aKind
== PrefValueKind::Default
|| IsLocked() || !HasUserValue()) {
977 if (!HasDefaultValue()) {
978 return Err(NS_ERROR_UNEXPECTED
);
980 return PrefValueKind::Default
;
982 return PrefValueKind::User
;
985 nsresult
GetValue(PrefValueKind aKind
, bool* aResult
) const {
987 MOZ_TRY_VAR(kind
, WantValueKind(PrefType::Bool
, aKind
));
989 *aResult
= GetBoolValue(kind
);
993 nsresult
GetValue(PrefValueKind aKind
, int32_t* aResult
) const {
995 MOZ_TRY_VAR(kind
, WantValueKind(PrefType::Int
, aKind
));
997 *aResult
= GetIntValue(kind
);
1001 nsresult
GetValue(PrefValueKind aKind
, uint32_t* aResult
) const {
1002 return GetValue(aKind
, reinterpret_cast<int32_t*>(aResult
));
1005 nsresult
GetValue(PrefValueKind aKind
, float* aResult
) const {
1006 nsAutoCString result
;
1007 nsresult rv
= GetValue(aKind
, result
);
1008 if (NS_SUCCEEDED(rv
)) {
1009 // ToFloat() does a locale-independent conversion.
1010 *aResult
= result
.ToFloat(&rv
);
1015 nsresult
GetValue(PrefValueKind aKind
, nsACString
& aResult
) const {
1017 MOZ_TRY_VAR(kind
, WantValueKind(PrefType::String
, aKind
));
1019 aResult
= GetStringValue(kind
);
1023 // Returns false if this pref doesn't have a user value worth saving.
1024 bool UserValueToStringForSaving(nsCString
& aStr
) {
1025 // Should we save the user value, if present? Only if it does not match the
1026 // default value, or it is sticky.
1027 if (HasUserValue() &&
1028 (!ValueMatches(PrefValueKind::Default
, Type(), GetValue()) ||
1030 if (IsTypeString()) {
1031 StrEscape(GetStringValue().get(), aStr
);
1033 } else if (IsTypeInt()) {
1034 aStr
.AppendInt(GetIntValue());
1036 } else if (IsTypeBool()) {
1037 aStr
= GetBoolValue() ? "true" : "false";
1042 // Do not save default prefs that haven't changed.
1046 bool Matches(PrefType aType
, PrefValueKind aKind
, PrefValue
& aValue
,
1047 bool aIsSticky
, bool aIsLocked
) const {
1048 return (ValueMatches(aKind
, aType
, aValue
) && aIsSticky
== IsSticky() &&
1049 aIsLocked
== IsLocked());
1052 bool ValueMatches(PrefValueKind aKind
, PrefType aType
,
1053 const PrefValue
& aValue
) const {
1054 if (!IsType(aType
)) {
1057 if (!(aKind
== PrefValueKind::Default
? HasDefaultValue()
1058 : HasUserValue())) {
1062 case PrefType::Bool
:
1063 return GetBoolValue(aKind
) == aValue
.mBoolVal
;
1065 return GetIntValue(aKind
) == aValue
.mIntVal
;
1066 case PrefType::String
:
1067 return strcmp(GetBareStringValue(aKind
), aValue
.mStringVal
) == 0;
1069 MOZ_ASSERT_UNREACHABLE("Unexpected preference type");
1075 void Pref::FromWrapper(PrefWrapper
& aWrapper
) {
1076 MOZ_ASSERT(aWrapper
.is
<SharedPrefMap::Pref
>());
1077 auto pref
= aWrapper
.as
<SharedPrefMap::Pref
>();
1079 MOZ_ASSERT(IsTypeNone());
1080 MOZ_ASSERT(mName
== pref
.NameString());
1082 mType
= uint32_t(pref
.Type());
1084 mIsLocked
= pref
.IsLocked();
1085 mIsSticky
= pref
.IsSticky();
1087 mHasDefaultValue
= pref
.HasDefaultValue();
1088 mHasUserValue
= pref
.HasUserValue();
1090 if (mHasDefaultValue
) {
1091 mDefaultValue
.Init(Type(), aWrapper
.GetValue(PrefValueKind::Default
));
1093 if (mHasUserValue
) {
1094 mUserValue
.Init(Type(), aWrapper
.GetValue(PrefValueKind::User
));
1098 class CallbackNode
{
1100 CallbackNode(const nsACString
& aDomain
, PrefChangedFunc aFunc
, void* aData
,
1101 Preferences::MatchKind aMatchKind
)
1102 : mDomain(AsVariant(nsCString(aDomain
))),
1105 mNextAndMatchKind(aMatchKind
) {}
1107 CallbackNode(const char** aDomains
, PrefChangedFunc aFunc
, void* aData
,
1108 Preferences::MatchKind aMatchKind
)
1109 : mDomain(AsVariant(aDomains
)),
1112 mNextAndMatchKind(aMatchKind
) {}
1114 // mDomain is a UniquePtr<>, so any uses of Domain() should only be temporary
1116 const Variant
<nsCString
, const char**>& Domain() const { return mDomain
; }
1118 PrefChangedFunc
Func() const { return mFunc
; }
1119 void ClearFunc() { mFunc
= nullptr; }
1121 void* Data() const { return mData
; }
1123 Preferences::MatchKind
MatchKind() const {
1124 return static_cast<Preferences::MatchKind
>(mNextAndMatchKind
&
1128 bool DomainIs(const nsACString
& aDomain
) const {
1129 return mDomain
.is
<nsCString
>() && mDomain
.as
<nsCString
>() == aDomain
;
1132 bool DomainIs(const char** aPrefs
) const {
1133 return mDomain
== AsVariant(aPrefs
);
1136 bool Matches(const nsACString
& aPrefName
) const {
1137 auto match
= [&](const nsACString
& aStr
) {
1138 return MatchKind() == Preferences::ExactMatch
1140 : StringBeginsWith(aPrefName
, aStr
);
1143 if (mDomain
.is
<nsCString
>()) {
1144 return match(mDomain
.as
<nsCString
>());
1146 for (const char** ptr
= mDomain
.as
<const char**>(); *ptr
; ptr
++) {
1147 if (match(nsDependentCString(*ptr
))) {
1154 CallbackNode
* Next() const {
1155 return reinterpret_cast<CallbackNode
*>(mNextAndMatchKind
& kNextMask
);
1158 void SetNext(CallbackNode
* aNext
) {
1159 uintptr_t matchKind
= mNextAndMatchKind
& kMatchKindMask
;
1160 mNextAndMatchKind
= reinterpret_cast<uintptr_t>(aNext
);
1161 MOZ_ASSERT((mNextAndMatchKind
& kMatchKindMask
) == 0);
1162 mNextAndMatchKind
|= matchKind
;
1165 void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf
, PrefsSizes
& aSizes
) {
1166 aSizes
.mCallbacksObjects
+= aMallocSizeOf(this);
1167 if (mDomain
.is
<nsCString
>()) {
1168 aSizes
.mCallbacksDomains
+=
1169 mDomain
.as
<nsCString
>().SizeOfExcludingThisIfUnshared(aMallocSizeOf
);
1174 static const uintptr_t kMatchKindMask
= uintptr_t(0x1);
1175 static const uintptr_t kNextMask
= ~kMatchKindMask
;
1177 Variant
<nsCString
, const char**> mDomain
;
1179 // If someone attempts to remove the node from the callback list while
1180 // NotifyCallbacks() is running, |func| is set to nullptr. Such nodes will
1181 // be removed at the end of NotifyCallbacks().
1182 PrefChangedFunc mFunc
;
1185 // Conceptually this is two fields:
1186 // - CallbackNode* mNext;
1187 // - Preferences::MatchKind mMatchKind;
1188 // They are combined into a tagged pointer to save memory.
1189 uintptr_t mNextAndMatchKind
;
1192 using PrefsHashTable
= HashSet
<UniquePtr
<Pref
>, PrefHasher
>;
1194 // The main prefs hash table. Inside a function so we can assert it's only
1195 // accessed on the main thread. (That assertion can be avoided but only do so
1196 // with great care!)
1197 static inline PrefsHashTable
*& HashTable(bool aOffMainThread
= false) {
1198 MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
1199 static PrefsHashTable
* sHashTable
= nullptr;
1204 // This defines the type used to store our `once` mirrors checker. We can't use
1205 // HashMap for now due to alignment restrictions when dealing with
1206 // std::function<void()> (see bug 1557617).
1207 typedef std::function
<void()> AntiFootgunCallback
;
1209 bool operator()(char const* a
, char const* b
) const {
1210 return std::strcmp(a
, b
) < 0;
1213 typedef std::map
<const char*, AntiFootgunCallback
, CompareStr
> AntiFootgunMap
;
1214 static AntiFootgunMap
* gOnceStaticPrefsAntiFootgun
;
1217 // The callback list contains all the priority callbacks followed by the
1218 // non-priority callbacks. gLastPriorityNode records where the first part ends.
1219 static CallbackNode
* gFirstCallback
= nullptr;
1220 static CallbackNode
* gLastPriorityNode
= nullptr;
1223 # define ACCESS_COUNTS
1226 #ifdef ACCESS_COUNTS
1227 using AccessCountsHashTable
= nsTHashMap
<nsCStringHashKey
, uint32_t>;
1228 static AccessCountsHashTable
* gAccessCounts
= nullptr;
1230 static void AddAccessCount(const nsACString
& aPrefName
) {
1231 // FIXME: Servo reads preferences from background threads in unsafe ways (bug
1232 // 1474789), and triggers assertions here if we try to add usage count entries
1233 // from background threads.
1234 if (NS_IsMainThread()) {
1235 uint32_t& count
= gAccessCounts
->LookupOrInsert(aPrefName
);
1240 static void AddAccessCount(const char* aPrefName
) {
1241 AddAccessCount(nsDependentCString(aPrefName
));
1244 static void MOZ_MAYBE_UNUSED
AddAccessCount(const nsACString
& aPrefName
) {}
1246 static void AddAccessCount(const char* aPrefName
) {}
1249 // These are only used during the call to NotifyCallbacks().
1250 static bool gCallbacksInProgress
= false;
1251 static bool gShouldCleanupDeadNodes
= false;
1253 class PrefsHashIter
{
1254 using Iterator
= decltype(HashTable()->modIter());
1255 using ElemType
= Pref
*;
1260 explicit PrefsHashIter(PrefsHashTable
* aTable
) : mIter(aTable
->modIter()) {}
1263 friend class PrefsHashIter
;
1265 PrefsHashIter
& mParent
;
1268 Elem(PrefsHashIter
& aIter
, bool aDone
) : mParent(aIter
), mDone(aDone
) {}
1270 Iterator
& Iter() { return mParent
.mIter
; }
1273 Elem
& operator*() { return *this; }
1279 return Iter().get().get();
1281 ElemType
get() const { return const_cast<Elem
*>(this)->get(); }
1283 ElemType
operator->() { return get(); }
1284 ElemType
operator->() const { return get(); }
1286 operator ElemType() { return get(); }
1288 void Remove() { Iter().remove(); }
1290 Elem
& operator++() {
1293 mDone
= Iter().done();
1297 bool operator!=(Elem
& other
) {
1298 return mDone
!= other
.mDone
|| this->get() != other
.get();
1302 Elem
begin() { return Elem(*this, mIter
.done()); }
1304 Elem
end() { return Elem(*this, true); }
1308 using Iterator
= decltype(HashTable()->iter());
1309 using ElemType
= PrefWrapper
;
1311 using HashElem
= PrefsHashIter::Elem
;
1312 using SharedElem
= SharedPrefMap::Pref
;
1314 using ElemTypeVariant
= Variant
<HashElem
, SharedElem
>;
1316 SharedPrefMap
* mSharedMap
;
1317 PrefsHashTable
* mHashTable
;
1318 PrefsHashIter mIter
;
1320 ElemTypeVariant mPos
;
1321 ElemTypeVariant mEnd
;
1323 Maybe
<PrefWrapper
> mEntry
;
1326 PrefsIter(PrefsHashTable
* aHashTable
, SharedPrefMap
* aSharedMap
)
1327 : mSharedMap(aSharedMap
),
1328 mHashTable(aHashTable
),
1330 mPos(AsVariant(mIter
.begin())),
1331 mEnd(AsVariant(mIter
.end())) {
1338 #define MATCH(type, ...) \
1342 type operator()(HashElem& pos) { \
1343 HashElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<HashElem>(); \
1346 type operator()(SharedElem& pos) { \
1347 SharedElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<SharedElem>(); \
1351 return mPos.match(Matcher{*this}); \
1354 bool Done() { MATCH(bool, return pos
== end
); }
1356 PrefWrapper
MakeEntry() { MATCH(PrefWrapper
, return PrefWrapper(pos
)); }
1366 return !Done() || NextIterator();
1369 bool NextIterator() {
1370 if (mPos
.is
<HashElem
>() && mSharedMap
) {
1371 mPos
= AsVariant(mSharedMap
->begin());
1372 mEnd
= AsVariant(mSharedMap
->end());
1378 bool IteratingBase() { return mPos
.is
<SharedElem
>(); }
1380 PrefWrapper
& Entry() {
1381 MOZ_ASSERT(!Done());
1383 if (!mEntry
.isSome()) {
1384 mEntry
.emplace(MakeEntry());
1386 return mEntry
.ref();
1391 friend class PrefsIter
;
1396 Elem(PrefsIter
& aIter
, bool aDone
) : mParent(aIter
), mDone(aDone
) {
1400 void Next() { mDone
= !mParent
.Next(); }
1402 void SkipDuplicates() {
1404 (mParent
.IteratingBase() ? mParent
.mHashTable
->has(ref().Name())
1405 : ref().IsTypeNone())) {
1411 Elem
& operator*() { return *this; }
1413 ElemType
& ref() { return mParent
.Entry(); }
1414 const ElemType
& ref() const { return const_cast<Elem
*>(this)->ref(); }
1416 ElemType
* operator->() { return &ref(); }
1417 const ElemType
* operator->() const { return &ref(); }
1419 operator ElemType() { return ref(); }
1421 Elem
& operator++() {
1428 bool operator!=(Elem
& other
) {
1429 if (mDone
!= other
.mDone
) {
1435 return &this->ref() != &other
.ref();
1439 Elem
begin() { return {*this, Done()}; }
1441 Elem
end() { return {*this, true}; }
1444 static Pref
* pref_HashTableLookup(const char* aPrefName
);
1446 static void NotifyCallbacks(const nsCString
& aPrefName
,
1447 const PrefWrapper
* aPref
= nullptr);
1449 static void NotifyCallbacks(const nsCString
& aPrefName
,
1450 const PrefWrapper
& aPref
) {
1451 NotifyCallbacks(aPrefName
, &aPref
);
1454 // The approximate number of preferences in the dynamic hashtable for the parent
1455 // and content processes, respectively. These numbers are used to determine the
1456 // initial size of the dynamic preference hashtables, and should be chosen to
1457 // avoid rehashing during normal usage. The actual number of preferences will,
1458 // or course, change over time, but these numbers only need to be within a
1459 // binary order of magnitude of the actual values to remain effective.
1461 // The number for the parent process should reflect the total number of
1462 // preferences in the database, since the parent process needs to initially
1463 // build a dynamic hashtable of the entire preference database. The number for
1464 // the child process should reflect the number of preferences which are likely
1465 // to change after the startup of the first content process, since content
1466 // processes only store changed preferences on top of a snapshot of the database
1467 // created at startup.
1469 // Note: The capacity of a hashtable doubles when its length reaches an exact
1470 // power of two. A table with an initial length of 64 is twice as large as one
1471 // with an initial length of 63. This is important in content processes, where
1472 // lookup speed is less critical and we pay the price of the additional overhead
1473 // for each content process. So the initial content length should generally be
1474 // *under* the next power-of-two larger than its expected length.
1475 constexpr size_t kHashTableInitialLengthParent
= 3000;
1476 constexpr size_t kHashTableInitialLengthContent
= 64;
1478 static PrefSaveData
pref_savePrefs() {
1479 MOZ_ASSERT(NS_IsMainThread());
1481 PrefSaveData
savedPrefs(HashTable()->count());
1483 for (auto& pref
: PrefsIter(HashTable(), gSharedMap
)) {
1484 nsAutoCString prefValueStr
;
1485 if (!pref
->UserValueToStringForSaving(prefValueStr
)) {
1489 nsAutoCString prefNameStr
;
1490 StrEscape(pref
->Name(), prefNameStr
);
1492 nsPrintfCString
str("user_pref(%s, %s);", prefNameStr
.get(),
1493 prefValueStr
.get());
1495 savedPrefs
.AppendElement(str
);
1503 // Note that this never changes in the parent process, and is only read in
1504 // content processes.
1505 static bool gContentProcessPrefsAreInited
= false;
1509 static Pref
* pref_HashTableLookup(const char* aPrefName
) {
1510 MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
1512 MOZ_ASSERT_IF(!XRE_IsParentProcess(), gContentProcessPrefsAreInited
);
1514 // We use readonlyThreadsafeLookup() because we often have concurrent lookups
1515 // from multiple Stylo threads. This is safe because those threads cannot
1516 // modify sHashTable, and the main thread is blocked while Stylo threads are
1517 // doing these lookups.
1518 auto p
= HashTable()->readonlyThreadsafeLookup(aPrefName
);
1519 return p
? p
->get() : nullptr;
1522 // While notifying preference callbacks, this holds the wrapper for the
1523 // preference being notified, in order to optimize lookups.
1525 // Note: Callbacks and lookups only happen on the main thread, so this is safe
1526 // to use without locking.
1527 static const PrefWrapper
* gCallbackPref
;
1529 Maybe
<PrefWrapper
> pref_SharedLookup(const char* aPrefName
) {
1530 MOZ_DIAGNOSTIC_ASSERT(gSharedMap
, "gSharedMap must be initialized");
1531 if (Maybe
<SharedPrefMap::Pref
> pref
= gSharedMap
->Get(aPrefName
)) {
1537 Maybe
<PrefWrapper
> pref_Lookup(const char* aPrefName
,
1538 bool aIncludeTypeNone
= false) {
1539 MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
1541 AddAccessCount(aPrefName
);
1543 if (gCallbackPref
&& strcmp(aPrefName
, gCallbackPref
->Name()) == 0) {
1544 return Some(*gCallbackPref
);
1546 if (Pref
* pref
= pref_HashTableLookup(aPrefName
)) {
1547 if (aIncludeTypeNone
|| !pref
->IsTypeNone()) {
1550 } else if (gSharedMap
) {
1551 return pref_SharedLookup(aPrefName
);
1557 static Result
<Pref
*, nsresult
> pref_LookupForModify(
1558 const nsCString
& aPrefName
,
1559 const std::function
<bool(const PrefWrapper
&)>& aCheckFn
) {
1560 Maybe
<PrefWrapper
> wrapper
=
1561 pref_Lookup(aPrefName
.get(), /* includeTypeNone */ true);
1562 if (wrapper
.isNothing()) {
1563 return Err(NS_ERROR_INVALID_ARG
);
1565 if (!aCheckFn(*wrapper
)) {
1568 if (wrapper
->is
<Pref
*>()) {
1569 return wrapper
->as
<Pref
*>();
1572 Pref
* pref
= new Pref(aPrefName
);
1573 if (!HashTable()->putNew(aPrefName
.get(), pref
)) {
1575 return Err(NS_ERROR_OUT_OF_MEMORY
);
1577 pref
->FromWrapper(*wrapper
);
1581 static nsresult
pref_SetPref(const nsCString
& aPrefName
, PrefType aType
,
1582 PrefValueKind aKind
, PrefValue aValue
,
1583 bool aIsSticky
, bool aIsLocked
, bool aFromInit
) {
1584 MOZ_ASSERT(XRE_IsParentProcess());
1585 MOZ_ASSERT(NS_IsMainThread());
1588 return NS_ERROR_OUT_OF_MEMORY
;
1591 Pref
* pref
= nullptr;
1594 pref_LookupForModify(aPrefName
, [&](const PrefWrapper
& aWrapper
) {
1595 return !aWrapper
.Matches(aType
, aKind
, aValue
, aIsSticky
, aIsLocked
);
1597 if (result
.isOk() && !(pref
= result
.unwrap())) {
1598 // No changes required.
1604 auto p
= HashTable()->lookupForAdd(aPrefName
.get());
1606 pref
= new Pref(aPrefName
);
1607 pref
->SetType(aType
);
1608 if (!HashTable()->add(p
, pref
)) {
1610 return NS_ERROR_OUT_OF_MEMORY
;
1617 bool valueChanged
= false;
1619 if (aKind
== PrefValueKind::Default
) {
1620 rv
= pref
->SetDefaultValue(aType
, aValue
, aIsSticky
, aIsLocked
,
1623 MOZ_ASSERT(!aIsLocked
); // `locked` is disallowed in user pref files
1624 rv
= pref
->SetUserValue(aType
, aValue
, aFromInit
, &valueChanged
);
1626 if (NS_FAILED(rv
)) {
1628 nsPrintfCString("Rejected attempt to change type of pref %s's %s value "
1631 (aKind
== PrefValueKind::Default
) ? "default" : "user",
1632 PrefTypeToString(pref
->Type()), PrefTypeToString(aType
))
1639 if (aKind
== PrefValueKind::User
) {
1640 Preferences::HandleDirty();
1642 NotifyCallbacks(aPrefName
, PrefWrapper(pref
));
1648 // Removes |node| from callback list. Returns the node after the deleted one.
1649 static CallbackNode
* pref_RemoveCallbackNode(CallbackNode
* aNode
,
1650 CallbackNode
* aPrevNode
) {
1651 MOZ_ASSERT(!aPrevNode
|| aPrevNode
->Next() == aNode
);
1652 MOZ_ASSERT(aPrevNode
|| gFirstCallback
== aNode
);
1653 MOZ_ASSERT(!gCallbacksInProgress
);
1655 CallbackNode
* next_node
= aNode
->Next();
1657 aPrevNode
->SetNext(next_node
);
1659 gFirstCallback
= next_node
;
1661 if (gLastPriorityNode
== aNode
) {
1662 gLastPriorityNode
= aPrevNode
;
1668 static void NotifyCallbacks(const nsCString
& aPrefName
,
1669 const PrefWrapper
* aPref
) {
1670 bool reentered
= gCallbacksInProgress
;
1672 gCallbackPref
= aPref
;
1673 auto cleanup
= MakeScopeExit([]() { gCallbackPref
= nullptr; });
1675 // Nodes must not be deleted while gCallbacksInProgress is true.
1676 // Nodes that need to be deleted are marked for deletion by nulling
1677 // out the |func| pointer. We release them at the end of this function
1678 // if we haven't reentered.
1679 gCallbacksInProgress
= true;
1681 for (CallbackNode
* node
= gFirstCallback
; node
; node
= node
->Next()) {
1683 if (node
->Matches(aPrefName
)) {
1684 (node
->Func())(aPrefName
.get(), node
->Data());
1689 gCallbacksInProgress
= reentered
;
1691 if (gShouldCleanupDeadNodes
&& !gCallbacksInProgress
) {
1692 CallbackNode
* prev_node
= nullptr;
1693 CallbackNode
* node
= gFirstCallback
;
1696 if (!node
->Func()) {
1697 node
= pref_RemoveCallbackNode(node
, prev_node
);
1700 node
= node
->Next();
1703 gShouldCleanupDeadNodes
= false;
1707 if (XRE_IsParentProcess() &&
1708 !StaticPrefs::preferences_force_disable_check_once_policy() &&
1709 (StaticPrefs::preferences_check_once_policy() || xpc::IsInAutomation())) {
1710 // Check that we aren't modifying a `once`-mirrored pref using that pref
1711 // name. We have about 100 `once`-mirrored prefs. std::map performs a
1712 // search in O(log n), so this is fast enough.
1713 MOZ_ASSERT(gOnceStaticPrefsAntiFootgun
);
1714 auto search
= gOnceStaticPrefsAntiFootgun
->find(aPrefName
.get());
1715 if (search
!= gOnceStaticPrefsAntiFootgun
->end()) {
1716 // Run the callback.
1723 //===========================================================================
1725 //===========================================================================
1729 // Keep this in sync with PrefFn in parser/src/lib.rs.
1730 typedef void (*PrefsParserPrefFn
)(const char* aPrefName
, PrefType aType
,
1731 PrefValueKind aKind
, PrefValue aValue
,
1732 bool aIsSticky
, bool aIsLocked
);
1734 // Keep this in sync with ErrorFn in parser/src/lib.rs.
1736 // `aMsg` is just a borrow of the string, and must be copied if it is used
1737 // outside the lifetime of the prefs_parser_parse() call.
1738 typedef void (*PrefsParserErrorFn
)(const char* aMsg
);
1740 // Keep this in sync with prefs_parser_parse() in parser/src/lib.rs.
1741 bool prefs_parser_parse(const char* aPath
, PrefValueKind aKind
,
1742 const char* aBuf
, size_t aLen
,
1743 PrefsParserPrefFn aPrefFn
, PrefsParserErrorFn aErrorFn
);
1749 ~Parser() = default;
1751 bool Parse(PrefValueKind aKind
, const char* aPath
, const nsCString
& aBuf
) {
1752 MOZ_ASSERT(XRE_IsParentProcess());
1753 return prefs_parser_parse(aPath
, aKind
, aBuf
.get(), aBuf
.Length(),
1754 HandlePref
, HandleError
);
1758 static void HandlePref(const char* aPrefName
, PrefType aType
,
1759 PrefValueKind aKind
, PrefValue aValue
, bool aIsSticky
,
1761 MOZ_ASSERT(XRE_IsParentProcess());
1762 pref_SetPref(nsDependentCString(aPrefName
), aType
, aKind
, aValue
, aIsSticky
,
1764 /* fromInit */ true);
1767 static void HandleError(const char* aMsg
) {
1769 nsCOMPtr
<nsIConsoleService
> console
=
1770 do_GetService("@mozilla.org/consoleservice;1", &rv
);
1771 if (NS_SUCCEEDED(rv
)) {
1772 console
->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg
).get());
1777 printf_stderr("%s\n", aMsg
);
1782 // The following code is test code for the gtest.
1784 static void TestParseErrorHandlePref(const char* aPrefName
, PrefType aType
,
1785 PrefValueKind aKind
, PrefValue aValue
,
1786 bool aIsSticky
, bool aIsLocked
) {}
1788 static nsCString gTestParseErrorMsgs
;
1790 static void TestParseErrorHandleError(const char* aMsg
) {
1791 gTestParseErrorMsgs
.Append(aMsg
);
1792 gTestParseErrorMsgs
.Append('\n');
1795 // Keep this in sync with the declaration in test/gtest/Parser.cpp.
1796 void TestParseError(PrefValueKind aKind
, const char* aText
,
1797 nsCString
& aErrorMsg
) {
1798 prefs_parser_parse("test", aKind
, aText
, strlen(aText
),
1799 TestParseErrorHandlePref
, TestParseErrorHandleError
);
1801 // Copy the error messages into the outparam, then clear them from
1802 // gTestParseErrorMsgs.
1803 aErrorMsg
.Assign(gTestParseErrorMsgs
);
1804 gTestParseErrorMsgs
.Truncate();
1807 //===========================================================================
1808 // nsPrefBranch et al.
1809 //===========================================================================
1812 class PreferenceServiceReporter
;
1813 } // namespace mozilla
1815 class PrefCallback
: public PLDHashEntryHdr
{
1816 friend class mozilla::PreferenceServiceReporter
;
1819 typedef PrefCallback
* KeyType
;
1820 typedef const PrefCallback
* KeyTypePointer
;
1822 static const PrefCallback
* KeyToPointer(PrefCallback
* aKey
) { return aKey
; }
1824 static PLDHashNumber
HashKey(const PrefCallback
* aKey
) {
1825 uint32_t hash
= HashString(aKey
->mDomain
);
1826 return AddToHash(hash
, aKey
->mCanonical
);
1830 // Create a PrefCallback with a strong reference to its observer.
1831 PrefCallback(const nsACString
& aDomain
, nsIObserver
* aObserver
,
1832 nsPrefBranch
* aBranch
)
1836 mStrongRef(aObserver
) {
1837 MOZ_COUNT_CTOR(PrefCallback
);
1838 nsCOMPtr
<nsISupports
> canonical
= do_QueryInterface(aObserver
);
1839 mCanonical
= canonical
;
1842 // Create a PrefCallback with a weak reference to its observer.
1843 PrefCallback(const nsACString
& aDomain
, nsISupportsWeakReference
* aObserver
,
1844 nsPrefBranch
* aBranch
)
1847 mWeakRef(do_GetWeakReference(aObserver
)),
1848 mStrongRef(nullptr) {
1849 MOZ_COUNT_CTOR(PrefCallback
);
1850 nsCOMPtr
<nsISupports
> canonical
= do_QueryInterface(aObserver
);
1851 mCanonical
= canonical
;
1854 // This is explicitly not a copy constructor.
1855 explicit PrefCallback(const PrefCallback
*& aCopy
)
1856 : mDomain(aCopy
->mDomain
),
1857 mBranch(aCopy
->mBranch
),
1858 mWeakRef(aCopy
->mWeakRef
),
1859 mStrongRef(aCopy
->mStrongRef
),
1860 mCanonical(aCopy
->mCanonical
) {
1861 MOZ_COUNT_CTOR(PrefCallback
);
1864 PrefCallback(const PrefCallback
&) = delete;
1865 PrefCallback(PrefCallback
&&) = default;
1867 MOZ_COUNTED_DTOR(PrefCallback
)
1869 bool KeyEquals(const PrefCallback
* aKey
) const {
1870 // We want to be able to look up a weakly-referencing PrefCallback after
1871 // its observer has died so we can remove it from the table. Once the
1872 // callback's observer dies, its canonical pointer is stale -- in
1873 // particular, we may have allocated a new observer in the same spot in
1874 // memory! So we can't just compare canonical pointers to determine whether
1875 // aKey refers to the same observer as this.
1877 // Our workaround is based on the way we use this hashtable: When we ask
1878 // the hashtable to remove a PrefCallback whose weak reference has expired,
1879 // we use as the key for removal the same object as was inserted into the
1880 // hashtable. Thus we can say that if one of the keys' weak references has
1881 // expired, the two keys are equal iff they're the same object.
1883 if (IsExpired() || aKey
->IsExpired()) {
1884 return this == aKey
;
1887 if (mCanonical
!= aKey
->mCanonical
) {
1891 return mDomain
.Equals(aKey
->mDomain
);
1894 PrefCallback
* GetKey() const { return const_cast<PrefCallback
*>(this); }
1896 // Get a reference to the callback's observer, or null if the observer was
1897 // weakly referenced and has been destroyed.
1898 already_AddRefed
<nsIObserver
> GetObserver() const {
1900 nsCOMPtr
<nsIObserver
> copy
= mStrongRef
;
1901 return copy
.forget();
1904 nsCOMPtr
<nsIObserver
> observer
= do_QueryReferent(mWeakRef
);
1905 return observer
.forget();
1908 const nsCString
& GetDomain() const { return mDomain
; }
1910 nsPrefBranch
* GetPrefBranch() const { return mBranch
; }
1912 // Has this callback's weak reference died?
1913 bool IsExpired() const {
1914 if (!IsWeak()) return false;
1916 nsCOMPtr
<nsIObserver
> observer(do_QueryReferent(mWeakRef
));
1920 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
1921 size_t n
= aMallocSizeOf(this);
1922 n
+= mDomain
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
);
1924 // All the other fields are non-owning pointers, so we don't measure them.
1929 enum { ALLOW_MEMMOVE
= true };
1933 nsPrefBranch
* mBranch
;
1935 // Exactly one of mWeakRef and mStrongRef should be non-null.
1937 nsCOMPtr
<nsIObserver
> mStrongRef
;
1939 // We need a canonical nsISupports pointer, per bug 578392.
1940 nsISupports
* mCanonical
;
1942 bool IsWeak() const { return !!mWeakRef
; }
1945 class nsPrefBranch final
: public nsIPrefBranch
,
1947 public nsSupportsWeakReference
{
1948 friend class mozilla::PreferenceServiceReporter
;
1952 NS_DECL_NSIPREFBRANCH
1955 nsPrefBranch(const char* aPrefRoot
, PrefValueKind aKind
);
1956 nsPrefBranch() = delete;
1958 static void NotifyObserver(const char* aNewpref
, void* aData
);
1960 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const;
1963 using PrefName
= nsCString
;
1965 virtual ~nsPrefBranch();
1967 int32_t GetRootLength() const { return mPrefRoot
.Length(); }
1969 nsresult
GetDefaultFromPropertiesFile(const char* aPrefName
,
1970 nsAString
& aReturn
);
1972 // As SetCharPref, but without any check on the length of |aValue|.
1973 nsresult
SetCharPrefNoLengthCheck(const char* aPrefName
,
1974 const nsACString
& aValue
);
1976 // Reject strings that are more than 1Mb, warn if strings are more than 16kb.
1977 nsresult
CheckSanityOfStringLength(const char* aPrefName
,
1978 const nsAString
& aValue
);
1979 nsresult
CheckSanityOfStringLength(const char* aPrefName
,
1980 const nsACString
& aValue
);
1981 nsresult
CheckSanityOfStringLength(const char* aPrefName
,
1982 const uint32_t aLength
);
1984 void RemoveExpiredCallback(PrefCallback
* aCallback
);
1986 PrefName
GetPrefName(const char* aPrefName
) const {
1987 return GetPrefName(nsDependentCString(aPrefName
));
1990 PrefName
GetPrefName(const nsACString
& aPrefName
) const;
1992 void FreeObserverList(void);
1994 const nsCString mPrefRoot
;
1995 PrefValueKind mKind
;
1997 bool mFreeingObserverList
;
1998 nsClassHashtable
<PrefCallback
, PrefCallback
> mObservers
;
2001 class nsPrefLocalizedString final
: public nsIPrefLocalizedString
{
2003 nsPrefLocalizedString();
2006 NS_FORWARD_NSISUPPORTSPRIMITIVE(mUnicodeString
->)
2007 NS_FORWARD_NSISUPPORTSSTRING(mUnicodeString
->)
2012 virtual ~nsPrefLocalizedString();
2014 nsCOMPtr
<nsISupportsString
> mUnicodeString
;
2017 //----------------------------------------------------------------------------
2019 //----------------------------------------------------------------------------
2021 nsPrefBranch::nsPrefBranch(const char* aPrefRoot
, PrefValueKind aKind
)
2022 : mPrefRoot(aPrefRoot
),
2024 mFreeingObserverList(false),
2026 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
2027 if (observerService
) {
2028 ++mRefCnt
; // must be > 0 when we call this, or we'll get deleted!
2030 // Add weakly so we don't have to clean up at shutdown.
2031 observerService
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, true);
2036 nsPrefBranch::~nsPrefBranch() { FreeObserverList(); }
2038 NS_IMPL_ISUPPORTS(nsPrefBranch
, nsIPrefBranch
, nsIObserver
,
2039 nsISupportsWeakReference
)
2042 nsPrefBranch::GetRoot(nsACString
& aRoot
) {
2048 nsPrefBranch::GetPrefType(const char* aPrefName
, int32_t* aRetVal
) {
2049 NS_ENSURE_ARG(aPrefName
);
2051 const PrefName
& prefName
= GetPrefName(aPrefName
);
2052 *aRetVal
= Preferences::GetType(prefName
.get());
2057 nsPrefBranch::GetBoolPrefWithDefault(const char* aPrefName
, bool aDefaultValue
,
2058 uint8_t aArgc
, bool* aRetVal
) {
2059 nsresult rv
= GetBoolPref(aPrefName
, aRetVal
);
2060 if (NS_FAILED(rv
) && aArgc
== 1) {
2061 *aRetVal
= aDefaultValue
;
2069 nsPrefBranch::GetBoolPref(const char* aPrefName
, bool* aRetVal
) {
2070 NS_ENSURE_ARG(aPrefName
);
2072 const PrefName
& pref
= GetPrefName(aPrefName
);
2073 return Preferences::GetBool(pref
.get(), aRetVal
, mKind
);
2077 nsPrefBranch::SetBoolPref(const char* aPrefName
, bool aValue
) {
2078 NS_ENSURE_ARG(aPrefName
);
2080 const PrefName
& pref
= GetPrefName(aPrefName
);
2081 return Preferences::SetBool(pref
.get(), aValue
, mKind
);
2085 nsPrefBranch::GetFloatPrefWithDefault(const char* aPrefName
,
2086 float aDefaultValue
, uint8_t aArgc
,
2088 nsresult rv
= GetFloatPref(aPrefName
, aRetVal
);
2090 if (NS_FAILED(rv
) && aArgc
== 1) {
2091 *aRetVal
= aDefaultValue
;
2099 nsPrefBranch::GetFloatPref(const char* aPrefName
, float* aRetVal
) {
2100 NS_ENSURE_ARG(aPrefName
);
2102 nsAutoCString stringVal
;
2103 nsresult rv
= GetCharPref(aPrefName
, stringVal
);
2104 if (NS_SUCCEEDED(rv
)) {
2105 // ToFloat() does a locale-independent conversion.
2106 *aRetVal
= stringVal
.ToFloat(&rv
);
2113 nsPrefBranch::GetCharPrefWithDefault(const char* aPrefName
,
2114 const nsACString
& aDefaultValue
,
2115 uint8_t aArgc
, nsACString
& aRetVal
) {
2116 nsresult rv
= GetCharPref(aPrefName
, aRetVal
);
2118 if (NS_FAILED(rv
) && aArgc
== 1) {
2119 aRetVal
= aDefaultValue
;
2127 nsPrefBranch::GetCharPref(const char* aPrefName
, nsACString
& aRetVal
) {
2128 NS_ENSURE_ARG(aPrefName
);
2130 const PrefName
& pref
= GetPrefName(aPrefName
);
2131 return Preferences::GetCString(pref
.get(), aRetVal
, mKind
);
2135 nsPrefBranch::SetCharPref(const char* aPrefName
, const nsACString
& aValue
) {
2136 nsresult rv
= CheckSanityOfStringLength(aPrefName
, aValue
);
2137 if (NS_FAILED(rv
)) {
2140 return SetCharPrefNoLengthCheck(aPrefName
, aValue
);
2143 nsresult
nsPrefBranch::SetCharPrefNoLengthCheck(const char* aPrefName
,
2144 const nsACString
& aValue
) {
2145 NS_ENSURE_ARG(aPrefName
);
2147 const PrefName
& pref
= GetPrefName(aPrefName
);
2148 return Preferences::SetCString(pref
.get(), aValue
, mKind
);
2152 nsPrefBranch::GetStringPref(const char* aPrefName
,
2153 const nsACString
& aDefaultValue
, uint8_t aArgc
,
2154 nsACString
& aRetVal
) {
2155 nsCString utf8String
;
2156 nsresult rv
= GetCharPref(aPrefName
, utf8String
);
2157 if (NS_SUCCEEDED(rv
)) {
2158 aRetVal
= utf8String
;
2163 aRetVal
= aDefaultValue
;
2171 nsPrefBranch::SetStringPref(const char* aPrefName
, const nsACString
& aValue
) {
2172 nsresult rv
= CheckSanityOfStringLength(aPrefName
, aValue
);
2173 if (NS_FAILED(rv
)) {
2177 return SetCharPrefNoLengthCheck(aPrefName
, aValue
);
2181 nsPrefBranch::GetIntPrefWithDefault(const char* aPrefName
,
2182 int32_t aDefaultValue
, uint8_t aArgc
,
2184 nsresult rv
= GetIntPref(aPrefName
, aRetVal
);
2186 if (NS_FAILED(rv
) && aArgc
== 1) {
2187 *aRetVal
= aDefaultValue
;
2195 nsPrefBranch::GetIntPref(const char* aPrefName
, int32_t* aRetVal
) {
2196 NS_ENSURE_ARG(aPrefName
);
2197 const PrefName
& pref
= GetPrefName(aPrefName
);
2198 return Preferences::GetInt(pref
.get(), aRetVal
, mKind
);
2202 nsPrefBranch::SetIntPref(const char* aPrefName
, int32_t aValue
) {
2203 NS_ENSURE_ARG(aPrefName
);
2205 const PrefName
& pref
= GetPrefName(aPrefName
);
2206 return Preferences::SetInt(pref
.get(), aValue
, mKind
);
2210 nsPrefBranch::GetComplexValue(const char* aPrefName
, const nsIID
& aType
,
2212 NS_ENSURE_ARG(aPrefName
);
2215 nsAutoCString utf8String
;
2217 // We have to do this one first because it's different to all the rest.
2218 if (aType
.Equals(NS_GET_IID(nsIPrefLocalizedString
))) {
2219 nsCOMPtr
<nsIPrefLocalizedString
> theString(
2220 do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID
, &rv
));
2221 if (NS_FAILED(rv
)) {
2225 const PrefName
& pref
= GetPrefName(aPrefName
);
2226 bool bNeedDefault
= false;
2228 if (mKind
== PrefValueKind::Default
) {
2229 bNeedDefault
= true;
2231 // if there is no user (or locked) value
2232 if (!Preferences::HasUserValue(pref
.get()) &&
2233 !Preferences::IsLocked(pref
.get())) {
2234 bNeedDefault
= true;
2238 // if we need to fetch the default value, do that instead, otherwise use the
2239 // value we pulled in at the top of this function
2241 nsAutoString utf16String
;
2242 rv
= GetDefaultFromPropertiesFile(pref
.get(), utf16String
);
2243 if (NS_SUCCEEDED(rv
)) {
2244 theString
->SetData(utf16String
);
2247 rv
= GetCharPref(aPrefName
, utf8String
);
2248 if (NS_SUCCEEDED(rv
)) {
2249 theString
->SetData(NS_ConvertUTF8toUTF16(utf8String
));
2253 if (NS_SUCCEEDED(rv
)) {
2254 theString
.forget(reinterpret_cast<nsIPrefLocalizedString
**>(aRetVal
));
2260 // if we can't get the pref, there's no point in being here
2261 rv
= GetCharPref(aPrefName
, utf8String
);
2262 if (NS_FAILED(rv
)) {
2266 if (aType
.Equals(NS_GET_IID(nsIFile
))) {
2267 ENSURE_PARENT_PROCESS("GetComplexValue(nsIFile)", aPrefName
);
2269 nsCOMPtr
<nsIFile
> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
, &rv
));
2271 if (NS_SUCCEEDED(rv
)) {
2272 rv
= file
->SetPersistentDescriptor(utf8String
);
2273 if (NS_SUCCEEDED(rv
)) {
2274 file
.forget(reinterpret_cast<nsIFile
**>(aRetVal
));
2281 if (aType
.Equals(NS_GET_IID(nsIRelativeFilePref
))) {
2282 ENSURE_PARENT_PROCESS("GetComplexValue(nsIRelativeFilePref)", aPrefName
);
2284 nsACString::const_iterator keyBegin
, strEnd
;
2285 utf8String
.BeginReading(keyBegin
);
2286 utf8String
.EndReading(strEnd
);
2288 // The pref has the format: [fromKey]a/b/c
2289 if (*keyBegin
++ != '[') {
2290 return NS_ERROR_FAILURE
;
2293 nsACString::const_iterator
keyEnd(keyBegin
);
2294 if (!FindCharInReadable(']', keyEnd
, strEnd
)) {
2295 return NS_ERROR_FAILURE
;
2298 nsAutoCString
key(Substring(keyBegin
, keyEnd
));
2300 nsCOMPtr
<nsIFile
> fromFile
;
2301 nsCOMPtr
<nsIProperties
> directoryService(
2302 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID
, &rv
));
2303 if (NS_FAILED(rv
)) {
2307 rv
= directoryService
->Get(key
.get(), NS_GET_IID(nsIFile
),
2308 getter_AddRefs(fromFile
));
2309 if (NS_FAILED(rv
)) {
2313 nsCOMPtr
<nsIFile
> theFile
;
2314 rv
= NS_NewNativeLocalFile(""_ns
, true, getter_AddRefs(theFile
));
2315 if (NS_FAILED(rv
)) {
2319 rv
= theFile
->SetRelativeDescriptor(fromFile
, Substring(++keyEnd
, strEnd
));
2320 if (NS_FAILED(rv
)) {
2324 nsCOMPtr
<nsIRelativeFilePref
> relativePref
= new nsRelativeFilePref();
2325 Unused
<< relativePref
->SetFile(theFile
);
2326 Unused
<< relativePref
->SetRelativeToKey(key
);
2328 relativePref
.forget(reinterpret_cast<nsIRelativeFilePref
**>(aRetVal
));
2332 NS_WARNING("nsPrefBranch::GetComplexValue - Unsupported interface type");
2333 return NS_NOINTERFACE
;
2336 nsresult
nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName
,
2337 const nsAString
& aValue
) {
2338 return CheckSanityOfStringLength(aPrefName
, aValue
.Length());
2341 nsresult
nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName
,
2342 const nsACString
& aValue
) {
2343 return CheckSanityOfStringLength(aPrefName
, aValue
.Length());
2346 nsresult
nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName
,
2347 const uint32_t aLength
) {
2348 if (aLength
> MAX_PREF_LENGTH
) {
2349 return NS_ERROR_ILLEGAL_VALUE
;
2351 if (aLength
<= MAX_ADVISABLE_PREF_LENGTH
) {
2356 nsCOMPtr
<nsIConsoleService
> console
=
2357 do_GetService("@mozilla.org/consoleservice;1", &rv
);
2358 if (NS_FAILED(rv
)) {
2362 nsAutoCString
message(nsPrintfCString(
2363 "Warning: attempting to write %d bytes to preference %s. This is bad "
2364 "for general performance and memory usage. Such an amount of data "
2365 "should rather be written to an external file. This preference will "
2366 "not be sent to any content processes.",
2367 aLength
, GetPrefName(aPrefName
).get()));
2369 rv
= console
->LogStringMessage(NS_ConvertUTF8toUTF16(message
).get());
2370 if (NS_FAILED(rv
)) {
2377 nsPrefBranch::SetComplexValue(const char* aPrefName
, const nsIID
& aType
,
2378 nsISupports
* aValue
) {
2379 ENSURE_PARENT_PROCESS("SetComplexValue", aPrefName
);
2380 NS_ENSURE_ARG(aPrefName
);
2382 nsresult rv
= NS_NOINTERFACE
;
2384 if (aType
.Equals(NS_GET_IID(nsIFile
))) {
2385 nsCOMPtr
<nsIFile
> file
= do_QueryInterface(aValue
);
2387 return NS_NOINTERFACE
;
2390 nsAutoCString descriptorString
;
2391 rv
= file
->GetPersistentDescriptor(descriptorString
);
2392 if (NS_SUCCEEDED(rv
)) {
2393 rv
= SetCharPrefNoLengthCheck(aPrefName
, descriptorString
);
2398 if (aType
.Equals(NS_GET_IID(nsIRelativeFilePref
))) {
2399 nsCOMPtr
<nsIRelativeFilePref
> relFilePref
= do_QueryInterface(aValue
);
2401 return NS_NOINTERFACE
;
2404 nsCOMPtr
<nsIFile
> file
;
2405 relFilePref
->GetFile(getter_AddRefs(file
));
2407 return NS_NOINTERFACE
;
2410 nsAutoCString relativeToKey
;
2411 (void)relFilePref
->GetRelativeToKey(relativeToKey
);
2413 nsCOMPtr
<nsIFile
> relativeToFile
;
2414 nsCOMPtr
<nsIProperties
> directoryService(
2415 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID
, &rv
));
2416 if (NS_FAILED(rv
)) {
2420 rv
= directoryService
->Get(relativeToKey
.get(), NS_GET_IID(nsIFile
),
2421 getter_AddRefs(relativeToFile
));
2422 if (NS_FAILED(rv
)) {
2426 nsAutoCString relDescriptor
;
2427 rv
= file
->GetRelativeDescriptor(relativeToFile
, relDescriptor
);
2428 if (NS_FAILED(rv
)) {
2432 nsAutoCString descriptorString
;
2433 descriptorString
.Append('[');
2434 descriptorString
.Append(relativeToKey
);
2435 descriptorString
.Append(']');
2436 descriptorString
.Append(relDescriptor
);
2437 return SetCharPrefNoLengthCheck(aPrefName
, descriptorString
);
2440 if (aType
.Equals(NS_GET_IID(nsIPrefLocalizedString
))) {
2441 nsCOMPtr
<nsISupportsString
> theString
= do_QueryInterface(aValue
);
2444 nsString wideString
;
2446 rv
= theString
->GetData(wideString
);
2447 if (NS_SUCCEEDED(rv
)) {
2448 // Check sanity of string length before any lengthy conversion
2449 rv
= CheckSanityOfStringLength(aPrefName
, wideString
);
2450 if (NS_FAILED(rv
)) {
2453 rv
= SetCharPrefNoLengthCheck(aPrefName
,
2454 NS_ConvertUTF16toUTF8(wideString
));
2460 NS_WARNING("nsPrefBranch::SetComplexValue - Unsupported interface type");
2461 return NS_NOINTERFACE
;
2465 nsPrefBranch::ClearUserPref(const char* aPrefName
) {
2466 NS_ENSURE_ARG(aPrefName
);
2468 const PrefName
& pref
= GetPrefName(aPrefName
);
2469 return Preferences::ClearUser(pref
.get());
2473 nsPrefBranch::PrefHasUserValue(const char* aPrefName
, bool* aRetVal
) {
2474 NS_ENSURE_ARG_POINTER(aRetVal
);
2475 NS_ENSURE_ARG(aPrefName
);
2477 const PrefName
& pref
= GetPrefName(aPrefName
);
2478 *aRetVal
= Preferences::HasUserValue(pref
.get());
2483 nsPrefBranch::LockPref(const char* aPrefName
) {
2484 NS_ENSURE_ARG(aPrefName
);
2486 const PrefName
& pref
= GetPrefName(aPrefName
);
2487 return Preferences::Lock(pref
.get());
2491 nsPrefBranch::PrefIsLocked(const char* aPrefName
, bool* aRetVal
) {
2492 NS_ENSURE_ARG_POINTER(aRetVal
);
2493 NS_ENSURE_ARG(aPrefName
);
2495 const PrefName
& pref
= GetPrefName(aPrefName
);
2496 *aRetVal
= Preferences::IsLocked(pref
.get());
2501 nsPrefBranch::UnlockPref(const char* aPrefName
) {
2502 NS_ENSURE_ARG(aPrefName
);
2504 const PrefName
& pref
= GetPrefName(aPrefName
);
2505 return Preferences::Unlock(pref
.get());
2509 nsPrefBranch::ResetBranch(const char* aStartingAt
) {
2510 return NS_ERROR_NOT_IMPLEMENTED
;
2514 nsPrefBranch::DeleteBranch(const char* aStartingAt
) {
2515 ENSURE_PARENT_PROCESS("DeleteBranch", aStartingAt
);
2516 NS_ENSURE_ARG(aStartingAt
);
2518 MOZ_ASSERT(NS_IsMainThread());
2521 return NS_ERROR_NOT_INITIALIZED
;
2524 const PrefName
& pref
= GetPrefName(aStartingAt
);
2525 nsAutoCString
branchName(pref
.get());
2527 // Add a trailing '.' if it doesn't already have one.
2528 if (branchName
.Length() > 1 && !StringEndsWith(branchName
, "."_ns
)) {
2532 const nsACString
& branchNameNoDot
=
2533 Substring(branchName
, 0, branchName
.Length() - 1);
2535 for (auto iter
= HashTable()->modIter(); !iter
.done(); iter
.next()) {
2536 // The first disjunct matches branches: e.g. a branch name "foo.bar."
2537 // matches a name "foo.bar.baz" (but it won't match "foo.barrel.baz").
2538 // The second disjunct matches leaf nodes: e.g. a branch name "foo.bar."
2539 // matches a name "foo.bar" (by ignoring the trailing '.').
2540 nsDependentCString
name(iter
.get()->Name());
2541 if (StringBeginsWith(name
, branchName
) || name
.Equals(branchNameNoDot
)) {
2543 // The saved callback pref may be invalid now.
2544 gCallbackPref
= nullptr;
2548 Preferences::HandleDirty();
2553 nsPrefBranch::GetChildList(const char* aStartingAt
,
2554 nsTArray
<nsCString
>& aChildArray
) {
2555 NS_ENSURE_ARG(aStartingAt
);
2557 MOZ_ASSERT(NS_IsMainThread());
2559 // This will contain a list of all the pref name strings. Allocated on the
2561 AutoTArray
<nsCString
, 32> prefArray
;
2563 const PrefName
& parent
= GetPrefName(aStartingAt
);
2564 size_t parentLen
= parent
.Length();
2565 for (auto& pref
: PrefsIter(HashTable(), gSharedMap
)) {
2566 if (strncmp(pref
->Name(), parent
.get(), parentLen
) == 0) {
2567 prefArray
.AppendElement(pref
->NameString());
2571 // Now that we've built up the list, run the callback on all the matching
2573 aChildArray
.SetCapacity(prefArray
.Length());
2574 for (auto& element
: prefArray
) {
2575 // we need to lop off mPrefRoot in case the user is planning to pass this
2576 // back to us because if they do we are going to add mPrefRoot again.
2577 aChildArray
.AppendElement(Substring(element
, mPrefRoot
.Length()));
2584 nsPrefBranch::AddObserverImpl(const nsACString
& aDomain
, nsIObserver
* aObserver
,
2586 UniquePtr
<PrefCallback
> pCallback
;
2588 NS_ENSURE_ARG(aObserver
);
2590 const nsCString
& prefName
= GetPrefName(aDomain
);
2592 // Hold a weak reference to the observer if so requested.
2594 nsCOMPtr
<nsISupportsWeakReference
> weakRefFactory
=
2595 do_QueryInterface(aObserver
);
2596 if (!weakRefFactory
) {
2597 // The caller didn't give us a object that supports weak reference...
2599 return NS_ERROR_INVALID_ARG
;
2602 // Construct a PrefCallback with a weak reference to the observer.
2603 pCallback
= MakeUnique
<PrefCallback
>(prefName
, weakRefFactory
, this);
2606 // Construct a PrefCallback with a strong reference to the observer.
2607 pCallback
= MakeUnique
<PrefCallback
>(prefName
, aObserver
, this);
2610 mObservers
.WithEntryHandle(pCallback
.get(), [&](auto&& p
) {
2612 NS_WARNING("Ignoring duplicate observer.");
2614 // We must pass a fully qualified preference name to the callback
2615 // aDomain == nullptr is the only possible failure, and we trapped it with
2616 // NS_ENSURE_ARG above.
2617 Preferences::RegisterCallback(NotifyObserver
, prefName
, pCallback
.get(),
2618 Preferences::PrefixMatch
,
2619 /* isPriority */ false);
2621 p
.Insert(std::move(pCallback
));
2629 nsPrefBranch::RemoveObserverImpl(const nsACString
& aDomain
,
2630 nsIObserver
* aObserver
) {
2631 NS_ENSURE_ARG(aObserver
);
2633 nsresult rv
= NS_OK
;
2635 // If we're in the middle of a call to FreeObserverList, don't process this
2636 // RemoveObserver call -- the observer in question will be removed soon, if
2637 // it hasn't been already.
2639 // It's important that we don't touch mObservers in any way -- even a Get()
2640 // which returns null might cause the hashtable to resize itself, which will
2641 // break the iteration in FreeObserverList.
2642 if (mFreeingObserverList
) {
2646 // Remove the relevant PrefCallback from mObservers and get an owning pointer
2647 // to it. Unregister the callback first, and then let the owning pointer go
2648 // out of scope and destroy the callback.
2649 const nsCString
& prefName
= GetPrefName(aDomain
);
2650 PrefCallback
key(prefName
, aObserver
, this);
2651 mozilla::UniquePtr
<PrefCallback
> pCallback
;
2652 mObservers
.Remove(&key
, &pCallback
);
2654 rv
= Preferences::UnregisterCallback(
2655 NotifyObserver
, prefName
, pCallback
.get(), Preferences::PrefixMatch
);
2662 nsPrefBranch::Observe(nsISupports
* aSubject
, const char* aTopic
,
2663 const char16_t
* aData
) {
2664 // Watch for xpcom shutdown and free our observers to eliminate any cyclic
2666 if (!nsCRT::strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
2673 void nsPrefBranch::NotifyObserver(const char* aNewPref
, void* aData
) {
2674 PrefCallback
* pCallback
= (PrefCallback
*)aData
;
2676 nsCOMPtr
<nsIObserver
> observer
= pCallback
->GetObserver();
2678 // The observer has expired. Let's remove this callback.
2679 pCallback
->GetPrefBranch()->RemoveExpiredCallback(pCallback
);
2683 // Remove any root this string may contain so as to not confuse the observer
2684 // by passing them something other than what they passed us as a topic.
2685 uint32_t len
= pCallback
->GetPrefBranch()->GetRootLength();
2686 nsDependentCString
suffix(aNewPref
+ len
);
2688 observer
->Observe(static_cast<nsIPrefBranch
*>(pCallback
->GetPrefBranch()),
2689 NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
,
2690 NS_ConvertASCIItoUTF16(suffix
).get());
2693 size_t nsPrefBranch::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
2694 size_t n
= aMallocSizeOf(this);
2696 n
+= mPrefRoot
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
);
2698 n
+= mObservers
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
2699 for (const auto& entry
: mObservers
) {
2700 const PrefCallback
* data
= entry
.GetWeak();
2701 n
+= data
->SizeOfIncludingThis(aMallocSizeOf
);
2707 void nsPrefBranch::FreeObserverList() {
2708 // We need to prevent anyone from modifying mObservers while we're iterating
2709 // over it. In particular, some clients will call RemoveObserver() when
2710 // they're removed and destructed via the iterator; we set
2711 // mFreeingObserverList to keep those calls from touching mObservers.
2712 mFreeingObserverList
= true;
2713 for (auto iter
= mObservers
.Iter(); !iter
.Done(); iter
.Next()) {
2714 auto callback
= iter
.UserData();
2715 Preferences::UnregisterCallback(nsPrefBranch::NotifyObserver
,
2716 callback
->GetDomain(), callback
,
2717 Preferences::PrefixMatch
);
2721 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
2722 if (observerService
) {
2723 observerService
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
2726 mFreeingObserverList
= false;
2729 void nsPrefBranch::RemoveExpiredCallback(PrefCallback
* aCallback
) {
2730 MOZ_ASSERT(aCallback
->IsExpired());
2731 mObservers
.Remove(aCallback
);
2734 nsresult
nsPrefBranch::GetDefaultFromPropertiesFile(const char* aPrefName
,
2735 nsAString
& aReturn
) {
2736 // The default value contains a URL to a .properties file.
2738 nsAutoCString propertyFileURL
;
2739 nsresult rv
= Preferences::GetCString(aPrefName
, propertyFileURL
,
2740 PrefValueKind::Default
);
2741 if (NS_FAILED(rv
)) {
2745 nsCOMPtr
<nsIStringBundleService
> bundleService
=
2746 components::StringBundle::Service();
2747 if (!bundleService
) {
2748 return NS_ERROR_FAILURE
;
2751 nsCOMPtr
<nsIStringBundle
> bundle
;
2752 rv
= bundleService
->CreateBundle(propertyFileURL
.get(),
2753 getter_AddRefs(bundle
));
2754 if (NS_FAILED(rv
)) {
2758 return bundle
->GetStringFromName(aPrefName
, aReturn
);
2761 nsPrefBranch::PrefName
nsPrefBranch::GetPrefName(
2762 const nsACString
& aPrefName
) const {
2763 if (mPrefRoot
.IsEmpty()) {
2764 return PrefName(PromiseFlatCString(aPrefName
));
2767 return PrefName(mPrefRoot
+ aPrefName
);
2770 //----------------------------------------------------------------------------
2771 // nsPrefLocalizedString
2772 //----------------------------------------------------------------------------
2774 nsPrefLocalizedString::nsPrefLocalizedString() = default;
2776 nsPrefLocalizedString::~nsPrefLocalizedString() = default;
2778 NS_IMPL_ISUPPORTS(nsPrefLocalizedString
, nsIPrefLocalizedString
,
2781 nsresult
nsPrefLocalizedString::Init() {
2783 mUnicodeString
= do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID
, &rv
);
2788 //----------------------------------------------------------------------------
2789 // nsRelativeFilePref
2790 //----------------------------------------------------------------------------
2792 NS_IMPL_ISUPPORTS(nsRelativeFilePref
, nsIRelativeFilePref
)
2794 nsRelativeFilePref::nsRelativeFilePref() = default;
2796 nsRelativeFilePref::~nsRelativeFilePref() = default;
2799 nsRelativeFilePref::GetFile(nsIFile
** aFile
) {
2800 NS_ENSURE_ARG_POINTER(aFile
);
2802 NS_IF_ADDREF(*aFile
);
2807 nsRelativeFilePref::SetFile(nsIFile
* aFile
) {
2813 nsRelativeFilePref::GetRelativeToKey(nsACString
& aRelativeToKey
) {
2814 aRelativeToKey
.Assign(mRelativeToKey
);
2819 nsRelativeFilePref::SetRelativeToKey(const nsACString
& aRelativeToKey
) {
2820 mRelativeToKey
.Assign(aRelativeToKey
);
2824 //===========================================================================
2825 // class Preferences and related things
2826 //===========================================================================
2830 #define INITIAL_PREF_FILES 10
2832 static NS_DEFINE_CID(kZipReaderCID
, NS_ZIPREADER_CID
);
2834 void Preferences::HandleDirty() {
2835 MOZ_ASSERT(XRE_IsParentProcess());
2837 if (!HashTable() || !sPreferences
) {
2841 if (sPreferences
->mProfileShutdown
) {
2842 NS_WARNING("Setting user pref after profile shutdown.");
2846 if (!sPreferences
->mDirty
) {
2847 sPreferences
->mDirty
= true;
2849 if (sPreferences
->mCurrentFile
&& sPreferences
->AllowOffMainThreadSave() &&
2850 !sPreferences
->mSavePending
) {
2851 sPreferences
->mSavePending
= true;
2852 static const int PREF_DELAY_MS
= 500;
2853 NS_DelayedDispatchToCurrentThread(
2854 NewRunnableMethod("Preferences::SavePrefFileAsynchronous",
2856 &Preferences::SavePrefFileAsynchronous
),
2862 static nsresult
openPrefFile(nsIFile
* aFile
, PrefValueKind aKind
);
2864 static nsresult
parsePrefData(const nsCString
& aData
, PrefValueKind aKind
);
2867 static const char kPrefFileHeader
[] =
2868 "// Mozilla User Preferences"
2871 "// DO NOT EDIT THIS FILE."
2875 "// If you make changes to this file while the application is running,"
2877 "// the changes will be overwritten when the application exits."
2881 "// To change a preference value, you can either:"
2883 "// - modify it via the UI (e.g. via about:config in the browser); or"
2885 "// - set it within a user.js file in your profile."
2890 // Note: if sShutdown is true, sPreferences will be nullptr.
2891 StaticRefPtr
<Preferences
> Preferences::sPreferences
;
2892 bool Preferences::sShutdown
= false;
2894 // This globally enables or disables OMT pref writing, both sync and async.
2895 static int32_t sAllowOMTPrefWrite
= -1;
2897 // Write the preference data to a file.
2898 class PreferencesWriter final
{
2900 PreferencesWriter() = default;
2902 static nsresult
Write(nsIFile
* aFile
, PrefSaveData
& aPrefs
) {
2903 nsCOMPtr
<nsIOutputStream
> outStreamSink
;
2904 nsCOMPtr
<nsIOutputStream
> outStream
;
2905 uint32_t writeAmount
;
2908 // Execute a "safe" save by saving through a tempfile.
2909 rv
= NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink
), aFile
,
2911 if (NS_FAILED(rv
)) {
2915 rv
= NS_NewBufferedOutputStream(getter_AddRefs(outStream
),
2916 outStreamSink
.forget(), 4096);
2917 if (NS_FAILED(rv
)) {
2921 struct CharComparator
{
2922 bool LessThan(const nsCString
& aA
, const nsCString
& aB
) const {
2926 bool Equals(const nsCString
& aA
, const nsCString
& aB
) const {
2931 // Sort the preferences to make a readable file on disk.
2932 aPrefs
.Sort(CharComparator());
2934 // Write out the file header.
2935 outStream
->Write(kPrefFileHeader
, sizeof(kPrefFileHeader
) - 1,
2938 for (nsCString
& pref
: aPrefs
) {
2939 outStream
->Write(pref
.get(), pref
.Length(), &writeAmount
);
2940 outStream
->Write(NS_LINEBREAK
, NS_LINEBREAK_LEN
, &writeAmount
);
2943 // Tell the safe output stream to overwrite the real prefs file.
2944 // (It'll abort if there were any errors during writing.)
2945 nsCOMPtr
<nsISafeOutputStream
> safeStream
= do_QueryInterface(outStream
);
2946 MOZ_ASSERT(safeStream
, "expected a safe output stream!");
2948 rv
= safeStream
->Finish();
2952 if (NS_FAILED(rv
)) {
2953 NS_WARNING("failed to save prefs file! possible data loss");
2960 static void Flush() {
2961 MOZ_DIAGNOSTIC_ASSERT(sPendingWriteCount
>= 0);
2962 // SpinEventLoopUntil is unfortunate, but ultimately it's the best thing
2963 // we can do here given the constraint that we need to ensure that
2964 // the preferences on disk match what we have in memory. We could
2965 // easily perform the write here ourselves by doing exactly what
2966 // happens in PWRunnable::Run. This would be the right thing to do
2967 // if we're stuck here because other unrelated runnables are taking
2968 // a long time, and the wrong thing to do if PreferencesWriter::Write
2969 // is what takes a long time, as we would be trading a SpinEventLoopUntil
2970 // for a synchronous disk write, wherein we could not even spin the
2971 // event loop. Given that PWRunnable generally runs on a thread pool,
2972 // if we're stuck here, it's likely because of PreferencesWriter::Write
2973 // and not some other runnable. Thus, spin away.
2974 mozilla::SpinEventLoopUntil([]() { return sPendingWriteCount
<= 0; });
2977 // This is the data that all of the runnables (see below) will attempt
2978 // to write. It will always have the most up to date version, or be
2979 // null, if the up to date information has already been written out.
2980 static Atomic
<PrefSaveData
*> sPendingWriteData
;
2982 // This is the number of writes via PWRunnables which have been dispatched
2983 // but not yet completed. This is intended to be used by Flush to ensure
2984 // that there are no outstanding writes left incomplete, and thus our prefs
2985 // on disk are in sync with what we have in memory.
2986 static Atomic
<int> sPendingWriteCount
;
2988 // See PWRunnable::Run for details on why we need this lock.
2989 static StaticMutex sWritingToFile
;
2992 Atomic
<PrefSaveData
*> PreferencesWriter::sPendingWriteData(nullptr);
2993 Atomic
<int> PreferencesWriter::sPendingWriteCount(0);
2994 StaticMutex
PreferencesWriter::sWritingToFile
;
2996 class PWRunnable
: public Runnable
{
2998 explicit PWRunnable(nsIFile
* aFile
) : Runnable("PWRunnable"), mFile(aFile
) {}
3000 NS_IMETHOD
Run() override
{
3001 // Preference writes are handled a bit strangely, in that a "newer"
3002 // write is generally regarded as always better. For this reason,
3003 // sPendingWriteData can be overwritten multiple times before anyone
3004 // gets around to actually using it, minimizing writes. However,
3005 // once we've acquired sPendingWriteData we've reached a
3006 // "point of no return" and have to complete the write.
3008 // Unfortunately, this design allows the following behaviour:
3010 // 1. write1 is queued up
3011 // 2. thread1 acquires write1
3012 // 3. write2 is queued up
3013 // 4. thread2 acquires write2
3014 // 5. thread1 and thread2 concurrently clobber each other
3016 // To avoid this, we use this lock to ensure that only one thread
3017 // at a time is trying to acquire the write, and when it does,
3018 // all other threads are prevented from acquiring writes until it
3019 // completes the write. New writes are still allowed to be queued
3022 // Although it's atomic, the acquire needs to be guarded by the mutex
3023 // to avoid reordering of writes -- we don't want an older write to
3024 // run after a newer one. To avoid this causing too much waiting, we check
3025 // if sPendingWriteData is already null before acquiring the mutex. If it
3026 // is, then there's definitely no work to be done (or someone is in the
3027 // middle of doing it for us).
3029 // Note that every time a new write is queued up, a new write task is
3030 // is also queued up, so there will always be a task that can see the newest
3033 // Ideally this lock wouldn't be necessary, and the PreferencesWriter
3034 // would be used more carefully, but it's hard to untangle all that.
3035 nsresult rv
= NS_OK
;
3036 if (PreferencesWriter::sPendingWriteData
) {
3037 StaticMutexAutoLock
lock(PreferencesWriter::sWritingToFile
);
3038 // If we get a nullptr on the exchange, it means that somebody
3039 // else has already processed the request, and we can just return.
3040 UniquePtr
<PrefSaveData
> prefs(
3041 PreferencesWriter::sPendingWriteData
.exchange(nullptr));
3043 rv
= PreferencesWriter::Write(mFile
, *prefs
);
3044 // Make a copy of these so we can have them in runnable lambda.
3045 // nsIFile is only there so that we would never release the
3046 // ref counted pointer off main thread.
3047 nsresult rvCopy
= rv
;
3048 nsCOMPtr
<nsIFile
> fileCopy(mFile
);
3049 SchedulerGroup::Dispatch(
3050 TaskCategory::Other
,
3051 NS_NewRunnableFunction("Preferences::WriterRunnable",
3052 [fileCopy
, rvCopy
] {
3053 MOZ_RELEASE_ASSERT(NS_IsMainThread());
3054 if (NS_FAILED(rvCopy
)) {
3055 Preferences::HandleDirty();
3060 // We've completed the write to the best of our abilities, whether
3061 // we had prefs to write or another runnable got to them first. If
3062 // PreferencesWriter::Write failed, this is still correct as the
3063 // write is no longer outstanding, and the above HandleDirty call
3064 // will just start the cycle again.
3065 PreferencesWriter::sPendingWriteCount
--;
3070 nsCOMPtr
<nsIFile
> mFile
;
3073 // Although this is a member of Preferences, it measures sPreferences and
3074 // several other global structures.
3076 void Preferences::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf
,
3077 PrefsSizes
& aSizes
) {
3078 if (!sPreferences
) {
3082 aSizes
.mMisc
+= aMallocSizeOf(sPreferences
.get());
3084 aSizes
.mRootBranches
+=
3085 static_cast<nsPrefBranch
*>(sPreferences
->mRootBranch
.get())
3086 ->SizeOfIncludingThis(aMallocSizeOf
) +
3087 static_cast<nsPrefBranch
*>(sPreferences
->mDefaultRootBranch
.get())
3088 ->SizeOfIncludingThis(aMallocSizeOf
);
3091 class PreferenceServiceReporter final
: public nsIMemoryReporter
{
3092 ~PreferenceServiceReporter() {}
3096 NS_DECL_NSIMEMORYREPORTER
3099 static const uint32_t kSuspectReferentCount
= 1000;
3102 NS_IMPL_ISUPPORTS(PreferenceServiceReporter
, nsIMemoryReporter
)
3104 MOZ_DEFINE_MALLOC_SIZE_OF(PreferenceServiceMallocSizeOf
)
3107 PreferenceServiceReporter::CollectReports(
3108 nsIHandleReportCallback
* aHandleReport
, nsISupports
* aData
,
3110 MOZ_ASSERT(NS_IsMainThread());
3112 MallocSizeOf mallocSizeOf
= PreferenceServiceMallocSizeOf
;
3115 Preferences::AddSizeOfIncludingThis(mallocSizeOf
, sizes
);
3118 sizes
.mHashTable
+= HashTable()->shallowSizeOfIncludingThis(mallocSizeOf
);
3119 for (auto iter
= HashTable()->iter(); !iter
.done(); iter
.next()) {
3120 iter
.get()->AddSizeOfIncludingThis(mallocSizeOf
, sizes
);
3124 sizes
.mPrefNameArena
+= PrefNameArena().SizeOfExcludingThis(mallocSizeOf
);
3126 for (CallbackNode
* node
= gFirstCallback
; node
; node
= node
->Next()) {
3127 node
->AddSizeOfIncludingThis(mallocSizeOf
, sizes
);
3131 sizes
.mMisc
+= mallocSizeOf(gSharedMap
);
3134 #ifdef ACCESS_COUNTS
3135 if (gAccessCounts
) {
3136 sizes
.mMisc
+= gAccessCounts
->ShallowSizeOfIncludingThis(mallocSizeOf
);
3140 MOZ_COLLECT_REPORT("explicit/preferences/hash-table", KIND_HEAP
, UNITS_BYTES
,
3141 sizes
.mHashTable
, "Memory used by libpref's hash table.");
3143 MOZ_COLLECT_REPORT("explicit/preferences/pref-values", KIND_HEAP
, UNITS_BYTES
,
3145 "Memory used by PrefValues hanging off the hash table.");
3147 MOZ_COLLECT_REPORT("explicit/preferences/string-values", KIND_HEAP
,
3148 UNITS_BYTES
, sizes
.mStringValues
,
3149 "Memory used by libpref's string pref values.");
3151 MOZ_COLLECT_REPORT("explicit/preferences/root-branches", KIND_HEAP
,
3152 UNITS_BYTES
, sizes
.mRootBranches
,
3153 "Memory used by libpref's root branches.");
3155 MOZ_COLLECT_REPORT("explicit/preferences/pref-name-arena", KIND_HEAP
,
3156 UNITS_BYTES
, sizes
.mPrefNameArena
,
3157 "Memory used by libpref's arena for pref names.");
3159 MOZ_COLLECT_REPORT("explicit/preferences/callbacks/objects", KIND_HEAP
,
3160 UNITS_BYTES
, sizes
.mCallbacksObjects
,
3161 "Memory used by pref callback objects.");
3163 MOZ_COLLECT_REPORT("explicit/preferences/callbacks/domains", KIND_HEAP
,
3164 UNITS_BYTES
, sizes
.mCallbacksDomains
,
3165 "Memory used by pref callback domains (pref names and "
3168 MOZ_COLLECT_REPORT("explicit/preferences/misc", KIND_HEAP
, UNITS_BYTES
,
3169 sizes
.mMisc
, "Miscellaneous memory used by libpref.");
3172 if (XRE_IsParentProcess()) {
3173 MOZ_COLLECT_REPORT("explicit/preferences/shared-memory-map", KIND_NONHEAP
,
3174 UNITS_BYTES
, gSharedMap
->MapSize(),
3175 "The shared memory mapping used to share a "
3176 "snapshot of preference values across processes.");
3180 nsPrefBranch
* rootBranch
=
3181 static_cast<nsPrefBranch
*>(Preferences::GetRootBranch());
3186 size_t numStrong
= 0;
3187 size_t numWeakAlive
= 0;
3188 size_t numWeakDead
= 0;
3189 nsTArray
<nsCString
> suspectPreferences
;
3190 // Count of the number of referents for each preference.
3191 nsTHashMap
<nsCStringHashKey
, uint32_t> prefCounter
;
3193 for (const auto& entry
: rootBranch
->mObservers
) {
3194 auto* callback
= entry
.GetWeak();
3196 if (callback
->IsWeak()) {
3197 nsCOMPtr
<nsIObserver
> callbackRef
= do_QueryReferent(callback
->mWeakRef
);
3207 const uint32_t currentCount
= prefCounter
.Get(callback
->GetDomain()) + 1;
3208 prefCounter
.InsertOrUpdate(callback
->GetDomain(), currentCount
);
3210 // Keep track of preferences that have a suspiciously large number of
3211 // referents (a symptom of a leak).
3212 if (currentCount
== kSuspectReferentCount
) {
3213 suspectPreferences
.AppendElement(callback
->GetDomain());
3217 for (uint32_t i
= 0; i
< suspectPreferences
.Length(); i
++) {
3218 nsCString
& suspect
= suspectPreferences
[i
];
3219 const uint32_t totalReferentCount
= prefCounter
.Get(suspect
);
3221 nsPrintfCString
suspectPath(
3222 "preference-service-suspect/"
3223 "referent(pref=%s)",
3226 aHandleReport
->Callback(
3227 /* process = */ ""_ns
, suspectPath
, KIND_OTHER
, UNITS_COUNT
,
3229 "A preference with a suspiciously large number "
3230 "referents (symptom of a leak)."_ns
,
3235 "preference-service/referent/strong", KIND_OTHER
, UNITS_COUNT
, numStrong
,
3236 "The number of strong referents held by the preference service.");
3239 "preference-service/referent/weak/alive", KIND_OTHER
, UNITS_COUNT
,
3241 "The number of weak referents held by the preference service that are "
3245 "preference-service/referent/weak/dead", KIND_OTHER
, UNITS_COUNT
,
3247 "The number of weak referents held by the preference service that are "
3255 class AddPreferencesMemoryReporterRunnable
: public Runnable
{
3257 AddPreferencesMemoryReporterRunnable()
3258 : Runnable("AddPreferencesMemoryReporterRunnable") {}
3260 NS_IMETHOD
Run() override
{
3261 return RegisterStrongMemoryReporter(new PreferenceServiceReporter());
3267 // A list of changed prefs sent from the parent via shared memory.
3268 static nsTArray
<dom::Pref
>* gChangedDomPrefs
;
3270 static const char kTelemetryPref
[] = "toolkit.telemetry.enabled";
3271 static const char kChannelPref
[] = "app.update.channel";
3273 #ifdef MOZ_WIDGET_ANDROID
3275 static Maybe
<bool> TelemetryPrefValue() {
3276 // Leave it unchanged if it's already set.
3277 // XXX: how could it already be set?
3278 if (Preferences::GetType(kTelemetryPref
) != nsIPrefBranch::PREF_INVALID
) {
3282 // Determine the correct default for toolkit.telemetry.enabled. If this
3283 // build has MOZ_TELEMETRY_ON_BY_DEFAULT *or* we're on the beta channel,
3284 // telemetry is on by default, otherwise not. This is necessary so that
3285 // beta users who are testing final release builds don't flipflop defaults.
3286 # ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
3289 nsAutoCString channelPrefValue
;
3290 Unused
<< Preferences::GetCString(kChannelPref
, channelPrefValue
,
3291 PrefValueKind::Default
);
3292 return Some(channelPrefValue
.EqualsLiteral("beta"));
3297 void Preferences::SetupTelemetryPref() {
3298 MOZ_ASSERT(XRE_IsParentProcess());
3300 Maybe
<bool> telemetryPrefValue
= TelemetryPrefValue();
3301 if (telemetryPrefValue
.isSome()) {
3302 Preferences::SetBool(kTelemetryPref
, *telemetryPrefValue
,
3303 PrefValueKind::Default
);
3307 #else // !MOZ_WIDGET_ANDROID
3309 static bool TelemetryPrefValue() {
3310 // For platforms with Unified Telemetry (here meaning not-Android),
3311 // toolkit.telemetry.enabled determines whether we send "extended" data.
3312 // We only want extended data from pre-release channels due to size.
3314 constexpr auto channel
= MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL
) ""_ns
;
3316 // Easy cases: Nightly, Aurora, Beta.
3317 if (channel
.EqualsLiteral("nightly") || channel
.EqualsLiteral("aurora") ||
3318 channel
.EqualsLiteral("beta")) {
3322 # ifndef MOZILLA_OFFICIAL
3323 // Local developer builds: non-official builds on the "default" channel.
3324 if (channel
.EqualsLiteral("default")) {
3329 // Release Candidate builds: builds that think they are release builds, but
3330 // are shipped to beta users.
3331 if (channel
.EqualsLiteral("release")) {
3332 nsAutoCString channelPrefValue
;
3333 Unused
<< Preferences::GetCString(kChannelPref
, channelPrefValue
,
3334 PrefValueKind::Default
);
3335 if (channelPrefValue
.EqualsLiteral("beta")) {
3344 void Preferences::SetupTelemetryPref() {
3345 MOZ_ASSERT(XRE_IsParentProcess());
3347 Preferences::SetBool(kTelemetryPref
, TelemetryPrefValue(),
3348 PrefValueKind::Default
);
3349 Preferences::Lock(kTelemetryPref
);
3352 static void CheckTelemetryPref() {
3353 MOZ_ASSERT(!XRE_IsParentProcess());
3355 // Make sure the children got passed the right telemetry pref details.
3356 DebugOnly
<bool> value
;
3357 MOZ_ASSERT(NS_SUCCEEDED(Preferences::GetBool(kTelemetryPref
, &value
)) &&
3358 value
== TelemetryPrefValue());
3359 MOZ_ASSERT(Preferences::IsLocked(kTelemetryPref
));
3362 #endif // MOZ_WIDGET_ANDROID
3365 already_AddRefed
<Preferences
> Preferences::GetInstanceForService() {
3367 return do_AddRef(sPreferences
);
3374 sPreferences
= new Preferences();
3376 MOZ_ASSERT(!HashTable());
3377 HashTable() = new PrefsHashTable(XRE_IsParentProcess()
3378 ? kHashTableInitialLengthParent
3379 : kHashTableInitialLengthContent
);
3382 gOnceStaticPrefsAntiFootgun
= new AntiFootgunMap();
3385 #ifdef ACCESS_COUNTS
3386 MOZ_ASSERT(!gAccessCounts
);
3387 gAccessCounts
= new AccessCountsHashTable();
3390 nsresult rv
= InitInitialObjects(/* isStartup */ true);
3391 if (NS_FAILED(rv
)) {
3392 sPreferences
= nullptr;
3396 if (!XRE_IsParentProcess()) {
3397 MOZ_ASSERT(gChangedDomPrefs
);
3398 for (unsigned int i
= 0; i
< gChangedDomPrefs
->Length(); i
++) {
3399 Preferences::SetPreference(gChangedDomPrefs
->ElementAt(i
));
3401 delete gChangedDomPrefs
;
3402 gChangedDomPrefs
= nullptr;
3404 #ifndef MOZ_WIDGET_ANDROID
3405 CheckTelemetryPref();
3409 // Check if there is a deployment configuration file. If so, set up the
3410 // pref config machinery, which will actually read the file.
3411 nsAutoCString lockFileName
;
3412 nsresult rv
= Preferences::GetCString("general.config.filename",
3413 lockFileName
, PrefValueKind::User
);
3414 if (NS_SUCCEEDED(rv
)) {
3415 NS_CreateServicesFromCategory(
3416 "pref-config-startup",
3417 static_cast<nsISupports
*>(static_cast<void*>(sPreferences
)),
3418 "pref-config-startup");
3421 nsCOMPtr
<nsIObserverService
> observerService
=
3422 services::GetObserverService();
3423 if (!observerService
) {
3424 sPreferences
= nullptr;
3428 observerService
->AddObserver(sPreferences
,
3429 "profile-before-change-telemetry", true);
3430 rv
= observerService
->AddObserver(sPreferences
, "profile-before-change",
3433 observerService
->AddObserver(sPreferences
, "suspend_process_notification",
3436 if (NS_FAILED(rv
)) {
3437 sPreferences
= nullptr;
3442 const char* defaultPrefs
= getenv("MOZ_DEFAULT_PREFS");
3444 parsePrefData(nsCString(defaultPrefs
), PrefValueKind::Default
);
3447 // Preferences::GetInstanceForService() can be called from GetService(), and
3448 // RegisterStrongMemoryReporter calls GetService(nsIMemoryReporter). To
3449 // avoid a potential recursive GetService() call, we can't register the
3450 // memory reporter here; instead, do it off a runnable.
3451 RefPtr
<AddPreferencesMemoryReporterRunnable
> runnable
=
3452 new AddPreferencesMemoryReporterRunnable();
3453 NS_DispatchToMainThread(runnable
);
3455 return do_AddRef(sPreferences
);
3459 bool Preferences::IsServiceAvailable() { return !!sPreferences
; }
3462 bool Preferences::InitStaticMembers() {
3463 MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
3465 if (MOZ_LIKELY(sPreferences
)) {
3470 MOZ_ASSERT(NS_IsMainThread());
3471 nsCOMPtr
<nsIPrefService
> prefService
=
3472 do_GetService(NS_PREFSERVICE_CONTRACTID
);
3475 return sPreferences
!= nullptr;
3479 void Preferences::Shutdown() {
3481 sShutdown
= true; // Don't create the singleton instance after here.
3482 sPreferences
= nullptr;
3486 Preferences::Preferences()
3487 : mRootBranch(new nsPrefBranch("", PrefValueKind::User
)),
3488 mDefaultRootBranch(new nsPrefBranch("", PrefValueKind::Default
)) {}
3490 Preferences::~Preferences() {
3491 MOZ_ASSERT(!sPreferences
);
3493 MOZ_ASSERT(!gCallbacksInProgress
);
3495 CallbackNode
* node
= gFirstCallback
;
3497 CallbackNode
* next_node
= node
->Next();
3501 gLastPriorityNode
= gFirstCallback
= nullptr;
3504 HashTable() = nullptr;
3507 delete gOnceStaticPrefsAntiFootgun
;
3508 gOnceStaticPrefsAntiFootgun
= nullptr;
3511 #ifdef ACCESS_COUNTS
3512 delete gAccessCounts
;
3515 gSharedMap
= nullptr;
3517 PrefNameArena().Clear();
3520 NS_IMPL_ISUPPORTS(Preferences
, nsIPrefService
, nsIObserver
, nsIPrefBranch
,
3521 nsISupportsWeakReference
)
3524 void Preferences::SerializePreferences(nsCString
& aStr
) {
3525 MOZ_RELEASE_ASSERT(InitStaticMembers());
3529 for (auto iter
= HashTable()->iter(); !iter
.done(); iter
.next()) {
3530 Pref
* pref
= iter
.get().get();
3531 if (!pref
->IsTypeNone() && pref
->HasAdvisablySizedValues()) {
3532 pref
->SerializeAndAppend(aStr
);
3540 void Preferences::DeserializePreferences(char* aStr
, size_t aPrefsLen
) {
3541 MOZ_ASSERT(!XRE_IsParentProcess());
3543 MOZ_ASSERT(!gChangedDomPrefs
);
3544 gChangedDomPrefs
= new nsTArray
<dom::Pref
>();
3547 while (*p
!= '\0') {
3549 p
= Pref::Deserialize(p
, &pref
);
3550 gChangedDomPrefs
->AppendElement(pref
);
3553 // We finished parsing on a '\0'. That should be the last char in the shared
3554 // memory. (aPrefsLen includes the '\0'.)
3555 MOZ_ASSERT(p
== aStr
+ aPrefsLen
- 1);
3558 MOZ_ASSERT(!gContentProcessPrefsAreInited
);
3559 gContentProcessPrefsAreInited
= true;
3563 // Forward declarations.
3564 namespace StaticPrefs
{
3566 static void InitAll();
3567 static void StartObservingAlwaysPrefs();
3568 static void InitOncePrefs();
3569 static void InitStaticPrefsFromShared();
3570 static void RegisterOncePrefs(SharedPrefMapBuilder
& aBuilder
);
3572 } // namespace StaticPrefs
3575 FileDescriptor
Preferences::EnsureSnapshot(size_t* aSize
) {
3576 MOZ_ASSERT(XRE_IsParentProcess());
3579 SharedPrefMapBuilder builder
;
3581 for (auto iter
= HashTable()->iter(); !iter
.done(); iter
.next()) {
3582 iter
.get()->AddToMap(builder
);
3585 // Store the current value of `once`-mirrored prefs. After this point they
3586 // will be immutable.
3587 StaticPrefs::RegisterOncePrefs(builder
);
3589 gSharedMap
= new SharedPrefMap(std::move(builder
));
3591 // Once we've built a snapshot of the database, there's no need to continue
3592 // storing dynamic copies of the preferences it contains. Once we reset the
3593 // hashtable, preference lookups will fall back to the snapshot for any
3594 // preferences not in the dynamic hashtable.
3596 // And since the majority of the database is now contained in the snapshot,
3597 // we can initialize the hashtable with the expected number of per-session
3598 // changed preferences, rather than the expected total number of
3600 HashTable()->clearAndCompact();
3601 Unused
<< HashTable()->reserve(kHashTableInitialLengthContent
);
3603 PrefNameArena().Clear();
3604 gCallbackPref
= nullptr;
3607 *aSize
= gSharedMap
->MapSize();
3608 return gSharedMap
->CloneFileDescriptor();
3612 void Preferences::InitSnapshot(const FileDescriptor
& aHandle
, size_t aSize
) {
3613 MOZ_ASSERT(!XRE_IsParentProcess());
3614 MOZ_ASSERT(!gSharedMap
);
3616 gSharedMap
= new SharedPrefMap(aHandle
, aSize
);
3618 StaticPrefs::InitStaticPrefsFromShared();
3622 void Preferences::InitializeUserPrefs() {
3623 MOZ_ASSERT(XRE_IsParentProcess());
3624 MOZ_ASSERT(!sPreferences
->mCurrentFile
, "Should only initialize prefs once");
3626 // Prefs which are set before we initialize the profile are silently
3627 // discarded. This is stupid, but there are various tests which depend on
3629 sPreferences
->ResetUserPrefs();
3631 nsCOMPtr
<nsIFile
> prefsFile
= sPreferences
->ReadSavedPrefs();
3632 sPreferences
->ReadUserOverridePrefs();
3634 sPreferences
->mDirty
= false;
3636 // Don't set mCurrentFile until we're done so that dirty flags work properly.
3637 sPreferences
->mCurrentFile
= std::move(prefsFile
);
3641 void Preferences::FinishInitializingUserPrefs() {
3642 sPreferences
->NotifyServiceObservers(NS_PREFSERVICE_READ_TOPIC_ID
);
3646 Preferences::Observe(nsISupports
* aSubject
, const char* aTopic
,
3647 const char16_t
* someData
) {
3648 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
3649 return NS_ERROR_NOT_AVAILABLE
;
3652 nsresult rv
= NS_OK
;
3654 if (!nsCRT::strcmp(aTopic
, "profile-before-change")) {
3655 // Normally prefs aren't written after this point, and so we kick off
3656 // an asynchronous pref save so that I/O can be done in parallel with
3658 if (AllowOffMainThreadSave()) {
3659 SavePrefFile(nullptr);
3662 } else if (!nsCRT::strcmp(aTopic
, "profile-before-change-telemetry")) {
3663 // It's possible that a profile-before-change observer after ours
3664 // set a pref. A blocking save here re-saves if necessary and also waits
3665 // for any pending saves to complete.
3666 SavePrefFileBlocking();
3667 MOZ_ASSERT(!mDirty
, "Preferences should not be dirty");
3668 mProfileShutdown
= true;
3670 } else if (!nsCRT::strcmp(aTopic
, "reload-default-prefs")) {
3671 // Reload the default prefs from file.
3672 Unused
<< InitInitialObjects(/* isStartup */ false);
3674 } else if (!nsCRT::strcmp(aTopic
, "suspend_process_notification")) {
3675 // Our process is being suspended. The OS may wake our process later,
3676 // or it may kill the process. In case our process is going to be killed
3677 // from the suspended state, we save preferences before suspending.
3678 rv
= SavePrefFileBlocking();
3685 Preferences::ReadDefaultPrefsFromFile(nsIFile
* aFile
) {
3686 ENSURE_PARENT_PROCESS("Preferences::ReadDefaultPrefsFromFile", "all prefs");
3689 NS_ERROR("ReadDefaultPrefsFromFile requires a parameter");
3690 return NS_ERROR_INVALID_ARG
;
3693 return openPrefFile(aFile
, PrefValueKind::Default
);
3697 Preferences::ReadUserPrefsFromFile(nsIFile
* aFile
) {
3698 ENSURE_PARENT_PROCESS("Preferences::ReadUserPrefsFromFile", "all prefs");
3701 NS_ERROR("ReadUserPrefsFromFile requires a parameter");
3702 return NS_ERROR_INVALID_ARG
;
3705 return openPrefFile(aFile
, PrefValueKind::User
);
3709 Preferences::ResetPrefs() {
3710 ENSURE_PARENT_PROCESS("Preferences::ResetPrefs", "all prefs");
3713 return NS_ERROR_NOT_AVAILABLE
;
3716 HashTable()->clearAndCompact();
3717 Unused
<< HashTable()->reserve(kHashTableInitialLengthParent
);
3719 PrefNameArena().Clear();
3721 return InitInitialObjects(/* isStartup */ false);
3725 Preferences::ResetUserPrefs() {
3726 ENSURE_PARENT_PROCESS("Preferences::ResetUserPrefs", "all prefs");
3727 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
3728 MOZ_ASSERT(NS_IsMainThread());
3730 Vector
<const char*> prefNames
;
3731 for (auto iter
= HashTable()->modIter(); !iter
.done(); iter
.next()) {
3732 Pref
* pref
= iter
.get().get();
3734 if (pref
->HasUserValue()) {
3735 if (!prefNames
.append(pref
->Name())) {
3736 return NS_ERROR_OUT_OF_MEMORY
;
3739 pref
->ClearUserValue();
3740 if (!pref
->HasDefaultValue()) {
3746 for (const char* prefName
: prefNames
) {
3747 NotifyCallbacks(nsDependentCString(prefName
));
3750 Preferences::HandleDirty();
3754 bool Preferences::AllowOffMainThreadSave() {
3755 // Put in a preference that allows us to disable off main thread preference
3757 if (sAllowOMTPrefWrite
< 0) {
3759 Preferences::GetBool("preferences.allow.omt-write", &value
);
3760 sAllowOMTPrefWrite
= value
? 1 : 0;
3763 return !!sAllowOMTPrefWrite
;
3766 nsresult
Preferences::SavePrefFileBlocking() {
3768 return SavePrefFileInternal(nullptr, SaveMethod::Blocking
);
3771 // If we weren't dirty to start, SavePrefFileInternal will early exit so
3772 // there is no guarantee that we don't have oustanding async saves in the
3773 // pipe. Since the contract of SavePrefFileOnMainThread is that the file on
3774 // disk matches the preferences, we have to make sure those requests are
3777 if (AllowOffMainThreadSave()) {
3778 PreferencesWriter::Flush();
3784 nsresult
Preferences::SavePrefFileAsynchronous() {
3785 return SavePrefFileInternal(nullptr, SaveMethod::Asynchronous
);
3789 Preferences::SavePrefFile(nsIFile
* aFile
) {
3790 // This is the method accessible from service API. Make it off main thread.
3791 return SavePrefFileInternal(aFile
, SaveMethod::Asynchronous
);
3795 void Preferences::SetPreference(const dom::Pref
& aDomPref
) {
3796 MOZ_ASSERT(!XRE_IsParentProcess());
3797 NS_ENSURE_TRUE(InitStaticMembers(), (void)0);
3799 const nsCString
& prefName
= aDomPref
.name();
3802 auto p
= HashTable()->lookupForAdd(prefName
.get());
3804 pref
= new Pref(prefName
);
3805 if (!HashTable()->add(p
, pref
)) {
3813 bool valueChanged
= false;
3814 pref
->FromDomPref(aDomPref
, &valueChanged
);
3816 // When the parent process clears a pref's user value we get a DomPref here
3817 // with no default value and no user value. There are two possibilities.
3819 // - There was an existing pref with only a user value. FromDomPref() will
3820 // have just cleared that user value, so the pref can be removed.
3822 // - There was no existing pref. FromDomPref() will have done nothing, and
3823 // `pref` will be valueless. We will end up adding and removing the value
3824 // needlessly, but that's ok because this case is rare.
3826 if (!pref
->HasDefaultValue() && !pref
->HasUserValue()) {
3827 // If the preference exists in the shared map, we need to keep the dynamic
3828 // entry around to mask it.
3829 if (gSharedMap
->Has(pref
->Name())) {
3830 pref
->SetType(PrefType::None
);
3832 HashTable()->remove(prefName
.get());
3837 // Note: we don't have to worry about HandleDirty() because we are setting
3838 // prefs in the content process that have come from the parent process.
3842 NotifyCallbacks(prefName
, PrefWrapper(pref
));
3844 NotifyCallbacks(prefName
);
3850 void Preferences::GetPreference(dom::Pref
* aDomPref
) {
3851 MOZ_ASSERT(XRE_IsParentProcess());
3853 Pref
* pref
= pref_HashTableLookup(aDomPref
->name().get());
3854 if (pref
&& pref
->HasAdvisablySizedValues()) {
3855 pref
->ToDomPref(aDomPref
);
3860 bool Preferences::ArePrefsInitedInContentProcess() {
3861 MOZ_ASSERT(!XRE_IsParentProcess());
3862 return gContentProcessPrefsAreInited
;
3867 Preferences::GetBranch(const char* aPrefRoot
, nsIPrefBranch
** aRetVal
) {
3868 if ((nullptr != aPrefRoot
) && (*aPrefRoot
!= '\0')) {
3869 // TODO: Cache this stuff and allow consumers to share branches (hold weak
3870 // references, I think).
3871 RefPtr
<nsPrefBranch
> prefBranch
=
3872 new nsPrefBranch(aPrefRoot
, PrefValueKind::User
);
3873 prefBranch
.forget(aRetVal
);
3875 // Special case: caching the default root.
3876 nsCOMPtr
<nsIPrefBranch
> root(sPreferences
->mRootBranch
);
3877 root
.forget(aRetVal
);
3884 Preferences::GetDefaultBranch(const char* aPrefRoot
, nsIPrefBranch
** aRetVal
) {
3885 if (!aPrefRoot
|| !aPrefRoot
[0]) {
3886 nsCOMPtr
<nsIPrefBranch
> root(sPreferences
->mDefaultRootBranch
);
3887 root
.forget(aRetVal
);
3891 // TODO: Cache this stuff and allow consumers to share branches (hold weak
3892 // references, I think).
3893 RefPtr
<nsPrefBranch
> prefBranch
=
3894 new nsPrefBranch(aPrefRoot
, PrefValueKind::Default
);
3896 return NS_ERROR_OUT_OF_MEMORY
;
3899 prefBranch
.forget(aRetVal
);
3904 Preferences::ReadStats(nsIPrefStatsCallback
* aCallback
) {
3905 #ifdef ACCESS_COUNTS
3906 for (const auto& entry
: *gAccessCounts
) {
3907 aCallback
->Visit(entry
.GetKey(), entry
.GetData());
3912 return NS_ERROR_NOT_IMPLEMENTED
;
3917 Preferences::ResetStats() {
3918 #ifdef ACCESS_COUNTS
3919 gAccessCounts
->Clear();
3922 return NS_ERROR_NOT_IMPLEMENTED
;
3926 // We would much prefer to use C++ lambdas, but we cannot convert
3927 // lambdas that capture (here, the underlying observer) to C pointer
3928 // to functions. So, here we are, with icky C callbacks. Be aware
3929 // that nothing is thread-safe here because there's a single global
3930 // `nsIPrefObserver` instance. Use this from the main thread only.
3931 nsIPrefObserver
* PrefObserver
= nullptr;
3933 void HandlePref(const char* aPrefName
, PrefType aType
, PrefValueKind aKind
,
3934 PrefValue aValue
, bool aIsSticky
, bool aIsLocked
) {
3935 MOZ_ASSERT(NS_IsMainThread());
3937 if (!PrefObserver
) {
3941 const char* kind
= aKind
== PrefValueKind::Default
? "Default" : "User";
3944 case PrefType::String
:
3945 PrefObserver
->OnStringPref(kind
, aPrefName
, aValue
.mStringVal
, aIsSticky
,
3949 PrefObserver
->OnIntPref(kind
, aPrefName
, aValue
.mIntVal
, aIsSticky
,
3952 case PrefType::Bool
:
3953 PrefObserver
->OnBoolPref(kind
, aPrefName
, aValue
.mBoolVal
, aIsSticky
,
3957 PrefObserver
->OnError("Unexpected pref type.");
3961 void HandleError(const char* aMsg
) {
3962 MOZ_ASSERT(NS_IsMainThread());
3964 if (!PrefObserver
) {
3968 PrefObserver
->OnError(aMsg
);
3972 Preferences::ParsePrefsFromBuffer(const nsTArray
<uint8_t>& aBytes
,
3973 nsIPrefObserver
* aObserver
,
3974 const char* aPathLabel
) {
3975 MOZ_ASSERT(NS_IsMainThread());
3977 // We need a null-terminated buffer.
3978 nsTArray
<uint8_t> data
= aBytes
.Clone();
3979 data
.AppendElement(0);
3981 // Parsing as default handles both `pref` and `user_pref`.
3982 PrefObserver
= aObserver
;
3983 prefs_parser_parse(aPathLabel
? aPathLabel
: "<ParsePrefsFromBuffer data>",
3984 PrefValueKind::Default
, (const char*)data
.Elements(),
3985 data
.Length() - 1, HandlePref
, HandleError
);
3986 PrefObserver
= nullptr;
3992 Preferences::GetDirty(bool* aRetVal
) {
3997 nsresult
Preferences::NotifyServiceObservers(const char* aTopic
) {
3998 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
3999 if (!observerService
) {
4000 return NS_ERROR_FAILURE
;
4003 auto subject
= static_cast<nsIPrefService
*>(this);
4004 observerService
->NotifyObservers(subject
, aTopic
, nullptr);
4009 already_AddRefed
<nsIFile
> Preferences::ReadSavedPrefs() {
4010 nsCOMPtr
<nsIFile
> file
;
4012 NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE
, getter_AddRefs(file
));
4013 if (NS_WARN_IF(NS_FAILED(rv
))) {
4017 rv
= openPrefFile(file
, PrefValueKind::User
);
4018 if (rv
== NS_ERROR_FILE_NOT_FOUND
) {
4019 // This is a normal case for new users.
4020 Telemetry::ScalarSet(
4021 Telemetry::ScalarID::PREFERENCES_CREATED_NEW_USER_PREFS_FILE
, true);
4023 } else if (NS_FAILED(rv
)) {
4024 // Save a backup copy of the current (invalid) prefs file, since all prefs
4025 // from the error line to the end of the file will be lost (bug 361102).
4026 // TODO we should notify the user about it (bug 523725).
4027 Telemetry::ScalarSet(
4028 Telemetry::ScalarID::PREFERENCES_PREFS_FILE_WAS_INVALID
, true);
4029 MakeBackupPrefFile(file
);
4032 return file
.forget();
4035 void Preferences::ReadUserOverridePrefs() {
4036 nsCOMPtr
<nsIFile
> aFile
;
4038 NS_GetSpecialDirectory(NS_APP_PREFS_50_DIR
, getter_AddRefs(aFile
));
4039 if (NS_WARN_IF(NS_FAILED(rv
))) {
4043 aFile
->AppendNative("user.js"_ns
);
4044 rv
= openPrefFile(aFile
, PrefValueKind::User
);
4045 if (rv
!= NS_ERROR_FILE_NOT_FOUND
) {
4046 // If the file exists and was at least partially read, record that in
4047 // telemetry as it may be a sign of pref injection.
4048 Telemetry::ScalarSet(Telemetry::ScalarID::PREFERENCES_READ_USER_JS
, true);
4052 nsresult
Preferences::MakeBackupPrefFile(nsIFile
* aFile
) {
4053 // Example: this copies "prefs.js" to "Invalidprefs.js" in the same directory.
4054 // "Invalidprefs.js" is removed if it exists, prior to making the copy.
4055 nsAutoString newFilename
;
4056 nsresult rv
= aFile
->GetLeafName(newFilename
);
4057 NS_ENSURE_SUCCESS(rv
, rv
);
4059 newFilename
.InsertLiteral(u
"Invalid", 0);
4060 nsCOMPtr
<nsIFile
> newFile
;
4061 rv
= aFile
->GetParent(getter_AddRefs(newFile
));
4062 NS_ENSURE_SUCCESS(rv
, rv
);
4064 rv
= newFile
->Append(newFilename
);
4065 NS_ENSURE_SUCCESS(rv
, rv
);
4067 bool exists
= false;
4068 newFile
->Exists(&exists
);
4070 rv
= newFile
->Remove(false);
4071 NS_ENSURE_SUCCESS(rv
, rv
);
4074 rv
= aFile
->CopyTo(nullptr, newFilename
);
4075 NS_ENSURE_SUCCESS(rv
, rv
);
4080 nsresult
Preferences::SavePrefFileInternal(nsIFile
* aFile
,
4081 SaveMethod aSaveMethod
) {
4082 ENSURE_PARENT_PROCESS("Preferences::SavePrefFileInternal", "all prefs");
4084 // We allow different behavior here when aFile argument is not null, but it
4085 // happens to be the same as the current file. It is not clear that we
4086 // should, but it does give us a "force" save on the unmodified pref file
4087 // (see the original bug 160377 when we added this.)
4089 if (nullptr == aFile
) {
4090 mSavePending
= false;
4092 // Off main thread writing only if allowed.
4093 if (!AllowOffMainThreadSave()) {
4094 aSaveMethod
= SaveMethod::Blocking
;
4097 // The mDirty flag tells us if we should write to mCurrentFile. We only
4098 // check this flag when the caller wants to write to the default.
4103 // Check for profile shutdown after mDirty because the runnables from
4104 // HandleDirty() can still be pending.
4105 if (mProfileShutdown
) {
4106 NS_WARNING("Cannot save pref file after profile shutdown.");
4107 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
4110 // It's possible that we never got a prefs file.
4111 nsresult rv
= NS_OK
;
4113 rv
= WritePrefFile(mCurrentFile
, aSaveMethod
);
4116 // If we succeeded writing to mCurrentFile, reset the dirty flag.
4117 if (NS_SUCCEEDED(rv
)) {
4123 // We only allow off main thread writes on mCurrentFile.
4124 return WritePrefFile(aFile
, SaveMethod::Blocking
);
4128 nsresult
Preferences::WritePrefFile(nsIFile
* aFile
, SaveMethod aSaveMethod
) {
4129 MOZ_ASSERT(XRE_IsParentProcess());
4132 return NS_ERROR_NOT_INITIALIZED
;
4135 AUTO_PROFILER_LABEL("Preferences::WritePrefFile", OTHER
);
4137 if (AllowOffMainThreadSave()) {
4138 nsresult rv
= NS_OK
;
4139 UniquePtr
<PrefSaveData
> prefs
= MakeUnique
<PrefSaveData
>(pref_savePrefs());
4141 // Put the newly constructed preference data into sPendingWriteData
4142 // for the next request to pick up
4143 prefs
.reset(PreferencesWriter::sPendingWriteData
.exchange(prefs
.release()));
4145 // There was a previous request that hasn't been processed,
4146 // and this is the data it had.
4150 // There were no previous requests. Dispatch one since sPendingWriteData has
4151 // the up to date information.
4152 nsCOMPtr
<nsIEventTarget
> target
=
4153 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
, &rv
);
4154 if (NS_SUCCEEDED(rv
)) {
4155 bool async
= aSaveMethod
== SaveMethod::Asynchronous
;
4157 // Increment sPendingWriteCount, even though it's redundant to track this
4158 // in the case of a sync runnable; it just makes it easier to simply
4159 // decrement this inside PWRunnable. We could alternatively increment
4160 // sPendingWriteCount in PWRunnable's constructor, but if for any reason
4161 // in future code we create a PWRunnable without dispatching it, we would
4162 // get stuck in an infinite SpinEventLoopUntil inside
4163 // PreferencesWriter::Flush. Better that in future code we miss an
4164 // increment of sPendingWriteCount and cause a simple crash due to it
4165 // ending up negative.
4166 PreferencesWriter::sPendingWriteCount
++;
4168 rv
= target
->Dispatch(new PWRunnable(aFile
),
4169 nsIEventTarget::DISPATCH_NORMAL
);
4171 // Note that we don't get the nsresult return value here.
4172 SyncRunnable::DispatchToThread(target
, new PWRunnable(aFile
), true);
4177 // If we can't get the thread for writing, for whatever reason, do the main
4178 // thread write after making some noise.
4179 MOZ_ASSERT(false, "failed to get the target thread for OMT pref write");
4182 // This will do a main thread write. It is safe to do it this way because
4183 // AllowOffMainThreadSave() returns a consistent value for the lifetime of
4184 // the parent process.
4185 PrefSaveData prefsData
= pref_savePrefs();
4186 return PreferencesWriter::Write(aFile
, prefsData
);
4189 static nsresult
openPrefFile(nsIFile
* aFile
, PrefValueKind aKind
) {
4190 MOZ_ASSERT(XRE_IsParentProcess());
4193 MOZ_TRY_VAR(data
, URLPreloader::ReadFile(aFile
));
4195 nsAutoString filenameUtf16
;
4196 aFile
->GetLeafName(filenameUtf16
);
4197 NS_ConvertUTF16toUTF8
filename(filenameUtf16
);
4200 aFile
->GetPath(path
);
4203 if (!parser
.Parse(aKind
, NS_ConvertUTF16toUTF8(path
).get(), data
)) {
4204 return NS_ERROR_FILE_CORRUPTED
;
4210 static nsresult
parsePrefData(const nsCString
& aData
, PrefValueKind aKind
) {
4211 const nsCString path
= "$MOZ_DEFAULT_PREFS"_ns
;
4214 if (!parser
.Parse(aKind
, path
.get(), aData
)) {
4215 return NS_ERROR_FILE_CORRUPTED
;
4221 static int pref_CompareFileNames(nsIFile
* aFile1
, nsIFile
* aFile2
,
4222 void* /* unused */) {
4223 nsAutoCString filename1
, filename2
;
4224 aFile1
->GetNativeLeafName(filename1
);
4225 aFile2
->GetNativeLeafName(filename2
);
4227 return Compare(filename2
, filename1
);
4230 // Load default pref files from a directory. The files in the directory are
4231 // sorted reverse-alphabetically; a set of "special file names" may be
4232 // specified which are loaded after all the others.
4233 static nsresult
pref_LoadPrefsInDir(nsIFile
* aDir
,
4234 char const* const* aSpecialFiles
,
4235 uint32_t aSpecialFilesCount
) {
4236 MOZ_ASSERT(XRE_IsParentProcess());
4240 nsCOMPtr
<nsIDirectoryEnumerator
> dirIterator
;
4242 // This may fail in some normal cases, such as embedders who do not use a
4244 rv
= aDir
->GetDirectoryEntries(getter_AddRefs(dirIterator
));
4245 if (NS_FAILED(rv
)) {
4246 // If the directory doesn't exist, then we have no reason to complain. We
4247 // loaded everything (and nothing) successfully.
4248 if (rv
== NS_ERROR_FILE_NOT_FOUND
||
4249 rv
== NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
) {
4255 nsCOMArray
<nsIFile
> prefFiles(INITIAL_PREF_FILES
);
4256 nsCOMArray
<nsIFile
> specialFiles(aSpecialFilesCount
);
4257 nsCOMPtr
<nsIFile
> prefFile
;
4259 while (NS_SUCCEEDED(dirIterator
->GetNextFile(getter_AddRefs(prefFile
))) &&
4261 nsAutoCString leafName
;
4262 prefFile
->GetNativeLeafName(leafName
);
4264 !leafName
.IsEmpty(),
4265 "Failure in default prefs: directory enumerator returned empty file?");
4267 // Skip non-js files.
4268 if (StringEndsWith(leafName
, ".js"_ns
,
4269 nsCaseInsensitiveCStringComparator
)) {
4270 bool shouldParse
= true;
4272 // Separate out special files.
4273 for (uint32_t i
= 0; i
< aSpecialFilesCount
; ++i
) {
4274 if (leafName
.Equals(nsDependentCString(aSpecialFiles
[i
]))) {
4275 shouldParse
= false;
4276 // Special files should be processed in order. We put them into the
4277 // array by index, which can make the array sparse.
4278 specialFiles
.ReplaceObjectAt(prefFile
, i
);
4283 prefFiles
.AppendObject(prefFile
);
4288 if (prefFiles
.Count() + specialFiles
.Count() == 0) {
4289 NS_WARNING("No default pref files found.");
4290 if (NS_SUCCEEDED(rv
)) {
4291 rv
= NS_SUCCESS_FILE_DIRECTORY_EMPTY
;
4296 prefFiles
.Sort(pref_CompareFileNames
, nullptr);
4298 uint32_t arrayCount
= prefFiles
.Count();
4300 for (i
= 0; i
< arrayCount
; ++i
) {
4301 rv2
= openPrefFile(prefFiles
[i
], PrefValueKind::Default
);
4302 if (NS_FAILED(rv2
)) {
4303 NS_ERROR("Default pref file not parsed successfully.");
4308 arrayCount
= specialFiles
.Count();
4309 for (i
= 0; i
< arrayCount
; ++i
) {
4310 // This may be a sparse array; test before parsing.
4311 nsIFile
* file
= specialFiles
[i
];
4313 rv2
= openPrefFile(file
, PrefValueKind::Default
);
4314 if (NS_FAILED(rv2
)) {
4315 NS_ERROR("Special default pref file not parsed successfully.");
4324 static nsresult
pref_ReadPrefFromJar(nsZipArchive
* aJarReader
,
4325 const char* aName
) {
4327 MOZ_TRY_VAR(manifest
,
4328 URLPreloader::ReadZip(aJarReader
, nsDependentCString(aName
)));
4331 if (!parser
.Parse(PrefValueKind::Default
, aName
, manifest
)) {
4332 return NS_ERROR_FILE_CORRUPTED
;
4338 static nsresult
pref_ReadDefaultPrefs(const RefPtr
<nsZipArchive
> jarReader
,
4340 UniquePtr
<nsZipFind
> find
;
4341 nsTArray
<nsCString
> prefEntries
;
4342 const char* entryName
;
4343 uint16_t entryNameLen
;
4345 nsresult rv
= jarReader
->FindInit(path
, getter_Transfers(find
));
4346 NS_ENSURE_SUCCESS(rv
, rv
);
4348 while (NS_SUCCEEDED(find
->FindNext(&entryName
, &entryNameLen
))) {
4349 prefEntries
.AppendElement(Substring(entryName
, entryNameLen
));
4353 for (uint32_t i
= prefEntries
.Length(); i
--;) {
4354 rv
= pref_ReadPrefFromJar(jarReader
, prefEntries
[i
].get());
4355 if (NS_FAILED(rv
)) {
4356 NS_WARNING("Error parsing preferences.");
4363 static nsCString
PrefValueToString(const bool* b
) {
4364 return nsCString(*b
? "true" : "false");
4366 static nsCString
PrefValueToString(const int* i
) {
4367 return nsPrintfCString("%d", *i
);
4369 static nsCString
PrefValueToString(const uint32_t* u
) {
4370 return nsPrintfCString("%d", *u
);
4372 static nsCString
PrefValueToString(const float* f
) {
4373 return nsPrintfCString("%f", *f
);
4375 static nsCString
PrefValueToString(const nsACString
& s
) { return nsCString(s
); }
4377 // These preference getter wrappers allow us to look up the value for static
4378 // preferences based on their native types, rather than manually mapping them to
4379 // the appropriate Preferences::Get* functions.
4380 // We define these methods in a struct which is made friend of Preferences in
4381 // order to access private members.
4383 struct PreferenceReadMarker
{
4384 static constexpr Span
<const char> MarkerTypeName() {
4385 return MakeStringSpan("PreferenceRead");
4387 static void StreamJSONMarkerData(
4388 baseprofiler::SpliceableJSONWriter
& aWriter
,
4389 const ProfilerString8View
& aPrefName
,
4390 const Maybe
<PrefValueKind
>& aPrefKind
, PrefType aPrefType
,
4391 const ProfilerString8View
& aPrefValue
) {
4392 aWriter
.StringProperty("prefName", aPrefName
);
4393 aWriter
.StringProperty("prefKind", PrefValueKindToString(aPrefKind
));
4394 aWriter
.StringProperty("prefType", PrefTypeToString(aPrefType
));
4395 aWriter
.StringProperty("prefValue", aPrefValue
);
4397 static MarkerSchema
MarkerTypeDisplay() {
4398 using MS
= MarkerSchema
;
4399 MS schema
{MS::Location::markerChart
, MS::Location::markerTable
};
4400 schema
.AddKeyLabelFormat("prefName", "Name", MS::Format::string
);
4401 schema
.AddKeyLabelFormat("prefKind", "Kind", MS::Format::string
);
4402 schema
.AddKeyLabelFormat("prefType", "Type", MS::Format::string
);
4403 schema
.AddKeyLabelFormat("prefValue", "Value", MS::Format::string
);
4408 static Span
<const char> PrefValueKindToString(
4409 const Maybe
<PrefValueKind
>& aKind
) {
4411 return *aKind
== PrefValueKind::Default
? MakeStringSpan("Default")
4412 : MakeStringSpan("User");
4417 static Span
<const char> PrefTypeToString(PrefType type
) {
4419 case PrefType::None
:
4423 case PrefType::Bool
:
4425 case PrefType::String
:
4428 MOZ_ASSERT_UNREACHABLE("Unknown preference type.");
4434 template <typename T
>
4435 static nsresult
GetPrefValue(const char* aPrefName
, T
&& aResult
,
4436 PrefValueKind aKind
) {
4437 nsresult rv
= NS_ERROR_UNEXPECTED
;
4438 NS_ENSURE_TRUE(Preferences::InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
4440 if (Maybe
<PrefWrapper
> pref
= pref_Lookup(aPrefName
)) {
4441 rv
= pref
->GetValue(aKind
, std::forward
<T
>(aResult
));
4443 if (profiler_feature_active(ProfilerFeature::PreferenceReads
)) {
4444 profiler_add_marker(
4445 "PreferenceRead", baseprofiler::category::OTHER_PreferenceRead
, {},
4446 PreferenceReadMarker
{},
4447 ProfilerString8View::WrapNullTerminatedString(aPrefName
),
4448 Some(aKind
), pref
->Type(), PrefValueToString(aResult
));
4455 template <typename T
>
4456 static nsresult
GetSharedPrefValue(const char* aName
, T
* aResult
) {
4457 nsresult rv
= NS_ERROR_UNEXPECTED
;
4459 if (Maybe
<PrefWrapper
> pref
= pref_SharedLookup(aName
)) {
4460 rv
= pref
->GetValue(PrefValueKind::User
, aResult
);
4462 if (profiler_feature_active(ProfilerFeature::PreferenceReads
)) {
4463 profiler_add_marker(
4464 "PreferenceRead", baseprofiler::category::OTHER_PreferenceRead
, {},
4465 PreferenceReadMarker
{},
4466 ProfilerString8View::WrapNullTerminatedString(aName
),
4467 Nothing() /* indicates Shared */, pref
->Type(),
4468 PrefValueToString(aResult
));
4475 template <typename T
>
4476 static T
GetPref(const char* aPrefName
, T aFallback
,
4477 PrefValueKind aKind
= PrefValueKind::User
) {
4478 T result
= aFallback
;
4479 GetPrefValue(aPrefName
, &result
, aKind
);
4483 template <typename T
>
4484 static void UpdateMirror(const char* aPref
, void* aMirror
) {
4485 StripAtomic
<T
> value
;
4487 nsresult rv
= GetPrefValue(aPref
, &value
, PrefValueKind::User
);
4488 if (NS_SUCCEEDED(rv
)) {
4489 *static_cast<T
*>(aMirror
) = value
;
4491 // GetPrefValue() can fail if the update is caused by the pref being
4492 // deleted or if it fails to make a cast. This assertion is the only place
4493 // where we safeguard these. In this case the mirror variable will be
4494 // untouched, thus keeping the value it had prior to the change.
4495 // (Note that this case won't happen for a deletion via DeleteBranch()
4496 // unless bug 343600 is fixed, but it will happen for a deletion via
4497 // ClearUserPref().)
4498 NS_WARNING(nsPrintfCString("Pref changed failure: %s\n", aPref
).get());
4503 template <typename T
>
4504 static nsresult
RegisterCallback(void* aMirror
, const nsACString
& aPref
) {
4505 return Preferences::RegisterCallback(UpdateMirror
<T
>, aPref
, aMirror
,
4506 Preferences::ExactMatch
,
4507 /* isPriority */ true);
4511 // Initialize default preference JavaScript buffers from appropriate TEXT
4514 nsresult
Preferences::InitInitialObjects(bool aIsStartup
) {
4515 MOZ_ASSERT(NS_IsMainThread());
4517 if (!XRE_IsParentProcess()) {
4518 MOZ_DIAGNOSTIC_ASSERT(gSharedMap
);
4520 StaticPrefs::StartObservingAlwaysPrefs();
4525 // Initialize static prefs before prefs from data files so that the latter
4526 // will override the former.
4527 StaticPrefs::InitAll();
4529 // In the omni.jar case, we load the following prefs:
4530 // - jar:$gre/omni.jar!/greprefs.js
4531 // - jar:$gre/omni.jar!/defaults/pref/*.js
4533 // In the non-omni.jar case, we load:
4534 // - $gre/greprefs.js
4536 // In both cases, we also load:
4537 // - $gre/defaults/pref/*.js
4539 // This is kept for bug 591866 (channel-prefs.js should not be in omni.jar)
4540 // in the `$app == $gre` case; we load all files instead of channel-prefs.js
4541 // only to have the same behaviour as `$app != $gre`, where this is required
4542 // as a supported location for GRE preferences.
4544 // When `$app != $gre`, we additionally load, in the omni.jar case:
4545 // - jar:$app/omni.jar!/defaults/preferences/*.js
4546 // - $app/defaults/preferences/*.js
4548 // and in the non-omni.jar case:
4549 // - $app/defaults/preferences/*.js
4551 // When `$app == $gre`, we additionally load, in the omni.jar case:
4552 // - jar:$gre/omni.jar!/defaults/preferences/*.js
4554 // Thus, in the omni.jar case, we always load app-specific default
4555 // preferences from omni.jar, whether or not `$app == $gre`.
4557 nsresult rv
= NS_ERROR_FAILURE
;
4558 UniquePtr
<nsZipFind
> find
;
4559 nsTArray
<nsCString
> prefEntries
;
4560 const char* entryName
;
4561 uint16_t entryNameLen
;
4563 RefPtr
<nsZipArchive
> jarReader
= Omnijar::GetReader(Omnijar::GRE
);
4565 #ifdef MOZ_WIDGET_ANDROID
4566 // Try to load an architecture-specific greprefs.js first. This will be
4567 // present in FAT AAR builds of GeckoView on Android.
4568 const char* abi
= getenv("MOZ_ANDROID_CPU_ABI");
4571 path
.AppendPrintf("%s/greprefs.js", abi
);
4572 rv
= pref_ReadPrefFromJar(jarReader
, path
.get());
4575 if (NS_FAILED(rv
)) {
4576 // Fallback to toplevel greprefs.js if arch-specific load fails.
4577 rv
= pref_ReadPrefFromJar(jarReader
, "greprefs.js");
4580 // Load jar:$gre/omni.jar!/greprefs.js.
4581 rv
= pref_ReadPrefFromJar(jarReader
, "greprefs.js");
4583 NS_ENSURE_SUCCESS(rv
, rv
);
4585 // Load jar:$gre/omni.jar!/defaults/pref/*.js.
4586 rv
= pref_ReadDefaultPrefs(jarReader
, "defaults/pref/*.js$");
4587 NS_ENSURE_SUCCESS(rv
, rv
);
4589 #ifdef MOZ_BACKGROUNDTASKS
4590 if (BackgroundTasks::IsBackgroundTaskMode()) {
4591 rv
= pref_ReadDefaultPrefs(jarReader
, "defaults/backgroundtasks/*.js$");
4592 NS_ENSURE_SUCCESS(rv
, rv
);
4596 #ifdef MOZ_WIDGET_ANDROID
4597 // Load jar:$gre/omni.jar!/defaults/pref/$MOZ_ANDROID_CPU_ABI/*.js.
4599 path
.AppendPrintf("jar:$gre/omni.jar!/defaults/pref/%s/*.js$", abi
);
4600 pref_ReadDefaultPrefs(jarReader
, path
.get());
4601 NS_ENSURE_SUCCESS(rv
, rv
);
4604 // Load $gre/greprefs.js.
4605 nsCOMPtr
<nsIFile
> greprefsFile
;
4606 rv
= NS_GetSpecialDirectory(NS_GRE_DIR
, getter_AddRefs(greprefsFile
));
4607 NS_ENSURE_SUCCESS(rv
, rv
);
4609 rv
= greprefsFile
->AppendNative("greprefs.js"_ns
);
4610 NS_ENSURE_SUCCESS(rv
, rv
);
4612 rv
= openPrefFile(greprefsFile
, PrefValueKind::Default
);
4613 if (NS_FAILED(rv
)) {
4615 "Error parsing GRE default preferences. Is this an old-style "
4620 // Load $gre/defaults/pref/*.js.
4621 nsCOMPtr
<nsIFile
> defaultPrefDir
;
4622 rv
= NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR
,
4623 getter_AddRefs(defaultPrefDir
));
4624 NS_ENSURE_SUCCESS(rv
, rv
);
4626 // These pref file names should not be used: we process them after all other
4627 // application pref files for backwards compatibility.
4628 static const char* specialFiles
[] = {
4629 #if defined(XP_MACOSX)
4631 #elif defined(XP_WIN)
4633 #elif defined(XP_UNIX)
4639 #elif defined(XP_BEOS)
4644 rv
= pref_LoadPrefsInDir(defaultPrefDir
, specialFiles
,
4645 ArrayLength(specialFiles
));
4646 if (NS_FAILED(rv
)) {
4647 NS_WARNING("Error parsing application default preferences.");
4650 // Load jar:$app/omni.jar!/defaults/preferences/*.js
4651 // or jar:$gre/omni.jar!/defaults/preferences/*.js.
4652 RefPtr
<nsZipArchive
> appJarReader
= Omnijar::GetReader(Omnijar::APP
);
4654 // GetReader(Omnijar::APP) returns null when `$app == $gre`, in
4655 // which case we look for app-specific default preferences in $gre.
4656 if (!appJarReader
) {
4657 appJarReader
= Omnijar::GetReader(Omnijar::GRE
);
4661 rv
= appJarReader
->FindInit("defaults/preferences/*.js$",
4662 getter_Transfers(find
));
4663 NS_ENSURE_SUCCESS(rv
, rv
);
4664 prefEntries
.Clear();
4665 while (NS_SUCCEEDED(find
->FindNext(&entryName
, &entryNameLen
))) {
4666 prefEntries
.AppendElement(Substring(entryName
, entryNameLen
));
4669 for (uint32_t i
= prefEntries
.Length(); i
--;) {
4670 rv
= pref_ReadPrefFromJar(appJarReader
, prefEntries
[i
].get());
4671 if (NS_FAILED(rv
)) {
4672 NS_WARNING("Error parsing preferences.");
4676 #ifdef MOZ_BACKGROUNDTASKS
4677 if (BackgroundTasks::IsBackgroundTaskMode()) {
4678 rv
= appJarReader
->FindInit("defaults/backgroundtasks/*.js$",
4679 getter_Transfers(find
));
4680 NS_ENSURE_SUCCESS(rv
, rv
);
4681 prefEntries
.Clear();
4682 while (NS_SUCCEEDED(find
->FindNext(&entryName
, &entryNameLen
))) {
4683 prefEntries
.AppendElement(Substring(entryName
, entryNameLen
));
4686 for (uint32_t i
= prefEntries
.Length(); i
--;) {
4687 rv
= pref_ReadPrefFromJar(appJarReader
, prefEntries
[i
].get());
4688 if (NS_FAILED(rv
)) {
4689 NS_WARNING("Error parsing preferences.");
4696 nsCOMPtr
<nsIProperties
> dirSvc(
4697 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID
, &rv
));
4698 NS_ENSURE_SUCCESS(rv
, rv
);
4700 nsCOMPtr
<nsISimpleEnumerator
> list
;
4701 dirSvc
->Get(NS_APP_PREFS_DEFAULTS_DIR_LIST
, NS_GET_IID(nsISimpleEnumerator
),
4702 getter_AddRefs(list
));
4705 while (NS_SUCCEEDED(list
->HasMoreElements(&hasMore
)) && hasMore
) {
4706 nsCOMPtr
<nsISupports
> elem
;
4707 list
->GetNext(getter_AddRefs(elem
));
4712 nsCOMPtr
<nsIFile
> path
= do_QueryInterface(elem
);
4717 // Do we care if a file provided by this process fails to load?
4718 pref_LoadPrefsInDir(path
, nullptr, 0);
4722 if (XRE_IsParentProcess()) {
4723 SetupTelemetryPref();
4727 // Now that all prefs have their initial values, install the callbacks for
4728 // `always`-mirrored static prefs. We do this now rather than in
4729 // StaticPrefs::InitAll() so that the callbacks don't need to be traversed
4730 // while we load prefs from data files.
4731 StaticPrefs::StartObservingAlwaysPrefs();
4734 NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID
, nullptr,
4735 NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID
);
4737 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
4738 NS_ENSURE_SUCCESS(rv
, rv
);
4740 observerService
->NotifyObservers(nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID
,
4747 nsresult
Preferences::GetBool(const char* aPrefName
, bool* aResult
,
4748 PrefValueKind aKind
) {
4749 MOZ_ASSERT(aResult
);
4750 return Internals::GetPrefValue(aPrefName
, aResult
, aKind
);
4754 nsresult
Preferences::GetInt(const char* aPrefName
, int32_t* aResult
,
4755 PrefValueKind aKind
) {
4756 MOZ_ASSERT(aResult
);
4757 return Internals::GetPrefValue(aPrefName
, aResult
, aKind
);
4761 nsresult
Preferences::GetFloat(const char* aPrefName
, float* aResult
,
4762 PrefValueKind aKind
) {
4763 MOZ_ASSERT(aResult
);
4764 return Internals::GetPrefValue(aPrefName
, aResult
, aKind
);
4768 nsresult
Preferences::GetCString(const char* aPrefName
, nsACString
& aResult
,
4769 PrefValueKind aKind
) {
4770 aResult
.SetIsVoid(true);
4771 return Internals::GetPrefValue(aPrefName
, aResult
, aKind
);
4775 nsresult
Preferences::GetString(const char* aPrefName
, nsAString
& aResult
,
4776 PrefValueKind aKind
) {
4777 nsAutoCString result
;
4778 nsresult rv
= Preferences::GetCString(aPrefName
, result
, aKind
);
4779 if (NS_SUCCEEDED(rv
)) {
4780 CopyUTF8toUTF16(result
, aResult
);
4786 nsresult
Preferences::GetLocalizedCString(const char* aPrefName
,
4787 nsACString
& aResult
,
4788 PrefValueKind aKind
) {
4789 nsAutoString result
;
4790 nsresult rv
= GetLocalizedString(aPrefName
, result
, aKind
);
4791 if (NS_SUCCEEDED(rv
)) {
4792 CopyUTF16toUTF8(result
, aResult
);
4798 nsresult
Preferences::GetLocalizedString(const char* aPrefName
,
4800 PrefValueKind aKind
) {
4801 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
4802 nsCOMPtr
<nsIPrefLocalizedString
> prefLocalString
;
4803 nsresult rv
= GetRootBranch(aKind
)->GetComplexValue(
4804 aPrefName
, NS_GET_IID(nsIPrefLocalizedString
),
4805 getter_AddRefs(prefLocalString
));
4806 if (NS_SUCCEEDED(rv
)) {
4807 MOZ_ASSERT(prefLocalString
, "Succeeded but the result is NULL");
4808 prefLocalString
->GetData(aResult
);
4814 nsresult
Preferences::GetComplex(const char* aPrefName
, const nsIID
& aType
,
4815 void** aResult
, PrefValueKind aKind
) {
4816 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
4817 return GetRootBranch(aKind
)->GetComplexValue(aPrefName
, aType
, aResult
);
4821 bool Preferences::GetBool(const char* aPrefName
, bool aFallback
,
4822 PrefValueKind aKind
) {
4823 return Internals::GetPref(aPrefName
, aFallback
, aKind
);
4827 int32_t Preferences::GetInt(const char* aPrefName
, int32_t aFallback
,
4828 PrefValueKind aKind
) {
4829 return Internals::GetPref(aPrefName
, aFallback
, aKind
);
4833 uint32_t Preferences::GetUint(const char* aPrefName
, uint32_t aFallback
,
4834 PrefValueKind aKind
) {
4835 return Internals::GetPref(aPrefName
, aFallback
, aKind
);
4839 float Preferences::GetFloat(const char* aPrefName
, float aFallback
,
4840 PrefValueKind aKind
) {
4841 return Internals::GetPref(aPrefName
, aFallback
, aKind
);
4845 nsresult
Preferences::SetCString(const char* aPrefName
,
4846 const nsACString
& aValue
,
4847 PrefValueKind aKind
) {
4848 ENSURE_PARENT_PROCESS("SetCString", aPrefName
);
4849 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
4851 if (aValue
.Length() > MAX_PREF_LENGTH
) {
4852 return NS_ERROR_ILLEGAL_VALUE
;
4855 // It's ok to stash a pointer to the temporary PromiseFlatCString's chars in
4856 // pref because pref_SetPref() duplicates those chars.
4857 PrefValue prefValue
;
4858 const nsCString
& flat
= PromiseFlatCString(aValue
);
4859 prefValue
.mStringVal
= flat
.get();
4860 return pref_SetPref(nsDependentCString(aPrefName
), PrefType::String
, aKind
,
4862 /* isSticky */ false,
4863 /* isLocked */ false,
4864 /* fromInit */ false);
4868 nsresult
Preferences::SetBool(const char* aPrefName
, bool aValue
,
4869 PrefValueKind aKind
) {
4870 ENSURE_PARENT_PROCESS("SetBool", aPrefName
);
4871 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
4873 PrefValue prefValue
;
4874 prefValue
.mBoolVal
= aValue
;
4875 return pref_SetPref(nsDependentCString(aPrefName
), PrefType::Bool
, aKind
,
4877 /* isSticky */ false,
4878 /* isLocked */ false,
4879 /* fromInit */ false);
4883 nsresult
Preferences::SetInt(const char* aPrefName
, int32_t aValue
,
4884 PrefValueKind aKind
) {
4885 ENSURE_PARENT_PROCESS("SetInt", aPrefName
);
4886 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
4888 PrefValue prefValue
;
4889 prefValue
.mIntVal
= aValue
;
4890 return pref_SetPref(nsDependentCString(aPrefName
), PrefType::Int
, aKind
,
4892 /* isSticky */ false,
4893 /* isLocked */ false,
4894 /* fromInit */ false);
4898 nsresult
Preferences::SetComplex(const char* aPrefName
, const nsIID
& aType
,
4899 nsISupports
* aValue
, PrefValueKind aKind
) {
4900 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
4901 return GetRootBranch(aKind
)->SetComplexValue(aPrefName
, aType
, aValue
);
4905 nsresult
Preferences::Lock(const char* aPrefName
) {
4906 ENSURE_PARENT_PROCESS("Lock", aPrefName
);
4907 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
4909 const auto& prefName
= nsDependentCString(aPrefName
);
4913 pref_LookupForModify(prefName
, [](const PrefWrapper
& aPref
) {
4914 return !aPref
.IsLocked();
4918 pref
->SetIsLocked(true);
4919 NotifyCallbacks(prefName
, PrefWrapper(pref
));
4926 nsresult
Preferences::Unlock(const char* aPrefName
) {
4927 ENSURE_PARENT_PROCESS("Unlock", aPrefName
);
4928 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
4930 const auto& prefName
= nsDependentCString(aPrefName
);
4934 pref_LookupForModify(prefName
, [](const PrefWrapper
& aPref
) {
4935 return aPref
.IsLocked();
4939 pref
->SetIsLocked(false);
4940 NotifyCallbacks(prefName
, PrefWrapper(pref
));
4947 bool Preferences::IsLocked(const char* aPrefName
) {
4948 NS_ENSURE_TRUE(InitStaticMembers(), false);
4950 Maybe
<PrefWrapper
> pref
= pref_Lookup(aPrefName
);
4951 return pref
.isSome() && pref
->IsLocked();
4955 nsresult
Preferences::ClearUser(const char* aPrefName
) {
4956 ENSURE_PARENT_PROCESS("ClearUser", aPrefName
);
4957 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
4959 const auto& prefName
= nsDependentCString
{aPrefName
};
4960 auto result
= pref_LookupForModify(
4961 prefName
, [](const PrefWrapper
& aPref
) { return aPref
.HasUserValue(); });
4962 if (result
.isErr()) {
4966 if (Pref
* pref
= result
.unwrap()) {
4967 pref
->ClearUserValue();
4969 if (!pref
->HasDefaultValue()) {
4970 if (!gSharedMap
|| !gSharedMap
->Has(pref
->Name())) {
4971 HashTable()->remove(aPrefName
);
4973 pref
->SetType(PrefType::None
);
4976 NotifyCallbacks(prefName
);
4978 NotifyCallbacks(prefName
, PrefWrapper(pref
));
4981 Preferences::HandleDirty();
4987 bool Preferences::HasUserValue(const char* aPrefName
) {
4988 NS_ENSURE_TRUE(InitStaticMembers(), false);
4990 Maybe
<PrefWrapper
> pref
= pref_Lookup(aPrefName
);
4991 return pref
.isSome() && pref
->HasUserValue();
4995 int32_t Preferences::GetType(const char* aPrefName
) {
4996 NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID
);
4999 return PREF_INVALID
;
5002 Maybe
<PrefWrapper
> pref
= pref_Lookup(aPrefName
);
5003 if (!pref
.isSome()) {
5004 return PREF_INVALID
;
5007 switch (pref
->Type()) {
5008 case PrefType::String
:
5014 case PrefType::Bool
:
5023 nsresult
Preferences::AddStrongObserver(nsIObserver
* aObserver
,
5024 const nsACString
& aPref
) {
5025 MOZ_ASSERT(aObserver
);
5026 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
5027 return sPreferences
->mRootBranch
->AddObserver(aPref
, aObserver
, false);
5031 nsresult
Preferences::AddWeakObserver(nsIObserver
* aObserver
,
5032 const nsACString
& aPref
) {
5033 MOZ_ASSERT(aObserver
);
5034 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
5035 return sPreferences
->mRootBranch
->AddObserver(aPref
, aObserver
, true);
5039 nsresult
Preferences::RemoveObserver(nsIObserver
* aObserver
,
5040 const nsACString
& aPref
) {
5041 MOZ_ASSERT(aObserver
);
5043 MOZ_ASSERT(!sPreferences
);
5044 return NS_OK
; // Observers have been released automatically.
5046 NS_ENSURE_TRUE(sPreferences
, NS_ERROR_NOT_AVAILABLE
);
5047 return sPreferences
->mRootBranch
->RemoveObserver(aPref
, aObserver
);
5050 template <typename T
>
5051 static void AssertNotMallocAllocated(T
* aPtr
) {
5052 #if defined(DEBUG) && defined(MOZ_MEMORY)
5053 jemalloc_ptr_info_t info
;
5054 jemalloc_ptr_info((void*)aPtr
, &info
);
5055 MOZ_ASSERT(info
.tag
== TagUnknown
);
5060 nsresult
Preferences::AddStrongObservers(nsIObserver
* aObserver
,
5061 const char** aPrefs
) {
5062 MOZ_ASSERT(aObserver
);
5063 for (uint32_t i
= 0; aPrefs
[i
]; i
++) {
5064 AssertNotMallocAllocated(aPrefs
[i
]);
5067 pref
.AssignLiteral(aPrefs
[i
], strlen(aPrefs
[i
]));
5068 nsresult rv
= AddStrongObserver(aObserver
, pref
);
5069 NS_ENSURE_SUCCESS(rv
, rv
);
5075 nsresult
Preferences::AddWeakObservers(nsIObserver
* aObserver
,
5076 const char** aPrefs
) {
5077 MOZ_ASSERT(aObserver
);
5078 for (uint32_t i
= 0; aPrefs
[i
]; i
++) {
5079 AssertNotMallocAllocated(aPrefs
[i
]);
5082 pref
.AssignLiteral(aPrefs
[i
], strlen(aPrefs
[i
]));
5083 nsresult rv
= AddWeakObserver(aObserver
, pref
);
5084 NS_ENSURE_SUCCESS(rv
, rv
);
5090 nsresult
Preferences::RemoveObservers(nsIObserver
* aObserver
,
5091 const char** aPrefs
) {
5092 MOZ_ASSERT(aObserver
);
5094 MOZ_ASSERT(!sPreferences
);
5095 return NS_OK
; // Observers have been released automatically.
5097 NS_ENSURE_TRUE(sPreferences
, NS_ERROR_NOT_AVAILABLE
);
5099 for (uint32_t i
= 0; aPrefs
[i
]; i
++) {
5100 nsresult rv
= RemoveObserver(aObserver
, nsDependentCString(aPrefs
[i
]));
5101 NS_ENSURE_SUCCESS(rv
, rv
);
5106 template <typename T
>
5108 nsresult
Preferences::RegisterCallbackImpl(PrefChangedFunc aCallback
,
5109 T
& aPrefNode
, void* aData
,
5110 MatchKind aMatchKind
,
5112 NS_ENSURE_ARG(aCallback
);
5114 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE
);
5116 auto node
= new CallbackNode(aPrefNode
, aCallback
, aData
, aMatchKind
);
5119 // Add to the start of the list.
5120 node
->SetNext(gFirstCallback
);
5121 gFirstCallback
= node
;
5122 if (!gLastPriorityNode
) {
5123 gLastPriorityNode
= node
;
5126 // Add to the start of the non-priority part of the list.
5127 if (gLastPriorityNode
) {
5128 node
->SetNext(gLastPriorityNode
->Next());
5129 gLastPriorityNode
->SetNext(node
);
5131 node
->SetNext(gFirstCallback
);
5132 gFirstCallback
= node
;
5140 nsresult
Preferences::RegisterCallback(PrefChangedFunc aCallback
,
5141 const nsACString
& aPrefNode
, void* aData
,
5142 MatchKind aMatchKind
, bool aIsPriority
) {
5143 return RegisterCallbackImpl(aCallback
, aPrefNode
, aData
, aMatchKind
,
5148 nsresult
Preferences::RegisterCallbacks(PrefChangedFunc aCallback
,
5149 const char** aPrefs
, void* aData
,
5150 MatchKind aMatchKind
) {
5151 return RegisterCallbackImpl(aCallback
, aPrefs
, aData
, aMatchKind
);
5155 nsresult
Preferences::RegisterCallbackAndCall(PrefChangedFunc aCallback
,
5156 const nsACString
& aPref
,
5158 MatchKind aMatchKind
) {
5159 MOZ_ASSERT(aCallback
);
5160 nsresult rv
= RegisterCallback(aCallback
, aPref
, aClosure
, aMatchKind
);
5161 if (NS_SUCCEEDED(rv
)) {
5162 (*aCallback
)(PromiseFlatCString(aPref
).get(), aClosure
);
5168 nsresult
Preferences::RegisterCallbacksAndCall(PrefChangedFunc aCallback
,
5169 const char** aPrefs
,
5171 MOZ_ASSERT(aCallback
);
5174 RegisterCallbacks(aCallback
, aPrefs
, aClosure
, MatchKind::ExactMatch
);
5175 if (NS_SUCCEEDED(rv
)) {
5176 for (const char** ptr
= aPrefs
; *ptr
; ptr
++) {
5177 (*aCallback
)(*ptr
, aClosure
);
5183 template <typename T
>
5185 nsresult
Preferences::UnregisterCallbackImpl(PrefChangedFunc aCallback
,
5186 T
& aPrefNode
, void* aData
,
5187 MatchKind aMatchKind
) {
5188 MOZ_ASSERT(aCallback
);
5190 MOZ_ASSERT(!sPreferences
);
5191 return NS_OK
; // Observers have been released automatically.
5193 NS_ENSURE_TRUE(sPreferences
, NS_ERROR_NOT_AVAILABLE
);
5195 nsresult rv
= NS_ERROR_FAILURE
;
5196 CallbackNode
* node
= gFirstCallback
;
5197 CallbackNode
* prev_node
= nullptr;
5200 if (node
->Func() == aCallback
&& node
->Data() == aData
&&
5201 node
->MatchKind() == aMatchKind
&& node
->DomainIs(aPrefNode
)) {
5202 if (gCallbacksInProgress
) {
5203 // Postpone the node removal until after callbacks enumeration is
5206 gShouldCleanupDeadNodes
= true;
5208 node
= node
->Next();
5210 node
= pref_RemoveCallbackNode(node
, prev_node
);
5215 node
= node
->Next();
5222 nsresult
Preferences::UnregisterCallback(PrefChangedFunc aCallback
,
5223 const nsACString
& aPrefNode
,
5224 void* aData
, MatchKind aMatchKind
) {
5225 return UnregisterCallbackImpl
<const nsACString
&>(aCallback
, aPrefNode
, aData
,
5230 nsresult
Preferences::UnregisterCallbacks(PrefChangedFunc aCallback
,
5231 const char** aPrefs
, void* aData
,
5232 MatchKind aMatchKind
) {
5233 return UnregisterCallbackImpl(aCallback
, aPrefs
, aData
, aMatchKind
);
5236 template <typename T
>
5237 static void AddMirrorCallback(T
* aMirror
, const nsACString
& aPref
) {
5238 MOZ_ASSERT(NS_IsMainThread());
5240 Internals::RegisterCallback
<T
>(aMirror
, aPref
);
5243 // Don't inline because it explodes compile times.
5244 template <typename T
>
5245 static MOZ_NEVER_INLINE
void AddMirror(T
* aMirror
, const nsACString
& aPref
,
5246 StripAtomic
<T
> aDefault
) {
5247 *aMirror
= Internals::GetPref(PromiseFlatCString(aPref
).get(), aDefault
);
5248 AddMirrorCallback(aMirror
, aPref
);
5251 // The InitPref_*() functions below end in a `_<type>` suffix because they are
5252 // used by the PREF macro definition in InitAll() below.
5254 static void InitPref_bool(const nsCString
& aName
, bool aDefaultValue
) {
5255 MOZ_ASSERT(XRE_IsParentProcess());
5257 value
.mBoolVal
= aDefaultValue
;
5258 pref_SetPref(aName
, PrefType::Bool
, PrefValueKind::Default
, value
,
5259 /* isSticky */ false,
5260 /* isLocked */ false,
5261 /* fromInit */ true);
5264 static void InitPref_int32_t(const nsCString
& aName
, int32_t aDefaultValue
) {
5265 MOZ_ASSERT(XRE_IsParentProcess());
5267 value
.mIntVal
= aDefaultValue
;
5268 pref_SetPref(aName
, PrefType::Int
, PrefValueKind::Default
, value
,
5269 /* isSticky */ false,
5270 /* isLocked */ false,
5271 /* fromInit */ true);
5274 static void InitPref_uint32_t(const nsCString
& aName
, uint32_t aDefaultValue
) {
5275 InitPref_int32_t(aName
, int32_t(aDefaultValue
));
5278 static void InitPref_float(const nsCString
& aName
, float aDefaultValue
) {
5279 MOZ_ASSERT(XRE_IsParentProcess());
5281 // Convert the value in a locale-independent way, including a trailing ".0"
5282 // if necessary to distinguish floating-point from integer prefs when viewing
5283 // them in about:config.
5284 nsAutoCString defaultValue
;
5285 defaultValue
.AppendFloat(aDefaultValue
);
5286 if (!defaultValue
.Contains('.') && !defaultValue
.Contains('e')) {
5287 defaultValue
.AppendLiteral(".0");
5289 value
.mStringVal
= defaultValue
.get();
5290 pref_SetPref(aName
, PrefType::String
, PrefValueKind::Default
, value
,
5291 /* isSticky */ false,
5292 /* isLocked */ false,
5293 /* fromInit */ true);
5296 static void InitPref_String(const nsCString
& aName
, const char* aDefaultValue
) {
5297 MOZ_ASSERT(XRE_IsParentProcess());
5299 value
.mStringVal
= aDefaultValue
;
5300 pref_SetPref(aName
, PrefType::String
, PrefValueKind::Default
, value
,
5301 /* isSticky */ false,
5302 /* isLocked */ false,
5303 /* fromInit */ true);
5306 static void InitPref(const nsCString
& aName
, bool aDefaultValue
) {
5307 InitPref_bool(aName
, aDefaultValue
);
5309 static void InitPref(const nsCString
& aName
, int32_t aDefaultValue
) {
5310 InitPref_int32_t(aName
, aDefaultValue
);
5312 static void InitPref(const nsCString
& aName
, uint32_t aDefaultValue
) {
5313 InitPref_uint32_t(aName
, aDefaultValue
);
5315 static void InitPref(const nsCString
& aName
, float aDefaultValue
) {
5316 InitPref_float(aName
, aDefaultValue
);
5319 template <typename T
>
5320 static void InitAlwaysPref(const nsCString
& aName
, T
* aCache
,
5321 StripAtomic
<T
> aDefaultValue
) {
5322 // Only called in the parent process. Set/reset the pref value and the
5323 // `always` mirror to the default value.
5324 // `once` mirrors will be initialized lazily in InitOncePrefs().
5325 InitPref(aName
, aDefaultValue
);
5326 *aCache
= aDefaultValue
;
5329 static Atomic
<bool> sOncePrefRead(false);
5330 static StaticMutex sOncePrefMutex
;
5332 namespace StaticPrefs
{
5334 void MaybeInitOncePrefs() {
5335 if (MOZ_LIKELY(sOncePrefRead
)) {
5336 // `once`-mirrored prefs have already been initialized to their default
5340 StaticMutexAutoLock
lock(sOncePrefMutex
);
5341 if (NS_IsMainThread()) {
5344 RefPtr
<Runnable
> runnable
= NS_NewRunnableFunction(
5345 "Preferences::MaybeInitOncePrefs", [&]() { InitOncePrefs(); });
5346 // This logic needs to run on the main thread
5347 SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), runnable
);
5349 sOncePrefRead
= true;
5352 // For mirrored prefs we generate a variable definition.
5353 #define NEVER_PREF(name, cpp_type, value)
5354 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, default_value) \
5355 cpp_type sMirror_##full_id(default_value);
5356 #define ONCE_PREF(name, base_id, full_id, cpp_type, default_value) \
5357 cpp_type sMirror_##full_id(default_value);
5358 #include "mozilla/StaticPrefListAll.h"
5363 static void InitAll() {
5364 MOZ_ASSERT(NS_IsMainThread());
5365 MOZ_ASSERT(XRE_IsParentProcess());
5367 // For all prefs we generate some initialization code.
5369 // The InitPref_*() functions have a type suffix to avoid ambiguity between
5370 // prefs having int32_t and float default values. That suffix is not needed
5371 // for the InitAlwaysPref() functions because they take a pointer parameter,
5372 // which prevents automatic int-to-float coercion.
5373 #define NEVER_PREF(name, cpp_type, value) \
5374 InitPref_##cpp_type(name ""_ns, value);
5375 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) \
5376 InitAlwaysPref(name ""_ns, &sMirror_##full_id, value);
5377 #define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
5378 InitPref_##cpp_type(name ""_ns, value);
5379 #include "mozilla/StaticPrefListAll.h"
5385 static void StartObservingAlwaysPrefs() {
5386 MOZ_ASSERT(NS_IsMainThread());
5388 // Call AddMirror so that our mirrors for `always` prefs will stay updated.
5389 // The call to AddMirror re-reads the current pref value into the mirror, so
5390 // our mirror will now be up-to-date even if some of the prefs have changed
5391 // since the call to InitAll().
5392 #define NEVER_PREF(name, cpp_type, value)
5393 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) \
5394 AddMirror(&sMirror_##full_id, name ""_ns, sMirror_##full_id);
5395 #define ONCE_PREF(name, base_id, full_id, cpp_type, value)
5396 #include "mozilla/StaticPrefListAll.h"
5402 static void InitOncePrefs() {
5403 // For `once`-mirrored prefs we generate some initialization code. This is
5404 // done in case the pref value was updated when reading pref data files. It's
5405 // necessary because we don't have callbacks registered for `once`-mirrored
5408 // In debug builds, we also install a mechanism that can check if the
5409 // preference value is modified after `once`-mirrored prefs are initialized.
5410 // In tests this would indicate a likely misuse of a `once`-mirrored pref and
5411 // suggest that it should instead be `always`-mirrored.
5412 #define NEVER_PREF(name, cpp_type, value)
5413 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value)
5415 # define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
5417 MOZ_ASSERT(gOnceStaticPrefsAntiFootgun); \
5418 sMirror_##full_id = Internals::GetPref(name, cpp_type(value)); \
5419 auto checkPref = [&]() { \
5420 MOZ_ASSERT(sOncePrefRead); \
5421 cpp_type staticPrefValue = full_id(); \
5422 cpp_type preferenceValue = \
5423 Internals::GetPref(GetPrefName_##base_id(), cpp_type(value)); \
5424 MOZ_ASSERT(staticPrefValue == preferenceValue, \
5425 "Preference '" name \
5426 "' got modified since StaticPrefs::" #full_id \
5427 " was initialized. Consider using an `always` mirror kind " \
5430 gOnceStaticPrefsAntiFootgun->insert( \
5431 std::pair<const char*, AntiFootgunCallback>(GetPrefName_##base_id(), \
5432 std::move(checkPref))); \
5435 # define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
5436 sMirror_##full_id = Internals::GetPref(name, cpp_type(value));
5439 #include "mozilla/StaticPrefListAll.h"
5445 } // namespace StaticPrefs
5447 static MOZ_MAYBE_UNUSED
void SaveOncePrefToSharedMap(
5448 SharedPrefMapBuilder
& aBuilder
, const nsACString
& aName
, bool aValue
) {
5449 auto oncePref
= MakeUnique
<Pref
>(aName
);
5450 oncePref
->SetType(PrefType::Bool
);
5451 oncePref
->SetIsSkippedByIteration(true);
5452 bool valueChanged
= false;
5453 MOZ_ALWAYS_SUCCEEDS(
5454 oncePref
->SetDefaultValue(PrefType::Bool
, PrefValue(aValue
),
5455 /* isSticky */ true,
5456 /* isLocked */ true, &valueChanged
));
5457 oncePref
->AddToMap(aBuilder
);
5460 static MOZ_MAYBE_UNUSED
void SaveOncePrefToSharedMap(
5461 SharedPrefMapBuilder
& aBuilder
, const nsACString
& aName
, int32_t aValue
) {
5462 auto oncePref
= MakeUnique
<Pref
>(aName
);
5463 oncePref
->SetType(PrefType::Int
);
5464 oncePref
->SetIsSkippedByIteration(true);
5465 bool valueChanged
= false;
5466 MOZ_ALWAYS_SUCCEEDS(
5467 oncePref
->SetDefaultValue(PrefType::Int
, PrefValue(aValue
),
5468 /* isSticky */ true,
5469 /* isLocked */ true, &valueChanged
));
5470 oncePref
->AddToMap(aBuilder
);
5473 static MOZ_MAYBE_UNUSED
void SaveOncePrefToSharedMap(
5474 SharedPrefMapBuilder
& aBuilder
, const nsACString
& aName
, uint32_t aValue
) {
5475 SaveOncePrefToSharedMap(aBuilder
, aName
, int32_t(aValue
));
5478 static MOZ_MAYBE_UNUSED
void SaveOncePrefToSharedMap(
5479 SharedPrefMapBuilder
& aBuilder
, const nsACString
& aName
, float aValue
) {
5480 auto oncePref
= MakeUnique
<Pref
>(aName
);
5481 oncePref
->SetType(PrefType::String
);
5482 oncePref
->SetIsSkippedByIteration(true);
5483 nsAutoCString value
;
5484 value
.AppendFloat(aValue
);
5485 bool valueChanged
= false;
5486 // It's ok to stash a pointer to the temporary PromiseFlatCString's chars in
5487 // pref because pref_SetPref() duplicates those chars.
5488 const nsCString
& flat
= PromiseFlatCString(value
);
5489 MOZ_ALWAYS_SUCCEEDS(
5490 oncePref
->SetDefaultValue(PrefType::String
, PrefValue(flat
.get()),
5491 /* isSticky */ true,
5492 /* isLocked */ true, &valueChanged
));
5493 oncePref
->AddToMap(aBuilder
);
5496 #define ONCE_PREF_NAME(name) "$$$" name "$$$"
5498 namespace StaticPrefs
{
5500 static void RegisterOncePrefs(SharedPrefMapBuilder
& aBuilder
) {
5501 MOZ_ASSERT(XRE_IsParentProcess());
5502 MOZ_DIAGNOSTIC_ASSERT(!gSharedMap
,
5503 "Must be called before gSharedMap has been created");
5504 MaybeInitOncePrefs();
5506 // For `once`-mirrored prefs we generate a save call, which saves the value
5507 // as it was at parent startup. It is stored in a special (hidden and locked)
5508 // entry in the global SharedPreferenceMap. In order for the entry to be
5509 // hidden and not appear in about:config nor ever be stored to disk, we set
5510 // its IsSkippedByIteration flag to true. We also distinguish it by adding a
5511 // "$$$" prefix and suffix to the preference name.
5512 #define NEVER_PREF(name, cpp_type, value)
5513 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value)
5514 #define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
5515 SaveOncePrefToSharedMap(aBuilder, ONCE_PREF_NAME(name) ""_ns, \
5516 cpp_type(sMirror_##full_id));
5517 #include "mozilla/StaticPrefListAll.h"
5523 static void InitStaticPrefsFromShared() {
5524 MOZ_ASSERT(!XRE_IsParentProcess());
5525 MOZ_DIAGNOSTIC_ASSERT(gSharedMap
,
5526 "Must be called once gSharedMap has been created");
5528 // For mirrored static prefs we generate some initialization code. Each
5529 // mirror variable is already initialized in the binary with the default
5530 // value. If the pref value hasn't changed from the default in the main
5531 // process (the common case) then the overwriting here won't change the
5532 // mirror variable's value.
5534 // Note that the MOZ_ASSERT calls below can fail in one obscure case: when a
5535 // Firefox update occurs and we get a main process from the old binary (with
5536 // static prefs {A,B,C,D}) plus a new content process from the new binary
5537 // (with static prefs {A,B,C,D,E}). The content process' call to
5538 // GetSharedPrefValue() for pref E will fail because the shared pref map was
5539 // created by the main process, which doesn't have pref E.
5541 // This silent failure is safe. The mirror variable for pref E is already
5542 // initialized to the default value in the content process, and the main
5543 // process cannot have changed pref E because it doesn't know about it!
5545 // Nonetheless, it's useful to have the MOZ_ASSERT here for testing of debug
5546 // builds, where this scenario involving inconsistent binaries should not
5548 #define NEVER_PREF(name, cpp_type, value)
5549 #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) \
5551 StripAtomic<cpp_type> val; \
5552 DebugOnly<nsresult> rv = Internals::GetSharedPrefValue(name, &val); \
5553 MOZ_ASSERT(NS_SUCCEEDED(rv)); \
5554 StaticPrefs::sMirror_##full_id = val; \
5556 #define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
5559 DebugOnly<nsresult> rv = \
5560 Internals::GetSharedPrefValue(ONCE_PREF_NAME(name), &val); \
5561 MOZ_ASSERT(NS_SUCCEEDED(rv)); \
5562 StaticPrefs::sMirror_##full_id = val; \
5564 #include "mozilla/StaticPrefListAll.h"
5569 // `once`-mirrored prefs have been set to their value in the step above and
5570 // outside the parent process they are immutable. We set sOncePrefRead so
5571 // that we can directly skip any lazy initializations.
5572 sOncePrefRead
= true;
5575 } // namespace StaticPrefs
5577 } // namespace mozilla
5579 #undef ENSURE_PARENT_PROCESS
5581 //===========================================================================
5582 // Module and factory stuff
5583 //===========================================================================
5585 NS_IMPL_COMPONENT_FACTORY(nsPrefLocalizedString
) {
5586 auto str
= MakeRefPtr
<nsPrefLocalizedString
>();
5587 if (NS_SUCCEEDED(str
->Init())) {
5588 return str
.forget().downcast
<nsISupports
>();
5595 void UnloadPrefsModule() { Preferences::Shutdown(); }
5597 } // namespace mozilla
5599 // This file contains the C wrappers for the C++ static pref getters, as used
5601 #include "init/StaticPrefsCGetters.cpp"