Bug 1722099 [wpt PR 29768] - Make FloatQuad::BoundingBox treat large finite values...
[gecko.git] / modules / libpref / Preferences.cpp
blob6d9453a4dc0396f1a65b38121b2d2ca840f7c805
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // Documentation for libpref is in modules/libpref/docs/index.rst.
9 #include <ctype.h>
10 #include <stdlib.h>
11 #include <string.h>
13 #include "SharedPrefMap.h"
15 #include "base/basictypes.h"
16 #include "MainThreadUtils.h"
17 #include "mozilla/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"
49 #include "nsCOMPtr.h"
50 #include "nsComponentManagerUtils.h"
51 #include "nsCRT.h"
52 #include "nsTHashMap.h"
53 #include "nsDirectoryServiceDefs.h"
54 #include "nsIConsoleService.h"
55 #include "nsIFile.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"
75 #include "nsString.h"
76 #include "nsTArray.h"
77 #include "nsThreadUtils.h"
78 #include "nsUTF8Utils.h"
79 #include "nsWeakReference.h"
80 #include "nsXPCOMCID.h"
81 #include "nsXPCOM.h"
82 #include "nsXULAppAPI.h"
83 #include "nsZipArchive.h"
84 #include "plbase64.h"
85 #include "PLDHashTable.h"
86 #include "plstr.h"
87 #include "prlink.h"
88 #include "xpcpublic.h"
89 #ifdef MOZ_BACKGROUNDTASKS
90 # include "mozilla/BackgroundTasks.h"
91 #endif
93 #ifdef DEBUG
94 # include <map>
95 #endif
97 #ifdef MOZ_MEMORY
98 # include "mozmemory.h"
99 #endif
101 #ifdef XP_WIN
102 # include "windows.h"
103 #endif
105 using namespace mozilla;
107 using ipc::FileDescriptor;
109 #ifdef DEBUG
111 # define ENSURE_PARENT_PROCESS(func, pref) \
112 do { \
113 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
114 nsPrintfCString msg( \
115 "ENSURE_PARENT_PROCESS: called %s on %s in a non-parent process", \
116 func, pref); \
117 NS_ERROR(msg.get()); \
118 return NS_ERROR_NOT_AVAILABLE; \
120 } while (0)
122 #else // DEBUG
124 # define ENSURE_PARENT_PROCESS(func, pref) \
125 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
126 return NS_ERROR_NOT_AVAILABLE; \
129 #endif // DEBUG
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());
147 aStr.Append('/');
148 aStr.Append(aChars);
151 static char* DeserializeString(char* aChars, nsCString& aStr) {
152 char* p = aChars;
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
158 return p;
161 // Keep this in sync with PrefValue in parser/src/lib.rs.
162 union PrefValue {
163 // PrefValues within Pref objects own their chars. PrefValues passed around
164 // as arguments don't own their chars.
165 const char* mStringVal;
166 int32_t mIntVal;
167 bool mBoolVal;
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) {
178 switch (aType) {
179 case PrefType::String: {
180 if (mStringVal && aValue.mStringVal) {
181 return strcmp(mStringVal, aValue.mStringVal) == 0;
183 if (!mStringVal && !aValue.mStringVal) {
184 return true;
186 return false;
189 case PrefType::Int:
190 return mIntVal == aValue.mIntVal;
192 case PrefType::Bool:
193 return mBoolVal == aValue.mBoolVal;
195 default:
196 MOZ_CRASH("Unhandled enum value");
200 template <typename T>
201 T Get() const;
203 void Init(PrefType aNewType, PrefValue aNewValue) {
204 if (aNewType == PrefType::String) {
205 MOZ_ASSERT(aNewValue.mStringVal);
206 aNewValue.mStringVal = moz_xstrdup(aNewValue.mStringVal);
208 *this = aNewValue;
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) {
222 if (aHasValue) {
223 Clear(aOldType);
225 Init(aNewType, aNewValue);
228 void ToDomPrefValue(PrefType aType, dom::PrefValue* aDomValue) {
229 switch (aType) {
230 case PrefType::String:
231 *aDomValue = nsDependentCString(mStringVal);
232 return;
234 case PrefType::Int:
235 *aDomValue = mIntVal;
236 return;
238 case PrefType::Bool:
239 *aDomValue = mBoolVal;
240 return;
242 default:
243 MOZ_CRASH();
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;
261 default:
262 MOZ_CRASH();
266 void SerializeAndAppend(PrefType aType, nsCString& aStr) {
267 switch (aType) {
268 case PrefType::Bool:
269 aStr.Append(mBoolVal ? 'T' : 'F');
270 break;
272 case PrefType::Int:
273 aStr.AppendInt(mIntVal);
274 break;
276 case PrefType::String: {
277 SerializeAndAppendString(nsDependentCString(mStringVal), aStr);
278 break;
281 case PrefType::None:
282 default:
283 MOZ_CRASH();
287 static char* Deserialize(PrefType aType, char* aStr,
288 Maybe<dom::PrefValue>* aDomValue) {
289 char* p = aStr;
291 switch (aType) {
292 case PrefType::Bool:
293 if (*p == 'T') {
294 *aDomValue = Some(true);
295 } else if (*p == 'F') {
296 *aDomValue = Some(false);
297 } else {
298 *aDomValue = Some(false);
299 NS_ERROR("bad bool pref value");
301 p++;
302 return p;
304 case PrefType::Int: {
305 *aDomValue = Some(int32_t(strtol(p, &p, 10)));
306 return p;
309 case PrefType::String: {
310 nsCString str;
311 p = DeserializeString(p, str);
312 *aDomValue = Some(str);
313 return p;
316 default:
317 MOZ_CRASH();
322 template <>
323 bool PrefValue::Get() const {
324 return mBoolVal;
327 template <>
328 int32_t PrefValue::Get() const {
329 return mIntVal;
332 template <>
333 nsDependentCString PrefValue::Get() const {
334 return nsDependentCString(mStringVal);
337 #ifdef DEBUG
338 const char* PrefTypeToString(PrefType aType) {
339 switch (aType) {
340 case PrefType::None:
341 return "none";
342 case PrefType::String:
343 return "string";
344 case PrefType::Int:
345 return "int";
346 case PrefType::Bool:
347 return "bool";
348 default:
349 MOZ_CRASH("Unhandled enum value");
352 #endif
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("\"\"");
358 return;
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
370 // \u2029.
371 const char* p;
373 aResult.Assign('"');
375 // Paranoid worst case all slashes will free quickly.
376 for (p = aOriginal; *p; ++p) {
377 switch (*p) {
378 case '\n':
379 aResult.AppendLiteral("\\n");
380 break;
382 case '\r':
383 aResult.AppendLiteral("\\r");
384 break;
386 case '\\':
387 aResult.AppendLiteral("\\\\");
388 break;
390 case '\"':
391 aResult.AppendLiteral("\\\"");
392 break;
394 default:
395 aResult.Append(*p);
396 break;
400 aResult.Append('"');
403 namespace mozilla {
404 struct PrefsSizes {
405 PrefsSizes()
406 : mHashTable(0),
407 mPrefValues(0),
408 mStringValues(0),
409 mRootBranches(0),
410 mPrefNameArena(0),
411 mCallbacksObjects(0),
412 mCallbacksDomains(0),
413 mMisc(0) {}
415 size_t mHashTable;
416 size_t mPrefValues;
417 size_t mStringValues;
418 size_t mRootBranches;
419 size_t mPrefNameArena;
420 size_t mCallbacksObjects;
421 size_t mCallbacksDomains;
422 size_t mMisc;
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;
437 class PrefWrapper;
439 class Pref {
440 public:
441 explicit Pref(const nsACString& aName)
442 : mName(ArenaStrdup(aName, PrefNameArena()), aName.Length()),
443 mType(static_cast<uint32_t>(PrefType::None)),
444 mIsSticky(false),
445 mIsLocked(false),
446 mHasDefaultValue(false),
447 mHasUserValue(false),
448 mIsSkippedByIteration(false),
449 mDefaultValue(),
450 mUserValue() {}
452 ~Pref() {
453 // There's no need to free mName because it's allocated in memory owned by
454 // sPrefNameArena.
456 mDefaultValue.Clear(Type());
457 mUserValue.Clear(Type());
460 const char* Name() const { return mName.get(); }
461 const nsDependentCString& NameString() const { return mName; }
463 // Types.
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); }
474 // Other properties.
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) {
496 if (IsTypeBool()) {
497 AddToMap<bool>(aMap);
498 } else if (IsTypeInt()) {
499 AddToMap<int32_t>(aMap);
500 } else if (IsTypeString()) {
501 AddToMap<nsDependentCString>(aMap);
502 } else {
503 MOZ_ASSERT_UNREACHABLE("Unexpected preference type");
507 // Other operations.
509 bool GetBoolValue(PrefValueKind aKind = PrefValueKind::User) const {
510 MOZ_ASSERT(IsTypeBool());
511 MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
512 : HasUserValue());
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()
521 : HasUserValue());
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()
531 : HasUserValue());
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());
552 } else {
553 aDomPref->defaultValue() = Nothing();
556 if (mHasUserValue) {
557 aDomPref->userValue() = Some(dom::PrefValue());
558 mUserValue.ToDomPrefValue(Type(), &aDomPref->userValue().ref());
559 } else {
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()) {
578 PrefValue value;
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);
583 SetType(type);
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()) {
593 PrefValue value;
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);
598 SetType(type);
599 mHasUserValue = true;
600 userValueChanged = true;
602 } else if (mHasUserValue) {
603 ClearUserValue();
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()) {
618 return true;
621 if (mHasDefaultValue &&
622 strlen(mDefaultValue.mStringVal) > MAX_ADVISABLE_PREF_LENGTH) {
623 return false;
626 if (mHasUserValue &&
627 strlen(mUserValue.mStringVal) > MAX_ADVISABLE_PREF_LENGTH) {
628 return false;
631 return true;
634 private:
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));
642 public:
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.
657 if (!IsLocked()) {
658 if (aIsLocked) {
659 SetIsLocked(true);
661 if (!ValueMatches(PrefValueKind::Default, aType, aValue)) {
662 mDefaultValue.Replace(mHasDefaultValue, Type(), aType, aValue);
663 mHasDefaultValue = true;
664 if (aIsSticky) {
665 mIsSticky = 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.
674 return NS_OK;
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
680 // value.
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 &&
689 !aFromInit) {
690 if (mHasUserValue) {
691 ClearUserValue();
692 if (!IsLocked()) {
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;
703 if (!IsLocked()) {
704 *aValueChanged = true;
707 return NS_OK;
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
728 // <int-value>.
730 // No whitespace is tolerated between tokens. <type> must match the types of
731 // the values.
733 // The serialization is text-based, rather than binary, for the following
734 // reasons.
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) {
763 switch (Type()) {
764 case PrefType::Bool:
765 aStr.Append('B');
766 break;
768 case PrefType::Int:
769 aStr.Append('I');
770 break;
772 case PrefType::String: {
773 aStr.Append('S');
774 break;
777 case PrefType::None:
778 default:
779 MOZ_CRASH();
782 aStr.Append(mIsLocked ? 'L' : '-');
783 aStr.Append(':');
785 SerializeAndAppendString(mName, aStr);
786 aStr.Append(':');
788 if (mHasDefaultValue) {
789 mDefaultValue.SerializeAndAppend(Type(), aStr);
791 aStr.Append(':');
793 if (mHasUserValue) {
794 mUserValue.SerializeAndAppend(Type(), aStr);
796 aStr.Append('\n');
799 static char* Deserialize(char* aStr, dom::Pref* aDomPref) {
800 char* p = aStr;
802 // The type.
803 PrefType type;
804 if (*p == 'B') {
805 type = PrefType::Bool;
806 } else if (*p == 'I') {
807 type = PrefType::Int;
808 } else if (*p == 'S') {
809 type = PrefType::String;
810 } else {
811 NS_ERROR("bad pref type");
812 type = PrefType::None;
814 p++; // move past the type char
816 // Locked?
817 bool isLocked;
818 if (*p == 'L') {
819 isLocked = true;
820 } else if (*p == '-') {
821 isLocked = false;
822 } else {
823 NS_ERROR("bad pref locked status");
824 isLocked = false;
826 p++; // move past the isLocked char
828 MOZ_ASSERT(*p == ':');
829 p++; // move past the ':'
831 // The pref name.
832 nsCString name;
833 p = DeserializeString(p, name);
835 MOZ_ASSERT(*p == ':');
836 p++; // move past the ':' preceding the default value
838 Maybe<dom::PrefValue> maybeDefaultValue;
839 if (*p != ':') {
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;
848 if (*p != '\n') {
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);
858 return p;
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);
868 if (mHasUserValue) {
869 aSizes.mStringValues += aMallocSizeOf(mUserValue.mStringVal);
874 private:
875 const nsDependentCString mName; // allocated in sPrefNameArena
877 uint32_t mType : 2;
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;
888 struct PrefHasher {
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()) {
896 return false;
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;
907 public:
908 MOZ_IMPLICIT PrefWrapper(Pref* aPref) : PrefWrapperBase(AsVariant(aPref)) {}
910 MOZ_IMPLICIT PrefWrapper(const SharedPrefMap::Pref& aPref)
911 : PrefWrapperBase(AsVariant(aPref)) {}
913 // Types.
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 { \
923 struct Matcher { \
924 retType operator()(const Pref* aPref) { return aPref->method(); } \
925 retType operator()(SharedPref& aPref) { return aPref.method(); } \
926 }; \
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)
937 #undef FORWARD
939 #define FORWARD(retType, method) \
940 retType method(PrefValueKind aKind = PrefValueKind::User) const { \
941 struct Matcher { \
942 PrefValueKind mKind; \
944 retType operator()(const Pref* aPref) { return aPref->method(mKind); } \
945 retType operator()(SharedPref& aPref) { return aPref.method(mKind); } \
946 }; \
947 return match(Matcher{aKind}); \
950 FORWARD(bool, GetBoolValue)
951 FORWARD(int32_t, GetIntValue)
952 FORWARD(nsCString, GetStringValue)
953 FORWARD(const char*, GetBareStringValue)
954 #undef FORWARD
956 PrefValue GetValue(PrefValueKind aKind = PrefValueKind::User) const {
957 switch (Type()) {
958 case PrefType::Bool:
959 return PrefValue{GetBoolValue(aKind)};
960 case PrefType::Int:
961 return PrefValue{GetIntValue(aKind)};
962 case PrefType::String:
963 return PrefValue{GetBareStringValue(aKind)};
964 default:
965 MOZ_ASSERT_UNREACHABLE("Unexpected pref type");
966 return PrefValue{};
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 {
986 PrefValueKind kind;
987 MOZ_TRY_VAR(kind, WantValueKind(PrefType::Bool, aKind));
989 *aResult = GetBoolValue(kind);
990 return NS_OK;
993 nsresult GetValue(PrefValueKind aKind, int32_t* aResult) const {
994 PrefValueKind kind;
995 MOZ_TRY_VAR(kind, WantValueKind(PrefType::Int, aKind));
997 *aResult = GetIntValue(kind);
998 return NS_OK;
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);
1012 return rv;
1015 nsresult GetValue(PrefValueKind aKind, nsACString& aResult) const {
1016 PrefValueKind kind;
1017 MOZ_TRY_VAR(kind, WantValueKind(PrefType::String, aKind));
1019 aResult = GetStringValue(kind);
1020 return NS_OK;
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()) ||
1029 IsSticky())) {
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";
1039 return true;
1042 // Do not save default prefs that haven't changed.
1043 return false;
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)) {
1055 return false;
1057 if (!(aKind == PrefValueKind::Default ? HasDefaultValue()
1058 : HasUserValue())) {
1059 return false;
1061 switch (aType) {
1062 case PrefType::Bool:
1063 return GetBoolValue(aKind) == aValue.mBoolVal;
1064 case PrefType::Int:
1065 return GetIntValue(aKind) == aValue.mIntVal;
1066 case PrefType::String:
1067 return strcmp(GetBareStringValue(aKind), aValue.mStringVal) == 0;
1068 default:
1069 MOZ_ASSERT_UNREACHABLE("Unexpected preference type");
1070 return false;
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 {
1099 public:
1100 CallbackNode(const nsACString& aDomain, PrefChangedFunc aFunc, void* aData,
1101 Preferences::MatchKind aMatchKind)
1102 : mDomain(AsVariant(nsCString(aDomain))),
1103 mFunc(aFunc),
1104 mData(aData),
1105 mNextAndMatchKind(aMatchKind) {}
1107 CallbackNode(const char** aDomains, PrefChangedFunc aFunc, void* aData,
1108 Preferences::MatchKind aMatchKind)
1109 : mDomain(AsVariant(aDomains)),
1110 mFunc(aFunc),
1111 mData(aData),
1112 mNextAndMatchKind(aMatchKind) {}
1114 // mDomain is a UniquePtr<>, so any uses of Domain() should only be temporary
1115 // borrows.
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 &
1125 kMatchKindMask);
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
1139 ? aPrefName == aStr
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))) {
1148 return true;
1151 return false;
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);
1173 private:
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;
1183 void* mData;
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;
1200 return sHashTable;
1203 #ifdef DEBUG
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;
1208 struct CompareStr {
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;
1215 #endif
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;
1222 #ifdef DEBUG
1223 # define ACCESS_COUNTS
1224 #endif
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);
1236 count++;
1240 static void AddAccessCount(const char* aPrefName) {
1241 AddAccessCount(nsDependentCString(aPrefName));
1243 #else
1244 static void MOZ_MAYBE_UNUSED AddAccessCount(const nsACString& aPrefName) {}
1246 static void AddAccessCount(const char* aPrefName) {}
1247 #endif
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*;
1257 Iterator mIter;
1259 public:
1260 explicit PrefsHashIter(PrefsHashTable* aTable) : mIter(aTable->modIter()) {}
1262 class Elem {
1263 friend class PrefsHashIter;
1265 PrefsHashIter& mParent;
1266 bool mDone;
1268 Elem(PrefsHashIter& aIter, bool aDone) : mParent(aIter), mDone(aDone) {}
1270 Iterator& Iter() { return mParent.mIter; }
1272 public:
1273 Elem& operator*() { return *this; }
1275 ElemType get() {
1276 if (mDone) {
1277 return nullptr;
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++() {
1291 MOZ_ASSERT(!mDone);
1292 Iter().next();
1293 mDone = Iter().done();
1294 return *this;
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); }
1307 class PrefsIter {
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;
1325 public:
1326 PrefsIter(PrefsHashTable* aHashTable, SharedPrefMap* aSharedMap)
1327 : mSharedMap(aSharedMap),
1328 mHashTable(aHashTable),
1329 mIter(aHashTable),
1330 mPos(AsVariant(mIter.begin())),
1331 mEnd(AsVariant(mIter.end())) {
1332 if (Done()) {
1333 NextIterator();
1337 private:
1338 #define MATCH(type, ...) \
1339 do { \
1340 struct Matcher { \
1341 PrefsIter& mIter; \
1342 type operator()(HashElem& pos) { \
1343 HashElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<HashElem>(); \
1344 __VA_ARGS__; \
1346 type operator()(SharedElem& pos) { \
1347 SharedElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<SharedElem>(); \
1348 __VA_ARGS__; \
1350 }; \
1351 return mPos.match(Matcher{*this}); \
1352 } while (0);
1354 bool Done() { MATCH(bool, return pos == end); }
1356 PrefWrapper MakeEntry() { MATCH(PrefWrapper, return PrefWrapper(pos)); }
1358 void NextEntry() {
1359 mEntry.reset();
1360 MATCH(void, ++pos);
1362 #undef MATCH
1364 bool Next() {
1365 NextEntry();
1366 return !Done() || NextIterator();
1369 bool NextIterator() {
1370 if (mPos.is<HashElem>() && mSharedMap) {
1371 mPos = AsVariant(mSharedMap->begin());
1372 mEnd = AsVariant(mSharedMap->end());
1373 return !Done();
1375 return false;
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();
1389 public:
1390 class Elem {
1391 friend class PrefsIter;
1393 PrefsIter& mParent;
1394 bool mDone;
1396 Elem(PrefsIter& aIter, bool aDone) : mParent(aIter), mDone(aDone) {
1397 SkipDuplicates();
1400 void Next() { mDone = !mParent.Next(); }
1402 void SkipDuplicates() {
1403 while (!mDone &&
1404 (mParent.IteratingBase() ? mParent.mHashTable->has(ref().Name())
1405 : ref().IsTypeNone())) {
1406 Next();
1410 public:
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++() {
1422 MOZ_ASSERT(!mDone);
1423 Next();
1424 SkipDuplicates();
1425 return *this;
1428 bool operator!=(Elem& other) {
1429 if (mDone != other.mDone) {
1430 return true;
1432 if (mDone) {
1433 return false;
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)) {
1486 continue;
1489 nsAutoCString prefNameStr;
1490 StrEscape(pref->Name(), prefNameStr);
1492 nsPrintfCString str("user_pref(%s, %s);", prefNameStr.get(),
1493 prefValueStr.get());
1495 savedPrefs.AppendElement(str);
1498 return savedPrefs;
1501 #ifdef DEBUG
1503 // Note that this never changes in the parent process, and is only read in
1504 // content processes.
1505 static bool gContentProcessPrefsAreInited = false;
1507 #endif // DEBUG
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)) {
1532 return Some(*pref);
1534 return Nothing();
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()) {
1548 return Some(pref);
1550 } else if (gSharedMap) {
1551 return pref_SharedLookup(aPrefName);
1554 return Nothing();
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)) {
1566 return nullptr;
1568 if (wrapper->is<Pref*>()) {
1569 return wrapper->as<Pref*>();
1572 Pref* pref = new Pref(aPrefName);
1573 if (!HashTable()->putNew(aPrefName.get(), pref)) {
1574 delete pref;
1575 return Err(NS_ERROR_OUT_OF_MEMORY);
1577 pref->FromWrapper(*wrapper);
1578 return pref;
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());
1587 if (!HashTable()) {
1588 return NS_ERROR_OUT_OF_MEMORY;
1591 Pref* pref = nullptr;
1592 if (gSharedMap) {
1593 auto result =
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.
1599 return NS_OK;
1603 if (!pref) {
1604 auto p = HashTable()->lookupForAdd(aPrefName.get());
1605 if (!p) {
1606 pref = new Pref(aPrefName);
1607 pref->SetType(aType);
1608 if (!HashTable()->add(p, pref)) {
1609 delete pref;
1610 return NS_ERROR_OUT_OF_MEMORY;
1612 } else {
1613 pref = p->get();
1617 bool valueChanged = false;
1618 nsresult rv;
1619 if (aKind == PrefValueKind::Default) {
1620 rv = pref->SetDefaultValue(aType, aValue, aIsSticky, aIsLocked,
1621 &valueChanged);
1622 } else {
1623 MOZ_ASSERT(!aIsLocked); // `locked` is disallowed in user pref files
1624 rv = pref->SetUserValue(aType, aValue, aFromInit, &valueChanged);
1626 if (NS_FAILED(rv)) {
1627 NS_WARNING(
1628 nsPrintfCString("Rejected attempt to change type of pref %s's %s value "
1629 "from %s to %s",
1630 aPrefName.get(),
1631 (aKind == PrefValueKind::Default) ? "default" : "user",
1632 PrefTypeToString(pref->Type()), PrefTypeToString(aType))
1633 .get());
1635 return rv;
1638 if (valueChanged) {
1639 if (aKind == PrefValueKind::User) {
1640 Preferences::HandleDirty();
1642 NotifyCallbacks(aPrefName, PrefWrapper(pref));
1645 return NS_OK;
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();
1656 if (aPrevNode) {
1657 aPrevNode->SetNext(next_node);
1658 } else {
1659 gFirstCallback = next_node;
1661 if (gLastPriorityNode == aNode) {
1662 gLastPriorityNode = aPrevNode;
1664 delete aNode;
1665 return next_node;
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()) {
1682 if (node->Func()) {
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;
1695 while (node) {
1696 if (!node->Func()) {
1697 node = pref_RemoveCallbackNode(node, prev_node);
1698 } else {
1699 prev_node = node;
1700 node = node->Next();
1703 gShouldCleanupDeadNodes = false;
1706 #ifdef DEBUG
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.
1717 (search->second)();
1720 #endif
1723 //===========================================================================
1724 // Prefs parsing
1725 //===========================================================================
1727 extern "C" {
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);
1746 class Parser {
1747 public:
1748 Parser() = default;
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);
1757 private:
1758 static void HandlePref(const char* aPrefName, PrefType aType,
1759 PrefValueKind aKind, PrefValue aValue, bool aIsSticky,
1760 bool aIsLocked) {
1761 MOZ_ASSERT(XRE_IsParentProcess());
1762 pref_SetPref(nsDependentCString(aPrefName), aType, aKind, aValue, aIsSticky,
1763 aIsLocked,
1764 /* fromInit */ true);
1767 static void HandleError(const char* aMsg) {
1768 nsresult rv;
1769 nsCOMPtr<nsIConsoleService> console =
1770 do_GetService("@mozilla.org/consoleservice;1", &rv);
1771 if (NS_SUCCEEDED(rv)) {
1772 console->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg).get());
1774 #ifdef DEBUG
1775 NS_ERROR(aMsg);
1776 #else
1777 printf_stderr("%s\n", aMsg);
1778 #endif
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 //===========================================================================
1811 namespace mozilla {
1812 class PreferenceServiceReporter;
1813 } // namespace mozilla
1815 class PrefCallback : public PLDHashEntryHdr {
1816 friend class mozilla::PreferenceServiceReporter;
1818 public:
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);
1829 public:
1830 // Create a PrefCallback with a strong reference to its observer.
1831 PrefCallback(const nsACString& aDomain, nsIObserver* aObserver,
1832 nsPrefBranch* aBranch)
1833 : mDomain(aDomain),
1834 mBranch(aBranch),
1835 mWeakRef(nullptr),
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)
1845 : mDomain(aDomain),
1846 mBranch(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) {
1888 return false;
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 {
1899 if (!IsWeak()) {
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));
1917 return !observer;
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.
1926 return n;
1929 enum { ALLOW_MEMMOVE = true };
1931 private:
1932 nsCString mDomain;
1933 nsPrefBranch* mBranch;
1935 // Exactly one of mWeakRef and mStrongRef should be non-null.
1936 nsWeakPtr mWeakRef;
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,
1946 public nsIObserver,
1947 public nsSupportsWeakReference {
1948 friend class mozilla::PreferenceServiceReporter;
1950 public:
1951 NS_DECL_ISUPPORTS
1952 NS_DECL_NSIPREFBRANCH
1953 NS_DECL_NSIOBSERVER
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;
1962 private:
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 {
2002 public:
2003 nsPrefLocalizedString();
2005 NS_DECL_ISUPPORTS
2006 NS_FORWARD_NSISUPPORTSPRIMITIVE(mUnicodeString->)
2007 NS_FORWARD_NSISUPPORTSSTRING(mUnicodeString->)
2009 nsresult Init();
2011 private:
2012 virtual ~nsPrefLocalizedString();
2014 nsCOMPtr<nsISupportsString> mUnicodeString;
2017 //----------------------------------------------------------------------------
2018 // nsPrefBranch
2019 //----------------------------------------------------------------------------
2021 nsPrefBranch::nsPrefBranch(const char* aPrefRoot, PrefValueKind aKind)
2022 : mPrefRoot(aPrefRoot),
2023 mKind(aKind),
2024 mFreeingObserverList(false),
2025 mObservers() {
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);
2032 --mRefCnt;
2036 nsPrefBranch::~nsPrefBranch() { FreeObserverList(); }
2038 NS_IMPL_ISUPPORTS(nsPrefBranch, nsIPrefBranch, nsIObserver,
2039 nsISupportsWeakReference)
2041 NS_IMETHODIMP
2042 nsPrefBranch::GetRoot(nsACString& aRoot) {
2043 aRoot = mPrefRoot;
2044 return NS_OK;
2047 NS_IMETHODIMP
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());
2053 return NS_OK;
2056 NS_IMETHODIMP
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;
2062 return NS_OK;
2065 return rv;
2068 NS_IMETHODIMP
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);
2076 NS_IMETHODIMP
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);
2084 NS_IMETHODIMP
2085 nsPrefBranch::GetFloatPrefWithDefault(const char* aPrefName,
2086 float aDefaultValue, uint8_t aArgc,
2087 float* aRetVal) {
2088 nsresult rv = GetFloatPref(aPrefName, aRetVal);
2090 if (NS_FAILED(rv) && aArgc == 1) {
2091 *aRetVal = aDefaultValue;
2092 return NS_OK;
2095 return rv;
2098 NS_IMETHODIMP
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);
2109 return rv;
2112 NS_IMETHODIMP
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;
2120 return NS_OK;
2123 return rv;
2126 NS_IMETHODIMP
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);
2134 NS_IMETHODIMP
2135 nsPrefBranch::SetCharPref(const char* aPrefName, const nsACString& aValue) {
2136 nsresult rv = CheckSanityOfStringLength(aPrefName, aValue);
2137 if (NS_FAILED(rv)) {
2138 return 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);
2151 NS_IMETHODIMP
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;
2159 return rv;
2162 if (aArgc == 1) {
2163 aRetVal = aDefaultValue;
2164 return NS_OK;
2167 return rv;
2170 NS_IMETHODIMP
2171 nsPrefBranch::SetStringPref(const char* aPrefName, const nsACString& aValue) {
2172 nsresult rv = CheckSanityOfStringLength(aPrefName, aValue);
2173 if (NS_FAILED(rv)) {
2174 return rv;
2177 return SetCharPrefNoLengthCheck(aPrefName, aValue);
2180 NS_IMETHODIMP
2181 nsPrefBranch::GetIntPrefWithDefault(const char* aPrefName,
2182 int32_t aDefaultValue, uint8_t aArgc,
2183 int32_t* aRetVal) {
2184 nsresult rv = GetIntPref(aPrefName, aRetVal);
2186 if (NS_FAILED(rv) && aArgc == 1) {
2187 *aRetVal = aDefaultValue;
2188 return NS_OK;
2191 return rv;
2194 NS_IMETHODIMP
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);
2201 NS_IMETHODIMP
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);
2209 NS_IMETHODIMP
2210 nsPrefBranch::GetComplexValue(const char* aPrefName, const nsIID& aType,
2211 void** aRetVal) {
2212 NS_ENSURE_ARG(aPrefName);
2214 nsresult rv;
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)) {
2222 return rv;
2225 const PrefName& pref = GetPrefName(aPrefName);
2226 bool bNeedDefault = false;
2228 if (mKind == PrefValueKind::Default) {
2229 bNeedDefault = true;
2230 } else {
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
2240 if (bNeedDefault) {
2241 nsAutoString utf16String;
2242 rv = GetDefaultFromPropertiesFile(pref.get(), utf16String);
2243 if (NS_SUCCEEDED(rv)) {
2244 theString->SetData(utf16String);
2246 } else {
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));
2257 return rv;
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)) {
2263 return 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));
2275 return NS_OK;
2278 return rv;
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)) {
2304 return rv;
2307 rv = directoryService->Get(key.get(), NS_GET_IID(nsIFile),
2308 getter_AddRefs(fromFile));
2309 if (NS_FAILED(rv)) {
2310 return rv;
2313 nsCOMPtr<nsIFile> theFile;
2314 rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(theFile));
2315 if (NS_FAILED(rv)) {
2316 return rv;
2319 rv = theFile->SetRelativeDescriptor(fromFile, Substring(++keyEnd, strEnd));
2320 if (NS_FAILED(rv)) {
2321 return rv;
2324 nsCOMPtr<nsIRelativeFilePref> relativePref = new nsRelativeFilePref();
2325 Unused << relativePref->SetFile(theFile);
2326 Unused << relativePref->SetRelativeToKey(key);
2328 relativePref.forget(reinterpret_cast<nsIRelativeFilePref**>(aRetVal));
2329 return NS_OK;
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) {
2352 return NS_OK;
2355 nsresult rv;
2356 nsCOMPtr<nsIConsoleService> console =
2357 do_GetService("@mozilla.org/consoleservice;1", &rv);
2358 if (NS_FAILED(rv)) {
2359 return 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)) {
2371 return rv;
2373 return NS_OK;
2376 NS_IMETHODIMP
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);
2386 if (!file) {
2387 return NS_NOINTERFACE;
2390 nsAutoCString descriptorString;
2391 rv = file->GetPersistentDescriptor(descriptorString);
2392 if (NS_SUCCEEDED(rv)) {
2393 rv = SetCharPrefNoLengthCheck(aPrefName, descriptorString);
2395 return rv;
2398 if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) {
2399 nsCOMPtr<nsIRelativeFilePref> relFilePref = do_QueryInterface(aValue);
2400 if (!relFilePref) {
2401 return NS_NOINTERFACE;
2404 nsCOMPtr<nsIFile> file;
2405 relFilePref->GetFile(getter_AddRefs(file));
2406 if (!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)) {
2417 return rv;
2420 rv = directoryService->Get(relativeToKey.get(), NS_GET_IID(nsIFile),
2421 getter_AddRefs(relativeToFile));
2422 if (NS_FAILED(rv)) {
2423 return rv;
2426 nsAutoCString relDescriptor;
2427 rv = file->GetRelativeDescriptor(relativeToFile, relDescriptor);
2428 if (NS_FAILED(rv)) {
2429 return 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);
2443 if (theString) {
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)) {
2451 return rv;
2453 rv = SetCharPrefNoLengthCheck(aPrefName,
2454 NS_ConvertUTF16toUTF8(wideString));
2457 return rv;
2460 NS_WARNING("nsPrefBranch::SetComplexValue - Unsupported interface type");
2461 return NS_NOINTERFACE;
2464 NS_IMETHODIMP
2465 nsPrefBranch::ClearUserPref(const char* aPrefName) {
2466 NS_ENSURE_ARG(aPrefName);
2468 const PrefName& pref = GetPrefName(aPrefName);
2469 return Preferences::ClearUser(pref.get());
2472 NS_IMETHODIMP
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());
2479 return NS_OK;
2482 NS_IMETHODIMP
2483 nsPrefBranch::LockPref(const char* aPrefName) {
2484 NS_ENSURE_ARG(aPrefName);
2486 const PrefName& pref = GetPrefName(aPrefName);
2487 return Preferences::Lock(pref.get());
2490 NS_IMETHODIMP
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());
2497 return NS_OK;
2500 NS_IMETHODIMP
2501 nsPrefBranch::UnlockPref(const char* aPrefName) {
2502 NS_ENSURE_ARG(aPrefName);
2504 const PrefName& pref = GetPrefName(aPrefName);
2505 return Preferences::Unlock(pref.get());
2508 NS_IMETHODIMP
2509 nsPrefBranch::ResetBranch(const char* aStartingAt) {
2510 return NS_ERROR_NOT_IMPLEMENTED;
2513 NS_IMETHODIMP
2514 nsPrefBranch::DeleteBranch(const char* aStartingAt) {
2515 ENSURE_PARENT_PROCESS("DeleteBranch", aStartingAt);
2516 NS_ENSURE_ARG(aStartingAt);
2518 MOZ_ASSERT(NS_IsMainThread());
2520 if (!HashTable()) {
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)) {
2529 branchName += '.';
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)) {
2542 iter.remove();
2543 // The saved callback pref may be invalid now.
2544 gCallbackPref = nullptr;
2548 Preferences::HandleDirty();
2549 return NS_OK;
2552 NS_IMETHODIMP
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
2560 // stack for speed.
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
2572 // elements.
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()));
2580 return NS_OK;
2583 NS_IMETHODIMP
2584 nsPrefBranch::AddObserverImpl(const nsACString& aDomain, nsIObserver* aObserver,
2585 bool aHoldWeak) {
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.
2593 if (aHoldWeak) {
2594 nsCOMPtr<nsISupportsWeakReference> weakRefFactory =
2595 do_QueryInterface(aObserver);
2596 if (!weakRefFactory) {
2597 // The caller didn't give us a object that supports weak reference...
2598 // tell them.
2599 return NS_ERROR_INVALID_ARG;
2602 // Construct a PrefCallback with a weak reference to the observer.
2603 pCallback = MakeUnique<PrefCallback>(prefName, weakRefFactory, this);
2605 } else {
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) {
2611 if (p) {
2612 NS_WARNING("Ignoring duplicate observer.");
2613 } else {
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));
2625 return NS_OK;
2628 NS_IMETHODIMP
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) {
2643 return NS_OK;
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);
2653 if (pCallback) {
2654 rv = Preferences::UnregisterCallback(
2655 NotifyObserver, prefName, pCallback.get(), Preferences::PrefixMatch);
2658 return rv;
2661 NS_IMETHODIMP
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
2665 // references.
2666 if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
2667 FreeObserverList();
2669 return NS_OK;
2672 /* static */
2673 void nsPrefBranch::NotifyObserver(const char* aNewPref, void* aData) {
2674 PrefCallback* pCallback = (PrefCallback*)aData;
2676 nsCOMPtr<nsIObserver> observer = pCallback->GetObserver();
2677 if (!observer) {
2678 // The observer has expired. Let's remove this callback.
2679 pCallback->GetPrefBranch()->RemoveExpiredCallback(pCallback);
2680 return;
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);
2704 return n;
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);
2718 iter.Remove();
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)) {
2742 return 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)) {
2755 return 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,
2779 nsISupportsString)
2781 nsresult nsPrefLocalizedString::Init() {
2782 nsresult rv;
2783 mUnicodeString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
2785 return rv;
2788 //----------------------------------------------------------------------------
2789 // nsRelativeFilePref
2790 //----------------------------------------------------------------------------
2792 NS_IMPL_ISUPPORTS(nsRelativeFilePref, nsIRelativeFilePref)
2794 nsRelativeFilePref::nsRelativeFilePref() = default;
2796 nsRelativeFilePref::~nsRelativeFilePref() = default;
2798 NS_IMETHODIMP
2799 nsRelativeFilePref::GetFile(nsIFile** aFile) {
2800 NS_ENSURE_ARG_POINTER(aFile);
2801 *aFile = mFile;
2802 NS_IF_ADDREF(*aFile);
2803 return NS_OK;
2806 NS_IMETHODIMP
2807 nsRelativeFilePref::SetFile(nsIFile* aFile) {
2808 mFile = aFile;
2809 return NS_OK;
2812 NS_IMETHODIMP
2813 nsRelativeFilePref::GetRelativeToKey(nsACString& aRelativeToKey) {
2814 aRelativeToKey.Assign(mRelativeToKey);
2815 return NS_OK;
2818 NS_IMETHODIMP
2819 nsRelativeFilePref::SetRelativeToKey(const nsACString& aRelativeToKey) {
2820 mRelativeToKey.Assign(aRelativeToKey);
2821 return NS_OK;
2824 //===========================================================================
2825 // class Preferences and related things
2826 //===========================================================================
2828 namespace mozilla {
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) {
2838 return;
2841 if (sPreferences->mProfileShutdown) {
2842 NS_WARNING("Setting user pref after profile shutdown.");
2843 return;
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",
2855 sPreferences.get(),
2856 &Preferences::SavePrefFileAsynchronous),
2857 PREF_DELAY_MS);
2862 static nsresult openPrefFile(nsIFile* aFile, PrefValueKind aKind);
2864 static nsresult parsePrefData(const nsCString& aData, PrefValueKind aKind);
2866 // clang-format off
2867 static const char kPrefFileHeader[] =
2868 "// Mozilla User Preferences"
2869 NS_LINEBREAK
2870 NS_LINEBREAK
2871 "// DO NOT EDIT THIS FILE."
2872 NS_LINEBREAK
2873 "//"
2874 NS_LINEBREAK
2875 "// If you make changes to this file while the application is running,"
2876 NS_LINEBREAK
2877 "// the changes will be overwritten when the application exits."
2878 NS_LINEBREAK
2879 "//"
2880 NS_LINEBREAK
2881 "// To change a preference value, you can either:"
2882 NS_LINEBREAK
2883 "// - modify it via the UI (e.g. via about:config in the browser); or"
2884 NS_LINEBREAK
2885 "// - set it within a user.js file in your profile."
2886 NS_LINEBREAK
2887 NS_LINEBREAK;
2888 // clang-format on
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 {
2899 public:
2900 PreferencesWriter() = default;
2902 static nsresult Write(nsIFile* aFile, PrefSaveData& aPrefs) {
2903 nsCOMPtr<nsIOutputStream> outStreamSink;
2904 nsCOMPtr<nsIOutputStream> outStream;
2905 uint32_t writeAmount;
2906 nsresult rv;
2908 // Execute a "safe" save by saving through a tempfile.
2909 rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink), aFile,
2910 -1, 0600);
2911 if (NS_FAILED(rv)) {
2912 return rv;
2915 rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream),
2916 outStreamSink.forget(), 4096);
2917 if (NS_FAILED(rv)) {
2918 return rv;
2921 struct CharComparator {
2922 bool LessThan(const nsCString& aA, const nsCString& aB) const {
2923 return aA < aB;
2926 bool Equals(const nsCString& aA, const nsCString& aB) const {
2927 return aA == aB;
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,
2936 &writeAmount);
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!");
2947 if (safeStream) {
2948 rv = safeStream->Finish();
2951 #ifdef DEBUG
2952 if (NS_FAILED(rv)) {
2953 NS_WARNING("failed to save prefs file! possible data loss");
2955 #endif
2957 return rv;
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 {
2997 public:
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
3020 // up in this time.
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
3031 // write.
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));
3042 if (prefs) {
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();
3057 }));
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--;
3066 return rv;
3069 protected:
3070 nsCOMPtr<nsIFile> mFile;
3073 // Although this is a member of Preferences, it measures sPreferences and
3074 // several other global structures.
3075 /* static */
3076 void Preferences::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
3077 PrefsSizes& aSizes) {
3078 if (!sPreferences) {
3079 return;
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() {}
3094 public:
3095 NS_DECL_ISUPPORTS
3096 NS_DECL_NSIMEMORYREPORTER
3098 protected:
3099 static const uint32_t kSuspectReferentCount = 1000;
3102 NS_IMPL_ISUPPORTS(PreferenceServiceReporter, nsIMemoryReporter)
3104 MOZ_DEFINE_MALLOC_SIZE_OF(PreferenceServiceMallocSizeOf)
3106 NS_IMETHODIMP
3107 PreferenceServiceReporter::CollectReports(
3108 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
3109 bool aAnonymize) {
3110 MOZ_ASSERT(NS_IsMainThread());
3112 MallocSizeOf mallocSizeOf = PreferenceServiceMallocSizeOf;
3113 PrefsSizes sizes;
3115 Preferences::AddSizeOfIncludingThis(mallocSizeOf, sizes);
3117 if (HashTable()) {
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);
3130 if (gSharedMap) {
3131 sizes.mMisc += mallocSizeOf(gSharedMap);
3134 #ifdef ACCESS_COUNTS
3135 if (gAccessCounts) {
3136 sizes.mMisc += gAccessCounts->ShallowSizeOfIncludingThis(mallocSizeOf);
3138 #endif
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,
3144 sizes.mPrefValues,
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 "
3166 "prefixes).");
3168 MOZ_COLLECT_REPORT("explicit/preferences/misc", KIND_HEAP, UNITS_BYTES,
3169 sizes.mMisc, "Miscellaneous memory used by libpref.");
3171 if (gSharedMap) {
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());
3182 if (!rootBranch) {
3183 return NS_OK;
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);
3198 if (callbackRef) {
3199 numWeakAlive++;
3200 } else {
3201 numWeakDead++;
3203 } else {
3204 numStrong++;
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)",
3224 suspect.get());
3226 aHandleReport->Callback(
3227 /* process = */ ""_ns, suspectPath, KIND_OTHER, UNITS_COUNT,
3228 totalReferentCount,
3229 "A preference with a suspiciously large number "
3230 "referents (symptom of a leak)."_ns,
3231 aData);
3234 MOZ_COLLECT_REPORT(
3235 "preference-service/referent/strong", KIND_OTHER, UNITS_COUNT, numStrong,
3236 "The number of strong referents held by the preference service.");
3238 MOZ_COLLECT_REPORT(
3239 "preference-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT,
3240 numWeakAlive,
3241 "The number of weak referents held by the preference service that are "
3242 "still alive.");
3244 MOZ_COLLECT_REPORT(
3245 "preference-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT,
3246 numWeakDead,
3247 "The number of weak referents held by the preference service that are "
3248 "dead.");
3250 return NS_OK;
3253 namespace {
3255 class AddPreferencesMemoryReporterRunnable : public Runnable {
3256 public:
3257 AddPreferencesMemoryReporterRunnable()
3258 : Runnable("AddPreferencesMemoryReporterRunnable") {}
3260 NS_IMETHOD Run() override {
3261 return RegisterStrongMemoryReporter(new PreferenceServiceReporter());
3265 } // namespace
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) {
3279 return Nothing();
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
3287 return Some(true);
3288 # else
3289 nsAutoCString channelPrefValue;
3290 Unused << Preferences::GetCString(kChannelPref, channelPrefValue,
3291 PrefValueKind::Default);
3292 return Some(channelPrefValue.EqualsLiteral("beta"));
3293 # endif
3296 /* static */
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")) {
3319 return true;
3322 # ifndef MOZILLA_OFFICIAL
3323 // Local developer builds: non-official builds on the "default" channel.
3324 if (channel.EqualsLiteral("default")) {
3325 return true;
3327 # endif
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")) {
3336 return true;
3340 return false;
3343 /* static */
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
3364 /* static */
3365 already_AddRefed<Preferences> Preferences::GetInstanceForService() {
3366 if (sPreferences) {
3367 return do_AddRef(sPreferences);
3370 if (sShutdown) {
3371 return nullptr;
3374 sPreferences = new Preferences();
3376 MOZ_ASSERT(!HashTable());
3377 HashTable() = new PrefsHashTable(XRE_IsParentProcess()
3378 ? kHashTableInitialLengthParent
3379 : kHashTableInitialLengthContent);
3381 #ifdef DEBUG
3382 gOnceStaticPrefsAntiFootgun = new AntiFootgunMap();
3383 #endif
3385 #ifdef ACCESS_COUNTS
3386 MOZ_ASSERT(!gAccessCounts);
3387 gAccessCounts = new AccessCountsHashTable();
3388 #endif
3390 nsresult rv = InitInitialObjects(/* isStartup */ true);
3391 if (NS_FAILED(rv)) {
3392 sPreferences = nullptr;
3393 return 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();
3406 #endif
3408 } else {
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;
3425 return nullptr;
3428 observerService->AddObserver(sPreferences,
3429 "profile-before-change-telemetry", true);
3430 rv = observerService->AddObserver(sPreferences, "profile-before-change",
3431 true);
3433 observerService->AddObserver(sPreferences, "suspend_process_notification",
3434 true);
3436 if (NS_FAILED(rv)) {
3437 sPreferences = nullptr;
3438 return nullptr;
3442 const char* defaultPrefs = getenv("MOZ_DEFAULT_PREFS");
3443 if (defaultPrefs) {
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);
3458 /* static */
3459 bool Preferences::IsServiceAvailable() { return !!sPreferences; }
3461 /* static */
3462 bool Preferences::InitStaticMembers() {
3463 MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
3465 if (MOZ_LIKELY(sPreferences)) {
3466 return true;
3469 if (!sShutdown) {
3470 MOZ_ASSERT(NS_IsMainThread());
3471 nsCOMPtr<nsIPrefService> prefService =
3472 do_GetService(NS_PREFSERVICE_CONTRACTID);
3475 return sPreferences != nullptr;
3478 /* static */
3479 void Preferences::Shutdown() {
3480 if (!sShutdown) {
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;
3496 while (node) {
3497 CallbackNode* next_node = node->Next();
3498 delete node;
3499 node = next_node;
3501 gLastPriorityNode = gFirstCallback = nullptr;
3503 delete HashTable();
3504 HashTable() = nullptr;
3506 #ifdef DEBUG
3507 delete gOnceStaticPrefsAntiFootgun;
3508 gOnceStaticPrefsAntiFootgun = nullptr;
3509 #endif
3511 #ifdef ACCESS_COUNTS
3512 delete gAccessCounts;
3513 #endif
3515 gSharedMap = nullptr;
3517 PrefNameArena().Clear();
3520 NS_IMPL_ISUPPORTS(Preferences, nsIPrefService, nsIObserver, nsIPrefBranch,
3521 nsISupportsWeakReference)
3523 /* static */
3524 void Preferences::SerializePreferences(nsCString& aStr) {
3525 MOZ_RELEASE_ASSERT(InitStaticMembers());
3527 aStr.Truncate();
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);
3536 aStr.Append('\0');
3539 /* static */
3540 void Preferences::DeserializePreferences(char* aStr, size_t aPrefsLen) {
3541 MOZ_ASSERT(!XRE_IsParentProcess());
3543 MOZ_ASSERT(!gChangedDomPrefs);
3544 gChangedDomPrefs = new nsTArray<dom::Pref>();
3546 char* p = aStr;
3547 while (*p != '\0') {
3548 dom::Pref pref;
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);
3557 #ifdef DEBUG
3558 MOZ_ASSERT(!gContentProcessPrefsAreInited);
3559 gContentProcessPrefsAreInited = true;
3560 #endif
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
3574 /* static */
3575 FileDescriptor Preferences::EnsureSnapshot(size_t* aSize) {
3576 MOZ_ASSERT(XRE_IsParentProcess());
3578 if (!gSharedMap) {
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
3599 // preferences.
3600 HashTable()->clearAndCompact();
3601 Unused << HashTable()->reserve(kHashTableInitialLengthContent);
3603 PrefNameArena().Clear();
3604 gCallbackPref = nullptr;
3607 *aSize = gSharedMap->MapSize();
3608 return gSharedMap->CloneFileDescriptor();
3611 /* static */
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();
3621 /* static */
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
3628 // this behavior.
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);
3640 /* static */
3641 void Preferences::FinishInitializingUserPrefs() {
3642 sPreferences->NotifyServiceObservers(NS_PREFSERVICE_READ_TOPIC_ID);
3645 NS_IMETHODIMP
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
3657 // other shutdown.
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();
3681 return rv;
3684 NS_IMETHODIMP
3685 Preferences::ReadDefaultPrefsFromFile(nsIFile* aFile) {
3686 ENSURE_PARENT_PROCESS("Preferences::ReadDefaultPrefsFromFile", "all prefs");
3688 if (!aFile) {
3689 NS_ERROR("ReadDefaultPrefsFromFile requires a parameter");
3690 return NS_ERROR_INVALID_ARG;
3693 return openPrefFile(aFile, PrefValueKind::Default);
3696 NS_IMETHODIMP
3697 Preferences::ReadUserPrefsFromFile(nsIFile* aFile) {
3698 ENSURE_PARENT_PROCESS("Preferences::ReadUserPrefsFromFile", "all prefs");
3700 if (!aFile) {
3701 NS_ERROR("ReadUserPrefsFromFile requires a parameter");
3702 return NS_ERROR_INVALID_ARG;
3705 return openPrefFile(aFile, PrefValueKind::User);
3708 NS_IMETHODIMP
3709 Preferences::ResetPrefs() {
3710 ENSURE_PARENT_PROCESS("Preferences::ResetPrefs", "all prefs");
3712 if (gSharedMap) {
3713 return NS_ERROR_NOT_AVAILABLE;
3716 HashTable()->clearAndCompact();
3717 Unused << HashTable()->reserve(kHashTableInitialLengthParent);
3719 PrefNameArena().Clear();
3721 return InitInitialObjects(/* isStartup */ false);
3724 NS_IMETHODIMP
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()) {
3741 iter.remove();
3746 for (const char* prefName : prefNames) {
3747 NotifyCallbacks(nsDependentCString(prefName));
3750 Preferences::HandleDirty();
3751 return NS_OK;
3754 bool Preferences::AllowOffMainThreadSave() {
3755 // Put in a preference that allows us to disable off main thread preference
3756 // file save.
3757 if (sAllowOMTPrefWrite < 0) {
3758 bool value = false;
3759 Preferences::GetBool("preferences.allow.omt-write", &value);
3760 sAllowOMTPrefWrite = value ? 1 : 0;
3763 return !!sAllowOMTPrefWrite;
3766 nsresult Preferences::SavePrefFileBlocking() {
3767 if (mDirty) {
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
3775 // completed.
3777 if (AllowOffMainThreadSave()) {
3778 PreferencesWriter::Flush();
3781 return NS_OK;
3784 nsresult Preferences::SavePrefFileAsynchronous() {
3785 return SavePrefFileInternal(nullptr, SaveMethod::Asynchronous);
3788 NS_IMETHODIMP
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);
3794 /* static */
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();
3801 Pref* pref;
3802 auto p = HashTable()->lookupForAdd(prefName.get());
3803 if (!p) {
3804 pref = new Pref(prefName);
3805 if (!HashTable()->add(p, pref)) {
3806 delete pref;
3807 return;
3809 } else {
3810 pref = p->get();
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);
3831 } else {
3832 HashTable()->remove(prefName.get());
3834 pref = nullptr;
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.
3840 if (valueChanged) {
3841 if (pref) {
3842 NotifyCallbacks(prefName, PrefWrapper(pref));
3843 } else {
3844 NotifyCallbacks(prefName);
3849 /* static */
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);
3859 #ifdef DEBUG
3860 bool Preferences::ArePrefsInitedInContentProcess() {
3861 MOZ_ASSERT(!XRE_IsParentProcess());
3862 return gContentProcessPrefsAreInited;
3864 #endif
3866 NS_IMETHODIMP
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);
3874 } else {
3875 // Special case: caching the default root.
3876 nsCOMPtr<nsIPrefBranch> root(sPreferences->mRootBranch);
3877 root.forget(aRetVal);
3880 return NS_OK;
3883 NS_IMETHODIMP
3884 Preferences::GetDefaultBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal) {
3885 if (!aPrefRoot || !aPrefRoot[0]) {
3886 nsCOMPtr<nsIPrefBranch> root(sPreferences->mDefaultRootBranch);
3887 root.forget(aRetVal);
3888 return NS_OK;
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);
3895 if (!prefBranch) {
3896 return NS_ERROR_OUT_OF_MEMORY;
3899 prefBranch.forget(aRetVal);
3900 return NS_OK;
3903 NS_IMETHODIMP
3904 Preferences::ReadStats(nsIPrefStatsCallback* aCallback) {
3905 #ifdef ACCESS_COUNTS
3906 for (const auto& entry : *gAccessCounts) {
3907 aCallback->Visit(entry.GetKey(), entry.GetData());
3910 return NS_OK;
3911 #else
3912 return NS_ERROR_NOT_IMPLEMENTED;
3913 #endif
3916 NS_IMETHODIMP
3917 Preferences::ResetStats() {
3918 #ifdef ACCESS_COUNTS
3919 gAccessCounts->Clear();
3920 return NS_OK;
3921 #else
3922 return NS_ERROR_NOT_IMPLEMENTED;
3923 #endif
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) {
3938 return;
3941 const char* kind = aKind == PrefValueKind::Default ? "Default" : "User";
3943 switch (aType) {
3944 case PrefType::String:
3945 PrefObserver->OnStringPref(kind, aPrefName, aValue.mStringVal, aIsSticky,
3946 aIsLocked);
3947 break;
3948 case PrefType::Int:
3949 PrefObserver->OnIntPref(kind, aPrefName, aValue.mIntVal, aIsSticky,
3950 aIsLocked);
3951 break;
3952 case PrefType::Bool:
3953 PrefObserver->OnBoolPref(kind, aPrefName, aValue.mBoolVal, aIsSticky,
3954 aIsLocked);
3955 break;
3956 default:
3957 PrefObserver->OnError("Unexpected pref type.");
3961 void HandleError(const char* aMsg) {
3962 MOZ_ASSERT(NS_IsMainThread());
3964 if (!PrefObserver) {
3965 return;
3968 PrefObserver->OnError(aMsg);
3971 NS_IMETHODIMP
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;
3988 return NS_OK;
3991 NS_IMETHODIMP
3992 Preferences::GetDirty(bool* aRetVal) {
3993 *aRetVal = mDirty;
3994 return NS_OK;
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);
4006 return NS_OK;
4009 already_AddRefed<nsIFile> Preferences::ReadSavedPrefs() {
4010 nsCOMPtr<nsIFile> file;
4011 nsresult rv =
4012 NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE, getter_AddRefs(file));
4013 if (NS_WARN_IF(NS_FAILED(rv))) {
4014 return nullptr;
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);
4022 rv = NS_OK;
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;
4037 nsresult rv =
4038 NS_GetSpecialDirectory(NS_APP_PREFS_50_DIR, getter_AddRefs(aFile));
4039 if (NS_WARN_IF(NS_FAILED(rv))) {
4040 return;
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);
4069 if (exists) {
4070 rv = newFile->Remove(false);
4071 NS_ENSURE_SUCCESS(rv, rv);
4074 rv = aFile->CopyTo(nullptr, newFilename);
4075 NS_ENSURE_SUCCESS(rv, rv);
4077 return 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.
4099 if (!mDirty) {
4100 return NS_OK;
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;
4112 if (mCurrentFile) {
4113 rv = WritePrefFile(mCurrentFile, aSaveMethod);
4116 // If we succeeded writing to mCurrentFile, reset the dirty flag.
4117 if (NS_SUCCEEDED(rv)) {
4118 mDirty = false;
4120 return rv;
4122 } else {
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());
4131 if (!HashTable()) {
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()));
4144 if (prefs) {
4145 // There was a previous request that hasn't been processed,
4146 // and this is the data it had.
4147 return rv;
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++;
4167 if (async) {
4168 rv = target->Dispatch(new PWRunnable(aFile),
4169 nsIEventTarget::DISPATCH_NORMAL);
4170 } else {
4171 // Note that we don't get the nsresult return value here.
4172 SyncRunnable::DispatchToThread(target, new PWRunnable(aFile), true);
4174 return rv;
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());
4192 nsCString data;
4193 MOZ_TRY_VAR(data, URLPreloader::ReadFile(aFile));
4195 nsAutoString filenameUtf16;
4196 aFile->GetLeafName(filenameUtf16);
4197 NS_ConvertUTF16toUTF8 filename(filenameUtf16);
4199 nsAutoString path;
4200 aFile->GetPath(path);
4202 Parser parser;
4203 if (!parser.Parse(aKind, NS_ConvertUTF16toUTF8(path).get(), data)) {
4204 return NS_ERROR_FILE_CORRUPTED;
4207 return NS_OK;
4210 static nsresult parsePrefData(const nsCString& aData, PrefValueKind aKind) {
4211 const nsCString path = "$MOZ_DEFAULT_PREFS"_ns;
4213 Parser parser;
4214 if (!parser.Parse(aKind, path.get(), aData)) {
4215 return NS_ERROR_FILE_CORRUPTED;
4218 return NS_OK;
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());
4238 nsresult rv, rv2;
4240 nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
4242 // This may fail in some normal cases, such as embedders who do not use a
4243 // GRE.
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) {
4250 rv = NS_OK;
4252 return rv;
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))) &&
4260 prefFile) {
4261 nsAutoCString leafName;
4262 prefFile->GetNativeLeafName(leafName);
4263 MOZ_ASSERT(
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);
4282 if (shouldParse) {
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;
4293 return rv;
4296 prefFiles.Sort(pref_CompareFileNames, nullptr);
4298 uint32_t arrayCount = prefFiles.Count();
4299 uint32_t i;
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.");
4304 rv = rv2;
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];
4312 if (file) {
4313 rv2 = openPrefFile(file, PrefValueKind::Default);
4314 if (NS_FAILED(rv2)) {
4315 NS_ERROR("Special default pref file not parsed successfully.");
4316 rv = rv2;
4321 return rv;
4324 static nsresult pref_ReadPrefFromJar(nsZipArchive* aJarReader,
4325 const char* aName) {
4326 nsCString manifest;
4327 MOZ_TRY_VAR(manifest,
4328 URLPreloader::ReadZip(aJarReader, nsDependentCString(aName)));
4330 Parser parser;
4331 if (!parser.Parse(PrefValueKind::Default, aName, manifest)) {
4332 return NS_ERROR_FILE_CORRUPTED;
4335 return NS_OK;
4338 static nsresult pref_ReadDefaultPrefs(const RefPtr<nsZipArchive> jarReader,
4339 const char* path) {
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));
4352 prefEntries.Sort();
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.");
4360 return NS_OK;
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.
4382 struct Internals {
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);
4404 return schema;
4407 private:
4408 static Span<const char> PrefValueKindToString(
4409 const Maybe<PrefValueKind>& aKind) {
4410 if (aKind) {
4411 return *aKind == PrefValueKind::Default ? MakeStringSpan("Default")
4412 : MakeStringSpan("User");
4414 return "Shared";
4417 static Span<const char> PrefTypeToString(PrefType type) {
4418 switch (type) {
4419 case PrefType::None:
4420 return "None";
4421 case PrefType::Int:
4422 return "Int";
4423 case PrefType::Bool:
4424 return "Bool";
4425 case PrefType::String:
4426 return "String";
4427 default:
4428 MOZ_ASSERT_UNREACHABLE("Unknown preference type.");
4429 return "Unknown";
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));
4452 return rv;
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));
4472 return rv;
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);
4480 return result;
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;
4490 } else {
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());
4499 MOZ_ASSERT(false);
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
4512 // resources.
4513 /* static */
4514 nsresult Preferences::InitInitialObjects(bool aIsStartup) {
4515 MOZ_ASSERT(NS_IsMainThread());
4517 if (!XRE_IsParentProcess()) {
4518 MOZ_DIAGNOSTIC_ASSERT(gSharedMap);
4519 if (aIsStartup) {
4520 StaticPrefs::StartObservingAlwaysPrefs();
4522 return NS_OK;
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);
4564 if (jarReader) {
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");
4569 if (abi) {
4570 nsAutoCString path;
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");
4579 #else
4580 // Load jar:$gre/omni.jar!/greprefs.js.
4581 rv = pref_ReadPrefFromJar(jarReader, "greprefs.js");
4582 #endif
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);
4594 #endif
4596 #ifdef MOZ_WIDGET_ANDROID
4597 // Load jar:$gre/omni.jar!/defaults/pref/$MOZ_ANDROID_CPU_ABI/*.js.
4598 nsAutoCString path;
4599 path.AppendPrintf("jar:$gre/omni.jar!/defaults/pref/%s/*.js$", abi);
4600 pref_ReadDefaultPrefs(jarReader, path.get());
4601 NS_ENSURE_SUCCESS(rv, rv);
4602 #endif
4603 } else {
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)) {
4614 NS_WARNING(
4615 "Error parsing GRE default preferences. Is this an old-style "
4616 "embedding app?");
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)
4630 "macprefs.js"
4631 #elif defined(XP_WIN)
4632 "winpref.js"
4633 #elif defined(XP_UNIX)
4634 "unix.js"
4635 # if defined(_AIX)
4637 "aix.js"
4638 # endif
4639 #elif defined(XP_BEOS)
4640 "beos.js"
4641 #endif
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);
4660 if (appJarReader) {
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));
4668 prefEntries.Sort();
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));
4685 prefEntries.Sort();
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.");
4693 #endif
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));
4703 if (list) {
4704 bool hasMore;
4705 while (NS_SUCCEEDED(list->HasMoreElements(&hasMore)) && hasMore) {
4706 nsCOMPtr<nsISupports> elem;
4707 list->GetNext(getter_AddRefs(elem));
4708 if (!elem) {
4709 continue;
4712 nsCOMPtr<nsIFile> path = do_QueryInterface(elem);
4713 if (!path) {
4714 continue;
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();
4726 if (aIsStartup) {
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,
4741 nullptr);
4743 return NS_OK;
4746 /* static */
4747 nsresult Preferences::GetBool(const char* aPrefName, bool* aResult,
4748 PrefValueKind aKind) {
4749 MOZ_ASSERT(aResult);
4750 return Internals::GetPrefValue(aPrefName, aResult, aKind);
4753 /* static */
4754 nsresult Preferences::GetInt(const char* aPrefName, int32_t* aResult,
4755 PrefValueKind aKind) {
4756 MOZ_ASSERT(aResult);
4757 return Internals::GetPrefValue(aPrefName, aResult, aKind);
4760 /* static */
4761 nsresult Preferences::GetFloat(const char* aPrefName, float* aResult,
4762 PrefValueKind aKind) {
4763 MOZ_ASSERT(aResult);
4764 return Internals::GetPrefValue(aPrefName, aResult, aKind);
4767 /* static */
4768 nsresult Preferences::GetCString(const char* aPrefName, nsACString& aResult,
4769 PrefValueKind aKind) {
4770 aResult.SetIsVoid(true);
4771 return Internals::GetPrefValue(aPrefName, aResult, aKind);
4774 /* static */
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);
4782 return rv;
4785 /* static */
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);
4794 return rv;
4797 /* static */
4798 nsresult Preferences::GetLocalizedString(const char* aPrefName,
4799 nsAString& aResult,
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);
4810 return rv;
4813 /* static */
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);
4820 /* static */
4821 bool Preferences::GetBool(const char* aPrefName, bool aFallback,
4822 PrefValueKind aKind) {
4823 return Internals::GetPref(aPrefName, aFallback, aKind);
4826 /* static */
4827 int32_t Preferences::GetInt(const char* aPrefName, int32_t aFallback,
4828 PrefValueKind aKind) {
4829 return Internals::GetPref(aPrefName, aFallback, aKind);
4832 /* static */
4833 uint32_t Preferences::GetUint(const char* aPrefName, uint32_t aFallback,
4834 PrefValueKind aKind) {
4835 return Internals::GetPref(aPrefName, aFallback, aKind);
4838 /* static */
4839 float Preferences::GetFloat(const char* aPrefName, float aFallback,
4840 PrefValueKind aKind) {
4841 return Internals::GetPref(aPrefName, aFallback, aKind);
4844 /* static */
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,
4861 prefValue,
4862 /* isSticky */ false,
4863 /* isLocked */ false,
4864 /* fromInit */ false);
4867 /* static */
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,
4876 prefValue,
4877 /* isSticky */ false,
4878 /* isLocked */ false,
4879 /* fromInit */ false);
4882 /* static */
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,
4891 prefValue,
4892 /* isSticky */ false,
4893 /* isLocked */ false,
4894 /* fromInit */ false);
4897 /* static */
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);
4904 /* static */
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);
4911 Pref* pref;
4912 MOZ_TRY_VAR(pref,
4913 pref_LookupForModify(prefName, [](const PrefWrapper& aPref) {
4914 return !aPref.IsLocked();
4915 }));
4917 if (pref) {
4918 pref->SetIsLocked(true);
4919 NotifyCallbacks(prefName, PrefWrapper(pref));
4922 return NS_OK;
4925 /* static */
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);
4932 Pref* pref;
4933 MOZ_TRY_VAR(pref,
4934 pref_LookupForModify(prefName, [](const PrefWrapper& aPref) {
4935 return aPref.IsLocked();
4936 }));
4938 if (pref) {
4939 pref->SetIsLocked(false);
4940 NotifyCallbacks(prefName, PrefWrapper(pref));
4943 return NS_OK;
4946 /* static */
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();
4954 /* static */
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()) {
4963 return NS_OK;
4966 if (Pref* pref = result.unwrap()) {
4967 pref->ClearUserValue();
4969 if (!pref->HasDefaultValue()) {
4970 if (!gSharedMap || !gSharedMap->Has(pref->Name())) {
4971 HashTable()->remove(aPrefName);
4972 } else {
4973 pref->SetType(PrefType::None);
4976 NotifyCallbacks(prefName);
4977 } else {
4978 NotifyCallbacks(prefName, PrefWrapper(pref));
4981 Preferences::HandleDirty();
4983 return NS_OK;
4986 /* static */
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();
4994 /* static */
4995 int32_t Preferences::GetType(const char* aPrefName) {
4996 NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
4998 if (!HashTable()) {
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:
5009 return PREF_STRING;
5011 case PrefType::Int:
5012 return PREF_INT;
5014 case PrefType::Bool:
5015 return PREF_BOOL;
5017 default:
5018 MOZ_CRASH();
5022 /* static */
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);
5030 /* static */
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);
5038 /* static */
5039 nsresult Preferences::RemoveObserver(nsIObserver* aObserver,
5040 const nsACString& aPref) {
5041 MOZ_ASSERT(aObserver);
5042 if (sShutdown) {
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);
5056 #endif
5059 /* static */
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]);
5066 nsCString pref;
5067 pref.AssignLiteral(aPrefs[i], strlen(aPrefs[i]));
5068 nsresult rv = AddStrongObserver(aObserver, pref);
5069 NS_ENSURE_SUCCESS(rv, rv);
5071 return NS_OK;
5074 /* static */
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]);
5081 nsCString pref;
5082 pref.AssignLiteral(aPrefs[i], strlen(aPrefs[i]));
5083 nsresult rv = AddWeakObserver(aObserver, pref);
5084 NS_ENSURE_SUCCESS(rv, rv);
5086 return NS_OK;
5089 /* static */
5090 nsresult Preferences::RemoveObservers(nsIObserver* aObserver,
5091 const char** aPrefs) {
5092 MOZ_ASSERT(aObserver);
5093 if (sShutdown) {
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);
5103 return NS_OK;
5106 template <typename T>
5107 /* static */
5108 nsresult Preferences::RegisterCallbackImpl(PrefChangedFunc aCallback,
5109 T& aPrefNode, void* aData,
5110 MatchKind aMatchKind,
5111 bool aIsPriority) {
5112 NS_ENSURE_ARG(aCallback);
5114 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
5116 auto node = new CallbackNode(aPrefNode, aCallback, aData, aMatchKind);
5118 if (aIsPriority) {
5119 // Add to the start of the list.
5120 node->SetNext(gFirstCallback);
5121 gFirstCallback = node;
5122 if (!gLastPriorityNode) {
5123 gLastPriorityNode = node;
5125 } else {
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);
5130 } else {
5131 node->SetNext(gFirstCallback);
5132 gFirstCallback = node;
5136 return NS_OK;
5139 /* static */
5140 nsresult Preferences::RegisterCallback(PrefChangedFunc aCallback,
5141 const nsACString& aPrefNode, void* aData,
5142 MatchKind aMatchKind, bool aIsPriority) {
5143 return RegisterCallbackImpl(aCallback, aPrefNode, aData, aMatchKind,
5144 aIsPriority);
5147 /* static */
5148 nsresult Preferences::RegisterCallbacks(PrefChangedFunc aCallback,
5149 const char** aPrefs, void* aData,
5150 MatchKind aMatchKind) {
5151 return RegisterCallbackImpl(aCallback, aPrefs, aData, aMatchKind);
5154 /* static */
5155 nsresult Preferences::RegisterCallbackAndCall(PrefChangedFunc aCallback,
5156 const nsACString& aPref,
5157 void* aClosure,
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);
5164 return rv;
5167 /* static */
5168 nsresult Preferences::RegisterCallbacksAndCall(PrefChangedFunc aCallback,
5169 const char** aPrefs,
5170 void* aClosure) {
5171 MOZ_ASSERT(aCallback);
5173 nsresult rv =
5174 RegisterCallbacks(aCallback, aPrefs, aClosure, MatchKind::ExactMatch);
5175 if (NS_SUCCEEDED(rv)) {
5176 for (const char** ptr = aPrefs; *ptr; ptr++) {
5177 (*aCallback)(*ptr, aClosure);
5180 return rv;
5183 template <typename T>
5184 /* static */
5185 nsresult Preferences::UnregisterCallbackImpl(PrefChangedFunc aCallback,
5186 T& aPrefNode, void* aData,
5187 MatchKind aMatchKind) {
5188 MOZ_ASSERT(aCallback);
5189 if (sShutdown) {
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;
5199 while (node) {
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
5204 // finished.
5205 node->ClearFunc();
5206 gShouldCleanupDeadNodes = true;
5207 prev_node = node;
5208 node = node->Next();
5209 } else {
5210 node = pref_RemoveCallbackNode(node, prev_node);
5212 rv = NS_OK;
5213 } else {
5214 prev_node = node;
5215 node = node->Next();
5218 return rv;
5221 /* static */
5222 nsresult Preferences::UnregisterCallback(PrefChangedFunc aCallback,
5223 const nsACString& aPrefNode,
5224 void* aData, MatchKind aMatchKind) {
5225 return UnregisterCallbackImpl<const nsACString&>(aCallback, aPrefNode, aData,
5226 aMatchKind);
5229 /* static */
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());
5256 PrefValue value;
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());
5266 PrefValue value;
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());
5280 PrefValue value;
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());
5298 PrefValue value;
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
5337 // value.
5338 return;
5340 StaticMutexAutoLock lock(sOncePrefMutex);
5341 if (NS_IsMainThread()) {
5342 InitOncePrefs();
5343 } else {
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"
5359 #undef NEVER_PREF
5360 #undef ALWAYS_PREF
5361 #undef ONCE_PREF
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"
5380 #undef NEVER_PREF
5381 #undef ALWAYS_PREF
5382 #undef ONCE_PREF
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"
5397 #undef NEVER_PREF
5398 #undef ALWAYS_PREF
5399 #undef ONCE_PREF
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
5406 // prefs.
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)
5414 #ifdef DEBUG
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 " \
5428 "instead"); \
5429 }; \
5430 gOnceStaticPrefsAntiFootgun->insert( \
5431 std::pair<const char*, AntiFootgunCallback>(GetPrefName_##base_id(), \
5432 std::move(checkPref))); \
5434 #else
5435 # define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
5436 sMirror_##full_id = Internals::GetPref(name, cpp_type(value));
5437 #endif
5439 #include "mozilla/StaticPrefListAll.h"
5440 #undef NEVER_PREF
5441 #undef ALWAYS_PREF
5442 #undef ONCE_PREF
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"
5518 #undef NEVER_PREF
5519 #undef ALWAYS_PREF
5520 #undef ONCE_PREF
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
5547 // occur.
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) \
5558 cpp_type val; \
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"
5565 #undef NEVER_PREF
5566 #undef ALWAYS_PREF
5567 #undef ONCE_PREF
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>();
5590 return nullptr;
5593 namespace mozilla {
5595 void UnloadPrefsModule() { Preferences::Shutdown(); }
5597 } // namespace mozilla
5599 // This file contains the C wrappers for the C++ static pref getters, as used
5600 // by Rust code.
5601 #include "init/StaticPrefsCGetters.cpp"