Source code

Revision control

Other Tools

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 "SharedPrefMap.h"
12
13
#include "base/basictypes.h"
14
#include "GeckoProfiler.h"
15
#include "MainThreadUtils.h"
16
#include "mozilla/ArenaAllocatorExtensions.h"
17
#include "mozilla/ArenaAllocator.h"
18
#include "mozilla/ArrayUtils.h"
19
#include "mozilla/Attributes.h"
20
#include "mozilla/Components.h"
21
#include "mozilla/dom/PContent.h"
22
#include "mozilla/HashFunctions.h"
23
#include "mozilla/HashTable.h"
24
#include "mozilla/Logging.h"
25
#include "mozilla/Maybe.h"
26
#include "mozilla/MemoryReporting.h"
27
#include "mozilla/Omnijar.h"
28
#include "mozilla/Preferences.h"
29
#include "mozilla/ResultExtensions.h"
30
#include "mozilla/ScopeExit.h"
31
#include "mozilla/Services.h"
32
#include "mozilla/ServoStyleSet.h"
33
#include "mozilla/StaticMutex.h"
34
#include "mozilla/StaticPrefs.h"
35
#include "mozilla/StaticPrefs_accessibility.h"
36
#include "mozilla/SyncRunnable.h"
37
#include "mozilla/SystemGroup.h"
38
#include "mozilla/Telemetry.h"
39
#include "mozilla/UniquePtrExtensions.h"
40
#include "mozilla/URLPreloader.h"
41
#include "mozilla/Variant.h"
42
#include "mozilla/Vector.h"
43
#include "nsAppDirectoryServiceDefs.h"
44
#include "nsAutoPtr.h"
45
#include "nsCategoryManagerUtils.h"
46
#include "nsClassHashtable.h"
47
#include "nsCOMArray.h"
48
#include "nsCOMPtr.h"
49
#include "nsCRT.h"
50
#include "nsDataHashtable.h"
51
#include "nsDirectoryServiceDefs.h"
52
#include "nsHashKeys.h"
53
#include "nsICategoryManager.h"
54
#include "nsIConsoleService.h"
55
#include "nsIDirectoryService.h"
56
#include "nsIFile.h"
57
#include "nsIInputStream.h"
58
#include "nsIMemoryReporter.h"
59
#include "nsIObserver.h"
60
#include "nsIObserverService.h"
61
#include "nsIOutputStream.h"
62
#include "nsIPrefBranch.h"
63
#include "nsIPrefLocalizedString.h"
64
#include "nsIRelativeFilePref.h"
65
#include "nsISafeOutputStream.h"
66
#include "nsISimpleEnumerator.h"
67
#include "nsIStringBundle.h"
68
#include "nsIStringEnumerator.h"
69
#include "nsISupportsImpl.h"
70
#include "nsISupportsPrimitives.h"
71
#include "nsIZipReader.h"
72
#include "nsNetUtil.h"
73
#include "nsPrintfCString.h"
74
#include "nsQuickSort.h"
75
#include "nsReadableUtils.h"
76
#include "nsRefPtrHashtable.h"
77
#include "nsRelativeFilePref.h"
78
#include "nsString.h"
79
#include "nsTArray.h"
80
#include "nsThreadUtils.h"
81
#include "nsUTF8Utils.h"
82
#include "nsWeakReference.h"
83
#include "nsXPCOMCID.h"
84
#include "nsXPCOM.h"
85
#include "nsXULAppAPI.h"
86
#include "nsZipArchive.h"
87
#include "plbase64.h"
88
#include "PLDHashTable.h"
89
#include "plstr.h"
90
#include "prlink.h"
91
#include "xpcpublic.h"
92
93
#ifdef DEBUG
94
# include <map>
95
#endif
96
97
#ifdef MOZ_MEMORY
98
# include "mozmemory.h"
99
#endif
100
101
#ifdef XP_WIN
102
# include "windows.h"
103
#endif
104
105
using namespace mozilla;
106
107
using ipc::FileDescriptor;
108
109
#ifdef DEBUG
110
111
# define ENSURE_PARENT_PROCESS(func, pref) \
112
do { \
113
if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
114
nsPrintfCString msg( \
115
"ENSURE_PARENT_PROCESS: called %s on %s in a non-parent process", \
116
func, pref); \
117
NS_ERROR(msg.get()); \
118
return NS_ERROR_NOT_AVAILABLE; \
119
} \
120
} while (0)
121
122
#else // DEBUG
123
124
# define ENSURE_PARENT_PROCESS(func, pref) \
125
if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
126
return NS_ERROR_NOT_AVAILABLE; \
127
}
128
129
#endif // DEBUG
130
131
//===========================================================================
132
// Low-level types and operations
133
//===========================================================================
134
135
typedef nsTArray<nsCString> PrefSaveData;
136
137
// 1 MB should be enough for everyone.
138
static const uint32_t MAX_PREF_LENGTH = 1 * 1024 * 1024;
139
// Actually, 4kb should be enough for everyone.
140
static const uint32_t MAX_ADVISABLE_PREF_LENGTH = 4 * 1024;
141
142
// This is used for pref names and string pref values. We encode the string
143
// length, then a '/', then the string chars. This encoding means there are no
144
// special chars that are forbidden or require escaping.
145
static void SerializeAndAppendString(const char* aChars, nsCString& aStr) {
146
aStr.AppendInt(uint32_t(strlen(aChars)));
147
aStr.Append('/');
148
aStr.Append(aChars);
149
}
150
151
static char* DeserializeString(char* aChars, nsCString& aStr) {
152
char* p = aChars;
153
uint32_t length = strtol(p, &p, 10);
154
MOZ_ASSERT(p[0] == '/');
155
p++; // move past the '/'
156
aStr.Assign(p, length);
157
p += length; // move past the string itself
158
return p;
159
}
160
161
// Keep this in sync with PrefValue in prefs_parser/src/lib.rs.
162
union PrefValue {
163
const char* mStringVal;
164
int32_t mIntVal;
165
bool mBoolVal;
166
167
PrefValue() = default;
168
169
explicit PrefValue(bool aVal) : mBoolVal(aVal) {}
170
171
explicit PrefValue(int32_t aVal) : mIntVal(aVal) {}
172
173
explicit PrefValue(const char* aVal) : mStringVal(aVal) {}
174
175
bool Equals(PrefType aType, PrefValue aValue) {
176
switch (aType) {
177
case PrefType::String: {
178
if (mStringVal && aValue.mStringVal) {
179
return strcmp(mStringVal, aValue.mStringVal) == 0;
180
}
181
if (!mStringVal && !aValue.mStringVal) {
182
return true;
183
}
184
return false;
185
}
186
187
case PrefType::Int:
188
return mIntVal == aValue.mIntVal;
189
190
case PrefType::Bool:
191
return mBoolVal == aValue.mBoolVal;
192
193
default:
194
MOZ_CRASH("Unhandled enum value");
195
}
196
}
197
198
template <typename T>
199
T Get() const;
200
201
void Init(PrefType aNewType, PrefValue aNewValue) {
202
if (aNewType == PrefType::String) {
203
MOZ_ASSERT(aNewValue.mStringVal);
204
aNewValue.mStringVal = moz_xstrdup(aNewValue.mStringVal);
205
}
206
*this = aNewValue;
207
}
208
209
void Clear(PrefType aType) {
210
if (aType == PrefType::String) {
211
free(const_cast<char*>(mStringVal));
212
}
213
214
// Zero the entire value (regardless of type) via mStringVal.
215
mStringVal = nullptr;
216
}
217
218
void Replace(bool aHasValue, PrefType aOldType, PrefType aNewType,
219
PrefValue aNewValue) {
220
if (aHasValue) {
221
Clear(aOldType);
222
}
223
Init(aNewType, aNewValue);
224
}
225
226
void ToDomPrefValue(PrefType aType, dom::PrefValue* aDomValue) {
227
switch (aType) {
228
case PrefType::String:
229
*aDomValue = nsDependentCString(mStringVal);
230
return;
231
232
case PrefType::Int:
233
*aDomValue = mIntVal;
234
return;
235
236
case PrefType::Bool:
237
*aDomValue = mBoolVal;
238
return;
239
240
default:
241
MOZ_CRASH();
242
}
243
}
244
245
PrefType FromDomPrefValue(const dom::PrefValue& aDomValue) {
246
switch (aDomValue.type()) {
247
case dom::PrefValue::TnsCString:
248
mStringVal = aDomValue.get_nsCString().get();
249
return PrefType::String;
250
251
case dom::PrefValue::Tint32_t:
252
mIntVal = aDomValue.get_int32_t();
253
return PrefType::Int;
254
255
case dom::PrefValue::Tbool:
256
mBoolVal = aDomValue.get_bool();
257
return PrefType::Bool;
258
259
default:
260
MOZ_CRASH();
261
}
262
}
263
264
void SerializeAndAppend(PrefType aType, nsCString& aStr) {
265
switch (aType) {
266
case PrefType::Bool:
267
aStr.Append(mBoolVal ? 'T' : 'F');
268
break;
269
270
case PrefType::Int:
271
aStr.AppendInt(mIntVal);
272
break;
273
274
case PrefType::String: {
275
SerializeAndAppendString(mStringVal, aStr);
276
break;
277
}
278
279
case PrefType::None:
280
default:
281
MOZ_CRASH();
282
}
283
}
284
285
static char* Deserialize(PrefType aType, char* aStr,
286
Maybe<dom::PrefValue>* aDomValue) {
287
char* p = aStr;
288
289
switch (aType) {
290
case PrefType::Bool:
291
if (*p == 'T') {
292
*aDomValue = Some(true);
293
} else if (*p == 'F') {
294
*aDomValue = Some(false);
295
} else {
296
*aDomValue = Some(false);
297
NS_ERROR("bad bool pref value");
298
}
299
p++;
300
return p;
301
302
case PrefType::Int: {
303
*aDomValue = Some(int32_t(strtol(p, &p, 10)));
304
return p;
305
}
306
307
case PrefType::String: {
308
nsCString str;
309
p = DeserializeString(p, str);
310
*aDomValue = Some(str);
311
return p;
312
}
313
314
default:
315
MOZ_CRASH();
316
}
317
}
318
};
319
320
template <>
321
bool PrefValue::Get() const {
322
return mBoolVal;
323
}
324
325
template <>
326
int32_t PrefValue::Get() const {
327
return mIntVal;
328
}
329
330
template <>
331
nsDependentCString PrefValue::Get() const {
332
return nsDependentCString(mStringVal);
333
}
334
335
#ifdef DEBUG
336
const char* PrefTypeToString(PrefType aType) {
337
switch (aType) {
338
case PrefType::None:
339
return "none";
340
case PrefType::String:
341
return "string";
342
case PrefType::Int:
343
return "int";
344
case PrefType::Bool:
345
return "bool";
346
default:
347
MOZ_CRASH("Unhandled enum value");
348
}
349
}
350
#endif
351
352
// Assign to aResult a quoted, escaped copy of aOriginal.
353
static void StrEscape(const char* aOriginal, nsCString& aResult) {
354
if (aOriginal == nullptr) {
355
aResult.AssignLiteral("\"\"");
356
return;
357
}
358
359
// JavaScript does not allow quotes, slashes, or line terminators inside
360
// strings so we must escape them. ECMAScript defines four line terminators,
361
// but we're only worrying about \r and \n here. We currently feed our pref
362
// script to the JS interpreter as Latin-1 so we won't encounter \u2028
363
// (line separator) or \u2029 (paragraph separator).
364
//
365
// WARNING: There are hints that we may be moving to storing prefs as utf8.
366
// If we ever feed them to the JS compiler as UTF8 then we'll have to worry
367
// about the multibyte sequences that would be interpreted as \u2028 and
368
// \u2029.
369
const char* p;
370
371
aResult.Assign('"');
372
373
// Paranoid worst case all slashes will free quickly.
374
for (p = aOriginal; *p; ++p) {
375
switch (*p) {
376
case '\n':
377
aResult.AppendLiteral("\\n");
378
break;
379
380
case '\r':
381
aResult.AppendLiteral("\\r");
382
break;
383
384
case '\\':
385
aResult.AppendLiteral("\\\\");
386
break;
387
388
case '\"':
389
aResult.AppendLiteral("\\\"");
390
break;
391
392
default:
393
aResult.Append(*p);
394
break;
395
}
396
}
397
398
aResult.Append('"');
399
}
400
401
namespace mozilla {
402
struct PrefsSizes {
403
PrefsSizes()
404
: mHashTable(0),
405
mPrefValues(0),
406
mStringValues(0),
407
mCacheData(0),
408
mRootBranches(0),
409
mPrefNameArena(0),
410
mCallbacksObjects(0),
411
mCallbacksDomains(0),
412
mMisc(0) {}
413
414
size_t mHashTable;
415
size_t mPrefValues;
416
size_t mStringValues;
417
size_t mCacheData;
418
size_t mRootBranches;
419
size_t mPrefNameArena;
420
size_t mCallbacksObjects;
421
size_t mCallbacksDomains;
422
size_t mMisc;
423
};
424
} // namespace mozilla
425
426
static StaticRefPtr<SharedPrefMap> gSharedMap;
427
428
static ArenaAllocator<4096, 1> gPrefNameArena;
429
430
class PrefWrapper;
431
432
class Pref {
433
public:
434
explicit Pref(const char* aName)
435
: mName(ArenaStrdup(aName, gPrefNameArena)),
436
mType(static_cast<uint32_t>(PrefType::None)),
437
mIsSticky(false),
438
mIsLocked(false),
439
mDefaultChanged(false),
440
mHasDefaultValue(false),
441
mHasUserValue(false),
442
mIsSkippedByIteration(false),
443
mDefaultValue(),
444
mUserValue() {}
445
446
~Pref() {
447
// There's no need to free mName because it's allocated in memory owned by
448
// gPrefNameArena.
449
450
mDefaultValue.Clear(Type());
451
mUserValue.Clear(Type());
452
}
453
454
const char* Name() const { return mName; }
455
nsDependentCString NameString() const { return nsDependentCString(mName); }
456
457
// Types.
458
459
PrefType Type() const { return static_cast<PrefType>(mType); }
460
void SetType(PrefType aType) { mType = static_cast<uint32_t>(aType); }
461
462
bool IsType(PrefType aType) const { return Type() == aType; }
463
bool IsTypeNone() const { return IsType(PrefType::None); }
464
bool IsTypeString() const { return IsType(PrefType::String); }
465
bool IsTypeInt() const { return IsType(PrefType::Int); }
466
bool IsTypeBool() const { return IsType(PrefType::Bool); }
467
468
// Other properties.
469
470
bool IsLocked() const { return mIsLocked; }
471
void SetIsLocked(bool aValue) { mIsLocked = aValue; }
472
bool IsSkippedByIteration() const { return mIsSkippedByIteration; }
473
void SetIsSkippedByIteration(bool aValue) { mIsSkippedByIteration = aValue; }
474
475
bool DefaultChanged() const { return mDefaultChanged; }
476
477
bool IsSticky() const { return mIsSticky; }
478
479
bool HasDefaultValue() const { return mHasDefaultValue; }
480
bool HasUserValue() const { return mHasUserValue; }
481
482
template <typename T>
483
void AddToMap(SharedPrefMapBuilder& aMap) {
484
aMap.Add(Name(),
485
{HasDefaultValue(), HasUserValue(), IsSticky(), IsLocked(),
486
DefaultChanged(), IsSkippedByIteration()},
487
HasDefaultValue() ? mDefaultValue.Get<T>() : T(),
488
HasUserValue() ? mUserValue.Get<T>() : T());
489
}
490
491
void AddToMap(SharedPrefMapBuilder& aMap) {
492
if (IsTypeBool()) {
493
AddToMap<bool>(aMap);
494
} else if (IsTypeInt()) {
495
AddToMap<int32_t>(aMap);
496
} else if (IsTypeString()) {
497
AddToMap<nsDependentCString>(aMap);
498
} else {
499
MOZ_ASSERT_UNREACHABLE("Unexpected preference type");
500
}
501
}
502
503
// Other operations.
504
505
bool GetBoolValue(PrefValueKind aKind = PrefValueKind::User) const {
506
MOZ_ASSERT(IsTypeBool());
507
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
508
: HasUserValue());
509
510
return aKind == PrefValueKind::Default ? mDefaultValue.mBoolVal
511
: mUserValue.mBoolVal;
512
}
513
514
int32_t GetIntValue(PrefValueKind aKind = PrefValueKind::User) const {
515
MOZ_ASSERT(IsTypeInt());
516
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
517
: HasUserValue());
518
519
return aKind == PrefValueKind::Default ? mDefaultValue.mIntVal
520
: mUserValue.mIntVal;
521
}
522
523
const char* GetBareStringValue(
524
PrefValueKind aKind = PrefValueKind::User) const {
525
MOZ_ASSERT(IsTypeString());
526
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
527
: HasUserValue());
528
529
return aKind == PrefValueKind::Default ? mDefaultValue.mStringVal
530
: mUserValue.mStringVal;
531
}
532
533
nsDependentCString GetStringValue(
534
PrefValueKind aKind = PrefValueKind::User) const {
535
return nsDependentCString(GetBareStringValue(aKind));
536
}
537
538
void ToDomPref(dom::Pref* aDomPref) {
539
MOZ_ASSERT(XRE_IsParentProcess());
540
541
aDomPref->name() = mName;
542
543
aDomPref->isLocked() = mIsLocked;
544
545
if (mHasDefaultValue) {
546
aDomPref->defaultValue() = Some(dom::PrefValue());
547
mDefaultValue.ToDomPrefValue(Type(), &aDomPref->defaultValue().ref());
548
} else {
549
aDomPref->defaultValue() = Nothing();
550
}
551
552
if (mHasUserValue) {
553
aDomPref->userValue() = Some(dom::PrefValue());
554
mUserValue.ToDomPrefValue(Type(), &aDomPref->userValue().ref());
555
} else {
556
aDomPref->userValue() = Nothing();
557
}
558
559
MOZ_ASSERT(aDomPref->defaultValue().isNothing() ||
560
aDomPref->userValue().isNothing() ||
561
(aDomPref->defaultValue().ref().type() ==
562
aDomPref->userValue().ref().type()));
563
}
564
565
void FromDomPref(const dom::Pref& aDomPref, bool* aValueChanged) {
566
MOZ_ASSERT(!XRE_IsParentProcess());
567
MOZ_ASSERT(strcmp(mName, aDomPref.name().get()) == 0);
568
569
mIsLocked = aDomPref.isLocked();
570
571
const Maybe<dom::PrefValue>& defaultValue = aDomPref.defaultValue();
572
bool defaultValueChanged = false;
573
if (defaultValue.isSome()) {
574
PrefValue value;
575
PrefType type = value.FromDomPrefValue(defaultValue.ref());
576
if (!ValueMatches(PrefValueKind::Default, type, value)) {
577
// Type() is PrefType::None if it's a newly added pref. This is ok.
578
mDefaultValue.Replace(mHasDefaultValue, Type(), type, value);
579
SetType(type);
580
mHasDefaultValue = true;
581
defaultValueChanged = true;
582
}
583
}
584
// Note: we never clear a default value.
585
586
const Maybe<dom::PrefValue>& userValue = aDomPref.userValue();
587
bool userValueChanged = false;
588
if (userValue.isSome()) {
589
PrefValue value;
590
PrefType type = value.FromDomPrefValue(userValue.ref());
591
if (!ValueMatches(PrefValueKind::User, type, value)) {
592
// Type() is PrefType::None if it's a newly added pref. This is ok.
593
mUserValue.Replace(mHasUserValue, Type(), type, value);
594
SetType(type);
595
mHasUserValue = true;
596
userValueChanged = true;
597
}
598
} else if (mHasUserValue) {
599
ClearUserValue();
600
userValueChanged = true;
601
}
602
603
if (userValueChanged || (defaultValueChanged && !mHasUserValue)) {
604
*aValueChanged = true;
605
}
606
}
607
608
void FromWrapper(PrefWrapper& aWrapper);
609
610
bool HasAdvisablySizedValues() {
611
MOZ_ASSERT(XRE_IsParentProcess());
612
613
if (!IsTypeString()) {
614
return true;
615
}
616
617
const char* stringVal;
618
if (mHasDefaultValue) {
619
stringVal = mDefaultValue.mStringVal;
620
if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
621
return false;
622
}
623
}
624
625
if (mHasUserValue) {
626
stringVal = mUserValue.mStringVal;
627
if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
628
return false;
629
}
630
}
631
632
return true;
633
}
634
635
private:
636
bool ValueMatches(PrefValueKind aKind, PrefType aType, PrefValue aValue) {
637
return IsType(aType) &&
638
(aKind == PrefValueKind::Default
639
? mHasDefaultValue && mDefaultValue.Equals(aType, aValue)
640
: mHasUserValue && mUserValue.Equals(aType, aValue));
641
}
642
643
public:
644
void ClearUserValue() {
645
mUserValue.Clear(Type());
646
mHasUserValue = false;
647
}
648
649
nsresult SetDefaultValue(PrefType aType, PrefValue aValue, bool aIsSticky,
650
bool aIsLocked, bool* aValueChanged) {
651
// Types must always match when setting the default value.
652
if (!IsType(aType)) {
653
return NS_ERROR_UNEXPECTED;
654
}
655
656
// Should we set the default value? Only if the pref is not locked, and
657
// doing so would change the default value.
658
if (!IsLocked()) {
659
if (aIsLocked) {
660
SetIsLocked(true);
661
}
662
if (!ValueMatches(PrefValueKind::Default, aType, aValue)) {
663
mDefaultValue.Replace(mHasDefaultValue, Type(), aType, aValue);
664
if (mHasDefaultValue) {
665
mDefaultChanged = true;
666
}
667
mHasDefaultValue = true;
668
if (aIsSticky) {
669
mIsSticky = true;
670
}
671
if (!mHasUserValue) {
672
*aValueChanged = true;
673
}
674
// What if we change the default to be the same as the user value?
675
// Should we clear the user value? Currently we don't.
676
}
677
}
678
return NS_OK;
679
}
680
681
nsresult SetUserValue(PrefType aType, PrefValue aValue, bool aFromInit,
682
bool* aValueChanged) {
683
// If we have a default value, types must match when setting the user
684
// value.
685
if (mHasDefaultValue && !IsType(aType)) {
686
return NS_ERROR_UNEXPECTED;
687
}
688
689
// Should we clear the user value, if present? Only if the new user value
690
// matches the default value, and the pref isn't sticky, and we aren't
691
// force-setting it during initialization.
692
if (ValueMatches(PrefValueKind::Default, aType, aValue) && !mIsSticky &&
693
!aFromInit) {
694
if (mHasUserValue) {
695
ClearUserValue();
696
if (!IsLocked()) {
697
*aValueChanged = true;
698
}
699
}
700
701
// Otherwise, should we set the user value? Only if doing so would
702
// change the user value.
703
} else if (!ValueMatches(PrefValueKind::User, aType, aValue)) {
704
mUserValue.Replace(mHasUserValue, Type(), aType, aValue);
705
SetType(aType); // needed because we may have changed the type
706
mHasUserValue = true;
707
if (!IsLocked()) {
708
*aValueChanged = true;
709
}
710
}
711
return NS_OK;
712
}
713
714
// Prefs are serialized in a manner that mirrors dom::Pref. The two should be
715
// kept in sync. E.g. if something is added to one it should also be added to
716
// the other. (It would be nice to be able to use the code generated from
717
// IPDL for serializing dom::Pref here instead of writing by hand this
718
// serialization/deserialization. Unfortunately, that generated code is
719
// difficult to use directly, outside of the IPDL IPC code.)
720
//
721
// The grammar for the serialized prefs has the following form.
722
//
723
// <pref> = <type> <locked> ':' <name> ':' <value>? ':' <value>? '\n'
724
// <type> = 'B' | 'I' | 'S'
725
// <locked> = 'L' | '-'
726
// <name> = <string-value>
727
// <value> = <bool-value> | <int-value> | <string-value>
728
// <bool-value> = 'T' | 'F'
729
// <int-value> = an integer literal accepted by strtol()
730
// <string-value> = <int-value> '/' <chars>
731
// <chars> = any char sequence of length dictated by the preceding
732
// <int-value>.
733
//
734
// No whitespace is tolerated between tokens. <type> must match the types of
735
// the values.
736
//
737
// The serialization is text-based, rather than binary, for the following
738
// reasons.
739
//
740
// - The size difference wouldn't be much different between text-based and
741
// binary. Most of the space is for strings (pref names and string pref
742
// values), which would be the same in both styles. And other differences
743
// would be minimal, e.g. small integers are shorter in text but long
744
// integers are longer in text.
745
//
746
// - Likewise, speed differences should be negligible.
747
//
748
// - It's much easier to debug a text-based serialization. E.g. you can
749
// print it and inspect it easily in a debugger.
750
//
751
// Examples of unlocked boolean prefs:
752
// - "B-:8/my.bool1:F:T\n"
753
// - "B-:8/my.bool2:F:\n"
754
// - "B-:8/my.bool3::T\n"
755
//
756
// Examples of locked integer prefs:
757
// - "IL:7/my.int1:0:1\n"
758
// - "IL:7/my.int2:123:\n"
759
// - "IL:7/my.int3::-99\n"
760
//
761
// Examples of unlocked string prefs:
762
// - "S-:10/my.string1:3/abc:4/wxyz\n"
763
// - "S-:10/my.string2:5/1.234:\n"
764
// - "S-:10/my.string3::7/string!\n"
765
766
void SerializeAndAppend(nsCString& aStr) {
767
switch (Type()) {
768
case PrefType::Bool:
769
aStr.Append('B');
770
break;
771
772
case PrefType::Int:
773
aStr.Append('I');
774
break;
775
776
case PrefType::String: {
777
aStr.Append('S');
778
break;
779
}
780
781
case PrefType::None:
782
default:
783
MOZ_CRASH();
784
}
785
786
aStr.Append(mIsLocked ? 'L' : '-');
787
aStr.Append(':');
788
789
SerializeAndAppendString(mName, aStr);
790
aStr.Append(':');
791
792
if (mHasDefaultValue) {
793
mDefaultValue.SerializeAndAppend(Type(), aStr);
794
}
795
aStr.Append(':');
796
797
if (mHasUserValue) {
798
mUserValue.SerializeAndAppend(Type(), aStr);
799
}
800
aStr.Append('\n');
801
}
802
803
static char* Deserialize(char* aStr, dom::Pref* aDomPref) {
804
char* p = aStr;
805
806
// The type.
807
PrefType type;
808
if (*p == 'B') {
809
type = PrefType::Bool;
810
} else if (*p == 'I') {
811
type = PrefType::Int;
812
} else if (*p == 'S') {
813
type = PrefType::String;
814
} else {
815
NS_ERROR("bad pref type");
816
type = PrefType::None;
817
}
818
p++; // move past the type char
819
820
// Locked?
821
bool isLocked;
822
if (*p == 'L') {
823
isLocked = true;
824
} else if (*p == '-') {
825
isLocked = false;
826
} else {
827
NS_ERROR("bad pref locked status");
828
isLocked = false;
829
}
830
p++; // move past the isLocked char
831
832
MOZ_ASSERT(*p == ':');
833
p++; // move past the ':'
834
835
// The pref name.
836
nsCString name;
837
p = DeserializeString(p, name);
838
839
MOZ_ASSERT(*p == ':');
840
p++; // move past the ':' preceding the default value
841
842
Maybe<dom::PrefValue> maybeDefaultValue;
843
if (*p != ':') {
844
dom::PrefValue defaultValue;
845
p = PrefValue::Deserialize(type, p, &maybeDefaultValue);
846
}
847
848
MOZ_ASSERT(*p == ':');
849
p++; // move past the ':' between the default and user values
850
851
Maybe<dom::PrefValue> maybeUserValue;
852
if (*p != '\n') {
853
dom::PrefValue userValue;
854
p = PrefValue::Deserialize(type, p, &maybeUserValue);
855
}
856
857
MOZ_ASSERT(*p == '\n');
858
p++; // move past the '\n' following the user value
859
860
*aDomPref = dom::Pref(name, isLocked, maybeDefaultValue, maybeUserValue);
861
862
return p;
863
}
864
865
void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, PrefsSizes& aSizes) {
866
// Note: mName is allocated in gPrefNameArena, measured elsewhere.
867
aSizes.mPrefValues += aMallocSizeOf(this);
868
if (IsTypeString()) {
869
if (mHasDefaultValue) {
870
aSizes.mStringValues += aMallocSizeOf(mDefaultValue.mStringVal);
871
}
872
if (mHasUserValue) {
873
aSizes.mStringValues += aMallocSizeOf(mUserValue.mStringVal);
874
}
875
}
876
}
877
878
private:
879
const char* mName; // allocated in gPrefNameArena
880
881
uint32_t mType : 2;
882
uint32_t mIsSticky : 1;
883
uint32_t mIsLocked : 1;
884
uint32_t mDefaultChanged : 1;
885
uint32_t mHasDefaultValue : 1;
886
uint32_t mHasUserValue : 1;
887
uint32_t mIsSkippedByIteration : 1;
888
889
PrefValue mDefaultValue;
890
PrefValue mUserValue;
891
};
892
893
struct PrefHasher {
894
using Key = UniquePtr<Pref>;
895
using Lookup = const char*;
896
897
static HashNumber hash(const Lookup& aLookup) { return HashString(aLookup); }
898
899
static bool match(const Key& aKey, const Lookup& aLookup) {
900
if (!aLookup || !aKey->Name()) {
901
return false;
902
}
903
904
return strcmp(aLookup, aKey->Name()) == 0;
905
}
906
};
907
908
using PrefWrapperBase = Variant<Pref*, SharedPrefMap::Pref>;
909
class MOZ_STACK_CLASS PrefWrapper : public PrefWrapperBase {
910
using SharedPref = const SharedPrefMap::Pref;
911
912
public:
913
MOZ_IMPLICIT PrefWrapper(Pref* aPref) : PrefWrapperBase(AsVariant(aPref)) {}
914
915
MOZ_IMPLICIT PrefWrapper(const SharedPrefMap::Pref& aPref)
916
: PrefWrapperBase(AsVariant(aPref)) {}
917
918
// Types.
919
920
bool IsType(PrefType aType) const { return Type() == aType; }
921
bool IsTypeNone() const { return IsType(PrefType::None); }
922
bool IsTypeString() const { return IsType(PrefType::String); }
923
bool IsTypeInt() const { return IsType(PrefType::Int); }
924
bool IsTypeBool() const { return IsType(PrefType::Bool); }
925
926
#define FORWARD(retType, method) \
927
retType method() const { \
928
struct Matcher { \
929
retType operator()(const Pref* aPref) { return aPref->method(); } \
930
retType operator()(SharedPref& aPref) { return aPref.method(); } \
931
}; \
932
return match(Matcher()); \
933
}
934
935
FORWARD(bool, DefaultChanged)
936
FORWARD(bool, IsLocked)
937
FORWARD(bool, IsSticky)
938
FORWARD(bool, HasDefaultValue)
939
FORWARD(bool, HasUserValue)
940
FORWARD(const char*, Name)
941
FORWARD(nsCString, NameString)
942
FORWARD(PrefType, Type)
943
#undef FORWARD
944
945
#define FORWARD(retType, method) \
946
retType method(PrefValueKind aKind = PrefValueKind::User) const { \
947
struct Matcher { \
948
PrefValueKind mKind; \
949
\
950
retType operator()(const Pref* aPref) { return aPref->method(mKind); } \
951
retType operator()(SharedPref& aPref) { return aPref.method(mKind); } \
952
}; \
953
return match(Matcher{aKind}); \
954
}
955
956
FORWARD(bool, GetBoolValue)
957
FORWARD(int32_t, GetIntValue)
958
FORWARD(nsCString, GetStringValue)
959
FORWARD(const char*, GetBareStringValue)
960
#undef FORWARD
961
962
PrefValue GetValue(PrefValueKind aKind = PrefValueKind::User) const {
963
switch (Type()) {
964
case PrefType::Bool:
965
return PrefValue{GetBoolValue(aKind)};
966
case PrefType::Int:
967
return PrefValue{GetIntValue(aKind)};
968
case PrefType::String:
969
return PrefValue{GetBareStringValue(aKind)};
970
default:
971
MOZ_ASSERT_UNREACHABLE("Unexpected pref type");
972
return PrefValue{};
973
}
974
}
975
976
Result<PrefValueKind, nsresult> WantValueKind(PrefType aType,
977
PrefValueKind aKind) const {
978
if (Type() != aType) {
979
return Err(NS_ERROR_UNEXPECTED);
980
}
981
982
if (aKind == PrefValueKind::Default || IsLocked() || !HasUserValue()) {
983
if (!HasDefaultValue()) {
984
return Err(NS_ERROR_UNEXPECTED);
985
}
986
return PrefValueKind::Default;
987
}
988
return PrefValueKind::User;
989
}
990
991
nsresult GetValue(PrefValueKind aKind, bool* aResult) const {
992
PrefValueKind kind;
993
MOZ_TRY_VAR(kind, WantValueKind(PrefType::Bool, aKind));
994
995
*aResult = GetBoolValue(kind);
996
return NS_OK;
997
}
998
999
nsresult GetValue(PrefValueKind aKind, int32_t* aResult) const {
1000
PrefValueKind kind;
1001
MOZ_TRY_VAR(kind, WantValueKind(PrefType::Int, aKind));
1002
1003
*aResult = GetIntValue(kind);
1004
return NS_OK;
1005
}
1006
1007
nsresult GetValue(PrefValueKind aKind, uint32_t* aResult) const {
1008
return GetValue(aKind, reinterpret_cast<int32_t*>(aResult));
1009
}
1010
1011
nsresult GetValue(PrefValueKind aKind, float* aResult) const {
1012
nsAutoCString result;
1013
nsresult rv = GetValue(aKind, result);
1014
if (NS_SUCCEEDED(rv)) {
1015
// ToFloat() does a locale-independent conversion.
1016
*aResult = result.ToFloat(&rv);
1017
}
1018
return rv;
1019
}
1020
1021
nsresult GetValue(PrefValueKind aKind, nsACString& aResult) const {
1022
PrefValueKind kind;
1023
MOZ_TRY_VAR(kind, WantValueKind(PrefType::String, aKind));
1024
1025
aResult = GetStringValue(kind);
1026
return NS_OK;
1027
}
1028
1029
// Returns false if this pref doesn't have a user value worth saving.
1030
bool UserValueToStringForSaving(nsCString& aStr) {
1031
// Should we save the user value, if present? Only if it does not match the
1032
// default value, or it is sticky.
1033
if (HasUserValue() &&
1034
(!ValueMatches(PrefValueKind::Default, Type(), GetValue()) ||
1035
IsSticky())) {
1036
if (IsTypeString()) {
1037
StrEscape(GetStringValue().get(), aStr);
1038
1039
} else if (IsTypeInt()) {
1040
aStr.AppendInt(GetIntValue());
1041
1042
} else if (IsTypeBool()) {
1043
aStr = GetBoolValue() ? "true" : "false";
1044
}
1045
return true;
1046
}
1047
1048
// Do not save default prefs that haven't changed.
1049
return false;
1050
}
1051
1052
bool Matches(PrefType aType, PrefValueKind aKind, PrefValue& aValue,
1053
bool aIsSticky, bool aIsLocked) const {
1054
return (ValueMatches(aKind, aType, aValue) && aIsSticky == IsSticky() &&
1055
aIsLocked == IsLocked());
1056
}
1057
1058
bool ValueMatches(PrefValueKind aKind, PrefType aType,
1059
const PrefValue& aValue) const {
1060
if (!IsType(aType)) {
1061
return false;
1062
}
1063
if (!(aKind == PrefValueKind::Default ? HasDefaultValue()
1064
: HasUserValue())) {
1065
return false;
1066
}
1067
switch (aType) {
1068
case PrefType::Bool:
1069
return GetBoolValue(aKind) == aValue.mBoolVal;
1070
case PrefType::Int:
1071
return GetIntValue(aKind) == aValue.mIntVal;
1072
case PrefType::String:
1073
return strcmp(GetBareStringValue(aKind), aValue.mStringVal) == 0;
1074
default:
1075
MOZ_ASSERT_UNREACHABLE("Unexpected preference type");
1076
return false;
1077
}
1078
}
1079
};
1080
1081
void Pref::FromWrapper(PrefWrapper& aWrapper) {
1082
MOZ_ASSERT(aWrapper.is<SharedPrefMap::Pref>());
1083
auto pref = aWrapper.as<SharedPrefMap::Pref>();
1084
1085
MOZ_ASSERT(IsTypeNone());
1086
MOZ_ASSERT(strcmp(mName, pref.Name()) == 0);
1087
1088
mType = uint32_t(pref.Type());
1089
1090
mIsLocked = pref.IsLocked();
1091
mIsSticky = pref.IsSticky();
1092
1093
mHasDefaultValue = pref.HasDefaultValue();
1094
mHasUserValue = pref.HasUserValue();
1095
1096
if (mHasDefaultValue) {
1097
mDefaultValue.Init(Type(), aWrapper.GetValue(PrefValueKind::Default));
1098
}
1099
if (mHasUserValue) {
1100
mUserValue.Init(Type(), aWrapper.GetValue(PrefValueKind::User));
1101
}
1102
}
1103
1104
class CallbackNode {
1105
public:
1106
CallbackNode(const nsACString& aDomain, PrefChangedFunc aFunc, void* aData,
1107
Preferences::MatchKind aMatchKind)
1108
: mDomain(AsVariant(nsCString(aDomain))),
1109
mFunc(aFunc),
1110
mData(aData),
1111
mNextAndMatchKind(aMatchKind) {}
1112
1113
CallbackNode(const char** aDomains, PrefChangedFunc aFunc, void* aData,
1114
Preferences::MatchKind aMatchKind)
1115
: mDomain(AsVariant(aDomains)),
1116
mFunc(aFunc),
1117
mData(aData),
1118
mNextAndMatchKind(aMatchKind) {}
1119
1120
// mDomain is a UniquePtr<>, so any uses of Domain() should only be temporary
1121
// borrows.
1122
const Variant<nsCString, const char**>& Domain() const { return mDomain; }
1123
1124
PrefChangedFunc Func() const { return mFunc; }
1125
void ClearFunc() { mFunc = nullptr; }
1126
1127
void* Data() const { return mData; }
1128
1129
Preferences::MatchKind MatchKind() const {
1130
return static_cast<Preferences::MatchKind>(mNextAndMatchKind &
1131
kMatchKindMask);
1132
}
1133
1134
bool DomainIs(const nsACString& aDomain) const {
1135
return mDomain.is<nsCString>() && mDomain.as<nsCString>() == aDomain;
1136
}
1137
1138
bool DomainIs(const char** aPrefs) const {
1139
return mDomain == AsVariant(aPrefs);
1140
}
1141
1142
bool Matches(const nsACString& aPrefName) const {
1143
auto match = [&](const nsACString& aStr) {
1144
return MatchKind() == Preferences::ExactMatch
1145
? aPrefName == aStr
1146
: StringBeginsWith(aPrefName, aStr);
1147
};
1148
1149
if (mDomain.is<nsCString>()) {
1150
return match(mDomain.as<nsCString>());
1151
}
1152
for (const char** ptr = mDomain.as<const char**>(); *ptr; ptr++) {
1153
if (match(nsDependentCString(*ptr))) {
1154
return true;
1155
}
1156
}
1157
return false;
1158
}
1159
1160
CallbackNode* Next() const {
1161
return reinterpret_cast<CallbackNode*>(mNextAndMatchKind & kNextMask);
1162
}
1163
1164
void SetNext(CallbackNode* aNext) {
1165
uintptr_t matchKind = mNextAndMatchKind & kMatchKindMask;
1166
mNextAndMatchKind = reinterpret_cast<uintptr_t>(aNext);
1167
MOZ_ASSERT((mNextAndMatchKind & kMatchKindMask) == 0);
1168
mNextAndMatchKind |= matchKind;
1169
}
1170
1171
void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, PrefsSizes& aSizes) {
1172
aSizes.mCallbacksObjects += aMallocSizeOf(this);
1173
if (mDomain.is<nsCString>()) {
1174
aSizes.mCallbacksDomains +=
1175
mDomain.as<nsCString>().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
1176
}
1177
}
1178
1179
private:
1180
static const uintptr_t kMatchKindMask = uintptr_t(0x1);
1181
static const uintptr_t kNextMask = ~kMatchKindMask;
1182
1183
Variant<nsCString, const char**> mDomain;
1184
1185
// If someone attempts to remove the node from the callback list while
1186
// NotifyCallbacks() is running, |func| is set to nullptr. Such nodes will
1187
// be removed at the end of NotifyCallbacks().
1188
PrefChangedFunc mFunc;
1189
void* mData;
1190
1191
// Conceptually this is two fields:
1192
// - CallbackNode* mNext;
1193
// - Preferences::MatchKind mMatchKind;
1194
// They are combined into a tagged pointer to save memory.
1195
uintptr_t mNextAndMatchKind;
1196
};
1197
1198
using PrefsHashTable = HashSet<UniquePtr<Pref>, PrefHasher>;
1199
1200
static PrefsHashTable* gHashTable;
1201
1202
#ifdef DEBUG
1203
// This defines the datatype used to store our `Once` StaticPrefs checker.
1204
// We can't use HashMap for now due to alignment restrictions when dealing with
1205
// std::function<void()> (see bug 1557617).
1206
typedef std::function<void()> AntiFootgunCallback;
1207
struct CompareStr {
1208
bool operator()(char const* a, char const* b) const {
1209
return std::strcmp(a, b) < 0;
1210
}
1211
};
1212
typedef std::map<const char*, AntiFootgunCallback, CompareStr> AntiFootgunMap;
1213
static AntiFootgunMap* gOnceStaticPrefsAntiFootgun;
1214
#endif
1215
1216
// The callback list contains all the priority callbacks followed by the
1217
// non-priority callbacks. gLastPriorityNode records where the first part ends.
1218
static CallbackNode* gFirstCallback = nullptr;
1219
static CallbackNode* gLastPriorityNode = nullptr;
1220
1221
#ifdef DEBUG
1222
# define ACCESS_COUNTS
1223
#endif
1224
1225
#ifdef ACCESS_COUNTS
1226
using AccessCountsHashTable = nsDataHashtable<nsCStringHashKey, uint32_t>;
1227
static AccessCountsHashTable* gAccessCounts;
1228
1229
static void AddAccessCount(const nsACString& aPrefName) {
1230
// FIXME: Servo reads preferences from background threads in unsafe ways (bug
1231
// 1474789), and triggers assertions here if we try to add usage count entries
1232
// from background threads.
1233
if (NS_IsMainThread()) {
1234
uint32_t& count = gAccessCounts->GetOrInsert(aPrefName);
1235
count++;
1236
}
1237
}
1238
1239
static void AddAccessCount(const char* aPrefName) {
1240
AddAccessCount(nsDependentCString(aPrefName));
1241
}
1242
#else
1243
static void MOZ_MAYBE_UNUSED AddAccessCount(const nsACString& aPrefName) {}
1244
1245
static void AddAccessCount(const char* aPrefName) {}
1246
#endif
1247
1248
// These are only used during the call to NotifyCallbacks().
1249
static bool gCallbacksInProgress = false;
1250
static bool gShouldCleanupDeadNodes = false;
1251
1252
class PrefsHashIter {
1253
using Iterator = decltype(gHashTable->modIter());
1254
using ElemType = Pref*;
1255
1256
Iterator mIter;
1257
1258
public:
1259
explicit PrefsHashIter(PrefsHashTable* aTable) : mIter(aTable->modIter()) {}
1260
1261
class Elem {
1262
friend class PrefsHashIter;
1263
1264
PrefsHashIter& mParent;
1265
bool mDone;
1266
1267
Elem(PrefsHashIter& aIter, bool aDone) : mParent(aIter), mDone(aDone) {}
1268
1269
Iterator& Iter() { return mParent.mIter; }
1270
1271
public:
1272
Elem& operator*() { return *this; }
1273
1274
ElemType get() {
1275
if (mDone) {
1276
return nullptr;
1277
}
1278
return Iter().get().get();
1279
}
1280
ElemType get() const { return const_cast<Elem*>(this)->get(); }
1281
1282
ElemType operator->() { return get(); }
1283
ElemType operator->() const { return get(); }
1284
1285
operator ElemType() { return get(); }
1286
1287
void Remove() { Iter().remove(); }
1288
1289
Elem& operator++() {
1290
MOZ_ASSERT(!mDone);
1291
Iter().next();
1292
mDone = Iter().done();
1293
return *this;
1294
}
1295
1296
bool operator!=(Elem& other) {
1297
return mDone != other.mDone || this->get() != other.get();
1298
}
1299
};
1300
1301
Elem begin() { return Elem(*this, mIter.done()); }
1302
1303
Elem end() { return Elem(*this, true); }
1304
};
1305
1306
class PrefsIter {
1307
using Iterator = decltype(gHashTable->iter());
1308
using ElemType = PrefWrapper;
1309
1310
using HashElem = PrefsHashIter::Elem;
1311
using SharedElem = SharedPrefMap::Pref;
1312
1313
using ElemTypeVariant = Variant<HashElem, SharedElem>;
1314
1315
SharedPrefMap* mSharedMap;
1316
PrefsHashTable* mHashTable;
1317
PrefsHashIter mIter;
1318
1319
ElemTypeVariant mPos;
1320
ElemTypeVariant mEnd;
1321
1322
Maybe<PrefWrapper> mEntry;
1323
1324
public:
1325
PrefsIter(PrefsHashTable* aHashTable, SharedPrefMap* aSharedMap)
1326
: mSharedMap(aSharedMap),
1327
mHashTable(aHashTable),
1328
mIter(aHashTable),
1329
mPos(AsVariant(mIter.begin())),
1330
mEnd(AsVariant(mIter.end())) {
1331
if (Done()) {
1332
NextIterator();
1333
}
1334
}
1335
1336
private:
1337
#define MATCH(type, ...) \
1338
do { \
1339
struct Matcher { \
1340
PrefsIter& mIter; \
1341
type operator()(HashElem& pos) { \
1342
HashElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<HashElem>(); \
1343
__VA_ARGS__; \
1344
} \
1345
type operator()(SharedElem& pos) { \
1346
SharedElem& end MOZ_MAYBE_UNUSED = mIter.mEnd.as<SharedElem>(); \
1347
__VA_ARGS__; \
1348
} \
1349
}; \
1350
return mPos.match(Matcher{*this}); \
1351
} while (0);
1352
1353
bool Done() { MATCH(bool, return pos == end); }
1354
1355
PrefWrapper MakeEntry() { MATCH(PrefWrapper, return PrefWrapper(pos)); }
1356
1357
void NextEntry() {
1358
mEntry.reset();
1359
MATCH(void, ++pos);
1360
}
1361
#undef MATCH
1362
1363
bool Next() {
1364
NextEntry();
1365
return !Done() || NextIterator();
1366
}
1367
1368
bool NextIterator() {
1369
if (mPos.is<HashElem>() && mSharedMap) {
1370
mPos = AsVariant(mSharedMap->begin());
1371
mEnd = AsVariant(mSharedMap->end());
1372
return !Done();
1373
}
1374
return false;
1375
}
1376
1377
bool IteratingBase() { return mPos.is<SharedElem>(); }
1378
1379
PrefWrapper& Entry() {
1380
MOZ_ASSERT(!Done());
1381
1382
if (!mEntry.isSome()) {
1383
mEntry.emplace(MakeEntry());
1384
}
1385
return mEntry.ref();
1386
}
1387
1388
public:
1389
class Elem {
1390
friend class PrefsIter;
1391
1392
PrefsIter& mParent;
1393
bool mDone;
1394
1395
Elem(PrefsIter& aIter, bool aDone) : mParent(aIter), mDone(aDone) {
1396
SkipDuplicates();
1397
}
1398
1399
void Next() { mDone = !mParent.Next(); }
1400
1401
void SkipDuplicates() {
1402
while (!mDone &&
1403
(mParent.IteratingBase() ? mParent.mHashTable->has(ref().Name())
1404
: ref().IsTypeNone())) {
1405
Next();
1406
}
1407
}
1408
1409
public:
1410
Elem& operator*() { return *this; }
1411
1412
ElemType& ref() { return mParent.Entry(); }
1413
const ElemType& ref() const { return const_cast<Elem*>(this)->ref(); }
1414
1415
ElemType* operator->() { return &ref(); }
1416
const ElemType* operator->() const { return &ref(); }
1417
1418
operator ElemType() { return ref(); }
1419
1420
void Remove() {
1421
MOZ_ASSERT(!mParent.IteratingBase());
1422
mParent.mPos.as<HashElem>().Remove();
1423
}
1424
1425
Elem& operator++() {
1426
MOZ_ASSERT(!mDone);
1427
Next();
1428
SkipDuplicates();
1429
return *this;
1430
}
1431
1432
bool operator!=(Elem& other) {
1433
if (mDone != other.mDone) {
1434
return true;
1435
}
1436
if (mDone) {
1437
return false;
1438
}
1439
return &this->ref() != &other.ref();
1440
}
1441
};
1442
1443
Elem begin() { return {*this, Done()}; }
1444
1445
Elem end() { return {*this, true}; }
1446
};
1447
1448
static Pref* pref_HashTableLookup(const char* aPrefName);
1449
1450
static void NotifyCallbacks(const char* aPrefName,
1451
const PrefWrapper* aPref = nullptr);
1452
1453
static void NotifyCallbacks(const char* aPrefName, const PrefWrapper& aPref) {
1454
NotifyCallbacks(aPrefName, &aPref);
1455
}
1456
1457
// The approximate number of preferences in the dynamic hashtable for the parent
1458
// and content processes, respectively. These numbers are used to determine the
1459
// initial size of the dynamic preference hashtables, and should be chosen to
1460
// avoid rehashing during normal usage. The actual number of preferences will,
1461
// or course, change over time, but these numbers only need to be within a
1462
// binary order of magnitude of the actual values to remain effective.
1463
//
1464
// The number for the parent process should reflect the total number of
1465
// preferences in the database, since the parent process needs to initially
1466
// build a dynamic hashtable of the entire preference database. The number for
1467
// the child process should reflect the number of preferences which are likely
1468
// to change after the startup of the first content process, since content
1469
// processes only store changed preferences on top of a snapshot of the database
1470
// created at startup.
1471
//
1472
// Note: The capacity of a hashtable doubles when its length reaches an exact
1473
// power of two. A table with an initial length of 64 is twice as large as one
1474
// with an initial length of 63. This is important in content processes, where
1475
// lookup speed is less critical and we pay the price of the additional overhead
1476
// for each content process. So the initial content length should generally be
1477
// *under* the next power-of-two larger than its expected length.
1478
constexpr size_t kHashTableInitialLengthParent = 3000;
1479
constexpr size_t kHashTableInitialLengthContent = 64;
1480
1481
static PrefSaveData pref_savePrefs() {
1482
MOZ_ASSERT(NS_IsMainThread());
1483
1484
PrefSaveData savedPrefs(gHashTable->count());
1485
1486
for (auto& pref : PrefsIter(gHashTable, gSharedMap)) {
1487
nsAutoCString prefValueStr;
1488
if (!pref->UserValueToStringForSaving(prefValueStr)) {
1489
continue;
1490
}
1491
1492
nsAutoCString prefNameStr;
1493
StrEscape(pref->Name(), prefNameStr);
1494
1495
nsPrintfCString str("user_pref(%s, %s);", prefNameStr.get(),
1496
prefValueStr.get());
1497
1498
savedPrefs.AppendElement(str);
1499
}
1500
1501
return savedPrefs;
1502
}
1503
1504
#ifdef DEBUG
1505
1506
// Note that this never changes in the parent process, and is only read in
1507
// content processes.
1508
static bool gContentProcessPrefsAreInited = false;
1509
1510
#endif // DEBUG
1511
1512
static Pref* pref_HashTableLookup(const char* aPrefName) {
1513
MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
1514
1515
MOZ_ASSERT_IF(!XRE_IsParentProcess(), gContentProcessPrefsAreInited);
1516
1517
// We use readonlyThreadsafeLookup() because we often have concurrent lookups
1518
// from multiple Stylo threads. This is safe because those threads cannot
1519
// modify gHashTable, and the main thread is blocked while Stylo threads are
1520
// doing these lookups.
1521
auto p = gHashTable->readonlyThreadsafeLookup(aPrefName);
1522
return p ? p->get() : nullptr;
1523
}
1524
1525
// While notifying preference callbacks, this holds the wrapper for the
1526
// preference being notified, in order to optimize lookups.
1527
//
1528
// Note: Callbacks and lookups only happen on the main thread, so this is safe
1529
// to use without locking.
1530
static const PrefWrapper* gCallbackPref;
1531
1532
Maybe<PrefWrapper> pref_SharedLookup(const char* aPrefName) {
1533
MOZ_DIAGNOSTIC_ASSERT(gSharedMap, "gSharedMap must be initialized");
1534
if (Maybe<SharedPrefMap::Pref> pref = gSharedMap->Get(aPrefName)) {
1535
return Some(*pref);
1536
}
1537
return Nothing();
1538
}
1539
1540
Maybe<PrefWrapper> pref_Lookup(const char* aPrefName,
1541
bool aIncludeTypeNone = false) {
1542
MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal());
1543
1544
AddAccessCount(aPrefName);
1545
1546
if (gCallbackPref && strcmp(aPrefName, gCallbackPref->Name()) == 0) {
1547
return Some(*gCallbackPref);
1548
}
1549
if (Pref* pref = pref_HashTableLookup(aPrefName)) {
1550
if (aIncludeTypeNone || !pref->IsTypeNone()) {
1551
return Some(pref);
1552
}
1553
} else if (gSharedMap) {
1554
return pref_SharedLookup(aPrefName);
1555
}
1556
1557
return Nothing();
1558
}
1559
1560
static Result<Pref*, nsresult> pref_LookupForModify(
1561
const char* aPrefName,
1562
const std::function<bool(const PrefWrapper&)>& aCheckFn) {
1563
Maybe<PrefWrapper> wrapper =
1564
pref_Lookup(aPrefName, /* includeTypeNone */ true);
1565
if (wrapper.isNothing()) {
1566
return Err(NS_ERROR_INVALID_ARG);
1567
}
1568
if (!aCheckFn(*wrapper)) {
1569
return nullptr;
1570
}
1571
if (wrapper->is<Pref*>()) {
1572
return wrapper->as<Pref*>();
1573
}
1574
1575
Pref* pref = new Pref(aPrefName);
1576
if (!gHashTable->putNew(aPrefName, pref)) {
1577
delete pref;
1578
return Err(NS_ERROR_OUT_OF_MEMORY);
1579
}
1580
pref->FromWrapper(*wrapper);
1581
return pref;
1582
}
1583
1584
static nsresult pref_SetPref(const char* aPrefName, PrefType aType,
1585
PrefValueKind aKind, PrefValue aValue,
1586
bool aIsSticky, bool aIsLocked, bool aFromInit) {
1587
MOZ_ASSERT(NS_IsMainThread());
1588
1589
if (!gHashTable) {
1590
return NS_ERROR_OUT_OF_MEMORY;
1591
}
1592
1593
Pref* pref = nullptr;
1594
if (gSharedMap) {
1595
auto result =
1596
pref_LookupForModify(aPrefName, [&](const PrefWrapper& aWrapper) {
1597
return !aWrapper.Matches(aType, aKind, aValue, aIsSticky, aIsLocked);
1598
});
1599
if (result.isOk() && !(pref = result.unwrap())) {
1600
// No changes required.
1601
return NS_OK;
1602
}
1603
}
1604
1605
if (!pref) {
1606
auto p = gHashTable->lookupForAdd(aPrefName);
1607
if (!p) {
1608
pref = new Pref(aPrefName);
1609
pref->SetType(aType);
1610
if (!gHashTable->add(p, pref)) {
1611
delete pref;
1612
return NS_ERROR_OUT_OF_MEMORY;
1613
}
1614
} else {
1615
pref = p->get();
1616
}
1617
}
1618
1619
bool valueChanged = false;
1620
nsresult rv;
1621
if (aKind == PrefValueKind::Default) {
1622
rv = pref->SetDefaultValue(aType, aValue, aIsSticky, aIsLocked,
1623
&valueChanged);
1624
} else {
1625
MOZ_ASSERT(!aIsLocked); // `locked` is disallowed in user pref files
1626
rv = pref->SetUserValue(aType, aValue, aFromInit, &valueChanged);
1627
}
1628
if (NS_FAILED(rv)) {
1629
NS_WARNING(
1630
nsPrintfCString("Rejected attempt to change type of pref %s's %s value "
1631
"from %s to %s",
1632
aPrefName,
1633
(aKind == PrefValueKind::Default) ? "default" : "user",
1634
PrefTypeToString(pref->Type()), PrefTypeToString(aType))
1635
.get());
1636
1637
return rv;
1638
}
1639
1640
if (valueChanged) {
1641
if (aKind == PrefValueKind::User && XRE_IsParentProcess()) {
1642
Preferences::HandleDirty();
1643
}
1644
NotifyCallbacks(aPrefName, PrefWrapper(pref));
1645
}
1646
1647
return NS_OK;
1648
}
1649
1650
// Removes |node| from callback list. Returns the node after the deleted one.
1651
static CallbackNode* pref_RemoveCallbackNode(CallbackNode* aNode,
1652
CallbackNode* aPrevNode) {
1653
MOZ_ASSERT(!aPrevNode || aPrevNode->Next() == aNode);
1654
MOZ_ASSERT(aPrevNode || gFirstCallback == aNode);
1655
MOZ_ASSERT(!gCallbacksInProgress);
1656
1657
CallbackNode* next_node = aNode->Next();
1658
if (aPrevNode) {
1659
aPrevNode->SetNext(next_node);
1660
} else {
1661
gFirstCallback = next_node;
1662
}
1663
if (gLastPriorityNode == aNode) {
1664
gLastPriorityNode = aPrevNode;
1665
}
1666
delete aNode;
1667
return next_node;
1668
}
1669
1670
static void NotifyCallbacks(const char* aPrefName, const PrefWrapper* aPref) {
1671
bool reentered = gCallbacksInProgress;
1672
1673
gCallbackPref = aPref;
1674
auto cleanup = MakeScopeExit([]() { gCallbackPref = nullptr; });
1675
1676
// Nodes must not be deleted while gCallbacksInProgress is true.
1677
// Nodes that need to be deleted are marked for deletion by nulling
1678
// out the |func| pointer. We release them at the end of this function
1679
// if we haven't reentered.
1680
gCallbacksInProgress = true;
1681
1682
nsDependentCString prefName(aPrefName);
1683
1684
for (CallbackNode* node = gFirstCallback; node; node = node->Next()) {
1685
if (node->Func()) {
1686
if (node->Matches(prefName)) {
1687
(node->Func())(aPrefName, node->Data());
1688
}
1689
}
1690
}
1691
1692
gCallbacksInProgress = reentered;
1693
1694
if (gShouldCleanupDeadNodes && !gCallbacksInProgress) {
1695
CallbackNode* prev_node = nullptr;
1696
CallbackNode* node = gFirstCallback;
1697
1698
while (node) {
1699
if (!node->Func()) {
1700
node = pref_RemoveCallbackNode(node, prev_node);
1701
} else {
1702
prev_node = node;
1703
node = node->Next();
1704
}
1705
}
1706
gShouldCleanupDeadNodes = false;
1707
}
1708
1709
#ifdef DEBUG
1710
if (XRE_IsParentProcess() &&
1711
!StaticPrefs::preferences_force_disable_check_once_policy() &&
1712
(StaticPrefs::preferences_check_once_policy() || xpc::IsInAutomation())) {
1713
// Check that we aren't modifying a `Once` pref using that prefName.
1714
// We have about 100 `Once` StaticPrefs defined. std::map performs a search
1715
// in O(log n), so this is fast enough for our case.
1716
MOZ_ASSERT(gOnceStaticPrefsAntiFootgun);
1717
auto search = gOnceStaticPrefsAntiFootgun->find(aPrefName);
1718
if (search != gOnceStaticPrefsAntiFootgun->end()) {
1719
// Run the callback.
1720
(search->second)();
1721
}
1722
}
1723
#endif
1724
}
1725
1726
//===========================================================================
1727
// Prefs parsing
1728
//===========================================================================
1729
1730
struct TelemetryLoadData {
1731
uint32_t mFileLoadSize_B;
1732
uint32_t mFileLoadNumPrefs;
1733
uint32_t mFileLoadTime_us;
1734
};
1735
1736
static nsDataHashtable<nsCStringHashKey, TelemetryLoadData>* gTelemetryLoadData;
1737
1738
extern "C" {
1739
1740
// Keep this in sync with PrefFn in prefs_parser/src/lib.rs.
1741
typedef void (*PrefsParserPrefFn)(const char* aPrefName, PrefType aType,
1742
PrefValueKind aKind, PrefValue aValue,
1743
bool aIsSticky, bool aIsLocked);
1744
1745
// Keep this in sync with ErrorFn in prefs_parser/src/lib.rs.
1746
//
1747
// `aMsg` is just a borrow of the string, and must be copied if it is used
1748
// outside the lifetime of the prefs_parser_parse() call.
1749
typedef void (*PrefsParserErrorFn)(const char* aMsg);
1750
1751
// Keep this in sync with prefs_parser_parse() in prefs_parser/src/lib.rs.
1752
bool prefs_parser_parse(const char* aPath, PrefValueKind aKind,
1753
const char* aBuf, size_t aLen,
1754
PrefsParserPrefFn aPrefFn, PrefsParserErrorFn aErrorFn);
1755
}
1756
1757
class Parser {
1758
public:
1759
Parser() = default;
1760
~Parser() = default;
1761
1762
bool Parse(const nsCString& aName, PrefValueKind aKind, const char* aPath,
1763
const TimeStamp& aStartTime, const nsCString& aBuf) {
1764
sNumPrefs = 0;
1765
bool ok = prefs_parser_parse(aPath, aKind, aBuf.get(), aBuf.Length(),
1766
HandlePref, HandleError);
1767
if (!ok) {
1768
return false;
1769
}
1770
1771
uint32_t loadTime_us = (TimeStamp::Now() - aStartTime).ToMicroseconds();
1772
1773
// Most prefs files are read before telemetry initializes, so we have to
1774
// save these measurements now and send them to telemetry later.
1775
TelemetryLoadData loadData = {uint32_t(aBuf.Length()), sNumPrefs,
1776
loadTime_us};
1777
gTelemetryLoadData->Put(aName, loadData);
1778
1779
return true;
1780
}
1781
1782
private:
1783
static void HandlePref(const char* aPrefName, PrefType aType,
1784
PrefValueKind aKind, PrefValue aValue, bool aIsSticky,
1785
bool aIsLocked) {
1786
sNumPrefs++;
1787
pref_SetPref(aPrefName, aType, aKind, aValue, aIsSticky, aIsLocked,
1788
/* fromInit */ true);
1789
}
1790
1791
static void HandleError(const char* aMsg) {
1792
nsresult rv;
1793
nsCOMPtr<nsIConsoleService> console =
1794
do_GetService("@mozilla.org/consoleservice;1", &rv);
1795
if (NS_SUCCEEDED(rv)) {
1796
console->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg).get());
1797
}
1798
#ifdef DEBUG
1799
NS_ERROR(aMsg);
1800
#else
1801
printf_stderr("%s\n", aMsg);
1802
#endif
1803
}
1804
1805
// This is static so that HandlePref() can increment it easily. This is ok
1806
// because prefs files are read one at a time.
1807
static uint32_t sNumPrefs;
1808
};
1809
1810
uint32_t Parser::sNumPrefs = 0;
1811
1812
// The following code is test code for the gtest.
1813
1814
static void TestParseErrorHandlePref(const char* aPrefName, PrefType aType,
1815
PrefValueKind aKind, PrefValue aValue,
1816
bool aIsSticky, bool aIsLocked) {}
1817
1818
static nsCString gTestParseErrorMsgs;
1819
1820
static void TestParseErrorHandleError(const char* aMsg) {
1821
gTestParseErrorMsgs.Append(aMsg);
1822
gTestParseErrorMsgs.Append('\n');
1823
}
1824
1825
// Keep this in sync with the declaration in test/gtest/Parser.cpp.
1826
void TestParseError(PrefValueKind aKind, const char* aText,
1827
nsCString& aErrorMsg) {
1828
prefs_parser_parse("test", aKind, aText, strlen(aText),
1829
TestParseErrorHandlePref, TestParseErrorHandleError);
1830
1831
// Copy the error messages into the outparam, then clear them from
1832
// gTestParseErrorMsgs.
1833
aErrorMsg.Assign(gTestParseErrorMsgs);
1834
gTestParseErrorMsgs.Truncate();
1835
}
1836
1837
void SendTelemetryLoadData() {
1838
for (auto iter = gTelemetryLoadData->Iter(); !iter.Done(); iter.Next()) {
1839
const nsCString& filename = PromiseFlatCString(iter.Key());
1840
const TelemetryLoadData& data = iter.Data();
1841
Telemetry::Accumulate(Telemetry::PREFERENCES_FILE_LOAD_SIZE_B, filename,
1842
data.mFileLoadSize_B);
1843
Telemetry::Accumulate(Telemetry::PREFERENCES_FILE_LOAD_NUM_PREFS, filename,
1844
data.mFileLoadNumPrefs);
1845
Telemetry::Accumulate(Telemetry::PREFERENCES_FILE_LOAD_TIME_US, filename,
1846
data.mFileLoadTime_us);
1847
}
1848
1849
gTelemetryLoadData->Clear();
1850
}
1851
1852
//===========================================================================
1853
// nsPrefBranch et al.
1854
//===========================================================================
1855
1856
namespace mozilla {
1857
class PreferenceServiceReporter;
1858
} // namespace mozilla
1859
1860
class PrefCallback : public PLDHashEntryHdr {
1861
friend class mozilla::PreferenceServiceReporter;
1862
1863
public:
1864
typedef PrefCallback* KeyType;
1865
typedef const PrefCallback* KeyTypePointer;
1866
1867
static const PrefCallback* KeyToPointer(PrefCallback* aKey) { return aKey; }
1868
1869
static PLDHashNumber HashKey(const PrefCallback* aKey) {
1870
uint32_t hash = HashString(aKey->mDomain);
1871
return AddToHash(hash, aKey->mCanonical);
1872
}
1873
1874
public:
1875
// Create a PrefCallback with a strong reference to its observer.
1876
PrefCallback(const nsACString& aDomain, nsIObserver* aObserver,
1877
nsPrefBranch* aBranch)
1878
: mDomain(aDomain),
1879
mBranch(aBranch),
1880
mWeakRef(nullptr),
1881
mStrongRef(aObserver) {
1882
MOZ_COUNT_CTOR(PrefCallback);
1883
nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver);
1884
mCanonical = canonical;
1885
}
1886
1887