· 8 years ago · Jul 20, 2018, 08:16 AM
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/. */
6
7#include <ctype.h>
8#include <stdlib.h>
9#include <string.h>
10
11#include "base/basictypes.h"
12#include "GeckoProfiler.h"
13#include "MainThreadUtils.h"
14#include "mozilla/ArenaAllocatorExtensions.h"
15#include "mozilla/ArenaAllocator.h"
16#include "mozilla/ArrayUtils.h"
17#include "mozilla/Attributes.h"
18#include "mozilla/dom/PContent.h"
19#include "mozilla/HashFunctions.h"
20#include "mozilla/Logging.h"
21#include "mozilla/Maybe.h"
22#include "mozilla/MemoryReporting.h"
23#include "mozilla/ModuleUtils.h"
24#include "mozilla/Omnijar.h"
25#include "mozilla/Preferences.h"
26#include "mozilla/ResultExtensions.h"
27#include "mozilla/ScopeExit.h"
28#include "mozilla/Services.h"
29#include "mozilla/ServoStyleSet.h"
30#include "mozilla/StaticPrefs.h"
31#include "mozilla/SyncRunnable.h"
32#include "mozilla/SystemGroup.h"
33#include "mozilla/Telemetry.h"
34#include "mozilla/UniquePtrExtensions.h"
35#include "mozilla/URLPreloader.h"
36#include "mozilla/Variant.h"
37#include "mozilla/Vector.h"
38#include "nsAppDirectoryServiceDefs.h"
39#include "nsAutoPtr.h"
40#include "nsCategoryManagerUtils.h"
41#include "nsClassHashtable.h"
42#include "nsCOMArray.h"
43#include "nsCOMPtr.h"
44#include "nsCRT.h"
45#include "nsDataHashtable.h"
46#include "nsDirectoryServiceDefs.h"
47#include "nsHashKeys.h"
48#include "nsICategoryManager.h"
49#include "nsIConsoleService.h"
50#include "nsIDirectoryService.h"
51#include "nsIFile.h"
52#include "nsIInputStream.h"
53#include "nsIMemoryReporter.h"
54#include "nsIObserver.h"
55#include "nsIObserverService.h"
56#include "nsIOutputStream.h"
57#include "nsIPrefBranch.h"
58#include "nsIPrefLocalizedString.h"
59#include "nsIRelativeFilePref.h"
60#include "nsISafeOutputStream.h"
61#include "nsISimpleEnumerator.h"
62#include "nsIStringBundle.h"
63#include "nsIStringEnumerator.h"
64#include "nsISupportsImpl.h"
65#include "nsISupportsPrimitives.h"
66#include "nsIZipReader.h"
67#include "nsNetUtil.h"
68#include "nsPrintfCString.h"
69#include "nsQuickSort.h"
70#include "nsReadableUtils.h"
71#include "nsRefPtrHashtable.h"
72#include "nsString.h"
73#include "nsTArray.h"
74#include "nsThreadUtils.h"
75#include "nsUTF8Utils.h"
76#include "nsWeakReference.h"
77#include "nsXPCOMCID.h"
78#include "nsXPCOM.h"
79#include "nsXULAppAPI.h"
80#include "nsZipArchive.h"
81#include "plbase64.h"
82#include "PLDHashTable.h"
83#include "plstr.h"
84#include "prlink.h"
85
86#ifdef MOZ_MEMORY
87#include "mozmemory.h"
88#endif
89
90#ifdef XP_WIN
91#include "windows.h"
92#endif
93
94using namespace mozilla;
95
96#ifdef DEBUG
97
98#define ENSURE_PARENT_PROCESS(func, pref) \
99 do { \
100 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
101 nsPrintfCString msg( \
102 "ENSURE_PARENT_PROCESS: called %s on %s in a non-parent process", \
103 func, \
104 pref); \
105 NS_ERROR(msg.get()); \
106 return NS_ERROR_NOT_AVAILABLE; \
107 } \
108 } while (0)
109
110#else // DEBUG
111
112#define ENSURE_PARENT_PROCESS(func, pref) \
113 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
114 return NS_ERROR_NOT_AVAILABLE; \
115 }
116
117#endif // DEBUG
118
119//===========================================================================
120// Low-level types and operations
121//===========================================================================
122
123typedef nsTArray<nsCString> PrefSaveData;
124
125// 1 MB should be enough for everyone.
126static const uint32_t MAX_PREF_LENGTH = 1 * 1024 * 1024;
127// Actually, 4kb should be enough for everyone.
128static const uint32_t MAX_ADVISABLE_PREF_LENGTH = 4 * 1024;
129
130// Keep this in sync with PrefType in parser/src/lib.rs.
131enum class PrefType : uint8_t
132{
133 None = 0, // only used when neither the default nor user value is set
134 String = 1,
135 Int = 2,
136 Bool = 3,
137};
138
139// This is used for pref names and string pref values. We encode the string
140// length, then a '/', then the string chars. This encoding means there are no
141// special chars that are forbidden or require escaping.
142static void
143SerializeAndAppendString(const char* aChars, nsCString& aStr)
144{
145 aStr.AppendInt(uint32_t(strlen(aChars)));
146 aStr.Append('/');
147 aStr.Append(aChars);
148}
149
150static char*
151DeserializeString(char* aChars, nsCString& aStr)
152{
153 char* p = aChars;
154 uint32_t length = strtol(p, &p, 10);
155 MOZ_ASSERT(p[0] == '/');
156 p++; // move past the '/'
157 aStr.Assign(p, length);
158 p += length; // move past the string itself
159 return p;
160}
161
162// Keep this in sync with PrefValue in prefs_parser/src/lib.rs.
163union PrefValue {
164 const char* mStringVal;
165 int32_t mIntVal;
166 bool mBoolVal;
167
168 bool Equals(PrefType aType, PrefValue aValue)
169 {
170 switch (aType) {
171 case PrefType::String: {
172 if (mStringVal && aValue.mStringVal) {
173 return strcmp(mStringVal, aValue.mStringVal) == 0;
174 }
175 if (!mStringVal && !aValue.mStringVal) {
176 return true;
177 }
178 return false;
179 }
180
181 case PrefType::Int:
182 return mIntVal == aValue.mIntVal;
183
184 case PrefType::Bool:
185 return mBoolVal == aValue.mBoolVal;
186
187 default:
188 MOZ_CRASH("Unhandled enum value");
189 }
190 }
191
192 void Init(PrefType aNewType, PrefValue aNewValue)
193 {
194 if (aNewType == PrefType::String) {
195 MOZ_ASSERT(aNewValue.mStringVal);
196 aNewValue.mStringVal = moz_xstrdup(aNewValue.mStringVal);
197 }
198 *this = aNewValue;
199 }
200
201 void Clear(PrefType aType)
202 {
203 if (aType == PrefType::String) {
204 free(const_cast<char*>(mStringVal));
205 }
206
207 // Zero the entire value (regardless of type) via mStringVal.
208 mStringVal = nullptr;
209 }
210
211 void Replace(bool aHasValue,
212 PrefType aOldType,
213 PrefType aNewType,
214 PrefValue aNewValue)
215 {
216 if (aHasValue) {
217 Clear(aOldType);
218 }
219 Init(aNewType, aNewValue);
220 }
221
222 void ToDomPrefValue(PrefType aType, dom::PrefValue* aDomValue)
223 {
224 switch (aType) {
225 case PrefType::String:
226 *aDomValue = nsDependentCString(mStringVal);
227 return;
228
229 case PrefType::Int:
230 *aDomValue = mIntVal;
231 return;
232
233 case PrefType::Bool:
234 *aDomValue = mBoolVal;
235 return;
236
237 default:
238 MOZ_CRASH();
239 }
240 }
241
242 PrefType FromDomPrefValue(const dom::PrefValue& aDomValue)
243 {
244 switch (aDomValue.type()) {
245 case dom::PrefValue::TnsCString:
246 mStringVal = aDomValue.get_nsCString().get();
247 return PrefType::String;
248
249 case dom::PrefValue::Tint32_t:
250 mIntVal = aDomValue.get_int32_t();
251 return PrefType::Int;
252
253 case dom::PrefValue::Tbool:
254 mBoolVal = aDomValue.get_bool();
255 return PrefType::Bool;
256
257 default:
258 MOZ_CRASH();
259 }
260 }
261
262 void SerializeAndAppend(PrefType aType, nsCString& aStr)
263 {
264 switch (aType) {
265 case PrefType::Bool:
266 aStr.Append(mBoolVal ? 'T' : 'F');
267 break;
268
269 case PrefType::Int:
270 aStr.AppendInt(mIntVal);
271 break;
272
273 case PrefType::String: {
274 SerializeAndAppendString(mStringVal, aStr);
275 break;
276 }
277
278 case PrefType::None:
279 default:
280 MOZ_CRASH();
281 }
282 }
283
284 static char* Deserialize(PrefType aType,
285 char* aStr,
286 dom::MaybePrefValue* aDomValue)
287 {
288 char* p = aStr;
289
290 switch (aType) {
291 case PrefType::Bool:
292 if (*p == 'T') {
293 *aDomValue = true;
294 } else if (*p == 'F') {
295 *aDomValue = false;
296 } else {
297 *aDomValue = false;
298 NS_ERROR("bad bool pref value");
299 }
300 p++;
301 return p;
302
303 case PrefType::Int: {
304 *aDomValue = int32_t(strtol(p, &p, 10));
305 return p;
306 }
307
308 case PrefType::String: {
309 nsCString str;
310 p = DeserializeString(p, str);
311 *aDomValue = str;
312 return p;
313 }
314
315 default:
316 MOZ_CRASH();
317 }
318 }
319};
320
321#ifdef DEBUG
322const char*
323PrefTypeToString(PrefType aType)
324{
325 switch (aType) {
326 case PrefType::None:
327 return "none";
328 case PrefType::String:
329 return "string";
330 case PrefType::Int:
331 return "int";
332 case PrefType::Bool:
333 return "bool";
334 default:
335 MOZ_CRASH("Unhandled enum value");
336 }
337}
338#endif
339
340// Assign to aResult a quoted, escaped copy of aOriginal.
341static void
342StrEscape(const char* aOriginal, nsCString& aResult)
343{
344 if (aOriginal == nullptr) {
345 aResult.AssignLiteral("\"\"");
346 return;
347 }
348
349 // JavaScript does not allow quotes, slashes, or line terminators inside
350 // strings so we must escape them. ECMAScript defines four line terminators,
351 // but we're only worrying about \r and \n here. We currently feed our pref
352 // script to the JS interpreter as Latin-1 so we won't encounter \u2028
353 // (line separator) or \u2029 (paragraph separator).
354 //
355 // WARNING: There are hints that we may be moving to storing prefs as utf8.
356 // If we ever feed them to the JS compiler as UTF8 then we'll have to worry
357 // about the multibyte sequences that would be interpreted as \u2028 and
358 // \u2029.
359 const char* p;
360
361 aResult.Assign('"');
362
363 // Paranoid worst case all slashes will free quickly.
364 for (p = aOriginal; *p; ++p) {
365 switch (*p) {
366 case '\n':
367 aResult.AppendLiteral("\\n");
368 break;
369
370 case '\r':
371 aResult.AppendLiteral("\\r");
372 break;
373
374 case '\\':
375 aResult.AppendLiteral("\\\\");
376 break;
377
378 case '\"':
379 aResult.AppendLiteral("\\\"");
380 break;
381
382 default:
383 aResult.Append(*p);
384 break;
385 }
386 }
387
388 aResult.Append('"');
389}
390
391namespace mozilla {
392struct PrefsSizes
393{
394 PrefsSizes()
395 : mHashTable(0)
396 , mPrefValues(0)
397 , mStringValues(0)
398 , mCacheData(0)
399 , mRootBranches(0)
400 , mPrefNameArena(0)
401 , mCallbacksObjects(0)
402 , mCallbacksDomains(0)
403 , mMisc(0)
404 {
405 }
406
407 size_t mHashTable;
408 size_t mPrefValues;
409 size_t mStringValues;
410 size_t mCacheData;
411 size_t mRootBranches;
412 size_t mPrefNameArena;
413 size_t mCallbacksObjects;
414 size_t mCallbacksDomains;
415 size_t mMisc;
416};
417}
418
419static ArenaAllocator<8192, 1> gPrefNameArena;
420
421class Pref
422{
423public:
424 explicit Pref(const char* aName)
425 : mName(ArenaStrdup(aName, gPrefNameArena))
426 , mType(static_cast<uint32_t>(PrefType::None))
427 , mIsSticky(false)
428 , mIsLocked(false)
429 , mHasDefaultValue(false)
430 , mHasUserValue(false)
431 , mHasChangedSinceInit(false)
432 , mDefaultValue()
433 , mUserValue()
434 {
435 }
436
437 ~Pref()
438 {
439 // There's no need to free mName because it's allocated in memory owned by
440 // gPrefNameArena.
441
442 mDefaultValue.Clear(Type());
443 mUserValue.Clear(Type());
444 }
445
446 const char* Name() { return mName; }
447
448 // Types.
449
450 PrefType Type() const { return static_cast<PrefType>(mType); }
451 void SetType(PrefType aType) { mType = static_cast<uint32_t>(aType); }
452
453 bool IsType(PrefType aType) const { return Type() == aType; }
454 bool IsTypeNone() const { return IsType(PrefType::None); }
455 bool IsTypeString() const { return IsType(PrefType::String); }
456 bool IsTypeInt() const { return IsType(PrefType::Int); }
457 bool IsTypeBool() const { return IsType(PrefType::Bool); }
458
459 // Other properties.
460
461 bool IsLocked() const { return mIsLocked; }
462 void SetIsLocked(bool aValue)
463 {
464 mIsLocked = aValue;
465 mHasChangedSinceInit = true;
466 }
467
468 bool HasDefaultValue() const { return mHasDefaultValue; }
469 bool HasUserValue() const { return mHasUserValue; }
470
471 // When a content process is created we could tell it about every pref. But
472 // the content process also initializes prefs from file, so we save a lot of
473 // IPC if we only tell it about prefs that have changed since initialization.
474 //
475 // Specifically, we send a pref if any of the following conditions are met.
476 //
477 // - If the pref has changed in any way (default value, user value, or other
478 // attribute, such as whether it is locked) since being initialized from
479 // file.
480 //
481 // - If the pref has a user value. (User values are more complicated than
482 // default values, because they can be loaded from file after
483 // initialization with Preferences::ReadUserPrefsFromFile(), so we are
484 // conservative with them.)
485 //
486 // In other words, prefs that only have a default value and haven't changed
487 // need not be sent. One could do better with effort, but it's ok to be
488 // conservative and this still greatly reduces the number of prefs sent.
489 //
490 // Note: This function is only useful in the parent process.
491 bool MustSendToContentProcesses() const
492 {
493 MOZ_ASSERT(XRE_IsParentProcess());
494 return mHasUserValue || mHasChangedSinceInit;
495 }
496
497 // Other operations.
498
499 bool MatchEntry(const char* aPrefName)
500 {
501 if (!mName || !aPrefName) {
502 return false;
503 }
504
505 return strcmp(mName, aPrefName) == 0;
506 }
507
508 nsresult GetBoolValue(PrefValueKind aKind, bool* aResult)
509 {
510 if (!IsTypeBool()) {
511 return NS_ERROR_UNEXPECTED;
512 }
513
514 if (aKind == PrefValueKind::Default || IsLocked() || !mHasUserValue) {
515 // Do we have a default?
516 if (!mHasDefaultValue) {
517 return NS_ERROR_UNEXPECTED;
518 }
519 *aResult = mDefaultValue.mBoolVal;
520 } else {
521 *aResult = mUserValue.mBoolVal;
522 }
523
524 return NS_OK;
525 }
526
527 nsresult GetIntValue(PrefValueKind aKind, int32_t* aResult)
528 {
529 if (!IsTypeInt()) {
530 return NS_ERROR_UNEXPECTED;
531 }
532
533 if (aKind == PrefValueKind::Default || IsLocked() || !mHasUserValue) {
534 // Do we have a default?
535 if (!mHasDefaultValue) {
536 return NS_ERROR_UNEXPECTED;
537 }
538 *aResult = mDefaultValue.mIntVal;
539 } else {
540 *aResult = mUserValue.mIntVal;
541 }
542
543 return NS_OK;
544 }
545
546 nsresult GetCStringValue(PrefValueKind aKind, nsACString& aResult)
547 {
548 if (!IsTypeString()) {
549 return NS_ERROR_UNEXPECTED;
550 }
551
552 if (aKind == PrefValueKind::Default || IsLocked() || !mHasUserValue) {
553 // Do we have a default?
554 if (!mHasDefaultValue) {
555 return NS_ERROR_UNEXPECTED;
556 }
557 MOZ_ASSERT(mDefaultValue.mStringVal);
558 aResult = mDefaultValue.mStringVal;
559 } else {
560 MOZ_ASSERT(mUserValue.mStringVal);
561 aResult = mUserValue.mStringVal;
562 }
563
564 return NS_OK;
565 }
566
567 void ToDomPref(dom::Pref* aDomPref)
568 {
569 MOZ_ASSERT(XRE_IsParentProcess());
570
571 aDomPref->name() = mName;
572
573 aDomPref->isLocked() = mIsLocked;
574
575 if (mHasDefaultValue) {
576 aDomPref->defaultValue() = dom::PrefValue();
577 mDefaultValue.ToDomPrefValue(Type(),
578 &aDomPref->defaultValue().get_PrefValue());
579 } else {
580 aDomPref->defaultValue() = null_t();
581 }
582
583 if (mHasUserValue) {
584 aDomPref->userValue() = dom::PrefValue();
585 mUserValue.ToDomPrefValue(Type(), &aDomPref->userValue().get_PrefValue());
586 } else {
587 aDomPref->userValue() = null_t();
588 }
589
590 MOZ_ASSERT(aDomPref->defaultValue().type() ==
591 dom::MaybePrefValue::Tnull_t ||
592 aDomPref->userValue().type() == dom::MaybePrefValue::Tnull_t ||
593 (aDomPref->defaultValue().get_PrefValue().type() ==
594 aDomPref->userValue().get_PrefValue().type()));
595 }
596
597 void FromDomPref(const dom::Pref& aDomPref, bool* aValueChanged)
598 {
599 MOZ_ASSERT(!XRE_IsParentProcess());
600 MOZ_ASSERT(strcmp(mName, aDomPref.name().get()) == 0);
601
602 mIsLocked = aDomPref.isLocked();
603
604 const dom::MaybePrefValue& defaultValue = aDomPref.defaultValue();
605 bool defaultValueChanged = false;
606 if (defaultValue.type() == dom::MaybePrefValue::TPrefValue) {
607 PrefValue value;
608 PrefType type = value.FromDomPrefValue(defaultValue.get_PrefValue());
609 if (!ValueMatches(PrefValueKind::Default, type, value)) {
610 // Type() is PrefType::None if it's a newly added pref. This is ok.
611 mDefaultValue.Replace(mHasDefaultValue, Type(), type, value);
612 SetType(type);
613 mHasDefaultValue = true;
614 defaultValueChanged = true;
615 }
616 }
617 // Note: we never clear a default value.
618
619 const dom::MaybePrefValue& userValue = aDomPref.userValue();
620 bool userValueChanged = false;
621 if (userValue.type() == dom::MaybePrefValue::TPrefValue) {
622 PrefValue value;
623 PrefType type = value.FromDomPrefValue(userValue.get_PrefValue());
624 if (!ValueMatches(PrefValueKind::User, type, value)) {
625 // Type() is PrefType::None if it's a newly added pref. This is ok.
626 mUserValue.Replace(mHasUserValue, Type(), type, value);
627 SetType(type);
628 mHasUserValue = true;
629 userValueChanged = true;
630 }
631 } else if (mHasUserValue) {
632 ClearUserValue();
633 userValueChanged = true;
634 }
635
636 mHasChangedSinceInit = true;
637
638 if (userValueChanged || (defaultValueChanged && !mHasUserValue)) {
639 *aValueChanged = true;
640 }
641 }
642
643 bool HasAdvisablySizedValues()
644 {
645 MOZ_ASSERT(XRE_IsParentProcess());
646
647 if (!IsTypeString()) {
648 return true;
649 }
650
651 const char* stringVal;
652 if (mHasDefaultValue) {
653 stringVal = mDefaultValue.mStringVal;
654 if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
655 return false;
656 }
657 }
658
659 if (mHasUserValue) {
660 stringVal = mUserValue.mStringVal;
661 if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
662 return false;
663 }
664 }
665
666 return true;
667 }
668
669private:
670 bool ValueMatches(PrefValueKind aKind, PrefType aType, PrefValue aValue)
671 {
672 return IsType(aType) &&
673 (aKind == PrefValueKind::Default
674 ? mHasDefaultValue && mDefaultValue.Equals(aType, aValue)
675 : mHasUserValue && mUserValue.Equals(aType, aValue));
676 }
677
678public:
679 void ClearUserValue()
680 {
681 mUserValue.Clear(Type());
682 mHasUserValue = false;
683 mHasChangedSinceInit = true;
684 }
685
686 nsresult SetDefaultValue(PrefType aType,
687 PrefValue aValue,
688 bool aIsSticky,
689 bool aIsLocked,
690 bool aFromInit,
691 bool* aValueChanged)
692 {
693 // Types must always match when setting the default value.
694 if (!IsType(aType)) {
695 return NS_ERROR_UNEXPECTED;
696 }
697
698 // Should we set the default value? Only if the pref is not locked, and
699 // doing so would change the default value.
700 if (!IsLocked()) {
701 if (aIsLocked) {
702 SetIsLocked(true);
703 }
704 if (!ValueMatches(PrefValueKind::Default, aType, aValue)) {
705 mDefaultValue.Replace(mHasDefaultValue, Type(), aType, aValue);
706 mHasDefaultValue = true;
707 if (!aFromInit) {
708 mHasChangedSinceInit = true;
709 }
710 if (aIsSticky) {
711 mIsSticky = true;
712 }
713 if (!mHasUserValue) {
714 *aValueChanged = true;
715 }
716 // What if we change the default to be the same as the user value?
717 // Should we clear the user value? Currently we don't.
718 }
719 }
720 return NS_OK;
721 }
722
723 nsresult SetUserValue(PrefType aType,
724 PrefValue aValue,
725 bool aFromInit,
726 bool* aValueChanged)
727 {
728 // If we have a default value, types must match when setting the user
729 // value.
730 if (mHasDefaultValue && !IsType(aType)) {
731 return NS_ERROR_UNEXPECTED;
732 }
733
734 // Should we clear the user value, if present? Only if the new user value
735 // matches the default value, and the pref isn't sticky, and we aren't
736 // force-setting it during initialization.
737 if (ValueMatches(PrefValueKind::Default, aType, aValue) && !mIsSticky &&
738 !aFromInit) {
739 if (mHasUserValue) {
740 ClearUserValue();
741 if (!IsLocked()) {
742 *aValueChanged = true;
743 }
744 }
745
746 // Otherwise, should we set the user value? Only if doing so would
747 // change the user value.
748 } else if (!ValueMatches(PrefValueKind::User, aType, aValue)) {
749 mUserValue.Replace(mHasUserValue, Type(), aType, aValue);
750 SetType(aType); // needed because we may have changed the type
751 mHasUserValue = true;
752 if (!aFromInit) {
753 mHasChangedSinceInit = true;
754 }
755 if (!IsLocked()) {
756 *aValueChanged = true;
757 }
758 }
759 return NS_OK;
760 }
761
762 // Returns false if this pref doesn't have a user value worth saving.
763 bool UserValueToStringForSaving(nsCString& aStr)
764 {
765 // Should we save the user value, if present? Only if it does not match the
766 // default value, or it is sticky.
767 if (mHasUserValue &&
768 (!ValueMatches(PrefValueKind::Default, Type(), mUserValue) ||
769 mIsSticky)) {
770 if (IsTypeString()) {
771 StrEscape(mUserValue.mStringVal, aStr);
772
773 } else if (IsTypeInt()) {
774 aStr.AppendInt(mUserValue.mIntVal);
775
776 } else if (IsTypeBool()) {
777 aStr = mUserValue.mBoolVal ? "true" : "false";
778 }
779 return true;
780 }
781
782 // Do not save default prefs that haven't changed.
783 return false;
784 }
785
786 // Prefs are serialized in a manner that mirrors dom::Pref. The two should be
787 // kept in sync. E.g. if something is added to one it should also be added to
788 // the other. (It would be nice to be able to use the code generated from
789 // IPDL for serializing dom::Pref here instead of writing by hand this
790 // serialization/deserialization. Unfortunately, that generated code is
791 // difficult to use directly, outside of the IPDL IPC code.)
792 //
793 // The grammar for the serialized prefs has the following form.
794 //
795 // <pref> = <type> <locked> ':' <name> ':' <value>? ':' <value>? '\n'
796 // <type> = 'B' | 'I' | 'S'
797 // <locked> = 'L' | '-'
798 // <name> = <string-value>
799 // <value> = <bool-value> | <int-value> | <string-value>
800 // <bool-value> = 'T' | 'F'
801 // <int-value> = an integer literal accepted by strtol()
802 // <string-value> = <int-value> '/' <chars>
803 // <chars> = any char sequence of length dictated by the preceding
804 // <int-value>.
805 //
806 // No whitespace is tolerated between tokens. <type> must match the types of
807 // the values.
808 //
809 // The serialization is text-based, rather than binary, for the following
810 // reasons.
811 //
812 // - The size difference wouldn't be much different between text-based and
813 // binary. Most of the space is for strings (pref names and string pref
814 // values), which would be the same in both styles. And other differences
815 // would be minimal, e.g. small integers are shorter in text but long
816 // integers are longer in text.
817 //
818 // - Likewise, speed differences should be negligible.
819 //
820 // - It's much easier to debug a text-based serialization. E.g. you can
821 // print it and inspect it easily in a debugger.
822 //
823 // Examples of unlocked boolean prefs:
824 // - "B-:8/my.bool1:F:T\n"
825 // - "B-:8/my.bool2:F:\n"
826 // - "B-:8/my.bool3::T\n"
827 //
828 // Examples of locked integer prefs:
829 // - "IL:7/my.int1:0:1\n"
830 // - "IL:7/my.int2:123:\n"
831 // - "IL:7/my.int3::-99\n"
832 //
833 // Examples of unlocked string prefs:
834 // - "S-:10/my.string1:3/abc:4/wxyz\n"
835 // - "S-:10/my.string2:5/1.234:\n"
836 // - "S-:10/my.string3::7/string!\n"
837
838 void SerializeAndAppend(nsCString& aStr)
839 {
840 switch (Type()) {
841 case PrefType::Bool:
842 aStr.Append('B');
843 break;
844
845 case PrefType::Int:
846 aStr.Append('I');
847 break;
848
849 case PrefType::String: {
850 aStr.Append('S');
851 break;
852 }
853
854 case PrefType::None:
855 default:
856 MOZ_CRASH();
857 }
858
859 aStr.Append(mIsLocked ? 'L' : '-');
860 aStr.Append(':');
861
862 SerializeAndAppendString(mName, aStr);
863 aStr.Append(':');
864
865 if (mHasDefaultValue) {
866 mDefaultValue.SerializeAndAppend(Type(), aStr);
867 }
868 aStr.Append(':');
869
870 if (mHasUserValue) {
871 mUserValue.SerializeAndAppend(Type(), aStr);
872 }
873 aStr.Append('\n');
874 }
875
876 static char* Deserialize(char* aStr, dom::Pref* aDomPref)
877 {
878 char* p = aStr;
879
880 // The type.
881 PrefType type;
882 if (*p == 'B') {
883 type = PrefType::Bool;
884 } else if (*p == 'I') {
885 type = PrefType::Int;
886 } else if (*p == 'S') {
887 type = PrefType::String;
888 } else {
889 NS_ERROR("bad pref type");
890 type = PrefType::None;
891 }
892 p++; // move past the type char
893
894 // Locked?
895 bool isLocked;
896 if (*p == 'L') {
897 isLocked = true;
898 } else if (*p == '-') {
899 isLocked = false;
900 } else {
901 NS_ERROR("bad pref locked status");
902 isLocked = false;
903 }
904 p++; // move past the isLocked char
905
906 MOZ_ASSERT(*p == ':');
907 p++; // move past the ':'
908
909 // The pref name.
910 nsCString name;
911 p = DeserializeString(p, name);
912
913 MOZ_ASSERT(*p == ':');
914 p++; // move past the ':' preceding the default value
915
916 dom::MaybePrefValue maybeDefaultValue;
917 if (*p != ':') {
918 dom::PrefValue defaultValue;
919 p = PrefValue::Deserialize(type, p, &maybeDefaultValue);
920 }
921
922 MOZ_ASSERT(*p == ':');
923 p++; // move past the ':' between the default and user values
924
925 dom::MaybePrefValue maybeUserValue;
926 if (*p != '\n') {
927 dom::PrefValue userValue;
928 p = PrefValue::Deserialize(type, p, &maybeUserValue);
929 }
930
931 MOZ_ASSERT(*p == '\n');
932 p++; // move past the '\n' following the user value
933
934 *aDomPref = dom::Pref(name, isLocked, maybeDefaultValue, maybeUserValue);
935
936 return p;
937 }
938
939 void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, PrefsSizes& aSizes)
940 {
941 // Note: mName is allocated in gPrefNameArena, measured elsewhere.
942 aSizes.mPrefValues += aMallocSizeOf(this);
943 if (IsTypeString()) {
944 if (mHasDefaultValue) {
945 aSizes.mStringValues += aMallocSizeOf(mDefaultValue.mStringVal);
946 }
947 if (mHasUserValue) {
948 aSizes.mStringValues += aMallocSizeOf(mUserValue.mStringVal);
949 }
950 }
951 }
952
953private:
954 const char* mName; // allocated in gPrefNameArena
955
956 uint32_t mType : 2;
957 uint32_t mIsSticky : 1;
958 uint32_t mIsLocked : 1;
959 uint32_t mHasDefaultValue : 1;
960 uint32_t mHasUserValue : 1;
961 uint32_t mHasChangedSinceInit : 1;
962
963 PrefValue mDefaultValue;
964 PrefValue mUserValue;
965};
966
967class PrefEntry : public PLDHashEntryHdr
968{
969public:
970#ifdef DEBUG
971 // This field is before mPref to minimize sizeof(PrefEntry) on 64-bit.
972 uint32_t mAccessCount;
973#endif
974 Pref* mPref; // Note: this is never null in a live entry.
975
976 static bool MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey)
977 {
978 auto entry = static_cast<const PrefEntry*>(aEntry);
979 auto prefName = static_cast<const char*>(aKey);
980
981 return entry->mPref->MatchEntry(prefName);
982 }
983
984 static void InitEntry(PLDHashEntryHdr* aEntry, const void* aKey)
985 {
986 auto entry = static_cast<PrefEntry*>(aEntry);
987 auto prefName = static_cast<const char*>(aKey);
988
989#ifdef DEBUG
990 entry->mAccessCount = 0;
991#endif
992 entry->mPref = new Pref(prefName);
993 }
994
995 static void ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
996 {
997 auto entry = static_cast<PrefEntry*>(aEntry);
998
999 delete entry->mPref;
1000 entry->mPref = nullptr;
1001 }
1002};
1003
1004class CallbackNode
1005{
1006public:
1007 CallbackNode(const nsACString& aDomain,
1008 PrefChangedFunc aFunc,
1009 void* aData,
1010 Preferences::MatchKind aMatchKind)
1011 : mDomain(aDomain)
1012 , mFunc(aFunc)
1013 , mData(aData)
1014 , mNextAndMatchKind(aMatchKind)
1015 {
1016 }
1017
1018 // mDomain is a UniquePtr<>, so any uses of Domain() should only be temporary
1019 // borrows.
1020 const nsCString& Domain() const { return mDomain; }
1021
1022 PrefChangedFunc Func() const { return mFunc; }
1023 void ClearFunc() { mFunc = nullptr; }
1024
1025 void* Data() const { return mData; }
1026
1027 Preferences::MatchKind MatchKind() const
1028 {
1029 return static_cast<Preferences::MatchKind>(mNextAndMatchKind &
1030 kMatchKindMask);
1031 }
1032
1033 CallbackNode* Next() const
1034 {
1035 return reinterpret_cast<CallbackNode*>(mNextAndMatchKind & kNextMask);
1036 }
1037
1038 void SetNext(CallbackNode* aNext)
1039 {
1040 uintptr_t matchKind = mNextAndMatchKind & kMatchKindMask;
1041 mNextAndMatchKind = reinterpret_cast<uintptr_t>(aNext);
1042 MOZ_ASSERT((mNextAndMatchKind & kMatchKindMask) == 0);
1043 mNextAndMatchKind |= matchKind;
1044 }
1045
1046 void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, PrefsSizes& aSizes)
1047 {
1048 aSizes.mCallbacksObjects += aMallocSizeOf(this);
1049 aSizes.mCallbacksDomains +=
1050 mDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
1051 }
1052
1053private:
1054 static const uintptr_t kMatchKindMask = uintptr_t(0x1);
1055 static const uintptr_t kNextMask = ~kMatchKindMask;
1056
1057 nsCString mDomain;
1058
1059 // If someone attempts to remove the node from the callback list while
1060 // NotifyCallbacks() is running, |func| is set to nullptr. Such nodes will
1061 // be removed at the end of NotifyCallbacks().
1062 PrefChangedFunc mFunc;
1063 void* mData;
1064
1065 // Conceptually this is two fields:
1066 // - CallbackNode* mNext;
1067 // - Preferences::MatchKind mMatchKind;
1068 // They are combined into a tagged pointer to save memory.
1069 uintptr_t mNextAndMatchKind;
1070};
1071
1072static PLDHashTable* gHashTable;
1073
1074// The callback list contains all the priority callbacks followed by the
1075// non-priority callbacks. gLastPriorityNode records where the first part ends.
1076static CallbackNode* gFirstCallback = nullptr;
1077static CallbackNode* gLastPriorityNode = nullptr;
1078
1079// These are only used during the call to NotifyCallbacks().
1080static bool gCallbacksInProgress = false;
1081static bool gShouldCleanupDeadNodes = false;
1082
1083static PLDHashTableOps pref_HashTableOps = {
1084 PLDHashTable::HashStringKey, PrefEntry::MatchEntry,
1085 PLDHashTable::MoveEntryStub, PrefEntry::ClearEntry,
1086 PrefEntry::InitEntry,
1087};
1088
1089static Pref*
1090pref_HashTableLookup(const char* aPrefName);
1091
1092static void
1093NotifyCallbacks(const char* aPrefName);
1094
1095#define PREF_HASHTABLE_INITIAL_LENGTH 1024
1096
1097static PrefSaveData
1098pref_savePrefs()
1099{
1100 MOZ_ASSERT(NS_IsMainThread());
1101
1102 PrefSaveData savedPrefs(gHashTable->EntryCount());
1103
1104 for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
1105 Pref* pref = static_cast<PrefEntry*>(iter.Get())->mPref;
1106
1107 nsAutoCString prefValueStr;
1108 if (!pref->UserValueToStringForSaving(prefValueStr)) {
1109 continue;
1110 }
1111
1112 nsAutoCString prefNameStr;
1113 StrEscape(pref->Name(), prefNameStr);
1114
1115 nsPrintfCString str(
1116 "user_pref(%s, %s);", prefNameStr.get(), prefValueStr.get());
1117
1118 savedPrefs.AppendElement(str);
1119 }
1120
1121 return savedPrefs;
1122}
1123
1124#ifdef DEBUG
1125
1126// Note that this never changes in the parent process, and is only read in
1127// content processes.
1128static bool gContentProcessPrefsAreInited = false;
1129
1130#endif // DEBUG
1131
1132static PrefEntry*
1133pref_HashTableLookupInner(const char* aPrefName)
1134{
1135 MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal());
1136
1137 MOZ_ASSERT_IF(!XRE_IsParentProcess(), gContentProcessPrefsAreInited);
1138
1139 return static_cast<PrefEntry*>(gHashTable->Search(aPrefName));
1140}
1141
1142static Pref*
1143pref_HashTableLookup(const char* aPrefName)
1144{
1145 PrefEntry* entry = pref_HashTableLookupInner(aPrefName);
1146 if (!entry) {
1147 return nullptr;
1148 }
1149
1150#ifdef DEBUG
1151 entry->mAccessCount += 1;
1152#endif
1153
1154 return entry->mPref;
1155}
1156
1157static nsresult
1158pref_SetPref(const char* aPrefName,
1159 PrefType aType,
1160 PrefValueKind aKind,
1161 PrefValue aValue,
1162 bool aIsSticky,
1163 bool aIsLocked,
1164 bool aFromInit)
1165{
1166 MOZ_ASSERT(NS_IsMainThread());
1167
1168 if (!gHashTable) {
1169 return NS_ERROR_OUT_OF_MEMORY;
1170 }
1171
1172 auto entry = static_cast<PrefEntry*>(gHashTable->Add(aPrefName, fallible));
1173 if (!entry) {
1174 return NS_ERROR_OUT_OF_MEMORY;
1175 }
1176
1177 Pref* pref = entry->mPref;
1178 if (pref->IsTypeNone()) {
1179 // New entry. Set the type.
1180 pref->SetType(aType);
1181 }
1182
1183 bool valueChanged = false;
1184 nsresult rv;
1185 if (aKind == PrefValueKind::Default) {
1186 rv = pref->SetDefaultValue(
1187 aType, aValue, aIsSticky, aIsLocked, aFromInit, &valueChanged);
1188 } else {
1189 MOZ_ASSERT(!aIsLocked); // `locked` is disallowed in user pref files
1190 rv = pref->SetUserValue(aType, aValue, aFromInit, &valueChanged);
1191 }
1192 if (NS_FAILED(rv)) {
1193 NS_WARNING(
1194 nsPrintfCString(
1195 "Rejected attempt to change type of pref %s's %s value from %s to %s",
1196 aPrefName,
1197 (aKind == PrefValueKind::Default) ? "default" : "user",
1198 PrefTypeToString(pref->Type()),
1199 PrefTypeToString(aType))
1200 .get());
1201
1202 return rv;
1203 }
1204
1205 if (valueChanged) {
1206 if (aKind == PrefValueKind::User && XRE_IsParentProcess()) {
1207 Preferences::HandleDirty();
1208 }
1209 NotifyCallbacks(aPrefName);
1210 }
1211
1212 return NS_OK;
1213}
1214
1215// Removes |node| from callback list. Returns the node after the deleted one.
1216static CallbackNode*
1217pref_RemoveCallbackNode(CallbackNode* aNode, CallbackNode* aPrevNode)
1218{
1219 MOZ_ASSERT(!aPrevNode || aPrevNode->Next() == aNode);
1220 MOZ_ASSERT(aPrevNode || gFirstCallback == aNode);
1221 MOZ_ASSERT(!gCallbacksInProgress);
1222
1223 CallbackNode* next_node = aNode->Next();
1224 if (aPrevNode) {
1225 aPrevNode->SetNext(next_node);
1226 } else {
1227 gFirstCallback = next_node;
1228 }
1229 if (gLastPriorityNode == aNode) {
1230 gLastPriorityNode = aPrevNode;
1231 }
1232 delete aNode;
1233 return next_node;
1234}
1235
1236static void
1237NotifyCallbacks(const char* aPrefName)
1238{
1239 bool reentered = gCallbacksInProgress;
1240
1241 // Nodes must not be deleted while gCallbacksInProgress is true.
1242 // Nodes that need to be deleted are marked for deletion by nulling
1243 // out the |func| pointer. We release them at the end of this function
1244 // if we haven't reentered.
1245 gCallbacksInProgress = true;
1246
1247 nsDependentCString prefName(aPrefName);
1248
1249 for (CallbackNode* node = gFirstCallback; node; node = node->Next()) {
1250 if (node->Func()) {
1251 bool matches = node->MatchKind() == Preferences::ExactMatch
1252 ? node->Domain() == prefName
1253 : StringBeginsWith(prefName, node->Domain());
1254 if (matches) {
1255 (node->Func())(aPrefName, node->Data());
1256 }
1257 }
1258 }
1259
1260 gCallbacksInProgress = reentered;
1261
1262 if (gShouldCleanupDeadNodes && !gCallbacksInProgress) {
1263 CallbackNode* prev_node = nullptr;
1264 CallbackNode* node = gFirstCallback;
1265
1266 while (node) {
1267 if (!node->Func()) {
1268 node = pref_RemoveCallbackNode(node, prev_node);
1269 } else {
1270 prev_node = node;
1271 node = node->Next();
1272 }
1273 }
1274 gShouldCleanupDeadNodes = false;
1275 }
1276}
1277
1278//===========================================================================
1279// Prefs parsing
1280//===========================================================================
1281
1282struct TelemetryLoadData
1283{
1284 uint32_t mFileLoadSize_B;
1285 uint32_t mFileLoadNumPrefs;
1286 uint32_t mFileLoadTime_us;
1287};
1288
1289static nsDataHashtable<nsCStringHashKey, TelemetryLoadData>* gTelemetryLoadData;
1290
1291extern "C" {
1292
1293// Keep this in sync with PrefFn in prefs_parser/src/lib.rs.
1294typedef void (*PrefsParserPrefFn)(const char* aPrefName,
1295 PrefType aType,
1296 PrefValueKind aKind,
1297 PrefValue aValue,
1298 bool aIsSticky,
1299 bool aIsLocked);
1300
1301// Keep this in sync with ErrorFn in prefs_parser/src/lib.rs.
1302//
1303// `aMsg` is just a borrow of the string, and must be copied if it is used
1304// outside the lifetime of the prefs_parser_parse() call.
1305typedef void (*PrefsParserErrorFn)(const char* aMsg);
1306
1307// Keep this in sync with prefs_parser_parse() in prefs_parser/src/lib.rs.
1308bool
1309prefs_parser_parse(const char* aPath,
1310 PrefValueKind aKind,
1311 const char* aBuf,
1312 size_t aLen,
1313 PrefsParserPrefFn aPrefFn,
1314 PrefsParserErrorFn aErrorFn);
1315}
1316
1317class Parser
1318{
1319public:
1320 Parser() = default;
1321 ~Parser() = default;
1322
1323 bool Parse(const nsCString& aName,
1324 PrefValueKind aKind,
1325 const char* aPath,
1326 const TimeStamp& aStartTime,
1327 const nsCString& aBuf)
1328 {
1329 sNumPrefs = 0;
1330 bool ok = prefs_parser_parse(
1331 aPath, aKind, aBuf.get(), aBuf.Length(), HandlePref, HandleError);
1332 if (!ok) {
1333 return false;
1334 }
1335
1336 uint32_t loadTime_us = (TimeStamp::Now() - aStartTime).ToMicroseconds();
1337
1338 // Most prefs files are read before telemetry initializes, so we have to
1339 // save these measurements now and send them to telemetry later.
1340 TelemetryLoadData loadData = { uint32_t(aBuf.Length()),
1341 sNumPrefs,
1342 loadTime_us };
1343 gTelemetryLoadData->Put(aName, loadData);
1344
1345 return true;
1346 }
1347
1348private:
1349 static void HandlePref(const char* aPrefName,
1350 PrefType aType,
1351 PrefValueKind aKind,
1352 PrefValue aValue,
1353 bool aIsSticky,
1354 bool aIsLocked)
1355 {
1356 sNumPrefs++;
1357 pref_SetPref(aPrefName,
1358 aType,
1359 aKind,
1360 aValue,
1361 aIsSticky,
1362 aIsLocked,
1363 /* fromInit */ true);
1364 }
1365
1366 static void HandleError(const char* aMsg)
1367 {
1368 nsresult rv;
1369 nsCOMPtr<nsIConsoleService> console =
1370 do_GetService("@mozilla.org/consoleservice;1", &rv);
1371 if (NS_SUCCEEDED(rv)) {
1372 console->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg).get());
1373 }
1374#ifdef DEBUG
1375 NS_ERROR(aMsg);
1376#else
1377 printf_stderr("%s\n", aMsg);
1378#endif
1379 }
1380
1381 // This is static so that HandlePref() can increment it easily. This is ok
1382 // because prefs files are read one at a time.
1383 static uint32_t sNumPrefs;
1384};
1385
1386uint32_t Parser::sNumPrefs = 0;
1387
1388// The following code is test code for the gtest.
1389
1390static void
1391TestParseErrorHandlePref(const char* aPrefName,
1392 PrefType aType,
1393 PrefValueKind aKind,
1394 PrefValue aValue,
1395 bool aIsSticky,
1396 bool aIsLocked)
1397{
1398}
1399
1400static nsCString gTestParseErrorMsgs;
1401
1402static void
1403TestParseErrorHandleError(const char* aMsg)
1404{
1405 gTestParseErrorMsgs.Append(aMsg);
1406 gTestParseErrorMsgs.Append('\n');
1407}
1408
1409// Keep this in sync with the declaration in test/gtest/Parser.cpp.
1410void
1411TestParseError(PrefValueKind aKind, const char* aText, nsCString& aErrorMsg)
1412{
1413 prefs_parser_parse("test",
1414 aKind,
1415 aText,
1416 strlen(aText),
1417 TestParseErrorHandlePref,
1418 TestParseErrorHandleError);
1419
1420 // Copy the error messages into the outparam, then clear them from
1421 // gTestParseErrorMsgs.
1422 aErrorMsg.Assign(gTestParseErrorMsgs);
1423 gTestParseErrorMsgs.Truncate();
1424}
1425
1426void
1427SendTelemetryLoadData()
1428{
1429 for (auto iter = gTelemetryLoadData->Iter(); !iter.Done(); iter.Next()) {
1430 const nsCString& filename = PromiseFlatCString(iter.Key());
1431 const TelemetryLoadData& data = iter.Data();
1432 Telemetry::Accumulate(
1433 Telemetry::PREFERENCES_FILE_LOAD_SIZE_B, filename, data.mFileLoadSize_B);
1434 Telemetry::Accumulate(Telemetry::PREFERENCES_FILE_LOAD_NUM_PREFS,
1435 filename,
1436 data.mFileLoadNumPrefs);
1437 Telemetry::Accumulate(Telemetry::PREFERENCES_FILE_LOAD_TIME_US,
1438 filename,
1439 data.mFileLoadTime_us);
1440 }
1441
1442 gTelemetryLoadData->Clear();
1443}
1444
1445//===========================================================================
1446// nsPrefBranch et al.
1447//===========================================================================
1448
1449namespace mozilla {
1450class PreferenceServiceReporter;
1451} // namespace mozilla
1452
1453class PrefCallback : public PLDHashEntryHdr
1454{
1455 friend class mozilla::PreferenceServiceReporter;
1456
1457public:
1458 typedef PrefCallback* KeyType;
1459 typedef const PrefCallback* KeyTypePointer;
1460
1461 static const PrefCallback* KeyToPointer(PrefCallback* aKey) { return aKey; }
1462
1463 static PLDHashNumber HashKey(const PrefCallback* aKey)
1464 {
1465 uint32_t hash = mozilla::HashString(aKey->mDomain);
1466 return mozilla::AddToHash(hash, aKey->mCanonical);
1467 }
1468
1469public:
1470 // Create a PrefCallback with a strong reference to its observer.
1471 PrefCallback(const nsACString& aDomain,
1472 nsIObserver* aObserver,
1473 nsPrefBranch* aBranch)
1474 : mDomain(aDomain)
1475 , mBranch(aBranch)
1476 , mWeakRef(nullptr)
1477 , mStrongRef(aObserver)
1478 {
1479 MOZ_COUNT_CTOR(PrefCallback);
1480 nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver);
1481 mCanonical = canonical;
1482 }
1483
1484 // Create a PrefCallback with a weak reference to its observer.
1485 PrefCallback(const nsACString& aDomain,
1486 nsISupportsWeakReference* aObserver,
1487 nsPrefBranch* aBranch)
1488 : mDomain(aDomain)
1489 , mBranch(aBranch)
1490 , mWeakRef(do_GetWeakReference(aObserver))
1491 , mStrongRef(nullptr)
1492 {
1493 MOZ_COUNT_CTOR(PrefCallback);
1494 nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver);
1495 mCanonical = canonical;
1496 }
1497
1498 // Copy constructor needs to be explicit or the linker complains.
1499 explicit PrefCallback(const PrefCallback*& aCopy)
1500 : mDomain(aCopy->mDomain)
1501 , mBranch(aCopy->mBranch)
1502 , mWeakRef(aCopy->mWeakRef)
1503 , mStrongRef(aCopy->mStrongRef)
1504 , mCanonical(aCopy->mCanonical)
1505 {
1506 MOZ_COUNT_CTOR(PrefCallback);
1507 }
1508
1509 ~PrefCallback() { MOZ_COUNT_DTOR(PrefCallback); }
1510
1511 bool KeyEquals(const PrefCallback* aKey) const
1512 {
1513 // We want to be able to look up a weakly-referencing PrefCallback after
1514 // its observer has died so we can remove it from the table. Once the
1515 // callback's observer dies, its canonical pointer is stale -- in
1516 // particular, we may have allocated a new observer in the same spot in
1517 // memory! So we can't just compare canonical pointers to determine whether
1518 // aKey refers to the same observer as this.
1519 //
1520 // Our workaround is based on the way we use this hashtable: When we ask
1521 // the hashtable to remove a PrefCallback whose weak reference has expired,
1522 // we use as the key for removal the same object as was inserted into the
1523 // hashtable. Thus we can say that if one of the keys' weak references has
1524 // expired, the two keys are equal iff they're the same object.
1525
1526 if (IsExpired() || aKey->IsExpired()) {
1527 return this == aKey;
1528 }
1529
1530 if (mCanonical != aKey->mCanonical) {
1531 return false;
1532 }
1533
1534 return mDomain.Equals(aKey->mDomain);
1535 }
1536
1537 PrefCallback* GetKey() const { return const_cast<PrefCallback*>(this); }
1538
1539 // Get a reference to the callback's observer, or null if the observer was
1540 // weakly referenced and has been destroyed.
1541 already_AddRefed<nsIObserver> GetObserver() const
1542 {
1543 if (!IsWeak()) {
1544 nsCOMPtr<nsIObserver> copy = mStrongRef;
1545 return copy.forget();
1546 }
1547
1548 nsCOMPtr<nsIObserver> observer = do_QueryReferent(mWeakRef);
1549 return observer.forget();
1550 }
1551
1552 const nsCString& GetDomain() const { return mDomain; }
1553
1554 nsPrefBranch* GetPrefBranch() const { return mBranch; }
1555
1556 // Has this callback's weak reference died?
1557 bool IsExpired() const
1558 {
1559 if (!IsWeak())
1560 return false;
1561
1562 nsCOMPtr<nsIObserver> observer(do_QueryReferent(mWeakRef));
1563 return !observer;
1564 }
1565
1566 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
1567 {
1568 size_t n = aMallocSizeOf(this);
1569 n += mDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
1570
1571 // All the other fields are non-owning pointers, so we don't measure them.
1572
1573 return n;
1574 }
1575
1576 enum
1577 {
1578 ALLOW_MEMMOVE = true
1579 };
1580
1581private:
1582 nsCString mDomain;
1583 nsPrefBranch* mBranch;
1584
1585 // Exactly one of mWeakRef and mStrongRef should be non-null.
1586 nsWeakPtr mWeakRef;
1587 nsCOMPtr<nsIObserver> mStrongRef;
1588
1589 // We need a canonical nsISupports pointer, per bug 578392.
1590 nsISupports* mCanonical;
1591
1592 bool IsWeak() const { return !!mWeakRef; }
1593};
1594
1595class nsPrefBranch final
1596 : public nsIPrefBranch
1597 , public nsIObserver
1598 , public nsSupportsWeakReference
1599{
1600 friend class mozilla::PreferenceServiceReporter;
1601
1602public:
1603 NS_DECL_ISUPPORTS
1604 NS_DECL_NSIPREFBRANCH
1605 NS_DECL_NSIOBSERVER
1606
1607 nsPrefBranch(const char* aPrefRoot, PrefValueKind aKind);
1608 nsPrefBranch() = delete;
1609
1610 static void NotifyObserver(const char* aNewpref, void* aData);
1611
1612 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
1613
1614private:
1615 // Helper class for either returning a raw cstring or nsCString.
1616 typedef mozilla::Variant<const char*, const nsCString> PrefNameBase;
1617 class PrefName : public PrefNameBase
1618 {
1619 public:
1620 explicit PrefName(const char* aName)
1621 : PrefNameBase(aName)
1622 {
1623 }
1624 explicit PrefName(const nsCString& aName)
1625 : PrefNameBase(aName)
1626 {
1627 }
1628
1629 // Use default move constructors, disallow copy constructors.
1630 PrefName(PrefName&& aOther) = default;
1631 PrefName& operator=(PrefName&& aOther) = default;
1632 PrefName(const PrefName&) = delete;
1633 PrefName& operator=(const PrefName&) = delete;
1634
1635 struct PtrMatcher
1636 {
1637 static const char* match(const char* aVal) { return aVal; }
1638 static const char* match(const nsCString& aVal) { return aVal.get(); }
1639 };
1640
1641 struct CStringMatcher
1642 {
1643 // Note: This is a reference, not an instance. It's used to pass our outer
1644 // method argument through to our matcher methods.
1645 nsACString& mStr;
1646
1647 void match(const char* aVal) { mStr.Assign(aVal); }
1648 void match(const nsCString& aVal) { mStr.Assign(aVal); }
1649 };
1650
1651 struct LenMatcher
1652 {
1653 static size_t match(const char* aVal) { return strlen(aVal); }
1654 static size_t match(const nsCString& aVal) { return aVal.Length(); }
1655 };
1656
1657 const char* get() const
1658 {
1659 static PtrMatcher m;
1660 return match(m);
1661 }
1662
1663 void get(nsACString& aStr) const { match(CStringMatcher{ aStr }); }
1664
1665 size_t Length() const
1666 {
1667 static LenMatcher m;
1668 return match(m);
1669 }
1670 };
1671
1672 virtual ~nsPrefBranch();
1673
1674 int32_t GetRootLength() const { return mPrefRoot.Length(); }
1675
1676 nsresult GetDefaultFromPropertiesFile(const char* aPrefName,
1677 nsAString& aReturn);
1678
1679 // As SetCharPref, but without any check on the length of |aValue|.
1680 nsresult SetCharPrefNoLengthCheck(const char* aPrefName,
1681 const nsACString& aValue);
1682
1683 // Reject strings that are more than 1Mb, warn if strings are more than 16kb.
1684 nsresult CheckSanityOfStringLength(const char* aPrefName,
1685 const nsAString& aValue);
1686 nsresult CheckSanityOfStringLength(const char* aPrefName,
1687 const nsACString& aValue);
1688 nsresult CheckSanityOfStringLength(const char* aPrefName,
1689 const uint32_t aLength);
1690
1691 void RemoveExpiredCallback(PrefCallback* aCallback);
1692
1693 PrefName GetPrefName(const char* aPrefName) const
1694 {
1695 return GetPrefName(nsDependentCString(aPrefName));
1696 }
1697
1698 PrefName GetPrefName(const nsACString& aPrefName) const;
1699
1700 void FreeObserverList(void);
1701
1702 const nsCString mPrefRoot;
1703 PrefValueKind mKind;
1704
1705 bool mFreeingObserverList;
1706 nsClassHashtable<PrefCallback, PrefCallback> mObservers;
1707};
1708
1709class nsPrefLocalizedString final : public nsIPrefLocalizedString
1710{
1711public:
1712 nsPrefLocalizedString();
1713
1714 NS_DECL_ISUPPORTS
1715 NS_FORWARD_NSISUPPORTSPRIMITIVE(mUnicodeString->)
1716 NS_FORWARD_NSISUPPORTSSTRING(mUnicodeString->)
1717
1718 nsresult Init();
1719
1720private:
1721 virtual ~nsPrefLocalizedString();
1722
1723 nsCOMPtr<nsISupportsString> mUnicodeString;
1724};
1725
1726class nsRelativeFilePref : public nsIRelativeFilePref
1727{
1728public:
1729 NS_DECL_ISUPPORTS
1730 NS_DECL_NSIRELATIVEFILEPREF
1731
1732 nsRelativeFilePref();
1733
1734private:
1735 virtual ~nsRelativeFilePref();
1736
1737 nsCOMPtr<nsIFile> mFile;
1738 nsCString mRelativeToKey;
1739};
1740
1741//----------------------------------------------------------------------------
1742// nsPrefBranch
1743//----------------------------------------------------------------------------
1744
1745nsPrefBranch::nsPrefBranch(const char* aPrefRoot, PrefValueKind aKind)
1746 : mPrefRoot(aPrefRoot)
1747 , mKind(aKind)
1748 , mFreeingObserverList(false)
1749 , mObservers()
1750{
1751 nsCOMPtr<nsIObserverService> observerService =
1752 mozilla::services::GetObserverService();
1753 if (observerService) {
1754 ++mRefCnt; // must be > 0 when we call this, or we'll get deleted!
1755
1756 // Add weakly so we don't have to clean up at shutdown.
1757 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
1758 --mRefCnt;
1759 }
1760}
1761
1762nsPrefBranch::~nsPrefBranch()
1763{
1764 FreeObserverList();
1765
1766 nsCOMPtr<nsIObserverService> observerService =
1767 mozilla::services::GetObserverService();
1768 if (observerService) {
1769 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1770 }
1771}
1772
1773NS_IMPL_ISUPPORTS(nsPrefBranch,
1774 nsIPrefBranch,
1775 nsIObserver,
1776 nsISupportsWeakReference)
1777
1778NS_IMETHODIMP
1779nsPrefBranch::GetRoot(nsACString& aRoot)
1780{
1781 aRoot = mPrefRoot;
1782 return NS_OK;
1783}
1784
1785NS_IMETHODIMP
1786nsPrefBranch::GetPrefType(const char* aPrefName, int32_t* aRetVal)
1787{
1788 NS_ENSURE_ARG(aPrefName);
1789
1790 const PrefName& prefName = GetPrefName(aPrefName);
1791 *aRetVal = Preferences::GetType(prefName.get());
1792 return NS_OK;
1793}
1794
1795NS_IMETHODIMP
1796nsPrefBranch::GetBoolPrefWithDefault(const char* aPrefName,
1797 bool aDefaultValue,
1798 uint8_t aArgc,
1799 bool* aRetVal)
1800{
1801 nsresult rv = GetBoolPref(aPrefName, aRetVal);
1802 if (NS_FAILED(rv) && aArgc == 1) {
1803 *aRetVal = aDefaultValue;
1804 return NS_OK;
1805 }
1806
1807 return rv;
1808}
1809
1810NS_IMETHODIMP
1811nsPrefBranch::GetBoolPref(const char* aPrefName, bool* aRetVal)
1812{
1813 NS_ENSURE_ARG(aPrefName);
1814
1815 const PrefName& pref = GetPrefName(aPrefName);
1816 return Preferences::GetBool(pref.get(), aRetVal, mKind);
1817}
1818
1819NS_IMETHODIMP
1820nsPrefBranch::SetBoolPref(const char* aPrefName, bool aValue)
1821{
1822 NS_ENSURE_ARG(aPrefName);
1823
1824 const PrefName& pref = GetPrefName(aPrefName);
1825 return Preferences::SetBool(pref.get(), aValue, mKind);
1826}
1827
1828NS_IMETHODIMP
1829nsPrefBranch::GetFloatPrefWithDefault(const char* aPrefName,
1830 float aDefaultValue,
1831 uint8_t aArgc,
1832 float* aRetVal)
1833{
1834 nsresult rv = GetFloatPref(aPrefName, aRetVal);
1835
1836 if (NS_FAILED(rv) && aArgc == 1) {
1837 *aRetVal = aDefaultValue;
1838 return NS_OK;
1839 }
1840
1841 return rv;
1842}
1843
1844NS_IMETHODIMP
1845nsPrefBranch::GetFloatPref(const char* aPrefName, float* aRetVal)
1846{
1847 NS_ENSURE_ARG(aPrefName);
1848
1849 nsAutoCString stringVal;
1850 nsresult rv = GetCharPref(aPrefName, stringVal);
1851 if (NS_SUCCEEDED(rv)) {
1852 *aRetVal = stringVal.ToFloat(&rv);
1853 }
1854
1855 return rv;
1856}
1857
1858NS_IMETHODIMP
1859nsPrefBranch::GetCharPrefWithDefault(const char* aPrefName,
1860 const nsACString& aDefaultValue,
1861 uint8_t aArgc,
1862 nsACString& aRetVal)
1863{
1864 nsresult rv = GetCharPref(aPrefName, aRetVal);
1865
1866 if (NS_FAILED(rv) && aArgc == 1) {
1867 aRetVal = aDefaultValue;
1868 return NS_OK;
1869 }
1870
1871 return rv;
1872}
1873
1874NS_IMETHODIMP
1875nsPrefBranch::GetCharPref(const char* aPrefName, nsACString& aRetVal)
1876{
1877 NS_ENSURE_ARG(aPrefName);
1878
1879 const PrefName& pref = GetPrefName(aPrefName);
1880 return Preferences::GetCString(pref.get(), aRetVal, mKind);
1881}
1882
1883NS_IMETHODIMP
1884nsPrefBranch::SetCharPref(const char* aPrefName, const nsACString& aValue)
1885{
1886 nsresult rv = CheckSanityOfStringLength(aPrefName, aValue);
1887 if (NS_FAILED(rv)) {
1888 return rv;
1889 }
1890 return SetCharPrefNoLengthCheck(aPrefName, aValue);
1891}
1892
1893nsresult
1894nsPrefBranch::SetCharPrefNoLengthCheck(const char* aPrefName,
1895 const nsACString& aValue)
1896{
1897 NS_ENSURE_ARG(aPrefName);
1898
1899 const PrefName& pref = GetPrefName(aPrefName);
1900 return Preferences::SetCString(pref.get(), aValue, mKind);
1901}
1902
1903NS_IMETHODIMP
1904nsPrefBranch::GetStringPref(const char* aPrefName,
1905 const nsACString& aDefaultValue,
1906 uint8_t aArgc,
1907 nsACString& aRetVal)
1908{
1909 nsCString utf8String;
1910 nsresult rv = GetCharPref(aPrefName, utf8String);
1911 if (NS_SUCCEEDED(rv)) {
1912 aRetVal = utf8String;
1913 return rv;
1914 }
1915
1916 if (aArgc == 1) {
1917 aRetVal = aDefaultValue;
1918 return NS_OK;
1919 }
1920
1921 return rv;
1922}
1923
1924NS_IMETHODIMP
1925nsPrefBranch::SetStringPref(const char* aPrefName, const nsACString& aValue)
1926{
1927 nsresult rv = CheckSanityOfStringLength(aPrefName, aValue);
1928 if (NS_FAILED(rv)) {
1929 return rv;
1930 }
1931
1932 return SetCharPrefNoLengthCheck(aPrefName, aValue);
1933}
1934
1935NS_IMETHODIMP
1936nsPrefBranch::GetIntPrefWithDefault(const char* aPrefName,
1937 int32_t aDefaultValue,
1938 uint8_t aArgc,
1939 int32_t* aRetVal)
1940{
1941 nsresult rv = GetIntPref(aPrefName, aRetVal);
1942
1943 if (NS_FAILED(rv) && aArgc == 1) {
1944 *aRetVal = aDefaultValue;
1945 return NS_OK;
1946 }
1947
1948 return rv;
1949}
1950
1951NS_IMETHODIMP
1952nsPrefBranch::GetIntPref(const char* aPrefName, int32_t* aRetVal)
1953{
1954 NS_ENSURE_ARG(aPrefName);
1955 const PrefName& pref = GetPrefName(aPrefName);
1956 return Preferences::GetInt(pref.get(), aRetVal, mKind);
1957}
1958
1959NS_IMETHODIMP
1960nsPrefBranch::SetIntPref(const char* aPrefName, int32_t aValue)
1961{
1962 NS_ENSURE_ARG(aPrefName);
1963
1964 const PrefName& pref = GetPrefName(aPrefName);
1965 return Preferences::SetInt(pref.get(), aValue, mKind);
1966}
1967
1968NS_IMETHODIMP
1969nsPrefBranch::GetComplexValue(const char* aPrefName,
1970 const nsIID& aType,
1971 void** aRetVal)
1972{
1973 NS_ENSURE_ARG(aPrefName);
1974
1975 nsresult rv;
1976 nsAutoCString utf8String;
1977
1978 // We have to do this one first because it's different to all the rest.
1979 if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) {
1980 nsCOMPtr<nsIPrefLocalizedString> theString(
1981 do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
1982 if (NS_FAILED(rv)) {
1983 return rv;
1984 }
1985
1986 const PrefName& pref = GetPrefName(aPrefName);
1987 bool bNeedDefault = false;
1988
1989 if (mKind == PrefValueKind::Default) {
1990 bNeedDefault = true;
1991 } else {
1992 // if there is no user (or locked) value
1993 if (!Preferences::HasUserValue(pref.get()) &&
1994 !Preferences::IsLocked(pref.get())) {
1995 bNeedDefault = true;
1996 }
1997 }
1998
1999 // if we need to fetch the default value, do that instead, otherwise use the
2000 // value we pulled in at the top of this function
2001 if (bNeedDefault) {
2002 nsAutoString utf16String;
2003 rv = GetDefaultFromPropertiesFile(pref.get(), utf16String);
2004 if (NS_SUCCEEDED(rv)) {
2005 theString->SetData(utf16String);
2006 }
2007 } else {
2008 rv = GetCharPref(aPrefName, utf8String);
2009 if (NS_SUCCEEDED(rv)) {
2010 theString->SetData(NS_ConvertUTF8toUTF16(utf8String));
2011 }
2012 }
2013
2014 if (NS_SUCCEEDED(rv)) {
2015 theString.forget(reinterpret_cast<nsIPrefLocalizedString**>(aRetVal));
2016 }
2017
2018 return rv;
2019 }
2020
2021 // if we can't get the pref, there's no point in being here
2022 rv = GetCharPref(aPrefName, utf8String);
2023 if (NS_FAILED(rv)) {
2024 return rv;
2025 }
2026
2027 if (aType.Equals(NS_GET_IID(nsIFile))) {
2028 ENSURE_PARENT_PROCESS("GetComplexValue(nsIFile)", aPrefName);
2029
2030 nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
2031
2032 if (NS_SUCCEEDED(rv)) {
2033 rv = file->SetPersistentDescriptor(utf8String);
2034 if (NS_SUCCEEDED(rv)) {
2035 file.forget(reinterpret_cast<nsIFile**>(aRetVal));
2036 return NS_OK;
2037 }
2038 }
2039 return rv;
2040 }
2041
2042 if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) {
2043 ENSURE_PARENT_PROCESS("GetComplexValue(nsIRelativeFilePref)", aPrefName);
2044
2045 nsACString::const_iterator keyBegin, strEnd;
2046 utf8String.BeginReading(keyBegin);
2047 utf8String.EndReading(strEnd);
2048
2049 // The pref has the format: [fromKey]a/b/c
2050 if (*keyBegin++ != '[') {
2051 return NS_ERROR_FAILURE;
2052 }
2053
2054 nsACString::const_iterator keyEnd(keyBegin);
2055 if (!FindCharInReadable(']', keyEnd, strEnd)) {
2056 return NS_ERROR_FAILURE;
2057 }
2058
2059 nsAutoCString key(Substring(keyBegin, keyEnd));
2060
2061 nsCOMPtr<nsIFile> fromFile;
2062 nsCOMPtr<nsIProperties> directoryService(
2063 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
2064 if (NS_FAILED(rv)) {
2065 return rv;
2066 }
2067
2068 rv = directoryService->Get(
2069 key.get(), NS_GET_IID(nsIFile), getter_AddRefs(fromFile));
2070 if (NS_FAILED(rv)) {
2071 return rv;
2072 }
2073
2074 nsCOMPtr<nsIFile> theFile;
2075 rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(theFile));
2076 if (NS_FAILED(rv)) {
2077 return rv;
2078 }
2079
2080 rv = theFile->SetRelativeDescriptor(fromFile, Substring(++keyEnd, strEnd));
2081 if (NS_FAILED(rv)) {
2082 return rv;
2083 }
2084
2085 nsCOMPtr<nsIRelativeFilePref> relativePref;
2086 rv = NS_NewRelativeFilePref(theFile, key, getter_AddRefs(relativePref));
2087 if (NS_FAILED(rv)) {
2088 return rv;
2089 }
2090
2091 relativePref.forget(reinterpret_cast<nsIRelativeFilePref**>(aRetVal));
2092 return NS_OK;
2093 }
2094
2095 NS_WARNING("nsPrefBranch::GetComplexValue - Unsupported interface type");
2096 return NS_NOINTERFACE;
2097}
2098
2099nsresult
2100nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName,
2101 const nsAString& aValue)
2102{
2103 return CheckSanityOfStringLength(aPrefName, aValue.Length());
2104}
2105
2106nsresult
2107nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName,
2108 const nsACString& aValue)
2109{
2110 return CheckSanityOfStringLength(aPrefName, aValue.Length());
2111}
2112
2113nsresult
2114nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName,
2115 const uint32_t aLength)
2116{
2117 if (aLength > MAX_PREF_LENGTH) {
2118 return NS_ERROR_ILLEGAL_VALUE;
2119 }
2120 if (aLength <= MAX_ADVISABLE_PREF_LENGTH) {
2121 return NS_OK;
2122 }
2123
2124 nsresult rv;
2125 nsCOMPtr<nsIConsoleService> console =
2126 do_GetService("@mozilla.org/consoleservice;1", &rv);
2127 if (NS_FAILED(rv)) {
2128 return rv;
2129 }
2130
2131 nsAutoCString message(nsPrintfCString(
2132 "Warning: attempting to write %d bytes to preference %s. This is bad "
2133 "for general performance and memory usage. Such an amount of data "
2134 "should rather be written to an external file. This preference will "
2135 "not be sent to any content processes.",
2136 aLength,
2137 GetPrefName(aPrefName).get()));
2138
2139 rv = console->LogStringMessage(NS_ConvertUTF8toUTF16(message).get());
2140 if (NS_FAILED(rv)) {
2141 return rv;
2142 }
2143 return NS_OK;
2144}
2145
2146NS_IMETHODIMP
2147nsPrefBranch::SetComplexValue(const char* aPrefName,
2148 const nsIID& aType,
2149 nsISupports* aValue)
2150{
2151 ENSURE_PARENT_PROCESS("SetComplexValue", aPrefName);
2152 NS_ENSURE_ARG(aPrefName);
2153
2154 nsresult rv = NS_NOINTERFACE;
2155
2156 if (aType.Equals(NS_GET_IID(nsIFile))) {
2157 nsCOMPtr<nsIFile> file = do_QueryInterface(aValue);
2158 if (!file) {
2159 return NS_NOINTERFACE;
2160 }
2161
2162 nsAutoCString descriptorString;
2163 rv = file->GetPersistentDescriptor(descriptorString);
2164 if (NS_SUCCEEDED(rv)) {
2165 rv = SetCharPrefNoLengthCheck(aPrefName, descriptorString);
2166 }
2167 return rv;
2168 }
2169
2170 if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) {
2171 nsCOMPtr<nsIRelativeFilePref> relFilePref = do_QueryInterface(aValue);
2172 if (!relFilePref) {
2173 return NS_NOINTERFACE;
2174 }
2175
2176 nsCOMPtr<nsIFile> file;
2177 relFilePref->GetFile(getter_AddRefs(file));
2178 if (!file) {
2179 return NS_NOINTERFACE;
2180 }
2181
2182 nsAutoCString relativeToKey;
2183 (void)relFilePref->GetRelativeToKey(relativeToKey);
2184
2185 nsCOMPtr<nsIFile> relativeToFile;
2186 nsCOMPtr<nsIProperties> directoryService(
2187 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
2188 if (NS_FAILED(rv)) {
2189 return rv;
2190 }
2191
2192 rv = directoryService->Get(
2193 relativeToKey.get(), NS_GET_IID(nsIFile), getter_AddRefs(relativeToFile));
2194 if (NS_FAILED(rv)) {
2195 return rv;
2196 }
2197
2198 nsAutoCString relDescriptor;
2199 rv = file->GetRelativeDescriptor(relativeToFile, relDescriptor);
2200 if (NS_FAILED(rv)) {
2201 return rv;
2202 }
2203
2204 nsAutoCString descriptorString;
2205 descriptorString.Append('[');
2206 descriptorString.Append(relativeToKey);
2207 descriptorString.Append(']');
2208 descriptorString.Append(relDescriptor);
2209 return SetCharPrefNoLengthCheck(aPrefName, descriptorString);
2210 }
2211
2212 if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) {
2213 nsCOMPtr<nsISupportsString> theString = do_QueryInterface(aValue);
2214
2215 if (theString) {
2216 nsString wideString;
2217
2218 rv = theString->GetData(wideString);
2219 if (NS_SUCCEEDED(rv)) {
2220 // Check sanity of string length before any lengthy conversion
2221 rv = CheckSanityOfStringLength(aPrefName, wideString);
2222 if (NS_FAILED(rv)) {
2223 return rv;
2224 }
2225 rv = SetCharPrefNoLengthCheck(aPrefName,
2226 NS_ConvertUTF16toUTF8(wideString));
2227 }
2228 }
2229 return rv;
2230 }
2231
2232 NS_WARNING("nsPrefBranch::SetComplexValue - Unsupported interface type");
2233 return NS_NOINTERFACE;
2234}
2235
2236NS_IMETHODIMP
2237nsPrefBranch::ClearUserPref(const char* aPrefName)
2238{
2239 NS_ENSURE_ARG(aPrefName);
2240
2241 const PrefName& pref = GetPrefName(aPrefName);
2242 return Preferences::ClearUser(pref.get());
2243}
2244
2245NS_IMETHODIMP
2246nsPrefBranch::PrefHasUserValue(const char* aPrefName, bool* aRetVal)
2247{
2248 NS_ENSURE_ARG_POINTER(aRetVal);
2249 NS_ENSURE_ARG(aPrefName);
2250
2251 const PrefName& pref = GetPrefName(aPrefName);
2252 *aRetVal = Preferences::HasUserValue(pref.get());
2253 return NS_OK;
2254}
2255
2256NS_IMETHODIMP
2257nsPrefBranch::LockPref(const char* aPrefName)
2258{
2259 NS_ENSURE_ARG(aPrefName);
2260
2261 const PrefName& pref = GetPrefName(aPrefName);
2262 return Preferences::Lock(pref.get());
2263}
2264
2265NS_IMETHODIMP
2266nsPrefBranch::PrefIsLocked(const char* aPrefName, bool* aRetVal)
2267{
2268 NS_ENSURE_ARG_POINTER(aRetVal);
2269 NS_ENSURE_ARG(aPrefName);
2270
2271 const PrefName& pref = GetPrefName(aPrefName);
2272 *aRetVal = Preferences::IsLocked(pref.get());
2273 return NS_OK;
2274}
2275
2276NS_IMETHODIMP
2277nsPrefBranch::UnlockPref(const char* aPrefName)
2278{
2279 NS_ENSURE_ARG(aPrefName);
2280
2281 const PrefName& pref = GetPrefName(aPrefName);
2282 return Preferences::Unlock(pref.get());
2283}
2284
2285NS_IMETHODIMP
2286nsPrefBranch::ResetBranch(const char* aStartingAt)
2287{
2288 return NS_ERROR_NOT_IMPLEMENTED;
2289}
2290
2291NS_IMETHODIMP
2292nsPrefBranch::DeleteBranch(const char* aStartingAt)
2293{
2294 ENSURE_PARENT_PROCESS("DeleteBranch", aStartingAt);
2295 NS_ENSURE_ARG(aStartingAt);
2296
2297 MOZ_ASSERT(NS_IsMainThread());
2298
2299 if (!gHashTable) {
2300 return NS_ERROR_NOT_INITIALIZED;
2301 }
2302
2303 const PrefName& pref = GetPrefName(aStartingAt);
2304 nsAutoCString branchName(pref.get());
2305
2306 // Add a trailing '.' if it doesn't already have one.
2307 if (branchName.Length() > 1 &&
2308 !StringEndsWith(branchName, NS_LITERAL_CSTRING("."))) {
2309 branchName += '.';
2310 }
2311
2312 const nsACString& branchNameNoDot =
2313 Substring(branchName, 0, branchName.Length() - 1);
2314
2315 for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
2316 Pref* pref = static_cast<PrefEntry*>(iter.Get())->mPref;
2317
2318 // The first disjunct matches branches: e.g. a branch name "foo.bar."
2319 // matches a name "foo.bar.baz" (but it won't match "foo.barrel.baz").
2320 // The second disjunct matches leaf nodes: e.g. a branch name "foo.bar."
2321 // matches a name "foo.bar" (by ignoring the trailing '.').
2322 nsDependentCString name(pref->Name());
2323 if (StringBeginsWith(name, branchName) || name.Equals(branchNameNoDot)) {
2324 iter.Remove();
2325 }
2326 }
2327
2328 Preferences::HandleDirty();
2329 return NS_OK;
2330}
2331
2332NS_IMETHODIMP
2333nsPrefBranch::GetChildList(const char* aStartingAt,
2334 uint32_t* aCount,
2335 char*** aChildArray)
2336{
2337 char** outArray;
2338 int32_t numPrefs;
2339 int32_t dwIndex;
2340 AutoTArray<nsCString, 32> prefArray;
2341
2342 NS_ENSURE_ARG(aStartingAt);
2343 NS_ENSURE_ARG_POINTER(aCount);
2344 NS_ENSURE_ARG_POINTER(aChildArray);
2345
2346 MOZ_ASSERT(NS_IsMainThread());
2347
2348 *aChildArray = nullptr;
2349 *aCount = 0;
2350
2351 // This will contain a list of all the pref name strings. Allocated on the
2352 // stack for speed.
2353
2354 const PrefName& parent = GetPrefName(aStartingAt);
2355 size_t parentLen = parent.Length();
2356 for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
2357 Pref* pref = static_cast<PrefEntry*>(iter.Get())->mPref;
2358 if (strncmp(pref->Name(), parent.get(), parentLen) == 0) {
2359 prefArray.AppendElement(pref->Name());
2360 }
2361 }
2362
2363 // Now that we've built up the list, run the callback on all the matching
2364 // elements.
2365 numPrefs = prefArray.Length();
2366
2367 if (numPrefs) {
2368 outArray = (char**)moz_xmalloc(numPrefs * sizeof(char*));
2369
2370 for (dwIndex = 0; dwIndex < numPrefs; ++dwIndex) {
2371 // we need to lop off mPrefRoot in case the user is planning to pass this
2372 // back to us because if they do we are going to add mPrefRoot again.
2373 const nsCString& element = prefArray[dwIndex];
2374 outArray[dwIndex] =
2375 (char*)nsMemory::Clone(element.get() + mPrefRoot.Length(),
2376 element.Length() - mPrefRoot.Length() + 1);
2377
2378 if (!outArray[dwIndex]) {
2379 // We ran out of memory... this is annoying.
2380 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(dwIndex, outArray);
2381 return NS_ERROR_OUT_OF_MEMORY;
2382 }
2383 }
2384 *aChildArray = outArray;
2385 }
2386 *aCount = numPrefs;
2387
2388 return NS_OK;
2389}
2390
2391NS_IMETHODIMP
2392nsPrefBranch::AddObserverImpl(const nsACString& aDomain,
2393 nsIObserver* aObserver,
2394 bool aHoldWeak)
2395{
2396 PrefCallback* pCallback;
2397
2398 NS_ENSURE_ARG(aObserver);
2399
2400 nsCString prefName;
2401 GetPrefName(aDomain).get(prefName);
2402
2403 // Hold a weak reference to the observer if so requested.
2404 if (aHoldWeak) {
2405 nsCOMPtr<nsISupportsWeakReference> weakRefFactory =
2406 do_QueryInterface(aObserver);
2407 if (!weakRefFactory) {
2408 // The caller didn't give us a object that supports weak reference...
2409 // tell them.
2410 return NS_ERROR_INVALID_ARG;
2411 }
2412
2413 // Construct a PrefCallback with a weak reference to the observer.
2414 pCallback = new PrefCallback(prefName, weakRefFactory, this);
2415
2416 } else {
2417 // Construct a PrefCallback with a strong reference to the observer.
2418 pCallback = new PrefCallback(prefName, aObserver, this);
2419 }
2420
2421 auto p = mObservers.LookupForAdd(pCallback);
2422 if (p) {
2423 NS_WARNING("Ignoring duplicate observer.");
2424 delete pCallback;
2425 return NS_OK;
2426 }
2427
2428 p.OrInsert([&pCallback]() { return pCallback; });
2429
2430 // We must pass a fully qualified preference name to the callback
2431 // aDomain == nullptr is the only possible failure, and we trapped it with
2432 // NS_ENSURE_ARG above.
2433 Preferences::RegisterCallback(NotifyObserver,
2434 prefName,
2435 pCallback,
2436 Preferences::PrefixMatch,
2437 /* isPriority */ false);
2438
2439 return NS_OK;
2440}
2441
2442NS_IMETHODIMP
2443nsPrefBranch::RemoveObserverImpl(const nsACString& aDomain,
2444 nsIObserver* aObserver)
2445{
2446 NS_ENSURE_ARG(aObserver);
2447
2448 nsresult rv = NS_OK;
2449
2450 // If we're in the middle of a call to FreeObserverList, don't process this
2451 // RemoveObserver call -- the observer in question will be removed soon, if
2452 // it hasn't been already.
2453 //
2454 // It's important that we don't touch mObservers in any way -- even a Get()
2455 // which returns null might cause the hashtable to resize itself, which will
2456 // break the iteration in FreeObserverList.
2457 if (mFreeingObserverList) {
2458 return NS_OK;
2459 }
2460
2461 // Remove the relevant PrefCallback from mObservers and get an owning pointer
2462 // to it. Unregister the callback first, and then let the owning pointer go
2463 // out of scope and destroy the callback.
2464 nsCString prefName;
2465 GetPrefName(aDomain).get(prefName);
2466 PrefCallback key(prefName, aObserver, this);
2467 nsAutoPtr<PrefCallback> pCallback;
2468 mObservers.Remove(&key, &pCallback);
2469 if (pCallback) {
2470 rv = Preferences::UnregisterCallback(
2471 NotifyObserver, prefName, pCallback, Preferences::PrefixMatch);
2472 }
2473
2474 return rv;
2475}
2476
2477NS_IMETHODIMP
2478nsPrefBranch::Observe(nsISupports* aSubject,
2479 const char* aTopic,
2480 const char16_t* aData)
2481{
2482 // Watch for xpcom shutdown and free our observers to eliminate any cyclic
2483 // references.
2484 if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
2485 FreeObserverList();
2486 }
2487 return NS_OK;
2488}
2489
2490/* static */ void
2491nsPrefBranch::NotifyObserver(const char* aNewPref, void* aData)
2492{
2493 PrefCallback* pCallback = (PrefCallback*)aData;
2494
2495 nsCOMPtr<nsIObserver> observer = pCallback->GetObserver();
2496 if (!observer) {
2497 // The observer has expired. Let's remove this callback.
2498 pCallback->GetPrefBranch()->RemoveExpiredCallback(pCallback);
2499 return;
2500 }
2501
2502 // Remove any root this string may contain so as to not confuse the observer
2503 // by passing them something other than what they passed us as a topic.
2504 uint32_t len = pCallback->GetPrefBranch()->GetRootLength();
2505 nsDependentCString suffix(aNewPref + len);
2506
2507 observer->Observe(static_cast<nsIPrefBranch*>(pCallback->GetPrefBranch()),
2508 NS_PREFBRANCH_PREFCHANGE_TOPIC_ID,
2509 NS_ConvertASCIItoUTF16(suffix).get());
2510}
2511
2512size_t
2513nsPrefBranch::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
2514{
2515 size_t n = aMallocSizeOf(this);
2516
2517 n += mPrefRoot.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
2518
2519 n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
2520 for (auto iter = mObservers.ConstIter(); !iter.Done(); iter.Next()) {
2521 const PrefCallback* data = iter.UserData();
2522 n += data->SizeOfIncludingThis(aMallocSizeOf);
2523 }
2524
2525 return n;
2526}
2527
2528void
2529nsPrefBranch::FreeObserverList()
2530{
2531 // We need to prevent anyone from modifying mObservers while we're iterating
2532 // over it. In particular, some clients will call RemoveObserver() when
2533 // they're removed and destructed via the iterator; we set
2534 // mFreeingObserverList to keep those calls from touching mObservers.
2535 mFreeingObserverList = true;
2536 for (auto iter = mObservers.Iter(); !iter.Done(); iter.Next()) {
2537 nsAutoPtr<PrefCallback>& callback = iter.Data();
2538 Preferences::UnregisterCallback(nsPrefBranch::NotifyObserver,
2539 callback->GetDomain(),
2540 callback,
2541 Preferences::PrefixMatch);
2542 iter.Remove();
2543 }
2544 mFreeingObserverList = false;
2545}
2546
2547void
2548nsPrefBranch::RemoveExpiredCallback(PrefCallback* aCallback)
2549{
2550 MOZ_ASSERT(aCallback->IsExpired());
2551 mObservers.Remove(aCallback);
2552}
2553
2554nsresult
2555nsPrefBranch::GetDefaultFromPropertiesFile(const char* aPrefName,
2556 nsAString& aReturn)
2557{
2558 // The default value contains a URL to a .properties file.
2559
2560 nsAutoCString propertyFileURL;
2561 nsresult rv =
2562 Preferences::GetCString(aPrefName, propertyFileURL, PrefValueKind::Default);
2563 if (NS_FAILED(rv)) {
2564 return rv;
2565 }
2566
2567 nsCOMPtr<nsIStringBundleService> bundleService =
2568 mozilla::services::GetStringBundleService();
2569 if (!bundleService) {
2570 return NS_ERROR_FAILURE;
2571 }
2572
2573 nsCOMPtr<nsIStringBundle> bundle;
2574 rv =
2575 bundleService->CreateBundle(propertyFileURL.get(), getter_AddRefs(bundle));
2576 if (NS_FAILED(rv)) {
2577 return rv;
2578 }
2579
2580 return bundle->GetStringFromName(aPrefName, aReturn);
2581}
2582
2583nsPrefBranch::PrefName
2584nsPrefBranch::GetPrefName(const nsACString& aPrefName) const
2585{
2586 if (mPrefRoot.IsEmpty()) {
2587 return PrefName(PromiseFlatCString(aPrefName));
2588 }
2589
2590 return PrefName(mPrefRoot + aPrefName);
2591}
2592
2593//----------------------------------------------------------------------------
2594// nsPrefLocalizedString
2595//----------------------------------------------------------------------------
2596
2597nsPrefLocalizedString::nsPrefLocalizedString() = default;
2598
2599nsPrefLocalizedString::~nsPrefLocalizedString() = default;
2600
2601NS_IMPL_ISUPPORTS(nsPrefLocalizedString,
2602 nsIPrefLocalizedString,
2603 nsISupportsString)
2604
2605nsresult
2606nsPrefLocalizedString::Init()
2607{
2608 nsresult rv;
2609 mUnicodeString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
2610
2611 return rv;
2612}
2613
2614//----------------------------------------------------------------------------
2615// nsRelativeFilePref
2616//----------------------------------------------------------------------------
2617
2618NS_IMPL_ISUPPORTS(nsRelativeFilePref, nsIRelativeFilePref)
2619
2620nsRelativeFilePref::nsRelativeFilePref() = default;
2621
2622nsRelativeFilePref::~nsRelativeFilePref() = default;
2623
2624NS_IMETHODIMP
2625nsRelativeFilePref::GetFile(nsIFile** aFile)
2626{
2627 NS_ENSURE_ARG_POINTER(aFile);
2628 *aFile = mFile;
2629 NS_IF_ADDREF(*aFile);
2630 return NS_OK;
2631}
2632
2633NS_IMETHODIMP
2634nsRelativeFilePref::SetFile(nsIFile* aFile)
2635{
2636 mFile = aFile;
2637 return NS_OK;
2638}
2639
2640NS_IMETHODIMP
2641nsRelativeFilePref::GetRelativeToKey(nsACString& aRelativeToKey)
2642{
2643 aRelativeToKey.Assign(mRelativeToKey);
2644 return NS_OK;
2645}
2646
2647NS_IMETHODIMP
2648nsRelativeFilePref::SetRelativeToKey(const nsACString& aRelativeToKey)
2649{
2650 mRelativeToKey.Assign(aRelativeToKey);
2651 return NS_OK;
2652}
2653
2654//===========================================================================
2655// class Preferences and related things
2656//===========================================================================
2657
2658namespace mozilla {
2659
2660#define INITIAL_PREF_FILES 10
2661
2662static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
2663
2664void
2665Preferences::HandleDirty()
2666{
2667 MOZ_ASSERT(XRE_IsParentProcess());
2668
2669 if (!gHashTable || !sPreferences) {
2670 return;
2671 }
2672
2673 if (sPreferences->mProfileShutdown) {
2674 NS_WARNING("Setting user pref after profile shutdown.");
2675 return;
2676 }
2677
2678 if (!sPreferences->mDirty) {
2679 sPreferences->mDirty = true;
2680
2681 if (sPreferences->mCurrentFile && sPreferences->AllowOffMainThreadSave() &&
2682 !sPreferences->mSavePending) {
2683 sPreferences->mSavePending = true;
2684 static const int PREF_DELAY_MS = 500;
2685 NS_DelayedDispatchToCurrentThread(
2686 mozilla::NewRunnableMethod("Preferences::SavePrefFileAsynchronous",
2687 sPreferences.get(),
2688 &Preferences::SavePrefFileAsynchronous),
2689 PREF_DELAY_MS);
2690 }
2691 }
2692}
2693
2694static nsresult
2695openPrefFile(nsIFile* aFile, PrefValueKind aKind);
2696
2697// clang-format off
2698static const char kPrefFileHeader[] =
2699 "// Mozilla User Preferences"
2700 NS_LINEBREAK
2701 NS_LINEBREAK
2702 "// DO NOT EDIT THIS FILE."
2703 NS_LINEBREAK
2704 "//"
2705 NS_LINEBREAK
2706 "// If you make changes to this file while the application is running,"
2707 NS_LINEBREAK
2708 "// the changes will be overwritten when the application exits."
2709 NS_LINEBREAK
2710 "//"
2711 NS_LINEBREAK
2712 "// To change a preference value, you can either:"
2713 NS_LINEBREAK
2714 "// - modify it via the UI (e.g. via about:config in the browser); or"
2715 NS_LINEBREAK
2716 "// - set it within a user.js file in your profile."
2717 NS_LINEBREAK
2718 NS_LINEBREAK;
2719// clang-format on
2720
2721// Note: if sShutdown is true, sPreferences will be nullptr.
2722StaticRefPtr<Preferences> Preferences::sPreferences;
2723bool Preferences::sShutdown = false;
2724
2725// This globally enables or disables OMT pref writing, both sync and async.
2726static int32_t sAllowOMTPrefWrite = -1;
2727
2728// Write the preference data to a file.
2729class PreferencesWriter final
2730{
2731public:
2732 PreferencesWriter() = default;
2733
2734 static nsresult Write(nsIFile* aFile, PrefSaveData& aPrefs)
2735 {
2736 nsCOMPtr<nsIOutputStream> outStreamSink;
2737 nsCOMPtr<nsIOutputStream> outStream;
2738 uint32_t writeAmount;
2739 nsresult rv;
2740
2741 // Execute a "safe" save by saving through a tempfile.
2742 rv = NS_NewSafeLocalFileOutputStream(
2743 getter_AddRefs(outStreamSink), aFile, -1, 0600);
2744 if (NS_FAILED(rv)) {
2745 return rv;
2746 }
2747
2748 rv = NS_NewBufferedOutputStream(
2749 getter_AddRefs(outStream), outStreamSink.forget(), 4096);
2750 if (NS_FAILED(rv)) {
2751 return rv;
2752 }
2753
2754 struct CharComparator
2755 {
2756 bool LessThan(const nsCString& aA, const nsCString& aB) const
2757 {
2758 return aA < aB;
2759 }
2760
2761 bool Equals(const nsCString& aA, const nsCString& aB) const
2762 {
2763 return aA == aB;
2764 }
2765 };
2766
2767 // Sort the preferences to make a readable file on disk.
2768 aPrefs.Sort(CharComparator());
2769
2770 // Write out the file header.
2771 outStream->Write(
2772 kPrefFileHeader, sizeof(kPrefFileHeader) - 1, &writeAmount);
2773
2774 for (nsCString& pref : aPrefs) {
2775 outStream->Write(pref.get(), pref.Length(), &writeAmount);
2776 outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount);
2777 }
2778
2779 // Tell the safe output stream to overwrite the real prefs file.
2780 // (It'll abort if there were any errors during writing.)
2781 nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream);
2782 MOZ_ASSERT(safeStream, "expected a safe output stream!");
2783 if (safeStream) {
2784 rv = safeStream->Finish();
2785 }
2786
2787#ifdef DEBUG
2788 if (NS_FAILED(rv)) {
2789 NS_WARNING("failed to save prefs file! possible data loss");
2790 }
2791#endif
2792
2793 return rv;
2794 }
2795
2796 static void Flush()
2797 {
2798 // This can be further optimized; instead of waiting for all of the writer
2799 // thread to be available, we just have to wait for all the pending writes
2800 // to be done.
2801 if (!sPendingWriteData.compareExchange(nullptr, nullptr)) {
2802 nsresult rv = NS_OK;
2803 nsCOMPtr<nsIEventTarget> target =
2804 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
2805 if (NS_SUCCEEDED(rv)) {
2806 target->Dispatch(NS_NewRunnableFunction("Preferences_dummy", [] {}),
2807 nsIEventTarget::DISPATCH_SYNC);
2808 }
2809 }
2810 }
2811
2812 // This is the data that all of the runnables (see below) will attempt
2813 // to write. It will always have the most up to date version, or be
2814 // null, if the up to date information has already been written out.
2815 static Atomic<PrefSaveData*> sPendingWriteData;
2816};
2817
2818Atomic<PrefSaveData*> PreferencesWriter::sPendingWriteData(nullptr);
2819
2820class PWRunnable : public Runnable
2821{
2822public:
2823 explicit PWRunnable(nsIFile* aFile)
2824 : Runnable("PWRunnable")
2825 , mFile(aFile)
2826 {
2827 }
2828
2829 NS_IMETHOD Run() override
2830 {
2831 // If we get a nullptr on the exchange, it means that somebody
2832 // else has already processed the request, and we can just return.
2833 mozilla::UniquePtr<PrefSaveData> prefs(
2834 PreferencesWriter::sPendingWriteData.exchange(nullptr));
2835 nsresult rv = NS_OK;
2836 if (prefs) {
2837 rv = PreferencesWriter::Write(mFile, *prefs);
2838
2839 // Make a copy of these so we can have them in runnable lambda.
2840 // nsIFile is only there so that we would never release the
2841 // ref counted pointer off main thread.
2842 nsresult rvCopy = rv;
2843 nsCOMPtr<nsIFile> fileCopy(mFile);
2844 SystemGroup::Dispatch(
2845 TaskCategory::Other,
2846 NS_NewRunnableFunction("Preferences::WriterRunnable",
2847 [fileCopy, rvCopy] {
2848 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2849 if (NS_FAILED(rvCopy)) {
2850 Preferences::HandleDirty();
2851 }
2852 }));
2853 }
2854 return rv;
2855 }
2856
2857protected:
2858 nsCOMPtr<nsIFile> mFile;
2859};
2860
2861struct CacheData
2862{
2863 void* mCacheLocation;
2864 union {
2865 bool mDefaultValueBool;
2866 int32_t mDefaultValueInt;
2867 uint32_t mDefaultValueUint;
2868 float mDefaultValueFloat;
2869 };
2870};
2871
2872// gCacheDataDesc holds information about prefs startup. It's being used for
2873// diagnosing prefs startup problems in bug 1276488.
2874static const char* gCacheDataDesc = "untouched";
2875
2876// gCacheData holds the CacheData objects used for VarCache prefs. It owns
2877// those objects, and also is used to detect if multiple VarCaches get tied to
2878// a single global variable.
2879static nsTArray<nsAutoPtr<CacheData>>* gCacheData = nullptr;
2880
2881#ifdef DEBUG
2882static bool
2883HaveExistingCacheFor(void* aPtr)
2884{
2885 MOZ_ASSERT(NS_IsMainThread());
2886 if (gCacheData) {
2887 for (size_t i = 0, count = gCacheData->Length(); i < count; ++i) {
2888 if ((*gCacheData)[i]->mCacheLocation == aPtr) {
2889 return true;
2890 }
2891 }
2892 }
2893 return false;
2894}
2895#endif
2896
2897static void
2898AssertNotAlreadyCached(const char* aPrefType, const char* aPref, void* aPtr)
2899{
2900#ifdef DEBUG
2901 MOZ_ASSERT(aPtr);
2902 if (HaveExistingCacheFor(aPtr)) {
2903 fprintf_stderr(
2904 stderr,
2905 "Attempt to add a %s pref cache for preference '%s' at address '%p'"
2906 "was made. However, a pref was already cached at this address.\n",
2907 aPrefType,
2908 aPref,
2909 aPtr);
2910 MOZ_ASSERT(false,
2911 "Should not have an existing pref cache for this address");
2912 }
2913#endif
2914}
2915
2916static void
2917AssertNotAlreadyCached(const char* aPrefType,
2918 const nsACString& aPref,
2919 void* aPtr)
2920{
2921 AssertNotAlreadyCached(aPrefType, PromiseFlatCString(aPref).get(), aPtr);
2922}
2923
2924// Although this is a member of Preferences, it measures sPreferences and
2925// several other global structures.
2926/* static */ void
2927Preferences::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
2928 PrefsSizes& aSizes)
2929{
2930 if (!sPreferences) {
2931 return;
2932 }
2933
2934 aSizes.mMisc += aMallocSizeOf(sPreferences.get());
2935
2936 aSizes.mRootBranches +=
2937 static_cast<nsPrefBranch*>(sPreferences->mRootBranch.get())
2938 ->SizeOfIncludingThis(aMallocSizeOf) +
2939 static_cast<nsPrefBranch*>(sPreferences->mDefaultRootBranch.get())
2940 ->SizeOfIncludingThis(aMallocSizeOf);
2941}
2942
2943class PreferenceServiceReporter final : public nsIMemoryReporter
2944{
2945 ~PreferenceServiceReporter() {}
2946
2947public:
2948 NS_DECL_ISUPPORTS
2949 NS_DECL_NSIMEMORYREPORTER
2950
2951protected:
2952 static const uint32_t kSuspectReferentCount = 1000;
2953};
2954
2955NS_IMPL_ISUPPORTS(PreferenceServiceReporter, nsIMemoryReporter)
2956
2957MOZ_DEFINE_MALLOC_SIZE_OF(PreferenceServiceMallocSizeOf)
2958
2959NS_IMETHODIMP
2960PreferenceServiceReporter::CollectReports(
2961 nsIHandleReportCallback* aHandleReport,
2962 nsISupports* aData,
2963 bool aAnonymize)
2964{
2965 MOZ_ASSERT(NS_IsMainThread());
2966
2967 MallocSizeOf mallocSizeOf = PreferenceServiceMallocSizeOf;
2968 PrefsSizes sizes;
2969
2970 Preferences::AddSizeOfIncludingThis(mallocSizeOf, sizes);
2971
2972 if (gHashTable) {
2973 sizes.mHashTable += gHashTable->ShallowSizeOfIncludingThis(mallocSizeOf);
2974 for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
2975 Pref* pref = static_cast<PrefEntry*>(iter.Get())->mPref;
2976 pref->AddSizeOfIncludingThis(mallocSizeOf, sizes);
2977 }
2978 }
2979
2980 if (gCacheData) {
2981 sizes.mCacheData += gCacheData->ShallowSizeOfIncludingThis(mallocSizeOf);
2982 for (uint32_t i = 0, count = gCacheData->Length(); i < count; ++i) {
2983 sizes.mCacheData += mallocSizeOf((*gCacheData)[i]);
2984 }
2985 }
2986
2987 sizes.mPrefNameArena += gPrefNameArena.SizeOfExcludingThis(mallocSizeOf);
2988
2989 for (CallbackNode* node = gFirstCallback; node; node = node->Next()) {
2990 node->AddSizeOfIncludingThis(mallocSizeOf, sizes);
2991 }
2992
2993 MOZ_COLLECT_REPORT("explicit/preferences/hash-table",
2994 KIND_HEAP,
2995 UNITS_BYTES,
2996 sizes.mHashTable,
2997 "Memory used by libpref's hash table.");
2998
2999 MOZ_COLLECT_REPORT("explicit/preferences/pref-values",
3000 KIND_HEAP,
3001 UNITS_BYTES,
3002 sizes.mPrefValues,
3003 "Memory used by PrefValues hanging off the hash table.");
3004
3005 MOZ_COLLECT_REPORT("explicit/preferences/string-values",
3006 KIND_HEAP,
3007 UNITS_BYTES,
3008 sizes.mStringValues,
3009 "Memory used by libpref's string pref values.");
3010
3011 MOZ_COLLECT_REPORT("explicit/preferences/cache-data",
3012 KIND_HEAP,
3013 UNITS_BYTES,
3014 sizes.mCacheData,
3015 "Memory used by libpref's VarCaches.");
3016
3017 MOZ_COLLECT_REPORT("explicit/preferences/root-branches",
3018 KIND_HEAP,
3019 UNITS_BYTES,
3020 sizes.mRootBranches,
3021 "Memory used by libpref's root branches.");
3022
3023 MOZ_COLLECT_REPORT("explicit/preferences/pref-name-arena",
3024 KIND_HEAP,
3025 UNITS_BYTES,
3026 sizes.mPrefNameArena,
3027 "Memory used by libpref's arena for pref names.");
3028
3029 MOZ_COLLECT_REPORT("explicit/preferences/callbacks/objects",
3030 KIND_HEAP,
3031 UNITS_BYTES,
3032 sizes.mCallbacksObjects,
3033 "Memory used by pref callback objects.");
3034
3035 MOZ_COLLECT_REPORT("explicit/preferences/callbacks/domains",
3036 KIND_HEAP,
3037 UNITS_BYTES,
3038 sizes.mCallbacksDomains,
3039 "Memory used by pref callback domains (pref names and "
3040 "prefixes).");
3041
3042 MOZ_COLLECT_REPORT("explicit/preferences/misc",
3043 KIND_HEAP,
3044 UNITS_BYTES,
3045 sizes.mMisc,
3046 "Miscellaneous memory used by libpref.");
3047
3048 nsPrefBranch* rootBranch =
3049 static_cast<nsPrefBranch*>(Preferences::GetRootBranch());
3050 if (!rootBranch) {
3051 return NS_OK;
3052 }
3053
3054 size_t numStrong = 0;
3055 size_t numWeakAlive = 0;
3056 size_t numWeakDead = 0;
3057 nsTArray<nsCString> suspectPreferences;
3058 // Count of the number of referents for each preference.
3059 nsDataHashtable<nsCStringHashKey, uint32_t> prefCounter;
3060
3061 for (auto iter = rootBranch->mObservers.Iter(); !iter.Done(); iter.Next()) {
3062 nsAutoPtr<PrefCallback>& callback = iter.Data();
3063
3064 if (callback->IsWeak()) {
3065 nsCOMPtr<nsIObserver> callbackRef = do_QueryReferent(callback->mWeakRef);
3066 if (callbackRef) {
3067 numWeakAlive++;
3068 } else {
3069 numWeakDead++;
3070 }
3071 } else {
3072 numStrong++;
3073 }
3074
3075 uint32_t oldCount = 0;
3076 prefCounter.Get(callback->GetDomain(), &oldCount);
3077 uint32_t currentCount = oldCount + 1;
3078 prefCounter.Put(callback->GetDomain(), currentCount);
3079
3080 // Keep track of preferences that have a suspiciously large number of
3081 // referents (a symptom of a leak).
3082 if (currentCount == kSuspectReferentCount) {
3083 suspectPreferences.AppendElement(callback->GetDomain());
3084 }
3085 }
3086
3087 for (uint32_t i = 0; i < suspectPreferences.Length(); i++) {
3088 nsCString& suspect = suspectPreferences[i];
3089 uint32_t totalReferentCount = 0;
3090 prefCounter.Get(suspect, &totalReferentCount);
3091
3092 nsPrintfCString suspectPath("preference-service-suspect/"
3093 "referent(pref=%s)",
3094 suspect.get());
3095
3096 aHandleReport->Callback(
3097 /* process = */ EmptyCString(),
3098 suspectPath,
3099 KIND_OTHER,
3100 UNITS_COUNT,
3101 totalReferentCount,
3102 NS_LITERAL_CSTRING(
3103 "A preference with a suspiciously large number referents (symptom of a "
3104 "leak)."),
3105 aData);
3106 }
3107
3108 MOZ_COLLECT_REPORT(
3109 "preference-service/referent/strong",
3110 KIND_OTHER,
3111 UNITS_COUNT,
3112 numStrong,
3113 "The number of strong referents held by the preference service.");
3114
3115 MOZ_COLLECT_REPORT(
3116 "preference-service/referent/weak/alive",
3117 KIND_OTHER,
3118 UNITS_COUNT,
3119 numWeakAlive,
3120 "The number of weak referents held by the preference service that are "
3121 "still alive.");
3122
3123 MOZ_COLLECT_REPORT(
3124 "preference-service/referent/weak/dead",
3125 KIND_OTHER,
3126 UNITS_COUNT,
3127 numWeakDead,
3128 "The number of weak referents held by the preference service that are "
3129 "dead.");
3130
3131 return NS_OK;
3132}
3133
3134namespace {
3135
3136class AddPreferencesMemoryReporterRunnable : public Runnable
3137{
3138public:
3139 AddPreferencesMemoryReporterRunnable()
3140 : Runnable("AddPreferencesMemoryReporterRunnable")
3141 {
3142 }
3143
3144 NS_IMETHOD Run() override
3145 {
3146 return RegisterStrongMemoryReporter(new PreferenceServiceReporter());
3147 }
3148};
3149
3150} // namespace
3151
3152// A list of changed prefs sent from the parent via shared memory.
3153static InfallibleTArray<dom::Pref>* gChangedDomPrefs;
3154
3155static const char kTelemetryPref[] = "toolkit.telemetry.enabled";
3156static const char kChannelPref[] = "app.update.channel";
3157
3158#ifdef MOZ_WIDGET_ANDROID
3159
3160static Maybe<bool>
3161TelemetryPrefValue()
3162{
3163 // Leave it unchanged if it's already set.
3164 // XXX: how could it already be set?
3165 if (Preferences::GetType(kTelemetryPref) != nsIPrefBranch::PREF_INVALID) {
3166 return Nothing();
3167 }
3168
3169 // Determine the correct default for toolkit.telemetry.enabled. If this
3170 // build has MOZ_TELEMETRY_ON_BY_DEFAULT *or* we're on the beta channel,
3171 // telemetry is on by default, otherwise not. This is necessary so that
3172 // beta users who are testing final release builds don't flipflop defaults.
3173#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
3174 return Some(true);
3175#else
3176 nsAutoCString channelPrefValue;
3177 Unused << Preferences::GetCString(
3178 kChannelPref, channelPrefValue, PrefValueKind::Default);
3179 return Some(channelPrefValue.EqualsLiteral("beta"));
3180#endif
3181}
3182
3183/* static */ void
3184Preferences::SetupTelemetryPref()
3185{
3186 MOZ_ASSERT(XRE_IsParentProcess());
3187
3188 Maybe<bool> telemetryPrefValue = TelemetryPrefValue();
3189 if (telemetryPrefValue.isSome()) {
3190 Preferences::SetBool(
3191 kTelemetryPref, *telemetryPrefValue, PrefValueKind::Default);
3192 }
3193}
3194
3195#else // !MOZ_WIDGET_ANDROID
3196
3197static bool
3198TelemetryPrefValue()
3199{
3200 // For platforms with Unified Telemetry (here meaning not-Android),
3201 // toolkit.telemetry.enabled determines whether we send "extended" data.
3202 // We only want extended data from pre-release channels due to size.
3203
3204 NS_NAMED_LITERAL_CSTRING(channel, NS_STRINGIFY(MOZ_UPDATE_CHANNEL));
3205
3206 // Easy cases: Nightly, Aurora, Beta.
3207 if (channel.EqualsLiteral("nightly") || channel.EqualsLiteral("aurora") ||
3208 channel.EqualsLiteral("beta")) {
3209 return true;
3210 }
3211
3212#ifndef MOZILLA_OFFICIAL
3213 // Local developer builds: non-official builds on the "default" channel.
3214 if (channel.EqualsLiteral("default")) {
3215 return true;
3216 }
3217#endif
3218
3219 // Release Candidate builds: builds that think they are release builds, but
3220 // are shipped to beta users.
3221 if (channel.EqualsLiteral("release")) {
3222 nsAutoCString channelPrefValue;
3223 Unused << Preferences::GetCString(
3224 kChannelPref, channelPrefValue, PrefValueKind::Default);
3225 if (channelPrefValue.EqualsLiteral("beta")) {
3226 return true;
3227 }
3228 }
3229
3230 return false;
3231}
3232
3233/* static */ void
3234Preferences::SetupTelemetryPref()
3235{
3236 MOZ_ASSERT(XRE_IsParentProcess());
3237
3238 Preferences::SetBool(
3239 kTelemetryPref, TelemetryPrefValue(), PrefValueKind::Default);
3240 Preferences::Lock(kTelemetryPref);
3241}
3242
3243static void
3244CheckTelemetryPref()
3245{
3246 MOZ_ASSERT(!XRE_IsParentProcess());
3247
3248 // Make sure the children got passed the right telemetry pref details.
3249 DebugOnly<bool> value;
3250 MOZ_ASSERT(NS_SUCCEEDED(Preferences::GetBool(kTelemetryPref, &value)) &&
3251 value == TelemetryPrefValue());
3252 MOZ_ASSERT(Preferences::IsLocked(kTelemetryPref));
3253}
3254
3255#endif // MOZ_WIDGET_ANDROID
3256
3257/* static */ already_AddRefed<Preferences>
3258Preferences::GetInstanceForService()
3259{
3260 if (sPreferences) {
3261 return do_AddRef(sPreferences);
3262 }
3263
3264 if (sShutdown) {
3265 gCacheDataDesc = "shutting down in GetInstanceForService()";
3266 return nullptr;
3267 }
3268
3269 sPreferences = new Preferences();
3270
3271 MOZ_ASSERT(!gHashTable);
3272 gHashTable = new PLDHashTable(
3273 &pref_HashTableOps, sizeof(PrefEntry), PREF_HASHTABLE_INITIAL_LENGTH);
3274
3275 gTelemetryLoadData =
3276 new nsDataHashtable<nsCStringHashKey, TelemetryLoadData>();
3277
3278 gCacheData = new nsTArray<nsAutoPtr<CacheData>>();
3279 gCacheDataDesc = "set by GetInstanceForService() (1)";
3280
3281 Result<Ok, const char*> res = InitInitialObjects(/* isStartup */ true);
3282 if (res.isErr()) {
3283 sPreferences = nullptr;
3284 gCacheDataDesc = res.unwrapErr();
3285 return nullptr;
3286 }
3287
3288 if (!XRE_IsParentProcess()) {
3289 MOZ_ASSERT(gChangedDomPrefs);
3290 for (unsigned int i = 0; i < gChangedDomPrefs->Length(); i++) {
3291 Preferences::SetPreference(gChangedDomPrefs->ElementAt(i));
3292 }
3293 delete gChangedDomPrefs;
3294 gChangedDomPrefs = nullptr;
3295
3296#ifndef MOZ_WIDGET_ANDROID
3297 CheckTelemetryPref();
3298#endif
3299
3300 } else {
3301 // Check if there is a deployment configuration file. If so, set up the
3302 // pref config machinery, which will actually read the file.
3303 nsAutoCString lockFileName;
3304 nsresult rv = Preferences::GetCString(
3305 "general.config.filename", lockFileName, PrefValueKind::User);
3306 if (NS_SUCCEEDED(rv)) {
3307 NS_CreateServicesFromCategory(
3308 "pref-config-startup",
3309 static_cast<nsISupports*>(static_cast<void*>(sPreferences)),
3310 "pref-config-startup");
3311 }
3312
3313 nsCOMPtr<nsIObserverService> observerService =
3314 mozilla::services::GetObserverService();
3315 if (!observerService) {
3316 sPreferences = nullptr;
3317 gCacheDataDesc = "GetObserverService() failed (1)";
3318 return nullptr;
3319 }
3320
3321 observerService->AddObserver(
3322 sPreferences, "profile-before-change-telemetry", true);
3323 rv =
3324 observerService->AddObserver(sPreferences, "profile-before-change", true);
3325
3326 observerService->AddObserver(
3327 sPreferences, "suspend_process_notification", true);
3328
3329 if (NS_FAILED(rv)) {
3330 sPreferences = nullptr;
3331 gCacheDataDesc = "AddObserver(\"profile-before-change\") failed";
3332 return nullptr;
3333 }
3334 }
3335
3336 gCacheDataDesc = "set by GetInstanceForService() (2)";
3337
3338 // Preferences::GetInstanceForService() can be called from GetService(), and
3339 // RegisterStrongMemoryReporter calls GetService(nsIMemoryReporter). To
3340 // avoid a potential recursive GetService() call, we can't register the
3341 // memory reporter here; instead, do it off a runnable.
3342 RefPtr<AddPreferencesMemoryReporterRunnable> runnable =
3343 new AddPreferencesMemoryReporterRunnable();
3344 NS_DispatchToMainThread(runnable);
3345
3346 return do_AddRef(sPreferences);
3347}
3348
3349/* static */ bool
3350Preferences::IsServiceAvailable()
3351{
3352 return !!sPreferences;
3353}
3354
3355/* static */ bool
3356Preferences::InitStaticMembers()
3357{
3358 MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal());
3359
3360 if (MOZ_LIKELY(sPreferences)) {
3361 return true;
3362 }
3363
3364 if (!sShutdown) {
3365 MOZ_ASSERT(NS_IsMainThread());
3366 nsCOMPtr<nsIPrefService> prefService =
3367 do_GetService(NS_PREFSERVICE_CONTRACTID);
3368 }
3369
3370 return sPreferences != nullptr;
3371}
3372
3373/* static */ void
3374Preferences::Shutdown()
3375{
3376 if (!sShutdown) {
3377 sShutdown = true; // Don't create the singleton instance after here.
3378 sPreferences = nullptr;
3379 }
3380}
3381
3382Preferences::Preferences()
3383 : mRootBranch(new nsPrefBranch("", PrefValueKind::User))
3384 , mDefaultRootBranch(new nsPrefBranch("", PrefValueKind::Default))
3385{
3386}
3387
3388Preferences::~Preferences()
3389{
3390 MOZ_ASSERT(!sPreferences);
3391
3392 delete gCacheData;
3393 gCacheData = nullptr;
3394
3395 MOZ_ASSERT(!gCallbacksInProgress);
3396
3397 CallbackNode* node = gFirstCallback;
3398 while (node) {
3399 CallbackNode* next_node = node->Next();
3400 delete node;
3401 node = next_node;
3402 }
3403 gLastPriorityNode = gFirstCallback = nullptr;
3404
3405 delete gHashTable;
3406 gHashTable = nullptr;
3407
3408 delete gTelemetryLoadData;
3409 gTelemetryLoadData = nullptr;
3410
3411 gPrefNameArena.Clear();
3412}
3413
3414NS_IMPL_ISUPPORTS(Preferences,
3415 nsIPrefService,
3416 nsIObserver,
3417 nsIPrefBranch,
3418 nsISupportsWeakReference)
3419
3420/* static */ void
3421Preferences::SerializePreferences(nsCString& aStr)
3422{
3423 MOZ_RELEASE_ASSERT(InitStaticMembers());
3424
3425 aStr.Truncate();
3426
3427 for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
3428 Pref* pref = static_cast<PrefEntry*>(iter.Get())->mPref;
3429 if (pref->MustSendToContentProcesses() && pref->HasAdvisablySizedValues()) {
3430 pref->SerializeAndAppend(aStr);
3431 }
3432 }
3433
3434 aStr.Append('\0');
3435}
3436
3437/* static */ void
3438Preferences::DeserializePreferences(char* aStr, size_t aPrefsLen)
3439{
3440 MOZ_ASSERT(!XRE_IsParentProcess());
3441
3442 MOZ_ASSERT(!gChangedDomPrefs);
3443 gChangedDomPrefs = new InfallibleTArray<dom::Pref>();
3444
3445 char* p = aStr;
3446 while (*p != '\0') {
3447 dom::Pref pref;
3448 p = Pref::Deserialize(p, &pref);
3449 gChangedDomPrefs->AppendElement(pref);
3450 }
3451
3452 // We finished parsing on a '\0'. That should be the last char in the shared
3453 // memory. (aPrefsLen includes the '\0'.)
3454 MOZ_ASSERT(p == aStr + aPrefsLen - 1);
3455
3456#ifdef DEBUG
3457 MOZ_ASSERT(!gContentProcessPrefsAreInited);
3458 gContentProcessPrefsAreInited = true;
3459#endif
3460}
3461
3462/* static */ void
3463Preferences::InitializeUserPrefs()
3464{
3465 MOZ_ASSERT(XRE_IsParentProcess());
3466 MOZ_ASSERT(!sPreferences->mCurrentFile, "Should only initialize prefs once");
3467
3468 // Prefs which are set before we initialize the profile are silently
3469 // discarded. This is stupid, but there are various tests which depend on
3470 // this behavior.
3471 sPreferences->ResetUserPrefs();
3472
3473 nsCOMPtr<nsIFile> prefsFile = sPreferences->ReadSavedPrefs();
3474 sPreferences->ReadUserOverridePrefs();
3475
3476 sPreferences->mDirty = false;
3477
3478 // Don't set mCurrentFile until we're done so that dirty flags work properly.
3479 sPreferences->mCurrentFile = prefsFile.forget();
3480
3481 sPreferences->NotifyServiceObservers(NS_PREFSERVICE_READ_TOPIC_ID);
3482
3483 // At this point all the prefs files have been read and telemetry has been
3484 // initialized. Send all the file load measurements to telemetry.
3485 SendTelemetryLoadData();
3486}
3487
3488NS_IMETHODIMP
3489Preferences::Observe(nsISupports* aSubject,
3490 const char* aTopic,
3491 const char16_t* someData)
3492{
3493 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
3494 return NS_ERROR_NOT_AVAILABLE;
3495 }
3496
3497 nsresult rv = NS_OK;
3498
3499 if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
3500 // Normally prefs aren't written after this point, and so we kick off
3501 // an asynchronous pref save so that I/O can be done in parallel with
3502 // other shutdown.
3503 if (AllowOffMainThreadSave()) {
3504 SavePrefFile(nullptr);
3505 }
3506
3507 } else if (!nsCRT::strcmp(aTopic, "profile-before-change-telemetry")) {
3508 // It's possible that a profile-before-change observer after ours
3509 // set a pref. A blocking save here re-saves if necessary and also waits
3510 // for any pending saves to complete.
3511 SavePrefFileBlocking();
3512 MOZ_ASSERT(!mDirty, "Preferences should not be dirty");
3513 mProfileShutdown = true;
3514
3515 } else if (!nsCRT::strcmp(aTopic, "reload-default-prefs")) {
3516 // Reload the default prefs from file.
3517 Unused << InitInitialObjects(/* isStartup */ false);
3518
3519 } else if (!nsCRT::strcmp(aTopic, "suspend_process_notification")) {
3520 // Our process is being suspended. The OS may wake our process later,
3521 // or it may kill the process. In case our process is going to be killed
3522 // from the suspended state, we save preferences before suspending.
3523 rv = SavePrefFileBlocking();
3524 }
3525
3526 return rv;
3527}
3528
3529NS_IMETHODIMP
3530Preferences::ReadDefaultPrefsFromFile(nsIFile* aFile)
3531{
3532 ENSURE_PARENT_PROCESS("Preferences::ReadDefaultPrefsFromFile", "all prefs");
3533
3534 if (!aFile) {
3535 NS_ERROR("ReadDefaultPrefsFromFile requires a parameter");
3536 return NS_ERROR_INVALID_ARG;
3537 }
3538
3539 return openPrefFile(aFile, PrefValueKind::Default);
3540}
3541
3542NS_IMETHODIMP
3543Preferences::ReadUserPrefsFromFile(nsIFile* aFile)
3544{
3545 ENSURE_PARENT_PROCESS("Preferences::ReadUserPrefsFromFile", "all prefs");
3546
3547 if (!aFile) {
3548 NS_ERROR("ReadUserPrefsFromFile requires a parameter");
3549 return NS_ERROR_INVALID_ARG;
3550 }
3551
3552 return openPrefFile(aFile, PrefValueKind::User);
3553}
3554
3555NS_IMETHODIMP
3556Preferences::ResetPrefs()
3557{
3558 ENSURE_PARENT_PROCESS("Preferences::ResetPrefs", "all prefs");
3559
3560 gHashTable->ClearAndPrepareForLength(PREF_HASHTABLE_INITIAL_LENGTH);
3561 gPrefNameArena.Clear();
3562
3563 return InitInitialObjects(/* isStartup */ false).isOk() ? NS_OK
3564 : NS_ERROR_FAILURE;
3565}
3566
3567NS_IMETHODIMP
3568Preferences::ResetUserPrefs()
3569{
3570 ENSURE_PARENT_PROCESS("Preferences::ResetUserPrefs", "all prefs");
3571 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
3572 MOZ_ASSERT(NS_IsMainThread());
3573
3574 Vector<const char*> prefNames;
3575 for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
3576 Pref* pref = static_cast<PrefEntry*>(iter.Get())->mPref;
3577
3578 if (pref->HasUserValue()) {
3579 if (!prefNames.append(pref->Name())) {
3580 return NS_ERROR_OUT_OF_MEMORY;
3581 }
3582
3583 pref->ClearUserValue();
3584 if (!pref->HasDefaultValue()) {
3585 iter.Remove();
3586 }
3587 }
3588 }
3589
3590 for (const char* prefName : prefNames) {
3591 NotifyCallbacks(prefName);
3592 }
3593
3594 Preferences::HandleDirty();
3595 return NS_OK;
3596}
3597
3598bool
3599Preferences::AllowOffMainThreadSave()
3600{
3601 // Put in a preference that allows us to disable off main thread preference
3602 // file save.
3603 if (sAllowOMTPrefWrite < 0) {
3604 bool value = false;
3605 Preferences::GetBool("preferences.allow.omt-write", &value);
3606 sAllowOMTPrefWrite = value ? 1 : 0;
3607 }
3608
3609 return !!sAllowOMTPrefWrite;
3610}
3611
3612nsresult
3613Preferences::SavePrefFileBlocking()
3614{
3615 if (mDirty) {
3616 return SavePrefFileInternal(nullptr, SaveMethod::Blocking);
3617 }
3618
3619 // If we weren't dirty to start, SavePrefFileInternal will early exit so
3620 // there is no guarantee that we don't have oustanding async saves in the
3621 // pipe. Since the contract of SavePrefFileOnMainThread is that the file on
3622 // disk matches the preferences, we have to make sure those requests are
3623 // completed.
3624
3625 if (AllowOffMainThreadSave()) {
3626 PreferencesWriter::Flush();
3627 }
3628
3629 return NS_OK;
3630}
3631
3632nsresult
3633Preferences::SavePrefFileAsynchronous()
3634{
3635 return SavePrefFileInternal(nullptr, SaveMethod::Asynchronous);
3636}
3637
3638NS_IMETHODIMP
3639Preferences::SavePrefFile(nsIFile* aFile)
3640{
3641 // This is the method accessible from service API. Make it off main thread.
3642 return SavePrefFileInternal(aFile, SaveMethod::Asynchronous);
3643}
3644
3645/* static */ void
3646Preferences::SetPreference(const dom::Pref& aDomPref)
3647{
3648 MOZ_ASSERT(!XRE_IsParentProcess());
3649 NS_ENSURE_TRUE(InitStaticMembers(), (void)0);
3650
3651 const char* prefName = aDomPref.name().get();
3652
3653 auto entry = static_cast<PrefEntry*>(gHashTable->Add(prefName, fallible));
3654 if (!entry) {
3655 return;
3656 }
3657
3658 Pref* pref = entry->mPref;
3659
3660 bool valueChanged = false;
3661 pref->FromDomPref(aDomPref, &valueChanged);
3662
3663 // When the parent process clears a pref's user value we get a DomPref here
3664 // with no default value and no user value. There are two possibilities.
3665 //
3666 // - There was an existing pref with only a user value. FromDomPref() will
3667 // have just cleared that user value, so the pref can be removed.
3668 //
3669 // - There was no existing pref. FromDomPref() will have done nothing, and
3670 // `pref` will be valueless. We will end up adding and removing the value
3671 // needlessly, but that's ok because this case is rare.
3672 //
3673 if (!pref->HasDefaultValue() && !pref->HasUserValue()) {
3674 gHashTable->RemoveEntry(entry);
3675 }
3676
3677 // Note: we don't have to worry about HandleDirty() because we are setting
3678 // prefs in the content process that have come from the parent process.
3679
3680 if (valueChanged) {
3681 NotifyCallbacks(prefName);
3682 }
3683}
3684
3685/* static */ void
3686Preferences::GetPreference(dom::Pref* aDomPref)
3687{
3688 MOZ_ASSERT(XRE_IsParentProcess());
3689
3690 Pref* pref = pref_HashTableLookup(aDomPref->name().get());
3691 if (pref && pref->HasAdvisablySizedValues()) {
3692 pref->ToDomPref(aDomPref);
3693 }
3694}
3695
3696#ifdef DEBUG
3697bool
3698Preferences::ArePrefsInitedInContentProcess()
3699{
3700 MOZ_ASSERT(!XRE_IsParentProcess());
3701 return gContentProcessPrefsAreInited;
3702}
3703#endif
3704
3705NS_IMETHODIMP
3706Preferences::GetBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal)
3707{
3708 if ((nullptr != aPrefRoot) && (*aPrefRoot != '\0')) {
3709 // TODO: Cache this stuff and allow consumers to share branches (hold weak
3710 // references, I think).
3711 RefPtr<nsPrefBranch> prefBranch =
3712 new nsPrefBranch(aPrefRoot, PrefValueKind::User);
3713 prefBranch.forget(aRetVal);
3714 } else {
3715 // Special case: caching the default root.
3716 nsCOMPtr<nsIPrefBranch> root(sPreferences->mRootBranch);
3717 root.forget(aRetVal);
3718 }
3719
3720 return NS_OK;
3721}
3722
3723NS_IMETHODIMP
3724Preferences::GetDefaultBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal)
3725{
3726 if (!aPrefRoot || !aPrefRoot[0]) {
3727 nsCOMPtr<nsIPrefBranch> root(sPreferences->mDefaultRootBranch);
3728 root.forget(aRetVal);
3729 return NS_OK;
3730 }
3731
3732 // TODO: Cache this stuff and allow consumers to share branches (hold weak
3733 // references, I think).
3734 RefPtr<nsPrefBranch> prefBranch =
3735 new nsPrefBranch(aPrefRoot, PrefValueKind::Default);
3736 if (!prefBranch) {
3737 return NS_ERROR_OUT_OF_MEMORY;
3738 }
3739
3740 prefBranch.forget(aRetVal);
3741 return NS_OK;
3742}
3743
3744NS_IMETHODIMP
3745Preferences::ReadStats(nsIPrefStatsCallback* aCallback)
3746{
3747#ifdef DEBUG
3748 for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
3749 PrefEntry* entry = static_cast<PrefEntry*>(iter.Get());
3750 aCallback->Visit(entry->mPref->Name(), entry->mAccessCount);
3751 }
3752
3753 return NS_OK;
3754#else
3755 return NS_ERROR_NOT_IMPLEMENTED;
3756#endif
3757}
3758
3759NS_IMETHODIMP
3760Preferences::ResetStats()
3761{
3762#ifdef DEBUG
3763 for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
3764 static_cast<PrefEntry*>(iter.Get())->mAccessCount = 0;
3765 }
3766 return NS_OK;
3767#else
3768 return NS_ERROR_NOT_IMPLEMENTED;
3769#endif
3770}
3771
3772NS_IMETHODIMP
3773Preferences::GetDirty(bool* aRetVal)
3774{
3775 *aRetVal = mDirty;
3776 return NS_OK;
3777}
3778
3779nsresult
3780Preferences::NotifyServiceObservers(const char* aTopic)
3781{
3782 nsCOMPtr<nsIObserverService> observerService =
3783 mozilla::services::GetObserverService();
3784 if (!observerService) {
3785 return NS_ERROR_FAILURE;
3786 }
3787
3788 auto subject = static_cast<nsIPrefService*>(this);
3789 observerService->NotifyObservers(subject, aTopic, nullptr);
3790
3791 return NS_OK;
3792}
3793
3794already_AddRefed<nsIFile>
3795Preferences::ReadSavedPrefs()
3796{
3797 nsCOMPtr<nsIFile> file;
3798 nsresult rv =
3799 NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE, getter_AddRefs(file));
3800 if (NS_WARN_IF(NS_FAILED(rv))) {
3801 return nullptr;
3802 }
3803
3804 rv = openPrefFile(file, PrefValueKind::User);
3805 if (rv == NS_ERROR_FILE_NOT_FOUND) {
3806 // This is a normal case for new users.
3807 Telemetry::ScalarSet(
3808 Telemetry::ScalarID::PREFERENCES_CREATED_NEW_USER_PREFS_FILE, true);
3809 rv = NS_OK;
3810 } else if (NS_FAILED(rv)) {
3811 // Save a backup copy of the current (invalid) prefs file, since all prefs
3812 // from the error line to the end of the file will be lost (bug 361102).
3813 // TODO we should notify the user about it (bug 523725).
3814 Telemetry::ScalarSet(
3815 Telemetry::ScalarID::PREFERENCES_PREFS_FILE_WAS_INVALID, true);
3816 MakeBackupPrefFile(file);
3817 }
3818
3819 return file.forget();
3820}
3821
3822void
3823Preferences::ReadUserOverridePrefs()
3824{
3825 nsCOMPtr<nsIFile> aFile;
3826 nsresult rv =
3827 NS_GetSpecialDirectory(NS_APP_PREFS_50_DIR, getter_AddRefs(aFile));
3828 if (NS_WARN_IF(NS_FAILED(rv))) {
3829 return;
3830 }
3831
3832 aFile->AppendNative(NS_LITERAL_CSTRING("user.js"));
3833 rv = openPrefFile(aFile, PrefValueKind::User);
3834 if (rv != NS_ERROR_FILE_NOT_FOUND) {
3835 // If the file exists and was at least partially read, record that in
3836 // telemetry as it may be a sign of pref injection.
3837 Telemetry::ScalarSet(Telemetry::ScalarID::PREFERENCES_READ_USER_JS, true);
3838 }
3839}
3840
3841nsresult
3842Preferences::MakeBackupPrefFile(nsIFile* aFile)
3843{
3844 // Example: this copies "prefs.js" to "Invalidprefs.js" in the same directory.
3845 // "Invalidprefs.js" is removed if it exists, prior to making the copy.
3846 nsAutoString newFilename;
3847 nsresult rv = aFile->GetLeafName(newFilename);
3848 NS_ENSURE_SUCCESS(rv, rv);
3849
3850 newFilename.InsertLiteral(u"Invalid", 0);
3851 nsCOMPtr<nsIFile> newFile;
3852 rv = aFile->GetParent(getter_AddRefs(newFile));
3853 NS_ENSURE_SUCCESS(rv, rv);
3854
3855 rv = newFile->Append(newFilename);
3856 NS_ENSURE_SUCCESS(rv, rv);
3857
3858 bool exists = false;
3859 newFile->Exists(&exists);
3860 if (exists) {
3861 rv = newFile->Remove(false);
3862 NS_ENSURE_SUCCESS(rv, rv);
3863 }
3864
3865 rv = aFile->CopyTo(nullptr, newFilename);
3866 NS_ENSURE_SUCCESS(rv, rv);
3867
3868 return rv;
3869}
3870
3871nsresult
3872Preferences::SavePrefFileInternal(nsIFile* aFile, SaveMethod aSaveMethod)
3873{
3874 ENSURE_PARENT_PROCESS("Preferences::SavePrefFileInternal", "all prefs");
3875
3876 // We allow different behavior here when aFile argument is not null, but it
3877 // happens to be the same as the current file. It is not clear that we
3878 // should, but it does give us a "force" save on the unmodified pref file
3879 // (see the original bug 160377 when we added this.)
3880
3881 if (nullptr == aFile) {
3882 mSavePending = false;
3883
3884 // Off main thread writing only if allowed.
3885 if (!AllowOffMainThreadSave()) {
3886 aSaveMethod = SaveMethod::Blocking;
3887 }
3888
3889 // The mDirty flag tells us if we should write to mCurrentFile. We only
3890 // check this flag when the caller wants to write to the default.
3891 if (!mDirty) {
3892 return NS_OK;
3893 }
3894
3895 // Check for profile shutdown after mDirty because the runnables from
3896 // HandleDirty() can still be pending.
3897 if (mProfileShutdown) {
3898 NS_WARNING("Cannot save pref file after profile shutdown.");
3899 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
3900 }
3901
3902 // It's possible that we never got a prefs file.
3903 nsresult rv = NS_OK;
3904 if (mCurrentFile) {
3905 rv = WritePrefFile(mCurrentFile, aSaveMethod);
3906 }
3907
3908 // If we succeeded writing to mCurrentFile, reset the dirty flag.
3909 if (NS_SUCCEEDED(rv)) {
3910 mDirty = false;
3911 }
3912 return rv;
3913
3914 } else {
3915 // We only allow off main thread writes on mCurrentFile.
3916 return WritePrefFile(aFile, SaveMethod::Blocking);
3917 }
3918}
3919
3920nsresult
3921Preferences::WritePrefFile(nsIFile* aFile, SaveMethod aSaveMethod)
3922{
3923 MOZ_ASSERT(XRE_IsParentProcess());
3924
3925 if (!gHashTable) {
3926 return NS_ERROR_NOT_INITIALIZED;
3927 }
3928
3929 AUTO_PROFILER_LABEL("Preferences::WritePrefFile", OTHER);
3930
3931 if (AllowOffMainThreadSave()) {
3932
3933 nsresult rv = NS_OK;
3934 mozilla::UniquePtr<PrefSaveData> prefs =
3935 MakeUnique<PrefSaveData>(pref_savePrefs());
3936
3937 // Put the newly constructed preference data into sPendingWriteData
3938 // for the next request to pick up
3939 prefs.reset(PreferencesWriter::sPendingWriteData.exchange(prefs.release()));
3940 if (prefs) {
3941 // There was a previous request that hasn't been processed,
3942 // and this is the data it had.
3943 return rv;
3944 }
3945
3946 // There were no previous requests. Dispatch one since sPendingWriteData has
3947 // the up to date information.
3948 nsCOMPtr<nsIEventTarget> target =
3949 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
3950 if (NS_SUCCEEDED(rv)) {
3951 bool async = aSaveMethod == SaveMethod::Asynchronous;
3952 if (async) {
3953 rv = target->Dispatch(new PWRunnable(aFile),
3954 nsIEventTarget::DISPATCH_NORMAL);
3955 } else {
3956 // Note that we don't get the nsresult return value here.
3957 SyncRunnable::DispatchToThread(target, new PWRunnable(aFile), true);
3958 }
3959 return rv;
3960 }
3961
3962 // If we can't get the thread for writing, for whatever reason, do the main
3963 // thread write after making some noise.
3964 MOZ_ASSERT(false, "failed to get the target thread for OMT pref write");
3965 }
3966
3967 // This will do a main thread write. It is safe to do it this way because
3968 // AllowOffMainThreadSave() returns a consistent value for the lifetime of
3969 // the parent process.
3970 PrefSaveData prefsData = pref_savePrefs();
3971 return PreferencesWriter::Write(aFile, prefsData);
3972}
3973
3974static nsresult
3975openPrefFile(nsIFile* aFile, PrefValueKind aKind)
3976{
3977 TimeStamp startTime = TimeStamp::Now();
3978
3979 nsCString data;
3980 MOZ_TRY_VAR(data, URLPreloader::ReadFile(aFile));
3981
3982 nsAutoString filenameUtf16;
3983 aFile->GetLeafName(filenameUtf16);
3984 NS_ConvertUTF16toUTF8 filename(filenameUtf16);
3985
3986 nsAutoString path;
3987 aFile->GetPath(path);
3988
3989 Parser parser;
3990 if (!parser.Parse(
3991 filename, aKind, NS_ConvertUTF16toUTF8(path).get(), startTime, data)) {
3992 return NS_ERROR_FILE_CORRUPTED;
3993 }
3994
3995 return NS_OK;
3996}
3997
3998static int
3999pref_CompareFileNames(nsIFile* aFile1, nsIFile* aFile2, void* /* unused */)
4000{
4001 nsAutoCString filename1, filename2;
4002 aFile1->GetNativeLeafName(filename1);
4003 aFile2->GetNativeLeafName(filename2);
4004
4005 return Compare(filename2, filename1);
4006}
4007
4008// Load default pref files from a directory. The files in the directory are
4009// sorted reverse-alphabetically; a set of "special file names" may be
4010// specified which are loaded after all the others.
4011static nsresult
4012pref_LoadPrefsInDir(nsIFile* aDir,
4013 char const* const* aSpecialFiles,
4014 uint32_t aSpecialFilesCount)
4015{
4016 nsresult rv, rv2;
4017
4018 nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
4019
4020 // This may fail in some normal cases, such as embedders who do not use a
4021 // GRE.
4022 rv = aDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
4023 if (NS_FAILED(rv)) {
4024 // If the directory doesn't exist, then we have no reason to complain. We
4025 // loaded everything (and nothing) successfully.
4026 if (rv == NS_ERROR_FILE_NOT_FOUND ||
4027 rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
4028 rv = NS_OK;
4029 }
4030 return rv;
4031 }
4032
4033 nsCOMArray<nsIFile> prefFiles(INITIAL_PREF_FILES);
4034 nsCOMArray<nsIFile> specialFiles(aSpecialFilesCount);
4035 nsCOMPtr<nsIFile> prefFile;
4036
4037 while (NS_SUCCEEDED(dirIterator->GetNextFile(getter_AddRefs(prefFile))) &&
4038 prefFile) {
4039 nsAutoCString leafName;
4040 prefFile->GetNativeLeafName(leafName);
4041 MOZ_ASSERT(
4042 !leafName.IsEmpty(),
4043 "Failure in default prefs: directory enumerator returned empty file?");
4044
4045 // Skip non-js files.
4046 if (StringEndsWith(leafName,
4047 NS_LITERAL_CSTRING(".js"),
4048 nsCaseInsensitiveCStringComparator())) {
4049 bool shouldParse = true;
4050
4051 // Separate out special files.
4052 for (uint32_t i = 0; i < aSpecialFilesCount; ++i) {
4053 if (leafName.Equals(nsDependentCString(aSpecialFiles[i]))) {
4054 shouldParse = false;
4055 // Special files should be processed in order. We put them into the
4056 // array by index, which can make the array sparse.
4057 specialFiles.ReplaceObjectAt(prefFile, i);
4058 }
4059 }
4060
4061 if (shouldParse) {
4062 prefFiles.AppendObject(prefFile);
4063 }
4064 }
4065 }
4066
4067 if (prefFiles.Count() + specialFiles.Count() == 0) {
4068 NS_WARNING("No default pref files found.");
4069 if (NS_SUCCEEDED(rv)) {
4070 rv = NS_SUCCESS_FILE_DIRECTORY_EMPTY;
4071 }
4072 return rv;
4073 }
4074
4075 prefFiles.Sort(pref_CompareFileNames, nullptr);
4076
4077 uint32_t arrayCount = prefFiles.Count();
4078 uint32_t i;
4079 for (i = 0; i < arrayCount; ++i) {
4080 rv2 = openPrefFile(prefFiles[i], PrefValueKind::Default);
4081 if (NS_FAILED(rv2)) {
4082 NS_ERROR("Default pref file not parsed successfully.");
4083 rv = rv2;
4084 }
4085 }
4086
4087 arrayCount = specialFiles.Count();
4088 for (i = 0; i < arrayCount; ++i) {
4089 // This may be a sparse array; test before parsing.
4090 nsIFile* file = specialFiles[i];
4091 if (file) {
4092 rv2 = openPrefFile(file, PrefValueKind::Default);
4093 if (NS_FAILED(rv2)) {
4094 NS_ERROR("Special default pref file not parsed successfully.");
4095 rv = rv2;
4096 }
4097 }
4098 }
4099
4100 return rv;
4101}
4102
4103static nsresult
4104pref_ReadPrefFromJar(nsZipArchive* aJarReader, const char* aName)
4105{
4106 TimeStamp startTime = TimeStamp::Now();
4107
4108 nsCString manifest;
4109 MOZ_TRY_VAR(manifest,
4110 URLPreloader::ReadZip(aJarReader, nsDependentCString(aName)));
4111
4112 Parser parser;
4113 if (!parser.Parse(nsDependentCString(aName),
4114 PrefValueKind::Default,
4115 aName,
4116 startTime,
4117 manifest)) {
4118 return NS_ERROR_FILE_CORRUPTED;
4119 }
4120
4121 return NS_OK;
4122}
4123
4124// Initialize default preference JavaScript buffers from appropriate TEXT
4125// resources.
4126/* static */ Result<Ok, const char*>
4127Preferences::InitInitialObjects(bool aIsStartup)
4128{
4129 // Initialize static prefs before prefs from data files so that the latter
4130 // will override the former.
4131 StaticPrefs::InitAll(aIsStartup);
4132
4133 // In the omni.jar case, we load the following prefs:
4134 // - jar:$gre/omni.jar!/greprefs.js
4135 // - jar:$gre/omni.jar!/defaults/pref/*.js
4136 //
4137 // In the non-omni.jar case, we load:
4138 // - $gre/greprefs.js
4139 //
4140 // In both cases, we also load:
4141 // - $gre/defaults/pref/*.js
4142 //
4143 // This is kept for bug 591866 (channel-prefs.js should not be in omni.jar)
4144 // in the `$app == $gre` case; we load all files instead of channel-prefs.js
4145 // only to have the same behaviour as `$app != $gre`, where this is required
4146 // as a supported location for GRE preferences.
4147 //
4148 // When `$app != $gre`, we additionally load, in the omni.jar case:
4149 // - jar:$app/omni.jar!/defaults/preferences/*.js
4150 // - $app/defaults/preferences/*.js
4151 //
4152 // and in the non-omni.jar case:
4153 // - $app/defaults/preferences/*.js
4154 //
4155 // When `$app == $gre`, we additionally load, in the omni.jar case:
4156 // - jar:$gre/omni.jar!/defaults/preferences/*.js
4157 //
4158 // Thus, in the omni.jar case, we always load app-specific default
4159 // preferences from omni.jar, whether or not `$app == $gre`.
4160
4161 nsresult rv;
4162 nsZipFind* findPtr;
4163 nsAutoPtr<nsZipFind> find;
4164 nsTArray<nsCString> prefEntries;
4165 const char* entryName;
4166 uint16_t entryNameLen;
4167
4168 RefPtr<nsZipArchive> jarReader =
4169 mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
4170 if (jarReader) {
4171 // Load jar:$gre/omni.jar!/greprefs.js.
4172 rv = pref_ReadPrefFromJar(jarReader, "greprefs.js");
4173 NS_ENSURE_SUCCESS(rv, Err("pref_ReadPrefFromJar() failed"));
4174
4175 // Load jar:$gre/omni.jar!/defaults/pref/*.js.
4176 rv = jarReader->FindInit("defaults/pref/*.js$", &findPtr);
4177 NS_ENSURE_SUCCESS(rv, Err("jarReader->FindInit() failed"));
4178
4179 find = findPtr;
4180 while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
4181 prefEntries.AppendElement(Substring(entryName, entryNameLen));
4182 }
4183
4184 prefEntries.Sort();
4185 for (uint32_t i = prefEntries.Length(); i--;) {
4186 rv = pref_ReadPrefFromJar(jarReader, prefEntries[i].get());
4187 if (NS_FAILED(rv)) {
4188 NS_WARNING("Error parsing preferences.");
4189 }
4190 }
4191
4192 } else {
4193 // Load $gre/greprefs.js.
4194 nsCOMPtr<nsIFile> greprefsFile;
4195 rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greprefsFile));
4196 NS_ENSURE_SUCCESS(rv, Err("NS_GetSpecialDirectory(NS_GRE_DIR) failed"));
4197
4198 rv = greprefsFile->AppendNative(NS_LITERAL_CSTRING("greprefs.js"));
4199 NS_ENSURE_SUCCESS(rv, Err("greprefsFile->AppendNative() failed"));
4200
4201 rv = openPrefFile(greprefsFile, PrefValueKind::Default);
4202 if (NS_FAILED(rv)) {
4203 NS_WARNING("Error parsing GRE default preferences. Is this an old-style "
4204 "embedding app?");
4205 }
4206 }
4207
4208 // Load $gre/defaults/pref/*.js.
4209 nsCOMPtr<nsIFile> defaultPrefDir;
4210 rv = NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR,
4211 getter_AddRefs(defaultPrefDir));
4212 NS_ENSURE_SUCCESS(
4213 rv, Err("NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR) failed"));
4214
4215 // These pref file names should not be used: we process them after all other
4216 // application pref files for backwards compatibility.
4217 static const char* specialFiles[] = {
4218#if defined(XP_MACOSX)
4219 "macprefs.js"
4220#elif defined(XP_WIN)
4221 "winpref.js"
4222#elif defined(XP_UNIX)
4223 "unix.js"
4224#if defined(_AIX)
4225 ,
4226 "aix.js"
4227#endif
4228#elif defined(XP_BEOS)
4229 "beos.js"
4230#endif
4231 };
4232
4233 rv = pref_LoadPrefsInDir(
4234 defaultPrefDir, specialFiles, ArrayLength(specialFiles));
4235 if (NS_FAILED(rv)) {
4236 NS_WARNING("Error parsing application default preferences.");
4237 }
4238
4239 // Load jar:$app/omni.jar!/defaults/preferences/*.js
4240 // or jar:$gre/omni.jar!/defaults/preferences/*.js.
4241 RefPtr<nsZipArchive> appJarReader =
4242 mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
4243
4244 // GetReader(mozilla::Omnijar::APP) returns null when `$app == $gre`, in
4245 // which case we look for app-specific default preferences in $gre.
4246 if (!appJarReader) {
4247 appJarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
4248 }
4249
4250 if (appJarReader) {
4251 rv = appJarReader->FindInit("defaults/preferences/*.js$", &findPtr);
4252 NS_ENSURE_SUCCESS(rv, Err("appJarReader->FindInit() failed"));
4253 find = findPtr;
4254 prefEntries.Clear();
4255 while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
4256 prefEntries.AppendElement(Substring(entryName, entryNameLen));
4257 }
4258 prefEntries.Sort();
4259 for (uint32_t i = prefEntries.Length(); i--;) {
4260 rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get());
4261 if (NS_FAILED(rv)) {
4262 NS_WARNING("Error parsing preferences.");
4263 }
4264 }
4265 }
4266
4267 nsCOMPtr<nsIProperties> dirSvc(
4268 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
4269 NS_ENSURE_SUCCESS(
4270 rv, Err("do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID) failed"));
4271
4272 nsCOMPtr<nsISimpleEnumerator> list;
4273 dirSvc->Get(NS_APP_PREFS_DEFAULTS_DIR_LIST,
4274 NS_GET_IID(nsISimpleEnumerator),
4275 getter_AddRefs(list));
4276 if (list) {
4277 bool hasMore;
4278 while (NS_SUCCEEDED(list->HasMoreElements(&hasMore)) && hasMore) {
4279 nsCOMPtr<nsISupports> elem;
4280 list->GetNext(getter_AddRefs(elem));
4281 if (!elem) {
4282 continue;
4283 }
4284
4285 nsCOMPtr<nsIFile> path = do_QueryInterface(elem);
4286 if (!path) {
4287 continue;
4288 }
4289
4290 // Do we care if a file provided by this process fails to load?
4291 pref_LoadPrefsInDir(path, nullptr, 0);
4292 }
4293 }
4294
4295 if (XRE_IsParentProcess()) {
4296 SetupTelemetryPref();
4297 }
4298
4299 NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID,
4300 nullptr,
4301 NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID);
4302
4303 nsCOMPtr<nsIObserverService> observerService =
4304 mozilla::services::GetObserverService();
4305 NS_ENSURE_SUCCESS(rv, Err("GetObserverService() failed (2)"));
4306
4307 observerService->NotifyObservers(
4308 nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, nullptr);
4309
4310 return Ok();
4311}
4312
4313/* static */ nsresult
4314Preferences::GetBool(const char* aPrefName, bool* aResult, PrefValueKind aKind)
4315{
4316 MOZ_ASSERT(aResult);
4317 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4318
4319 Pref* pref = pref_HashTableLookup(aPrefName);
4320 return pref ? pref->GetBoolValue(aKind, aResult) : NS_ERROR_UNEXPECTED;
4321}
4322
4323/* static */ nsresult
4324Preferences::GetInt(const char* aPrefName,
4325 int32_t* aResult,
4326 PrefValueKind aKind)
4327{
4328 MOZ_ASSERT(aResult);
4329 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4330
4331 Pref* pref = pref_HashTableLookup(aPrefName);
4332 return pref ? pref->GetIntValue(aKind, aResult) : NS_ERROR_UNEXPECTED;
4333}
4334
4335/* static */ nsresult
4336Preferences::GetFloat(const char* aPrefName,
4337 float* aResult,
4338 PrefValueKind aKind)
4339{
4340 MOZ_ASSERT(aResult);
4341
4342 nsAutoCString result;
4343 nsresult rv = Preferences::GetCString(aPrefName, result, aKind);
4344 if (NS_SUCCEEDED(rv)) {
4345 *aResult = result.ToFloat(&rv);
4346 }
4347 return rv;
4348}
4349
4350/* static */ nsresult
4351Preferences::GetCString(const char* aPrefName,
4352 nsACString& aResult,
4353 PrefValueKind aKind)
4354{
4355 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4356
4357 aResult.SetIsVoid(true);
4358
4359 Pref* pref = pref_HashTableLookup(aPrefName);
4360 return pref ? pref->GetCStringValue(aKind, aResult) : NS_ERROR_UNEXPECTED;
4361}
4362
4363/* static */ nsresult
4364Preferences::GetString(const char* aPrefName,
4365 nsAString& aResult,
4366 PrefValueKind aKind)
4367{
4368 nsAutoCString result;
4369 nsresult rv = Preferences::GetCString(aPrefName, result, aKind);
4370 if (NS_SUCCEEDED(rv)) {
4371 CopyUTF8toUTF16(result, aResult);
4372 }
4373 return rv;
4374}
4375
4376/* static */ nsresult
4377Preferences::GetLocalizedCString(const char* aPrefName,
4378 nsACString& aResult,
4379 PrefValueKind aKind)
4380{
4381 nsAutoString result;
4382 nsresult rv = GetLocalizedString(aPrefName, result, aKind);
4383 if (NS_SUCCEEDED(rv)) {
4384 CopyUTF16toUTF8(result, aResult);
4385 }
4386 return rv;
4387}
4388
4389/* static */ nsresult
4390Preferences::GetLocalizedString(const char* aPrefName,
4391 nsAString& aResult,
4392 PrefValueKind aKind)
4393{
4394 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4395 nsCOMPtr<nsIPrefLocalizedString> prefLocalString;
4396 nsresult rv =
4397 GetRootBranch(aKind)->GetComplexValue(aPrefName,
4398 NS_GET_IID(nsIPrefLocalizedString),
4399 getter_AddRefs(prefLocalString));
4400 if (NS_SUCCEEDED(rv)) {
4401 MOZ_ASSERT(prefLocalString, "Succeeded but the result is NULL");
4402 prefLocalString->GetData(aResult);
4403 }
4404 return rv;
4405}
4406
4407/* static */ nsresult
4408Preferences::GetComplex(const char* aPrefName,
4409 const nsIID& aType,
4410 void** aResult,
4411 PrefValueKind aKind)
4412{
4413 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4414 return GetRootBranch(aKind)->GetComplexValue(aPrefName, aType, aResult);
4415}
4416
4417/* static */ nsresult
4418Preferences::SetCString(const char* aPrefName,
4419 const nsACString& aValue,
4420 PrefValueKind aKind)
4421{
4422 ENSURE_PARENT_PROCESS("SetCString", aPrefName);
4423 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4424
4425 if (aValue.Length() > MAX_PREF_LENGTH) {
4426 return NS_ERROR_ILLEGAL_VALUE;
4427 }
4428
4429 // It's ok to stash a pointer to the temporary PromiseFlatCString's chars in
4430 // pref because pref_SetPref() duplicates those chars.
4431 PrefValue prefValue;
4432 const nsCString& flat = PromiseFlatCString(aValue);
4433 prefValue.mStringVal = flat.get();
4434 return pref_SetPref(aPrefName,
4435 PrefType::String,
4436 aKind,
4437 prefValue,
4438 /* isSticky */ false,
4439 /* isLocked */ false,
4440 /* fromInit */ false);
4441}
4442
4443/* static */ nsresult
4444Preferences::SetBool(const char* aPrefName, bool aValue, PrefValueKind aKind)
4445{
4446 ENSURE_PARENT_PROCESS("SetBool", aPrefName);
4447 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4448
4449 PrefValue prefValue;
4450 prefValue.mBoolVal = aValue;
4451 return pref_SetPref(aPrefName,
4452 PrefType::Bool,
4453 aKind,
4454 prefValue,
4455 /* isSticky */ false,
4456 /* isLocked */ false,
4457 /* fromInit */ false);
4458}
4459
4460/* static */ nsresult
4461Preferences::SetInt(const char* aPrefName, int32_t aValue, PrefValueKind aKind)
4462{
4463 ENSURE_PARENT_PROCESS("SetInt", aPrefName);
4464 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4465
4466 PrefValue prefValue;
4467 prefValue.mIntVal = aValue;
4468 return pref_SetPref(aPrefName,
4469 PrefType::Int,
4470 aKind,
4471 prefValue,
4472 /* isSticky */ false,
4473 /* isLocked */ false,
4474 /* fromInit */ false);
4475}
4476
4477/* static */ nsresult
4478Preferences::SetComplex(const char* aPrefName,
4479 const nsIID& aType,
4480 nsISupports* aValue,
4481 PrefValueKind aKind)
4482{
4483 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4484 return GetRootBranch(aKind)->SetComplexValue(aPrefName, aType, aValue);
4485}
4486
4487/* static */ nsresult
4488Preferences::Lock(const char* aPrefName)
4489{
4490 ENSURE_PARENT_PROCESS("Lock", aPrefName);
4491 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4492
4493 Pref* pref = pref_HashTableLookup(aPrefName);
4494 if (!pref) {
4495 return NS_ERROR_UNEXPECTED;
4496 }
4497
4498 if (!pref->IsLocked()) {
4499 pref->SetIsLocked(true);
4500 NotifyCallbacks(aPrefName);
4501 }
4502
4503 return NS_OK;
4504}
4505
4506/* static */ nsresult
4507Preferences::Unlock(const char* aPrefName)
4508{
4509 ENSURE_PARENT_PROCESS("Unlock", aPrefName);
4510 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4511
4512 Pref* pref = pref_HashTableLookup(aPrefName);
4513 if (!pref) {
4514 return NS_ERROR_UNEXPECTED;
4515 }
4516
4517 if (pref->IsLocked()) {
4518 pref->SetIsLocked(false);
4519 NotifyCallbacks(aPrefName);
4520 }
4521
4522 return NS_OK;
4523}
4524
4525/* static */ bool
4526Preferences::IsLocked(const char* aPrefName)
4527{
4528 NS_ENSURE_TRUE(InitStaticMembers(), false);
4529
4530 Pref* pref = pref_HashTableLookup(aPrefName);
4531 return pref && pref->IsLocked();
4532}
4533
4534/* static */ nsresult
4535Preferences::ClearUser(const char* aPrefName)
4536{
4537 ENSURE_PARENT_PROCESS("ClearUser", aPrefName);
4538 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4539
4540 PrefEntry* entry = pref_HashTableLookupInner(aPrefName);
4541 Pref* pref;
4542 if (entry && (pref = entry->mPref) && pref->HasUserValue()) {
4543 pref->ClearUserValue();
4544
4545 if (!pref->HasDefaultValue()) {
4546 gHashTable->RemoveEntry(entry);
4547 }
4548
4549 NotifyCallbacks(aPrefName);
4550 Preferences::HandleDirty();
4551 }
4552 return NS_OK;
4553}
4554
4555/* static */ bool
4556Preferences::HasUserValue(const char* aPrefName)
4557{
4558 NS_ENSURE_TRUE(InitStaticMembers(), false);
4559
4560 Pref* pref = pref_HashTableLookup(aPrefName);
4561 return pref && pref->HasUserValue();
4562}
4563
4564/* static */ int32_t
4565Preferences::GetType(const char* aPrefName)
4566{
4567 NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
4568
4569 Pref* pref;
4570 if (!gHashTable || !(pref = pref_HashTableLookup(aPrefName))) {
4571 return PREF_INVALID;
4572 }
4573
4574 switch (pref->Type()) {
4575 case PrefType::String:
4576 return PREF_STRING;
4577
4578 case PrefType::Int:
4579 return PREF_INT;
4580
4581 case PrefType::Bool:
4582 return PREF_BOOL;
4583
4584 default:
4585 MOZ_CRASH();
4586 }
4587}
4588
4589/* static */ nsresult
4590Preferences::AddStrongObserver(nsIObserver* aObserver, const nsACString& aPref)
4591{
4592 MOZ_ASSERT(aObserver);
4593 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4594 return sPreferences->mRootBranch->AddObserver(aPref, aObserver, false);
4595}
4596
4597/* static */ nsresult
4598Preferences::AddWeakObserver(nsIObserver* aObserver, const nsACString& aPref)
4599{
4600 MOZ_ASSERT(aObserver);
4601 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4602 return sPreferences->mRootBranch->AddObserver(aPref, aObserver, true);
4603}
4604
4605/* static */ nsresult
4606Preferences::RemoveObserver(nsIObserver* aObserver, const nsACString& aPref)
4607{
4608 MOZ_ASSERT(aObserver);
4609 if (sShutdown) {
4610 MOZ_ASSERT(!sPreferences);
4611 return NS_OK; // Observers have been released automatically.
4612 }
4613 NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
4614 return sPreferences->mRootBranch->RemoveObserver(aPref, aObserver);
4615}
4616
4617template<typename T>
4618static void
4619AssertNotMallocAllocated(T* aPtr)
4620{
4621#if defined(DEBUG) && defined(MOZ_MEMORY)
4622 jemalloc_ptr_info_t info;
4623 jemalloc_ptr_info((void*)aPtr, &info);
4624 MOZ_ASSERT(info.tag == TagUnknown);
4625#endif
4626}
4627
4628/* static */ nsresult
4629Preferences::AddStrongObservers(nsIObserver* aObserver, const char** aPrefs)
4630{
4631 MOZ_ASSERT(aObserver);
4632 for (uint32_t i = 0; aPrefs[i]; i++) {
4633 AssertNotMallocAllocated(aPrefs[i]);
4634
4635 nsCString pref;
4636 pref.AssignLiteral(aPrefs[i], strlen(aPrefs[i]));
4637 nsresult rv = AddStrongObserver(aObserver, pref);
4638 NS_ENSURE_SUCCESS(rv, rv);
4639 }
4640 return NS_OK;
4641}
4642
4643/* static */ nsresult
4644Preferences::AddWeakObservers(nsIObserver* aObserver, const char** aPrefs)
4645{
4646 MOZ_ASSERT(aObserver);
4647 for (uint32_t i = 0; aPrefs[i]; i++) {
4648 AssertNotMallocAllocated(aPrefs[i]);
4649
4650 nsCString pref;
4651 pref.AssignLiteral(aPrefs[i], strlen(aPrefs[i]));
4652 nsresult rv = AddWeakObserver(aObserver, pref);
4653 NS_ENSURE_SUCCESS(rv, rv);
4654 }
4655 return NS_OK;
4656}
4657
4658/* static */ nsresult
4659Preferences::RemoveObservers(nsIObserver* aObserver, const char** aPrefs)
4660{
4661 MOZ_ASSERT(aObserver);
4662 if (sShutdown) {
4663 MOZ_ASSERT(!sPreferences);
4664 return NS_OK; // Observers have been released automatically.
4665 }
4666 NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
4667
4668 for (uint32_t i = 0; aPrefs[i]; i++) {
4669 nsresult rv = RemoveObserver(aObserver, nsDependentCString(aPrefs[i]));
4670 NS_ENSURE_SUCCESS(rv, rv);
4671 }
4672 return NS_OK;
4673}
4674
4675/* static */ nsresult
4676Preferences::RegisterCallback(PrefChangedFunc aCallback,
4677 const nsACString& aPrefNode,
4678 void* aData,
4679 MatchKind aMatchKind,
4680 bool aIsPriority)
4681{
4682 NS_ENSURE_ARG(aCallback);
4683
4684 NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
4685
4686 auto node = new CallbackNode(aPrefNode, aCallback, aData, aMatchKind);
4687
4688 if (aIsPriority) {
4689 // Add to the start of the list.
4690 node->SetNext(gFirstCallback);
4691 gFirstCallback = node;
4692 if (!gLastPriorityNode) {
4693 gLastPriorityNode = node;
4694 }
4695 } else {
4696 // Add to the start of the non-priority part of the list.
4697 if (gLastPriorityNode) {
4698 node->SetNext(gLastPriorityNode->Next());
4699 gLastPriorityNode->SetNext(node);
4700 } else {
4701 node->SetNext(gFirstCallback);
4702 gFirstCallback = node;
4703 }
4704 }
4705
4706 return NS_OK;
4707}
4708
4709/* static */ nsresult
4710Preferences::RegisterCallbackAndCall(PrefChangedFunc aCallback,
4711 const nsACString& aPref,
4712 void* aClosure,
4713 MatchKind aMatchKind)
4714{
4715 MOZ_ASSERT(aCallback);
4716 nsresult rv = RegisterCallback(aCallback, aPref, aClosure, aMatchKind);
4717 if (NS_SUCCEEDED(rv)) {
4718 (*aCallback)(PromiseFlatCString(aPref).get(), aClosure);
4719 }
4720 return rv;
4721}
4722
4723/* static */ nsresult
4724Preferences::UnregisterCallback(PrefChangedFunc aCallback,
4725 const nsACString& aPrefNode,
4726 void* aData,
4727 MatchKind aMatchKind)
4728{
4729 MOZ_ASSERT(aCallback);
4730 if (sShutdown) {
4731 MOZ_ASSERT(!sPreferences);
4732 return NS_OK; // Observers have been released automatically.
4733 }
4734 NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
4735
4736 nsresult rv = NS_ERROR_FAILURE;
4737 CallbackNode* node = gFirstCallback;
4738 CallbackNode* prev_node = nullptr;
4739
4740 while (node) {
4741 if (node->Func() == aCallback && node->Data() == aData &&
4742 node->MatchKind() == aMatchKind && node->Domain() == aPrefNode) {
4743 if (gCallbacksInProgress) {
4744 // Postpone the node removal until after callbacks enumeration is
4745 // finished.
4746 node->ClearFunc();
4747 gShouldCleanupDeadNodes = true;
4748 prev_node = node;
4749 node = node->Next();
4750 } else {
4751 node = pref_RemoveCallbackNode(node, prev_node);
4752 }
4753 rv = NS_OK;
4754 } else {
4755 prev_node = node;
4756 node = node->Next();
4757 }
4758 }
4759 return rv;
4760}
4761
4762static void
4763CacheDataAppendElement(CacheData* aData)
4764{
4765 if (!gCacheData) {
4766 MOZ_CRASH_UNSAFE_PRINTF("!gCacheData: %s", gCacheDataDesc);
4767 }
4768 gCacheData->AppendElement(aData);
4769}
4770
4771static void
4772BoolVarChanged(const char* aPref, void* aClosure)
4773{
4774 CacheData* cache = static_cast<CacheData*>(aClosure);
4775 *static_cast<bool*>(cache->mCacheLocation) =
4776 Preferences::GetBool(aPref, cache->mDefaultValueBool);
4777}
4778
4779/* static */ nsresult
4780Preferences::AddBoolVarCache(bool* aCache,
4781 const nsACString& aPref,
4782 bool aDefault,
4783 bool aSkipAssignment)
4784{
4785 AssertNotAlreadyCached("bool", aPref, aCache);
4786 if (!aSkipAssignment) {
4787 *aCache = GetBool(PromiseFlatCString(aPref).get(), aDefault);
4788 }
4789 CacheData* data = new CacheData();
4790 data->mCacheLocation = aCache;
4791 data->mDefaultValueBool = aDefault;
4792 CacheDataAppendElement(data);
4793 Preferences::RegisterCallback(BoolVarChanged,
4794 aPref,
4795 data,
4796 Preferences::ExactMatch,
4797 /* isPriority */ true);
4798 return NS_OK;
4799}
4800
4801template<MemoryOrdering Order>
4802static void
4803AtomicBoolVarChanged(const char* aPref, void* aClosure)
4804{
4805 CacheData* cache = static_cast<CacheData*>(aClosure);
4806 *static_cast<Atomic<bool, Order>*>(cache->mCacheLocation) =
4807 Preferences::GetBool(aPref, cache->mDefaultValueBool);
4808}
4809
4810template<MemoryOrdering Order>
4811/* static */ nsresult
4812Preferences::AddAtomicBoolVarCache(Atomic<bool, Order>* aCache,
4813 const nsACString& aPref,
4814 bool aDefault,
4815 bool aSkipAssignment)
4816{
4817 AssertNotAlreadyCached("bool", aPref, aCache);
4818 if (!aSkipAssignment) {
4819 *aCache = GetBool(PromiseFlatCString(aPref).get(), aDefault);
4820 }
4821 CacheData* data = new CacheData();
4822 data->mCacheLocation = aCache;
4823 data->mDefaultValueBool = aDefault;
4824 CacheDataAppendElement(data);
4825 Preferences::RegisterCallback(AtomicBoolVarChanged<Order>,
4826 aPref,
4827 data,
4828 Preferences::ExactMatch,
4829 /* isPriority */ true);
4830 return NS_OK;
4831}
4832
4833static void
4834IntVarChanged(const char* aPref, void* aClosure)
4835{
4836 CacheData* cache = static_cast<CacheData*>(aClosure);
4837 *static_cast<int32_t*>(cache->mCacheLocation) =
4838 Preferences::GetInt(aPref, cache->mDefaultValueInt);
4839}
4840
4841/* static */ nsresult
4842Preferences::AddIntVarCache(int32_t* aCache,
4843 const nsACString& aPref,
4844 int32_t aDefault,
4845 bool aSkipAssignment)
4846{
4847 AssertNotAlreadyCached("int", aPref, aCache);
4848 if (!aSkipAssignment) {
4849 *aCache = GetInt(PromiseFlatCString(aPref).get(), aDefault);
4850 }
4851 CacheData* data = new CacheData();
4852 data->mCacheLocation = aCache;
4853 data->mDefaultValueInt = aDefault;
4854 CacheDataAppendElement(data);
4855 Preferences::RegisterCallback(
4856 IntVarChanged, aPref, data, Preferences::ExactMatch, /* isPriority */ true);
4857 return NS_OK;
4858}
4859
4860template<MemoryOrdering Order>
4861static void
4862AtomicIntVarChanged(const char* aPref, void* aClosure)
4863{
4864 CacheData* cache = static_cast<CacheData*>(aClosure);
4865 *static_cast<Atomic<int32_t, Order>*>(cache->mCacheLocation) =
4866 Preferences::GetInt(aPref, cache->mDefaultValueUint);
4867}
4868
4869template<MemoryOrdering Order>
4870/* static */ nsresult
4871Preferences::AddAtomicIntVarCache(Atomic<int32_t, Order>* aCache,
4872 const nsACString& aPref,
4873 int32_t aDefault,
4874 bool aSkipAssignment)
4875{
4876 AssertNotAlreadyCached("int", aPref, aCache);
4877 if (!aSkipAssignment) {
4878 *aCache = GetInt(PromiseFlatCString(aPref).get(), aDefault);
4879 }
4880 CacheData* data = new CacheData();
4881 data->mCacheLocation = aCache;
4882 data->mDefaultValueUint = aDefault;
4883 CacheDataAppendElement(data);
4884 Preferences::RegisterCallback(AtomicIntVarChanged<Order>,
4885 aPref,
4886 data,
4887 Preferences::ExactMatch,
4888 /* isPriority */ true);
4889 return NS_OK;
4890}
4891
4892static void
4893UintVarChanged(const char* aPref, void* aClosure)
4894{
4895 CacheData* cache = static_cast<CacheData*>(aClosure);
4896 *static_cast<uint32_t*>(cache->mCacheLocation) =
4897 Preferences::GetUint(aPref, cache->mDefaultValueUint);
4898}
4899
4900/* static */ nsresult
4901Preferences::AddUintVarCache(uint32_t* aCache,
4902 const nsACString& aPref,
4903 uint32_t aDefault,
4904 bool aSkipAssignment)
4905{
4906 AssertNotAlreadyCached("uint", aPref, aCache);
4907 if (!aSkipAssignment) {
4908 *aCache = GetUint(PromiseFlatCString(aPref).get(), aDefault);
4909 }
4910 CacheData* data = new CacheData();
4911 data->mCacheLocation = aCache;
4912 data->mDefaultValueUint = aDefault;
4913 CacheDataAppendElement(data);
4914 Preferences::RegisterCallback(UintVarChanged,
4915 aPref,
4916 data,
4917 Preferences::ExactMatch,
4918 /* isPriority */ true);
4919 return NS_OK;
4920}
4921
4922template<MemoryOrdering Order>
4923static void
4924AtomicUintVarChanged(const char* aPref, void* aClosure)
4925{
4926 CacheData* cache = static_cast<CacheData*>(aClosure);
4927 *static_cast<Atomic<uint32_t, Order>*>(cache->mCacheLocation) =
4928 Preferences::GetUint(aPref, cache->mDefaultValueUint);
4929}
4930
4931template<MemoryOrdering Order>
4932/* static */ nsresult
4933Preferences::AddAtomicUintVarCache(Atomic<uint32_t, Order>* aCache,
4934 const nsACString& aPref,
4935 uint32_t aDefault,
4936 bool aSkipAssignment)
4937{
4938 AssertNotAlreadyCached("uint", aPref, aCache);
4939 if (!aSkipAssignment) {
4940 *aCache = GetUint(PromiseFlatCString(aPref).get(), aDefault);
4941 }
4942 CacheData* data = new CacheData();
4943 data->mCacheLocation = aCache;
4944 data->mDefaultValueUint = aDefault;
4945 CacheDataAppendElement(data);
4946 Preferences::RegisterCallback(AtomicUintVarChanged<Order>,
4947 aPref,
4948 data,
4949 Preferences::ExactMatch,
4950 /* isPriority */ true);
4951 return NS_OK;
4952}
4953
4954// Since the definition of template functions is not in a header file, we
4955// need to explicitly specify the instantiations that are required. Currently
4956// limited orders are needed and therefore implemented.
4957template nsresult
4958Preferences::AddAtomicBoolVarCache(Atomic<bool, Relaxed>*,
4959 const nsACString&,
4960 bool,
4961 bool);
4962
4963template nsresult
4964Preferences::AddAtomicBoolVarCache(Atomic<bool, ReleaseAcquire>*,
4965 const nsACString&,
4966 bool,
4967 bool);
4968
4969template nsresult
4970Preferences::AddAtomicBoolVarCache(Atomic<bool, SequentiallyConsistent>*,
4971 const nsACString&,
4972 bool,
4973 bool);
4974
4975template nsresult
4976Preferences::AddAtomicIntVarCache(Atomic<int32_t, Relaxed>*,
4977 const nsACString&,
4978 int32_t,
4979 bool);
4980
4981template nsresult
4982Preferences::AddAtomicUintVarCache(Atomic<uint32_t, Relaxed>*,
4983 const nsACString&,
4984 uint32_t,
4985 bool);
4986
4987template nsresult
4988Preferences::AddAtomicUintVarCache(Atomic<uint32_t, ReleaseAcquire>*,
4989 const nsACString&,
4990 uint32_t,
4991 bool);
4992
4993template nsresult
4994Preferences::AddAtomicUintVarCache(Atomic<uint32_t, SequentiallyConsistent>*,
4995 const nsACString&,
4996 uint32_t,
4997 bool);
4998
4999static void
5000FloatVarChanged(const char* aPref, void* aClosure)
5001{
5002 CacheData* cache = static_cast<CacheData*>(aClosure);
5003 *static_cast<float*>(cache->mCacheLocation) =
5004 Preferences::GetFloat(aPref, cache->mDefaultValueFloat);
5005}
5006
5007/* static */ nsresult
5008Preferences::AddFloatVarCache(float* aCache,
5009 const nsACString& aPref,
5010 float aDefault,
5011 bool aSkipAssignment)
5012{
5013 AssertNotAlreadyCached("float", aPref, aCache);
5014 if (!aSkipAssignment) {
5015 *aCache = GetFloat(PromiseFlatCString(aPref).get(), aDefault);
5016 }
5017 CacheData* data = new CacheData();
5018 data->mCacheLocation = aCache;
5019 data->mDefaultValueFloat = aDefault;
5020 CacheDataAppendElement(data);
5021 Preferences::RegisterCallback(FloatVarChanged,
5022 aPref,
5023 data,
5024 Preferences::ExactMatch,
5025 /* isPriority */ true);
5026 return NS_OK;
5027}
5028
5029// For a VarCache pref like this:
5030//
5031// VARCACHE_PREF("my.varcache", my_varcache, int32_t, 99)
5032//
5033// we generate a static variable definition:
5034//
5035// int32_t StaticPrefs::sVarCache_my_varcache(99);
5036//
5037#define PREF(name, cpp_type, value)
5038#define VARCACHE_PREF(name, id, cpp_type, value) \
5039 cpp_type StaticPrefs::sVarCache_##id(value);
5040#include "mozilla/StaticPrefList.h"
5041#undef PREF
5042#undef VARCACHE_PREF
5043
5044// The SetPref_*() functions below end in a `_<type>` suffix because they are
5045// used by the PREF macro definition in InitAll() below.
5046
5047static void
5048SetPref_bool(const char* aName, bool aDefaultValue)
5049{
5050 PrefValue value;
5051 value.mBoolVal = aDefaultValue;
5052 pref_SetPref(aName,
5053 PrefType::Bool,
5054 PrefValueKind::Default,
5055 value,
5056 /* isSticky */ false,
5057 /* isLocked */ false,
5058 /* fromInit */ true);
5059}
5060
5061static void
5062SetPref_int32_t(const char* aName, int32_t aDefaultValue)
5063{
5064 PrefValue value;
5065 value.mIntVal = aDefaultValue;
5066 pref_SetPref(aName,
5067 PrefType::Int,
5068 PrefValueKind::Default,
5069 value,
5070 /* isSticky */ false,
5071 /* isLocked */ false,
5072 /* fromInit */ true);
5073}
5074
5075static void
5076SetPref_float(const char* aName, float aDefaultValue)
5077{
5078 PrefValue value;
5079 nsPrintfCString defaultValue("%f", aDefaultValue);
5080 value.mStringVal = defaultValue.get();
5081 pref_SetPref(aName,
5082 PrefType::String,
5083 PrefValueKind::Default,
5084 value,
5085 /* isSticky */ false,
5086 /* isLocked */ false,
5087 /* fromInit */ true);
5088}
5089
5090// XXX: this will eventually become used
5091MOZ_MAYBE_UNUSED static void
5092SetPref_String(const char* aName, const char* aDefaultValue)
5093{
5094 PrefValue value;
5095 value.mStringVal = aDefaultValue;
5096 pref_SetPref(aName,
5097 PrefType::String,
5098 PrefValueKind::Default,
5099 value,
5100 /* isSticky */ false,
5101 /* isLocked */ false,
5102 /* fromInit */ true);
5103}
5104
5105static void
5106InitVarCachePref(const nsACString& aName,
5107 bool* aCache,
5108 bool aDefaultValue,
5109 bool aIsStartup)
5110{
5111 SetPref_bool(PromiseFlatCString(aName).get(), aDefaultValue);
5112 *aCache = aDefaultValue;
5113 if (aIsStartup) {
5114 Preferences::AddBoolVarCache(aCache, aName, aDefaultValue, true);
5115 }
5116}
5117
5118template<MemoryOrdering Order>
5119static void
5120InitVarCachePref(const nsACString& aName,
5121 Atomic<bool, Order>* aCache,
5122 bool aDefaultValue,
5123 bool aIsStartup)
5124{
5125 SetPref_bool(PromiseFlatCString(aName).get(), aDefaultValue);
5126 *aCache = aDefaultValue;
5127 if (aIsStartup) {
5128 Preferences::AddAtomicBoolVarCache(aCache, aName, aDefaultValue, true);
5129 }
5130}
5131
5132// XXX: this will eventually become used
5133MOZ_MAYBE_UNUSED static void
5134InitVarCachePref(const nsACString& aName,
5135 int32_t* aCache,
5136 int32_t aDefaultValue,
5137 bool aIsStartup)
5138{
5139 SetPref_int32_t(PromiseFlatCString(aName).get(), aDefaultValue);
5140 *aCache = aDefaultValue;
5141 if (aIsStartup) {
5142 Preferences::AddIntVarCache(aCache, aName, aDefaultValue, true);
5143 }
5144}
5145
5146template<MemoryOrdering Order>
5147static void
5148InitVarCachePref(const nsACString& aName,
5149 Atomic<int32_t, Order>* aCache,
5150 int32_t aDefaultValue,
5151 bool aIsStartup)
5152{
5153 SetPref_int32_t(PromiseFlatCString(aName).get(), aDefaultValue);
5154 *aCache = aDefaultValue;
5155 if (aIsStartup) {
5156 Preferences::AddAtomicIntVarCache(aCache, aName, aDefaultValue, true);
5157 }
5158}
5159
5160static void
5161InitVarCachePref(const nsACString& aName,
5162 uint32_t* aCache,
5163 uint32_t aDefaultValue,
5164 bool aIsStartup)
5165{
5166 SetPref_int32_t(PromiseFlatCString(aName).get(),
5167 static_cast<int32_t>(aDefaultValue));
5168 *aCache = aDefaultValue;
5169 if (aIsStartup) {
5170 Preferences::AddUintVarCache(aCache, aName, aDefaultValue, true);
5171 }
5172}
5173
5174template<MemoryOrdering Order>
5175static void
5176InitVarCachePref(const nsACString& aName,
5177 Atomic<uint32_t, Order>* aCache,
5178 uint32_t aDefaultValue,
5179 bool aIsStartup)
5180{
5181 SetPref_int32_t(PromiseFlatCString(aName).get(),
5182 static_cast<int32_t>(aDefaultValue));
5183 *aCache = aDefaultValue;
5184 if (aIsStartup) {
5185 Preferences::AddAtomicUintVarCache(aCache, aName, aDefaultValue, true);
5186 }
5187}
5188
5189// XXX: this will eventually become used
5190MOZ_MAYBE_UNUSED static void
5191InitVarCachePref(const nsACString& aName,
5192 float* aCache,
5193 float aDefaultValue,
5194 bool aIsStartup)
5195{
5196 SetPref_float(PromiseFlatCString(aName).get(), aDefaultValue);
5197 *aCache = aDefaultValue;
5198 if (aIsStartup) {
5199 Preferences::AddFloatVarCache(aCache, aName, aDefaultValue, true);
5200 }
5201}
5202
5203/* static */ void
5204StaticPrefs::InitAll(bool aIsStartup)
5205{
5206// For prefs like these:
5207//
5208// PREF("foo.bar.baz", bool, true)
5209// VARCACHE_PREF("my.varcache", my_varcache, int32_t, 99)
5210//
5211// we generate registration calls:
5212//
5213// SetPref_bool("foo.bar.baz", true);
5214// InitVarCachePref("my.varcache", &StaticPrefs::sVarCache_my_varcache, 99,
5215// aIsStartup);
5216//
5217// The SetPref_*() functions have a type suffix to avoid ambiguity between
5218// prefs having int32_t and float default values. That suffix is not needed for
5219// the InitVarCachePref() functions because they take a pointer parameter,
5220// which prevents automatic int-to-float coercion.
5221#define PREF(name, cpp_type, value) SetPref_##cpp_type(name, value);
5222#define VARCACHE_PREF(name, id, cpp_type, value) \
5223 InitVarCachePref(NS_LITERAL_CSTRING(name), \
5224 &StaticPrefs::sVarCache_##id, \
5225 value, \
5226 aIsStartup);
5227#include "mozilla/StaticPrefList.h"
5228#undef PREF
5229#undef VARCACHE_PREF
5230}
5231
5232} // namespace mozilla
5233
5234#undef ENSURE_PARENT_PROCESS
5235
5236//===========================================================================
5237// Module and factory stuff
5238//===========================================================================
5239
5240NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(Preferences,
5241 Preferences::GetInstanceForService)
5242NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrefLocalizedString, Init)
5243NS_GENERIC_FACTORY_CONSTRUCTOR(nsRelativeFilePref)
5244
5245static NS_DEFINE_CID(kPrefServiceCID, NS_PREFSERVICE_CID);
5246static NS_DEFINE_CID(kPrefLocalizedStringCID, NS_PREFLOCALIZEDSTRING_CID);
5247static NS_DEFINE_CID(kRelativeFilePrefCID, NS_RELATIVEFILEPREF_CID);
5248
5249static mozilla::Module::CIDEntry kPrefCIDs[] = {
5250 { &kPrefServiceCID, true, nullptr, PreferencesConstructor },
5251 { &kPrefLocalizedStringCID,
5252 false,
5253 nullptr,
5254 nsPrefLocalizedStringConstructor },
5255 { &kRelativeFilePrefCID, false, nullptr, nsRelativeFilePrefConstructor },
5256 { nullptr }
5257};
5258
5259static mozilla::Module::ContractIDEntry kPrefContracts[] = {
5260 { NS_PREFSERVICE_CONTRACTID, &kPrefServiceCID },
5261 { NS_PREFLOCALIZEDSTRING_CONTRACTID, &kPrefLocalizedStringCID },
5262 { NS_RELATIVEFILEPREF_CONTRACTID, &kRelativeFilePrefCID },
5263 { nullptr }
5264};
5265
5266static void
5267UnloadPrefsModule()
5268{
5269 Preferences::Shutdown();
5270}
5271
5272static const mozilla::Module kPrefModule = { mozilla::Module::kVersion,
5273 kPrefCIDs,
5274 kPrefContracts,
5275 nullptr,
5276 nullptr,
5277 nullptr,
5278 UnloadPrefsModule };
5279
5280NSMODULE_DEFN(nsPrefModule) = &kPrefModule;