/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Documentation for libpref is in modules/libpref/docs/index.rst.
//=========================================================================== // Low-level types and operations //===========================================================================
typedef nsTArray<nsCString> PrefSaveData;
// 1 MB should be enough for everyone. staticconst uint32_t MAX_PREF_LENGTH = 1 * 1024 * 1024; // Actually, 4kb should be enough for everyone. staticconst uint32_t MAX_ADVISABLE_PREF_LENGTH = 4 * 1024;
// This is used for pref names and string pref values. We encode the string // length, then a '/', then the string chars. This encoding means there are no // special chars that are forbidden or require escaping. staticvoid SerializeAndAppendString(const nsCString& aChars, nsCString& aStr) {
aStr.AppendInt(uint64_t(aChars.Length()));
aStr.Append('/');
aStr.Append(aChars);
}
staticchar* DeserializeString(char* aChars, nsCString& aStr) { char* p = aChars;
uint32_t length = strtol(p, &p, 10);
MOZ_ASSERT(p[0] == '/');
p++; // move past the '/'
aStr.Assign(p, length);
p += length; // move past the string itself return p;
}
// Keep this in sync with PrefValue in parser/src/lib.rs. union PrefValue { // PrefValues within Pref objects own their chars. PrefValues passed around // as arguments don't own their chars. constchar* mStringVal;
int32_t mIntVal; bool mBoolVal;
#ifdef DEBUG constchar* PrefTypeToString(PrefType aType) { switch (aType) { case PrefType::None: return"none"; case PrefType::String: return"string"; case PrefType::Int: return"int"; case PrefType::Bool: return"bool"; default:
MOZ_CRASH("Unhandled enum value");
}
} #endif
// Assign to aResult a quoted, escaped copy of aOriginal. staticvoid StrEscape(constchar* aOriginal, nsCString& aResult) { if (aOriginal == nullptr) {
aResult.AssignLiteral("\"\""); return;
}
// JavaScript does not allow quotes, slashes, or line terminators inside // strings so we must escape them. ECMAScript defines four line terminators, // but we're only worrying about \r and \n here. We currently feed our pref // script to the JS interpreter as Latin-1 so we won't encounter \u2028 // (line separator) or \u2029 (paragraph separator). // // WARNING: There are hints that we may be moving to storing prefs as utf8. // If we ever feed them to the JS compiler as UTF8 then we'll have to worry // about the multibyte sequences that would be interpreted as \u2028 and // \u2029. constchar* p;
aResult.Assign('"');
// Paranoid worst case all slashes will free quickly. for (p = aOriginal; *p; ++p) { switch (*p) { case'\n':
aResult.AppendLiteral("\\n"); break;
case'\r':
aResult.AppendLiteral("\\r"); break;
case'\\':
aResult.AppendLiteral("\\\\"); break;
case'\"':
aResult.AppendLiteral("\\\""); break;
default:
aResult.Append(*p); break;
}
}
aResult.Append('"');
}
// Mimic the behaviour of nsTStringRepr::ToFloat before bug 840706 to preserve // error case handling for parsing pref strings. Many callers do not check error // codes, so the returned values may be used even if an error is set. // // This method should never return NaN, but may return +-inf if the provided // number is too large to fit in a float. staticfloat ParsePrefFloat(const nsCString& aString, nsresult* aError) { if (aString.IsEmpty()) {
*aError = NS_ERROR_ILLEGAL_VALUE; return 0.f;
}
// PR_strtod does a locale-independent conversion. char* stopped = nullptr; float result = PR_strtod(aString.get(), &stopped);
// Defensively avoid potential breakage caused by returning NaN into // unsuspecting code. AFAIK this should never happen as PR_strtod cannot // return NaN as currently configured. if (std::isnan(result)) {
MOZ_ASSERT_UNREACHABLE("PR_strtod shouldn't return NaN");
*aError = NS_ERROR_ILLEGAL_VALUE; return 0.f;
}
// Arena for Pref names. // Never access sPrefNameArena directly, always use PrefNameArena() // because it must only be accessed on the Main Thread typedef ArenaAllocator<4096, 1> NameArena; static NameArena* sPrefNameArena;
const Maybe<dom::PrefValue>& defaultValue = aDomPref.defaultValue(); bool defaultValueChanged = false; if (defaultValue.isSome()) {
PrefValue value;
PrefType type = value.FromDomPrefValue(defaultValue.ref()); if (!ValueMatches(PrefValueKind::Default, type, value)) { // Type() is PrefType::None if it's a newly added pref. This is ok.
mDefaultValue.Replace(mHasDefaultValue, Type(), type, value);
SetType(type);
mHasDefaultValue = true;
defaultValueChanged = true;
}
} // Note: we never clear a default value.
const Maybe<dom::PrefValue>& userValue = aDomPref.userValue(); bool userValueChanged = false; if (userValue.isSome()) {
PrefValue value;
PrefType type = value.FromDomPrefValue(userValue.ref()); if (!ValueMatches(PrefValueKind::User, type, value)) { // Type() is PrefType::None if it's a newly added pref. This is ok.
mUserValue.Replace(mHasUserValue, Type(), type, value);
SetType(type);
mHasUserValue = true;
userValueChanged = true;
}
} elseif (mHasUserValue) {
ClearUserValue();
userValueChanged = true;
}
nsresult SetDefaultValue(PrefType aType, PrefValue aValue, bool aIsSticky, bool aIsLocked, bool* aValueChanged) { // Types must always match when setting the default value. if (!IsType(aType)) { return NS_ERROR_UNEXPECTED;
}
// Should we set the default value? Only if the pref is not locked, and // doing so would change the default value. if (!IsLocked()) { if (aIsLocked) {
SetIsLocked(true);
} if (!ValueMatches(PrefValueKind::Default, aType, aValue)) {
mDefaultValue.Replace(mHasDefaultValue, Type(), aType, aValue);
mHasDefaultValue = true; if (aIsSticky) {
mIsSticky = true;
} if (!mHasUserValue) {
*aValueChanged = true;
} // What if we change the default to be the same as the user value? // Should we clear the user value? Currently we don't.
}
} return NS_OK;
}
nsresult SetUserValue(PrefType aType, PrefValue aValue, bool aFromInit, bool* aValueChanged) { // If we have a default value, types must match when setting the user // value. if (mHasDefaultValue && !IsType(aType)) { return NS_ERROR_UNEXPECTED;
}
// Should we clear the user value, if present? Only if the new user value // matches the default value, and the pref isn't sticky, and we aren't // force-setting it during initialization. if (ValueMatches(PrefValueKind::Default, aType, aValue) && !mIsSticky &&
!aFromInit) { if (mHasUserValue) {
ClearUserValue(); if (!IsLocked()) {
*aValueChanged = true;
}
}
// Otherwise, should we set the user value? Only if doing so would // change the user value.
} elseif (!ValueMatches(PrefValueKind::User, aType, aValue)) {
mUserValue.Replace(mHasUserValue, Type(), aType, aValue);
SetType(aType); // needed because we may have changed the type
mHasUserValue = true; if (!IsLocked()) {
*aValueChanged = true;
}
} return NS_OK;
}
// Prefs are serialized in a manner that mirrors dom::Pref. The two should be // kept in sync. E.g. if something is added to one it should also be added to // the other. (It would be nice to be able to use the code generated from // IPDL for serializing dom::Pref here instead of writing by hand this // serialization/deserialization. Unfortunately, that generated code is // difficult to use directly, outside of the IPDL IPC code.) // // The grammar for the serialized prefs has the following form. // // <pref> = <type> <locked> <sanitized> ':' <name> ':' <value>? ':' // <value>? '\n' // <type> = 'B' | 'I' | 'S' // <locked> = 'L' | '-' // <sanitized> = 'S' | '-' // <name> = <string-value> // <value> = <bool-value> | <int-value> | <string-value> // <bool-value> = 'T' | 'F' // <int-value> = an integer literal accepted by strtol() // <string-value> = <int-value> '/' <chars> // <chars> = any char sequence of length dictated by the preceding // <int-value>. // // No whitespace is tolerated between tokens. <type> must match the types of // the values. // // The serialization is text-based, rather than binary, for the following // reasons. // // - The size difference wouldn't be much different between text-based and // binary. Most of the space is for strings (pref names and string pref // values), which would be the same in both styles. And other differences // would be minimal, e.g. small integers are shorter in text but long // integers are longer in text. // // - Likewise, speed differences should be negligible. // // - It's much easier to debug a text-based serialization. E.g. you can // print it and inspect it easily in a debugger. // // Examples of unlocked boolean prefs: // - "B--:8/my.bool1:F:T\n" // - "B--:8/my.bool2:F:\n" // - "B--:8/my.bool3::T\n" // // Examples of sanitized, unlocked boolean prefs: // - "B-S:8/my.bool1:F:T\n" // - "B-S:8/my.bool2:F:\n" // - "B-S:8/my.bool3::T\n" // // Examples of locked integer prefs: // - "IL-:7/my.int1:0:1\n" // - "IL-:7/my.int2:123:\n" // - "IL-:7/my.int3::-99\n" // // Examples of unlocked string prefs: // - "S--:10/my.string1:3/abc:4/wxyz\n" // - "S--:10/my.string2:5/1.234:\n" // - "S--:10/my.string3::7/string!\n"
using PrefWrapperBase = Variant<Pref*, SharedPrefMap::Pref>; class MOZ_STACK_CLASS PrefWrapper : public PrefWrapperBase { using SharedPref = const SharedPrefMap::Pref;
PrefValue GetValue(PrefValueKind aKind = PrefValueKind::User) const { switch (Type()) { case PrefType::Bool: return PrefValue{GetBoolValue(aKind)}; case PrefType::Int: return PrefValue{GetIntValue(aKind)}; case PrefType::String: return PrefValue{GetBareStringValue(aKind)}; case PrefType::None: // This check will be performed in the above functions; but for NoneType // we need to do it explicitly, then fall-through. if (IsPreferenceSanitized(Name())) {
glean::security::pref_usage_content_process.Record(Some(
glean::security::PrefUsageContentProcessExtra{Some(Name())}));
if (sCrashOnBlocklistedPref) {
MOZ_CRASH_UNSAFE_PRINTF( "Should not access the preference '%s' in the Content " "Processes",
Name());
}
}
[[fallthrough]]; default:
MOZ_ASSERT_UNREACHABLE("Unexpected pref type"); return PrefValue{};
}
}
Result<PrefValueKind, nsresult> WantValueKind(PrefType aType,
PrefValueKind aKind) const { // WantValueKind may short-circuit GetValue functions and cause them to // return early, before this check occurs in GetFooValue() if (this->is<Pref*>() && IsPreferenceSanitized(this->as<Pref*>())) {
glean::security::pref_usage_content_process.Record(
Some(glean::security::PrefUsageContentProcessExtra{Some(Name())}));
if (sCrashOnBlocklistedPref) {
MOZ_CRASH_UNSAFE_PRINTF( "Should not access the preference '%s' in the Content Processes",
Name());
}
} elseif (!this->is<Pref*>()) { // While we could use Name() above, and avoid the Variant checks, it // would less efficient than needed and we can instead do a debug-only // assert here to limit the inefficientcy
MOZ_ASSERT(!IsPreferenceSanitized(Name()), "We should never have a sanitized SharedPrefMap::Pref.");
}
if (Type() != aType) { return Err(NS_ERROR_UNEXPECTED);
}
// Returns false if this pref doesn't have a user value worth saving. bool UserValueToStringForSaving(nsCString& aStr) { // Should we save the user value, if present? Only if it does not match the // default value, or it is sticky. if (HasUserValue() &&
(!ValueMatches(PrefValueKind::Default, Type(), GetValue()) ||
IsSticky())) { if (IsTypeString()) {
StrEscape(GetStringValue().get(), aStr);
// mDomain is a UniquePtr<>, so any uses of Domain() should only be temporary // borrows. const Variant<nsCString, constchar* const*>& Domain() const { return mDomain;
}
// If someone attempts to remove the node from the callback list while // NotifyCallbacks() is running, |func| is set to nullptr. Such nodes will // be removed at the end of NotifyCallbacks().
PrefChangedFunc mFunc; void* mData;
// Conceptually this is two fields: // - CallbackNode* mNext; // - Preferences::MatchKind mMatchKind; // They are combined into a tagged pointer to save memory.
uintptr_t mNextAndMatchKind;
};
using PrefsHashTable = HashSet<UniquePtr<Pref>, PrefHasher>;
// The main prefs hash table. Inside a function so we can assert it's only // accessed on the main thread. (That assertion can be avoided but only do so // with great care!) staticinline PrefsHashTable*& HashTable(bool aOffMainThread = false) {
MOZ_ASSERT(NS_IsMainThread() || ServoStyleSet::IsInServoTraversal()); static PrefsHashTable* sHashTable = nullptr; return sHashTable;
}
#ifdef DEBUG // This defines the type used to store our `once` mirrors checker. We can't use // HashMap for now due to alignment restrictions when dealing with // std::function<void()> (see bug 1557617). typedef std::function<void()> AntiFootgunCallback; struct CompareStr { booloperator()(charconst* a, charconst* b) const { return std::strcmp(a, b) < 0;
}
}; typedef std::map<constchar*, AntiFootgunCallback, CompareStr> AntiFootgunMap; static StaticAutoPtr<AntiFootgunMap> gOnceStaticPrefsAntiFootgun; #endif
// The callback list contains all the priority callbacks followed by the // non-priority callbacks. gLastPriorityNode records where the first part ends. static CallbackNode* gFirstCallback = nullptr; static CallbackNode* gLastPriorityNode = nullptr;
#ifdef DEBUG # define ACCESS_COUNTS #endif
#ifdef ACCESS_COUNTS using AccessCountsHashTable = nsTHashMap<nsCStringHashKey, uint32_t>; static StaticAutoPtr<AccessCountsHashTable> gAccessCounts;
staticvoid AddAccessCount(const nsACString& aPrefName) { // FIXME: Servo reads preferences from background threads in unsafe ways (bug // 1474789), and triggers assertions here if we try to add usage count entries // from background threads. if (NS_IsMainThread()) {
JS::AutoSuppressGCAnalysis nogc; // Hash functions will not GC.
uint32_t& count = gAccessCounts->LookupOrInsert(aPrefName);
count++;
}
}
// The approximate number of preferences in the dynamic hashtable for the parent // and content processes, respectively. These numbers are used to determine the // initial size of the dynamic preference hashtables, and should be chosen to // avoid rehashing during normal usage. The actual number of preferences will, // or course, change over time, but these numbers only need to be within a // binary order of magnitude of the actual values to remain effective. // // The number for the parent process should reflect the total number of // preferences in the database, since the parent process needs to initially // build a dynamic hashtable of the entire preference database. The number for // the child process should reflect the number of preferences which are likely // to change after the startup of the first content process, since content // processes only store changed preferences on top of a snapshot of the database // created at startup. // // Note: The capacity of a hashtable doubles when its length reaches an exact // power of two. A table with an initial length of 64 is twice as large as one // with an initial length of 63. This is important in content processes, where // lookup speed is less critical and we pay the price of the additional overhead // for each content process. So the initial content length should generally be // *under* the next power-of-two larger than its expected length.
constexpr size_t kHashTableInitialLengthParent = 3000;
constexpr size_t kHashTableInitialLengthContent = 64;
// We use readonlyThreadsafeLookup() because we often have concurrent lookups // from multiple Stylo threads. This is safe because those threads cannot // modify sHashTable, and the main thread is blocked while Stylo threads are // doing these lookups. auto p = HashTable()->readonlyThreadsafeLookup(aPrefName); return p ? p->get() : nullptr;
}
// While notifying preference callbacks, this holds the wrapper for the // preference being notified, in order to optimize lookups. // // Note: Callbacks and lookups only happen on the main thread, so this is safe // to use without locking. staticconst PrefWrapper* gCallbackPref;
Maybe<PrefWrapper> pref_SharedLookup(constchar* aPrefName) {
MOZ_DIAGNOSTIC_ASSERT(gSharedMap, "gSharedMap must be initialized"); if (Maybe<SharedPrefMap::Pref> pref = gSharedMap->Get(aPrefName)) { return Some(*pref);
} return Nothing();
}
// Nodes must not be deleted while gCallbacksInProgress is true. // Nodes that need to be deleted are marked for deletion by nulling // out the |func| pointer. We release them at the end of this function // if we haven't reentered.
gCallbacksInProgress = true;
for (CallbackNode* node = gFirstCallback; node; node = node->Next()) { if (node->Func()) { if (node->Matches(aPrefName)) {
(node->Func())(aPrefName.get(), node->Data());
}
}
}
#ifdef DEBUG if (XRE_IsParentProcess() &&
!StaticPrefs::preferences_force_disable_check_once_policy() &&
(StaticPrefs::preferences_check_once_policy() || xpc::IsInAutomation())) { // Check that we aren't modifying a `once`-mirrored pref using that pref // name. We have about 100 `once`-mirrored prefs. std::map performs a // search in O(log n), so this is fast enough.
MOZ_ASSERT(gOnceStaticPrefsAntiFootgun); auto search = gOnceStaticPrefsAntiFootgun->find(aPrefName.get()); if (search != gOnceStaticPrefsAntiFootgun->end()) { // Run the callback.
(search->second)();
}
} #endif
}
// Keep this in sync with PrefFn in parser/src/lib.rs. typedefvoid (*PrefsParserPrefFn)(constchar* aPrefName, PrefType aType,
PrefValueKind aKind, PrefValue aValue, bool aIsSticky, bool aIsLocked);
// Keep this in sync with ErrorFn in parser/src/lib.rs. // // `aMsg` is just a borrow of the string, and must be copied if it is used // outside the lifetime of the prefs_parser_parse() call. typedefvoid (*PrefsParserErrorFn)(constchar* aMsg);
// Keep this in sync with prefs_parser_parse() in parser/src/lib.rs. bool prefs_parser_parse(constchar* aPath, PrefValueKind aKind, constchar* aBuf, size_t aLen,
PrefsParserPrefFn aPrefFn, PrefsParserErrorFn aErrorFn);
}
class Parser { public:
Parser() = default;
~Parser() = default;
// Keep this in sync with the declaration in test/gtest/Parser.cpp. void TestParseError(PrefValueKind aKind, constchar* aText,
nsCString& aErrorMsg) {
prefs_parser_parse("test", aKind, aText, strlen(aText),
TestParseErrorHandlePref, TestParseErrorHandleError);
// Copy the error messages into the outparam, then clear them from // gTestParseErrorMsgs.
aErrorMsg.Assign(gTestParseErrorMsgs);
gTestParseErrorMsgs.Truncate();
}
//=========================================================================== // nsPrefBranch et al. //===========================================================================
namespace mozilla { class PreferenceServiceReporter;
} // namespace mozilla
class PrefCallback : public PLDHashEntryHdr { friendclass mozilla::PreferenceServiceReporter;
bool KeyEquals(const PrefCallback* aKey) const { // We want to be able to look up a weakly-referencing PrefCallback after // its observer has died so we can remove it from the table. Once the // callback's observer dies, its canonical pointer is stale -- in // particular, we may have allocated a new observer in the same spot in // memory! So we can't just compare canonical pointers to determine whether // aKey refers to the same observer as this. // // Our workaround is based on the way we use this hashtable: When we ask // the hashtable to remove a PrefCallback whose weak reference has expired, // we use as the key for removal the same object as was inserted into the // hashtable. Thus we can say that if one of the keys' weak references has // expired, the two keys are equal iff they're the same object.
if (IsExpired() || aKey->IsExpired()) { returnthis == aKey;
}
if (mCanonical != aKey->mCanonical) { returnfalse;
}
// Get a reference to the callback's observer, or null if the observer was // weakly referenced and has been destroyed.
already_AddRefed<nsIObserver> GetObserver() const { if (!IsWeak()) {
nsCOMPtr<nsIObserver> copy = mStrongRef; return copy.forget();
}
// As SetCharPref, but without any check on the length of |aValue|.
nsresult SetCharPrefNoLengthCheck(constchar* aPrefName, const nsACString& aValue);
// Reject strings that are more than 1Mb, warn if strings are more than 16kb.
nsresult CheckSanityOfStringLength(constchar* aPrefName, const nsAString& aValue);
nsresult CheckSanityOfStringLength(constchar* aPrefName, const nsACString& aValue);
nsresult CheckSanityOfStringLength(constchar* aPrefName, const uint32_t aLength);
nsPrefBranch::nsPrefBranch(constchar* aPrefRoot, PrefValueKind aKind)
: mPrefRoot(aPrefRoot), mKind(aKind), mFreeingObserverList(false) {
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); if (observerService) {
++mRefCnt; // must be > 0 when we call this, or we'll get deleted!
// Add weakly so we don't have to clean up at shutdown.
observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
--mRefCnt;
}
}
// We have to do this one first because it's different to all the rest. if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) {
nsCOMPtr<nsIPrefLocalizedString> theString(
do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv)); if (NS_FAILED(rv)) { return rv;
}
if (mKind == PrefValueKind::Default) {
bNeedDefault = true;
} else { // if there is no user (or locked) value if (!Preferences::HasUserValue(pref.get()) &&
!Preferences::IsLocked(pref.get())) {
bNeedDefault = true;
}
}
// if we need to fetch the default value, do that instead, otherwise use the // value we pulled in at the top of this function if (bNeedDefault) {
nsAutoString utf16String;
rv = GetDefaultFromPropertiesFile(pref.get(), utf16String); if (NS_SUCCEEDED(rv)) {
theString->SetData(utf16String);
}
} else {
rv = GetCharPref(aPrefName, utf8String); if (NS_SUCCEEDED(rv)) {
theString->SetData(NS_ConvertUTF8toUTF16(utf8String));
}
}
if (NS_SUCCEEDED(rv)) {
theString.forget(reinterpret_cast<nsIPrefLocalizedString**>(aRetVal));
}
return rv;
}
// if we can't get the pref, there's no point in being here
rv = GetCharPref(aPrefName, utf8String); if (NS_FAILED(rv)) { return rv;
}
nsAutoCString message(nsPrintfCString( "Warning: attempting to write %d bytes to preference %s. This is bad " "for general performance and memory usage. Such an amount of data " "should rather be written to an external file.",
aLength, GetPrefName(aPrefName).get()));
for (auto iter = HashTable()->modIter(); !iter.done(); iter.next()) { // The first disjunct matches branches: e.g. a branch name "foo.bar." // matches a name "foo.bar.baz" (but it won't match "foo.barrel.baz"). // The second disjunct matches leaf nodes: e.g. a branch name "foo.bar." // matches a name "foo.bar" (by ignoring the trailing '.').
nsDependentCString name(iter.get()->Name()); if (StringBeginsWith(name, branchName) || name.Equals(branchNameNoDot)) {
iter.remove(); // The saved callback pref may be invalid now.
gCallbackPref = nullptr;
}
}
// Now that we've built up the list, run the callback on all the matching // elements.
aChildArray.SetCapacity(prefArray.Length()); for (auto& element : prefArray) { // we need to lop off mPrefRoot in case the user is planning to pass this // back to us because if they do we are going to add mPrefRoot again.
aChildArray.AppendElement(Substring(element, mPrefRoot.Length()));
}
// Hold a weak reference to the observer if so requested. if (aHoldWeak) {
nsCOMPtr<nsISupportsWeakReference> weakRefFactory =
do_QueryInterface(aObserver); if (!weakRefFactory) { // The caller didn't give us a object that supports weak reference... // tell them. return NS_ERROR_INVALID_ARG;
}
// Construct a PrefCallback with a weak reference to the observer.
pCallback = MakeUnique<PrefCallback>(prefName, weakRefFactory, this);
} else { // Construct a PrefCallback with a strong reference to the observer.
pCallback = MakeUnique<PrefCallback>(prefName, aObserver, this);
}
mObservers.WithEntryHandle(pCallback.get(), [&](auto&& p) { if (p) {
NS_WARNING(
nsPrintfCString("Ignoring duplicate observer: %s", prefName.get())
.get());
} else { // We must pass a fully qualified preference name to the callback // aDomain == nullptr is the only possible failure, and we trapped it with // NS_ENSURE_ARG above.
Preferences::RegisterCallback(NotifyObserver, prefName, pCallback.get(),
Preferences::PrefixMatch, /* isPriority */ false);
// If we're in the middle of a call to FreeObserverList, don't process this // RemoveObserver call -- the observer in question will be removed soon, if // it hasn't been already. // // It's important that we don't touch mObservers in any way -- even a Get() // which returns null might cause the hashtable to resize itself, which will // break the iteration in FreeObserverList. if (mFreeingObserverList) { return NS_OK;
}
// Remove the relevant PrefCallback from mObservers and get an owning pointer // to it. Unregister the callback first, and then let the owning pointer go // out of scope and destroy the callback. const nsCString& prefName = GetPrefName(aDomain);
PrefCallback key(prefName, aObserver, this);
mozilla::UniquePtr<PrefCallback> pCallback;
mObservers.Remove(&key, &pCallback); if (pCallback) {
rv = Preferences::UnregisterCallback(
NotifyObserver, prefName, pCallback.get(), Preferences::PrefixMatch);
}
return rv;
}
NS_IMETHODIMP
nsPrefBranch::Observe(nsISupports* aSubject, constchar* aTopic, const char16_t* aData) { // Watch for xpcom shutdown and free our observers to eliminate any cyclic // references. if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
FreeObserverList();
} return NS_OK;
}
nsCOMPtr<nsIObserver> observer = pCallback->GetObserver(); if (!observer) { // The observer has expired. Let's remove this callback.
pCallback->GetPrefBranch()->RemoveExpiredCallback(pCallback); return;
}
// Remove any root this string may contain so as to not confuse the observer // by passing them something other than what they passed us as a topic.
uint32_t len = pCallback->GetPrefBranch()->GetRootLength();
nsDependentCString suffix(aNewPref + len);
size_t nsPrefBranch::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
size_t n = aMallocSizeOf(this);
n += mPrefRoot.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf); for (constauto& entry : mObservers) { const PrefCallback* data = entry.GetWeak();
n += data->SizeOfIncludingThis(aMallocSizeOf);
}
return n;
}
void nsPrefBranch::FreeObserverList() { // We need to prevent anyone from modifying mObservers while we're iterating // over it. In particular, some clients will call RemoveObserver() when // they're removed and destructed via the iterator; we set // mFreeingObserverList to keep those calls from touching mObservers.
mFreeingObserverList = true; for (auto iter = mObservers.Iter(); !iter.Done(); iter.Next()) { auto callback = iter.UserData();
Preferences::UnregisterCallback(nsPrefBranch::NotifyObserver,
callback->GetDomain(), callback,
Preferences::PrefixMatch);
iter.Remove();
}
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); if (observerService) {
observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
}
nsresult nsPrefBranch::GetDefaultFromPropertiesFile(constchar* aPrefName,
nsAString& aReturn) { // The default value contains a URL to a .properties file.
//=========================================================================== // class Preferences and related things //===========================================================================
// clang-format off staticconstchar kPrefFileHeader[] = "// Mozilla User Preferences"
NS_LINEBREAK
NS_LINEBREAK "// DO NOT EDIT THIS FILE."
NS_LINEBREAK "//"
NS_LINEBREAK "// If you make changes to this file while the application is running,"
NS_LINEBREAK "// the changes will be overwritten when the application exits."
NS_LINEBREAK "//"
NS_LINEBREAK "// To change a preference value, you can either:"
NS_LINEBREAK "// - modify it via the UI (e.g. via about:config in the browser); or"
NS_LINEBREAK "// - set it within a user.js file in your profile."
NS_LINEBREAK
NS_LINEBREAK; // clang-format on
// Note: if sShutdown is true, sPreferences will be nullptr.
StaticRefPtr<Preferences> Preferences::sPreferences; bool Preferences::sShutdown = false;
// This globally enables or disables OMT pref writing, both sync and async. static int32_t sAllowOMTPrefWrite = -1;
// Write the preference data to a file. class PreferencesWriter final { public:
PreferencesWriter() = default;
// Execute a "safe" save by saving through a tempfile.
rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink), aFile,
-1, 0600); if (NS_FAILED(rv)) { return rv;
}
// Tell the safe output stream to overwrite the real prefs file. // (It'll abort if there were any errors during writing.)
nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream);
MOZ_ASSERT(safeStream, "expected a safe output stream!"); if (safeStream) {
rv = safeStream->Finish();
}
#ifdef DEBUG if (NS_FAILED(rv)) {
NS_WARNING("failed to save prefs file! possible data loss");
} #endif
return rv;
}
staticvoid Flush() {
MOZ_DIAGNOSTIC_ASSERT(sPendingWriteCount >= 0); // SpinEventLoopUntil is unfortunate, but ultimately it's the best thing // we can do here given the constraint that we need to ensure that // the preferences on disk match what we have in memory. We could // easily perform the write here ourselves by doing exactly what // happens in PWRunnable::Run. This would be the right thing to do // if we're stuck here because other unrelated runnables are taking // a long time, and the wrong thing to do if PreferencesWriter::Write // is what takes a long time, as we would be trading a SpinEventLoopUntil // for a synchronous disk write, wherein we could not even spin the // event loop. Given that PWRunnable generally runs on a thread pool, // if we're stuck here, it's likely because of PreferencesWriter::Write // and not some other runnable. Thus, spin away.
mozilla::SpinEventLoopUntil("PreferencesWriter::Flush"_ns,
[]() { return sPendingWriteCount <= 0; });
}
// This is the data that all of the runnables (see below) will attempt // to write. It will always have the most up to date version, or be // null, if the up to date information has already been written out. static Atomic<PrefSaveData*> sPendingWriteData;
// This is the number of writes via PWRunnables which have been dispatched // but not yet completed. This is intended to be used by Flush to ensure // that there are no outstanding writes left incomplete, and thus our prefs // on disk are in sync with what we have in memory. static Atomic<int> sPendingWriteCount;
// See PWRunnable::Run for details on why we need this lock. static StaticMutex sWritingToFile MOZ_UNANNOTATED;
};
class PWRunnable : public Runnable { public: explicit PWRunnable(
nsIFile* aFile,
UniquePtr<MozPromiseHolder<Preferences::WritePrefFilePromise>>
aPromiseHolder)
: Runnable("PWRunnable"),
mFile(aFile),
mPromiseHolder(std::move(aPromiseHolder)) {}
NS_IMETHOD Run() override { // Preference writes are handled a bit strangely, in that a "newer" // write is generally regarded as always better. For this reason, // sPendingWriteData can be overwritten multiple times before anyone // gets around to actually using it, minimizing writes. However, // once we've acquired sPendingWriteData we've reached a // "point of no return" and have to complete the write. // // Unfortunately, this design allows the following behaviour: // // 1. write1 is queued up // 2. thread1 acquires write1 // 3. write2 is queued up // 4. thread2 acquires write2 // 5. thread1 and thread2 concurrently clobber each other // // To avoid this, we use this lock to ensure that only one thread // at a time is trying to acquire the write, and when it does, // all other threads are prevented from acquiring writes until it // completes the write. New writes are still allowed to be queued // up in this time. // // Although it's atomic, the acquire needs to be guarded by the mutex // to avoid reordering of writes -- we don't want an older write to // run after a newer one. To avoid this causing too much waiting, we check // if sPendingWriteData is already null before acquiring the mutex. If it // is, then there's definitely no work to be done (or someone is in the // middle of doing it for us). // // Note that every time a new write is queued up, a new write task is // is also queued up, so there will always be a task that can see the newest // write. // // Ideally this lock wouldn't be necessary, and the PreferencesWriter // would be used more carefully, but it's hard to untangle all that.
nsresult rv = NS_OK; if (PreferencesWriter::sPendingWriteData) {
StaticMutexAutoLock lock(PreferencesWriter::sWritingToFile); // If we get a nullptr on the exchange, it means that somebody // else has already processed the request, and we can just return.
UniquePtr<PrefSaveData> prefs(
PreferencesWriter::sPendingWriteData.exchange(nullptr)); if (prefs) {
rv = PreferencesWriter::Write(mFile, *prefs); // Make a copy of these so we can have them in runnable lambda. // nsIFile is only there so that we would never release the // ref counted pointer off main thread.
nsresult rvCopy = rv;
nsCOMPtr<nsIFile> fileCopy(mFile);
SchedulerGroup::Dispatch(NS_NewRunnableFunction( "Preferences::WriterRunnable",
[fileCopy, rvCopy, promiseHolder = std::move(mPromiseHolder)] {
MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (NS_FAILED(rvCopy)) {
Preferences::HandleDirty();
} if (promiseHolder) {
promiseHolder->ResolveIfExists(true, __func__);
}
}));
}
} // We've completed the write to the best of our abilities, whether // we had prefs to write or another runnable got to them first. If // PreferencesWriter::Write failed, this is still correct as the // write is no longer outstanding, and the above HandleDirty call // will just start the cycle again.
PreferencesWriter::sPendingWriteCount--; return rv;
}
private:
~PWRunnable() { if (mPromiseHolder) {
mPromiseHolder->RejectIfExists(NS_ERROR_ABORT, __func__);
}
}
// Although this is a member of Preferences, it measures sPreferences and // several other global structures. /* static */ void Preferences::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
PrefsSizes& aSizes) { if (!sPreferences) { return;
}
if (gSharedMap) {
sizes.mMisc += mallocSizeOf(gSharedMap);
}
#ifdef ACCESS_COUNTS if (gAccessCounts) {
sizes.mMisc += gAccessCounts->ShallowSizeOfIncludingThis(mallocSizeOf);
} #endif
MOZ_COLLECT_REPORT("explicit/preferences/hash-table", KIND_HEAP, UNITS_BYTES,
sizes.mHashTable, "Memory used by libpref's hash table.");
MOZ_COLLECT_REPORT("explicit/preferences/pref-values", KIND_HEAP, UNITS_BYTES,
sizes.mPrefValues, "Memory used by PrefValues hanging off the hash table.");
MOZ_COLLECT_REPORT("explicit/preferences/string-values", KIND_HEAP,
UNITS_BYTES, sizes.mStringValues, "Memory used by libpref's string pref values.");
MOZ_COLLECT_REPORT("explicit/preferences/root-branches", KIND_HEAP,
UNITS_BYTES, sizes.mRootBranches, "Memory used by libpref's root branches.");
MOZ_COLLECT_REPORT("explicit/preferences/pref-name-arena", KIND_HEAP,
UNITS_BYTES, sizes.mPrefNameArena, "Memory used by libpref's arena for pref names.");
MOZ_COLLECT_REPORT("explicit/preferences/callbacks/objects", KIND_HEAP,
UNITS_BYTES, sizes.mCallbacksObjects, "Memory used by pref callback objects.");
MOZ_COLLECT_REPORT("explicit/preferences/callbacks/domains", KIND_HEAP,
UNITS_BYTES, sizes.mCallbacksDomains, "Memory used by pref callback domains (pref names and " "prefixes).");
MOZ_COLLECT_REPORT("explicit/preferences/misc", KIND_HEAP, UNITS_BYTES,
sizes.mMisc, "Miscellaneous memory used by libpref.");
if (gSharedMap) { if (XRE_IsParentProcess()) {
MOZ_COLLECT_REPORT("explicit/preferences/shared-memory-map", KIND_NONHEAP,
UNITS_BYTES, gSharedMap->MapSize(), "The shared memory mapping used to share a " "snapshot of preference values across processes.");
}
}
nsPrefBranch* rootBranch = static_cast<nsPrefBranch*>(Preferences::GetRootBranch()); if (!rootBranch) { return NS_OK;
}
size_t numStrong = 0;
size_t numWeakAlive = 0;
size_t numWeakDead = 0;
nsTArray<nsCString> suspectPreferences; // Count of the number of referents for each preference.
nsTHashMap<nsCStringHashKey, uint32_t> prefCounter;
for (constauto& entry : rootBranch->mObservers) { auto* callback = entry.GetWeak();
// Keep track of preferences that have a suspiciously large number of // referents (a symptom of a leak). if (currentCount == kSuspectReferentCount) {
suspectPreferences.AppendElement(callback->GetDomain());
}
}
for (uint32_t i = 0; i < suspectPreferences.Length(); i++) {
nsCString& suspect = suspectPreferences[i]; const uint32_t totalReferentCount = prefCounter.Get(suspect);
aHandleReport->Callback( /* process = */ ""_ns, suspectPath, KIND_OTHER, UNITS_COUNT,
totalReferentCount, "A preference with a suspiciously large number " "referents (symptom of a leak)."_ns,
aData);
}
MOZ_COLLECT_REPORT( "preference-service/referent/strong", KIND_OTHER, UNITS_COUNT, numStrong, "The number of strong referents held by the preference service.");
MOZ_COLLECT_REPORT( "preference-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT,
numWeakAlive, "The number of weak referents held by the preference service that are " "still alive.");
MOZ_COLLECT_REPORT( "preference-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT,
numWeakDead, "The number of weak referents held by the preference service that are " "dead.");
return NS_OK;
}
namespace {
class AddPreferencesMemoryReporterRunnable : public Runnable { public:
AddPreferencesMemoryReporterRunnable()
: Runnable("AddPreferencesMemoryReporterRunnable") {}
static Maybe<bool> TelemetryPrefValue() { // Leave it unchanged if it's already set. // XXX: how could it already be set? if (Preferences::GetType(kTelemetryPref) != nsIPrefBranch::PREF_INVALID) { return Nothing();
}
// Determine the correct default for toolkit.telemetry.enabled. If this // build has MOZ_TELEMETRY_ON_BY_DEFAULT *or* we're on the beta channel, // telemetry is on by default, otherwise not. This is necessary so that // beta users who are testing final release builds don't flipflop defaults. # ifdef MOZ_TELEMETRY_ON_BY_DEFAULT return Some(true); # else
nsAutoCString channelPrefValue;
Unused << Preferences::GetCString(kChannelPref, channelPrefValue,
PrefValueKind::Default); return Some(channelPrefValue.EqualsLiteral("beta")); # endif
}
staticbool TelemetryPrefValue() { // For platforms with Unified Telemetry (here meaning not-Android), // toolkit.telemetry.enabled determines whether we send "extended" data. // We only want extended data from pre-release channels due to size.
constexpr auto channel = MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL) ""_ns;
# ifndef MOZILLA_OFFICIAL // Local developer builds: non-official builds on the "default" channel. if (channel.EqualsLiteral("default")) { returntrue;
} # endif
// Release Candidate builds: builds that think they are release builds, but // are shipped to beta users. if (channel.EqualsLiteral("release")) {
nsAutoCString channelPrefValue;
Unused << Preferences::GetCString(kChannelPref, channelPrefValue,
PrefValueKind::Default); if (channelPrefValue.EqualsLiteral("beta")) { returntrue;
}
}
if (!XRE_IsParentProcess()) {
MOZ_ASSERT(gChangedDomPrefs); for (unsignedint i = 0; i < gChangedDomPrefs->Length(); i++) {
Preferences::SetPreference(gChangedDomPrefs->ElementAt(i));
}
gChangedDomPrefs = nullptr;
} else { // Check if there is a deployment configuration file. If so, set up the // pref config machinery, which will actually read the file.
nsAutoCString lockFileName;
nsresult rv = Preferences::GetCString("general.config.filename",
lockFileName, PrefValueKind::User); if (NS_SUCCEEDED(rv)) {
NS_CreateServicesFromCategory( "pref-config-startup", static_cast<nsISupports*>(static_cast<void*>(sPreferences)), "pref-config-startup");
}
if (NS_FAILED(rv)) {
sPreferences = nullptr; return nullptr;
}
}
constchar* defaultPrefs = getenv("MOZ_DEFAULT_PREFS"); if (defaultPrefs) {
parsePrefData(nsCString(defaultPrefs), PrefValueKind::Default);
}
// Preferences::GetInstanceForService() can be called from GetService(), and // RegisterStrongMemoryReporter calls GetService(nsIMemoryReporter). To // avoid a potential recursive GetService() call, we can't register the // memory reporter here; instead, do it off a runnable.
RefPtr<AddPreferencesMemoryReporterRunnable> runnable = new AddPreferencesMemoryReporterRunnable();
NS_DispatchToMainThread(runnable);
MOZ_ASSERT(!gChangedDomPrefs);
gChangedDomPrefs = new nsTArray<dom::Pref>();
char* p = aStr; while (*p != '\0') {
dom::Pref pref;
p = Pref::Deserialize(p, &pref);
gChangedDomPrefs->AppendElement(pref);
}
// We finished parsing on a '\0'. That should be the last char in the shared // memory. (aPrefsLen includes the '\0'.)
MOZ_ASSERT(p == aStr + aPrefsLen - 1);
nsTArray<Pref*> toRepopulate;
NameArena* newPrefNameArena = new NameArena(); for (auto iter = HashTable()->modIter(); !iter.done(); iter.next()) { if (!ShouldSanitizePreference(iter.get().get())) {
iter.get()->AddToMap(builder);
} else {
Pref* pref = iter.getMutable().release();
pref->RelocateName(newPrefNameArena);
toRepopulate.AppendElement(pref);
}
}
// Store the current value of `once`-mirrored prefs. After this point they // will be immutable.
StaticPrefs::RegisterOncePrefs(builder);
gSharedMap = new SharedPrefMap(std::move(builder));
// Once we've built a snapshot of the database, there's no need to continue // storing dynamic copies of the preferences it contains. Once we reset the // hashtable, preference lookups will fall back to the snapshot for any // preferences not in the dynamic hashtable. // // And since the majority of the database is now contained in the snapshot, // we can initialize the hashtable with the expected number of per-session // changed preferences, rather than the expected total number of // preferences.
HashTable()->clearAndCompact();
Unused << HashTable()->reserve(kHashTableInitialLengthContent);
for (uint32_t i = 0; i < toRepopulate.Length(); i++) { auto pref = toRepopulate[i]; auto p = HashTable()->lookupForAdd(pref->Name());
MOZ_ASSERT(!p.found());
Unused << HashTable()->add(p, pref);
}
}
// Prefs which are set before we initialize the profile are silently // discarded. This is stupid, but there are various tests which depend on // this behavior.
sPreferences->ResetUserPrefs();
if (!nsCRT::strcmp(aTopic, "profile-before-change")) { // Normally prefs aren't written after this point, and so we kick off // an asynchronous pref save so that I/O can be done in parallel with // other shutdown. if (AllowOffMainThreadSave()) {
SavePrefFile(nullptr);
}
} elseif (!nsCRT::strcmp(aTopic, "profile-before-change-telemetry")) { // It's possible that a profile-before-change observer after ours // set a pref. A blocking save here re-saves if necessary and also waits // for any pending saves to complete.
SavePrefFileBlocking();
MOZ_ASSERT(!mDirty, "Preferences should not be dirty");
mProfileShutdown = true;
} elseif (!nsCRT::strcmp(aTopic, "suspend_process_notification")) { // Our process is being suspended. The OS may wake our process later, // or it may kill the process. In case our process is going to be killed // from the suspended state, we save preferences before suspending.
rv = SavePrefFileBlocking();
}
return rv;
}
NS_IMETHODIMP
Preferences::ReadDefaultPrefsFromFile(nsIFile* aFile) {
ENSURE_PARENT_PROCESS("Preferences::ReadDefaultPrefsFromFile", "all prefs");
if (!aFile) {
NS_ERROR("ReadDefaultPrefsFromFile requires a parameter"); return NS_ERROR_INVALID_ARG;
}
nsresult Preferences::ResetUserPrefs() {
ENSURE_PARENT_PROCESS("Preferences::ResetUserPrefs", "all prefs");
NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
MOZ_ASSERT(NS_IsMainThread());
Vector<constchar*> prefNames; for (auto iter = HashTable()->modIter(); !iter.done(); iter.next()) {
Pref* pref = iter.get().get();
if (pref->HasUserValue()) { if (!prefNames.append(pref->Name())) { return NS_ERROR_OUT_OF_MEMORY;
}
pref->ClearUserValue(); if (!pref->HasDefaultValue()) {
iter.remove();
}
}
}
for (constchar* prefName : prefNames) {
NotifyCallbacks(nsDependentCString(prefName));
}
Preferences::HandleDirty(); return NS_OK;
}
bool Preferences::AllowOffMainThreadSave() { // Put in a preference that allows us to disable off main thread preference // file save. if (sAllowOMTPrefWrite < 0) { bool value = false;
Preferences::GetBool("preferences.allow.omt-write", &value);
sAllowOMTPrefWrite = value ? 1 : 0;
}
return !!sAllowOMTPrefWrite;
}
nsresult Preferences::SavePrefFileBlocking() { if (mDirty) { return SavePrefFileInternal(nullptr, SaveMethod::Blocking);
}
// If we weren't dirty to start, SavePrefFileInternal will early exit so // there is no guarantee that we don't have oustanding async saves in the // pipe. Since the contract of SavePrefFileOnMainThread is that the file on // disk matches the preferences, we have to make sure those requests are // completed.
if (AllowOffMainThreadSave()) {
PreferencesWriter::Flush();
}
NS_IMETHODIMP
Preferences::SavePrefFile(nsIFile* aFile) { // This is the method accessible from service API. Make it off main thread. return SavePrefFileInternal(aFile, SaveMethod::Asynchronous);
}
if (MOZ_UNLIKELY(result.Failed())) { return result.StealNSResult();
}
nsMainThreadPtrHandle<Promise> domPromiseHolder( new nsMainThreadPtrHolder<Promise>("Preferences::BackupPrefFile promise",
promise));
auto mozPromiseHolder = MakeUnique<MozPromiseHolder<WritePrefFilePromise>>();
RefPtr<WritePrefFilePromise> writePrefPromise =
mozPromiseHolder->Ensure(__func__);
nsresult rv = WritePrefFile(aFile, SaveMethod::Asynchronous,
std::move(mozPromiseHolder)); if (NS_FAILED(rv)) { // WritePrefFile is responsible for rejecting the underlying MozPromise in // the event that it the method failed somewhere. return rv;
}
// When the parent process clears a pref's user value we get a DomPref here // with no default value and no user value. There are two possibilities. // // - There was an existing pref with only a user value. FromDomPref() will // have just cleared that user value, so the pref can be removed. // // - There was no existing pref. FromDomPref() will have done nothing, and // `pref` will be valueless. We will end up adding and removing the value // needlessly, but that's ok because this case is rare. // if (!pref->HasDefaultValue() && !pref->HasUserValue() &&
!pref->IsSanitized()) { // If the preference exists in the shared map, we need to keep the dynamic // entry around to mask it. if (gSharedMap->Has(pref->Name())) {
pref->SetType(PrefType::None);
} else {
HashTable()->remove(prefName.get());
}
pref = nullptr;
}
// Note: we don't have to worry about HandleDirty() because we are setting // prefs in the content process that have come from the parent process.
if (valueChanged) { if (pref) {
NotifyCallbacks(prefName, PrefWrapper(pref));
} else {
NotifyCallbacks(prefName);
}
}
}
// TODO: Cache this stuff and allow consumers to share branches (hold weak // references, I think).
RefPtr<nsPrefBranch> prefBranch = new nsPrefBranch(aPrefRoot, PrefValueKind::Default); if (!prefBranch) { return NS_ERROR_OUT_OF_MEMORY;
}
// We would much prefer to use C++ lambdas, but we cannot convert // lambdas that capture (here, the underlying observer) to C pointer // to functions. So, here we are, with icky C callbacks. Be aware // that nothing is thread-safe here because there's a single global // `nsIPrefObserver` instance. Use this from the main thread only.
nsIPrefObserver* PrefObserver = nullptr;
rv = openPrefFile(file, PrefValueKind::User); if (rv == NS_ERROR_FILE_NOT_FOUND) { // This is a normal case for new users.
rv = NS_OK;
} else { // Store the last modified time of the file while we've got it. // We don't really care if this fails.
Unused << file->GetLastModifiedTime(&mUserPrefsFileLastModifiedAtStartup);
if (NS_FAILED(rv)) { // Save a backup copy of the current (invalid) prefs file, since all prefs // from the error line to the end of the file will be lost (bug 361102). // TODO we should notify the user about it (bug 523725).
glean::preferences::prefs_file_was_invalid.Set(true);
MakeBackupPrefFile(file);
}
}
nsresult Preferences::MakeBackupPrefFile(nsIFile* aFile) { // Example: this copies "prefs.js" to "Invalidprefs.js" in the same directory. // "Invalidprefs.js" is removed if it exists, prior to making the copy.
nsAutoString newFilename;
nsresult rv = aFile->GetLeafName(newFilename);
NS_ENSURE_SUCCESS(rv, rv);
nsresult Preferences::SavePrefFileInternal(nsIFile* aFile,
SaveMethod aSaveMethod) {
ENSURE_PARENT_PROCESS("Preferences::SavePrefFileInternal", "all prefs");
// We allow different behavior here when aFile argument is not null, but it // happens to be the same as the current file. It is not clear that we // should, but it does give us a "force" save on the unmodified pref file // (see the original bug 160377 when we added this.)
if (nullptr == aFile) {
mSavePending = false;
// Off main thread writing only if allowed. if (!AllowOffMainThreadSave()) {
aSaveMethod = SaveMethod::Blocking;
}
// The mDirty flag tells us if we should write to mCurrentFile. We only // check this flag when the caller wants to write to the default. if (!mDirty) { return NS_OK;
}
// Check for profile shutdown after mDirty because the runnables from // HandleDirty() can still be pending. if (mProfileShutdown) {
NS_WARNING("Cannot save pref file after profile shutdown."); return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
// It's possible that we never got a prefs file.
nsresult rv = NS_OK; if (mCurrentFile) {
rv = WritePrefFile(mCurrentFile, aSaveMethod);
}
// If we succeeded writing to mCurrentFile, reset the dirty flag. if (NS_SUCCEEDED(rv)) {
mDirty = false;
} return rv;
} else { // We only allow off main thread writes on mCurrentFile using this method. // If you want to write asynchronously, use BackupPrefFile instead. return WritePrefFile(aFile, SaveMethod::Blocking);
}
}
if (mCurrentFile) {
rv = mCurrentFile->Equals(aFile, &writingToCurrent); if (NS_FAILED(rv)) {
REJECT_IF_PROMISE_HOLDER_EXISTS(rv);
}
}
// Put the newly constructed preference data into sPendingWriteData // for the next request to pick up
prefs.reset(PreferencesWriter::sPendingWriteData.exchange(prefs.release())); if (prefs && !writingToCurrent) {
MOZ_ASSERT(!aPromiseHolder, "Shouldn't be able to enter here if aPromiseHolder is set"); // There was a previous request writing to the default location that // hasn't been processed. It will do the work of eventually writing this // latest batch of data to disk. return NS_OK;
}
// There were no previous requests. Dispatch one since sPendingWriteData has // the up to date information.
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { bool async = aSaveMethod == SaveMethod::Asynchronous;
// Increment sPendingWriteCount, even though it's redundant to track this // in the case of a sync runnable; it just makes it easier to simply // decrement this inside PWRunnable. We cannot use the constructor / // destructor for increment/decrement, as on dispatch failure we might // leak the runnable in order to not destroy it on the wrong thread, which // would make us get stuck in an infinite SpinEventLoopUntil inside // PreferencesWriter::Flush. Better that in future code we miss an // increment of sPendingWriteCount and cause a simple crash due to it // ending up negative. // // If aPromiseHolder is not null, ownership is transferred to PWRunnable. // The PWRunnable will automatically reject the MozPromise if it is // destroyed before being resolved or rejected by the Run method.
PreferencesWriter::sPendingWriteCount++; if (async) {
rv = target->Dispatch(new PWRunnable(aFile, std::move(aPromiseHolder)),
nsIEventTarget::DISPATCH_NORMAL);
} else {
rv = SyncRunnable::DispatchToThread(
target, new PWRunnable(aFile, std::move(aPromiseHolder)), true);
} if (NS_FAILED(rv)) { // If our dispatch failed, we should correct our bookkeeping to // avoid shutdown hangs.
PreferencesWriter::sPendingWriteCount--; // No need to reject the aPromiseHolder here, as the PWRunnable will // have already done so. return rv;
} return NS_OK;
}
// If we can't get the thread for writing, for whatever reason, do the main // thread write after making some noise.
MOZ_ASSERT(false, "failed to get the target thread for OMT pref write");
}
// This will do a main thread write. It is safe to do it this way because // AllowOffMainThreadSave() returns a consistent value for the lifetime of // the parent process.
PrefSaveData prefsData = pref_savePrefs();
// If we were given a MozPromiseHolder, this means the caller is attempting // to write prefs asynchronously to the disk - but if we get here, it means // that AllowOffMainThreadSave() return false, and that we will be forced // to write on the main thread instead. We still have to resolve or reject // that MozPromise regardless.
nsresult rv = PreferencesWriter::Write(aFile, prefsData); if (aPromiseHolder) {
NS_WARNING( "Cannot write to prefs asynchronously, as AllowOffMainThreadSave() " "returned false."); if (NS_SUCCEEDED(rv)) {
aPromiseHolder->ResolveIfExists(true, __func__);
} else {
aPromiseHolder->RejectIfExists(rv, __func__);
}
} return rv;
// Load default pref files from a directory. The files in the directory are // sorted reverse-alphabetically. static nsresult pref_LoadPrefsInDir(nsIFile* aDir) {
MOZ_ASSERT(XRE_IsParentProcess());
nsresult rv, rv2;
nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
// This may fail in some normal cases, such as embedders who do not use a // GRE.
rv = aDir->GetDirectoryEntries(getter_AddRefs(dirIterator)); if (NS_FAILED(rv)) { // If the directory doesn't exist, then we have no reason to complain. We // loaded everything (and nothing) successfully. if (rv == NS_ERROR_FILE_NOT_FOUND) {
rv = NS_OK;
} return rv;
}
// These preference getter wrappers allow us to look up the value for static // preferences based on their native types, rather than manually mapping them to // the appropriate Preferences::Get* functions. // We define these methods in a struct which is made friend of Preferences in // order to access private members. struct Internals { template <typename T> static nsresult GetPrefValue(constchar* aPrefName, T&& aResult,
PrefValueKind aKind) {
nsresult rv = NS_ERROR_UNEXPECTED;
NS_ENSURE_TRUE(Preferences::InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
if (Maybe<PrefWrapper> pref = pref_Lookup(aPrefName)) {
rv = pref->GetValue(aKind, std::forward<T>(aResult));
nsresult rv = GetPrefValue(aPref, &value, PrefValueKind::User); if (NS_SUCCEEDED(rv)) {
AssignMirror(*static_cast<T*>(aMirror),
std::forward<StripAtomic<T>>(value));
} else { // GetPrefValue() can fail if the update is caused by the pref being // deleted or if it fails to make a cast. This assertion is the only place // where we safeguard these. In this case the mirror variable will be // untouched, thus keeping the value it had prior to the change. // (Note that this case won't happen for a deletion via DeleteBranch() // unless bug 343600 is fixed, but it will happen for a deletion via // ClearUserPref().)
NS_WARNING(nsPrintfCString("Pref changed failure: %s\n", aPref).get());
MOZ_ASSERT(false);
}
}
// Initialize default preference JavaScript buffers from appropriate TEXT // resources. /* static */
nsresult Preferences::InitInitialObjects(bool aIsStartup) {
MOZ_ASSERT(NS_IsMainThread());
if (!XRE_IsParentProcess()) {
MOZ_DIAGNOSTIC_ASSERT(gSharedMap); if (aIsStartup) {
StaticPrefs::StartObservingAlwaysPrefs();
} return NS_OK;
}
// Initialize static prefs before prefs from data files so that the latter // will override the former.
StaticPrefs::InitAll();
// In the omni.jar case, we load the following prefs: // - jar:$gre/omni.jar!/greprefs.js // - jar:$gre/omni.jar!/defaults/pref/*.js // // In the non-omni.jar case, we load: // - $gre/greprefs.js // // In both cases, we also load: // - $gre/defaults/pref/*.js // // This is kept for bug 591866 (channel-prefs.js should not be in omni.jar) // in the `$app == $gre` case; we load all files instead of channel-prefs.js // only to have the same behaviour as `$app != $gre`, where this is required // as a supported location for GRE preferences. // // When `$app != $gre`, we additionally load, in the omni.jar case: // - jar:$app/omni.jar!/defaults/preferences/*.js // - $app/defaults/preferences/*.js // // and in the non-omni.jar case: // - $app/defaults/preferences/*.js // // When `$app == $gre`, we additionally load, in the omni.jar case: // - jar:$gre/omni.jar!/defaults/preferences/*.js // // Thus, in the omni.jar case, we always load app-specific default // preferences from omni.jar, whether or not `$app == $gre`.
RefPtr<nsZipArchive> jarReader = Omnijar::GetReader(Omnijar::GRE); if (jarReader) { #ifdef MOZ_WIDGET_ANDROID // Try to load an architecture-specific greprefs.js first. This will be // present in FAT AAR builds of GeckoView on Android. constchar* abi = getenv("MOZ_ANDROID_CPU_ABI"); if (abi) {
nsAutoCString path;
path.AppendPrintf("%s/greprefs.js", abi);
rv = pref_ReadPrefFromJar(jarReader, path.get());
}
if (NS_FAILED(rv)) { // Fallback to toplevel greprefs.js if arch-specific load fails.
rv = pref_ReadPrefFromJar(jarReader, "greprefs.js");
} #else // Load jar:$gre/omni.jar!/greprefs.js.
rv = pref_ReadPrefFromJar(jarReader, "greprefs.js"); #endif
NS_ENSURE_SUCCESS(rv, rv);
rv = openPrefFile(greprefsFile, PrefValueKind::Default); if (NS_FAILED(rv)) {
NS_WARNING( "Error parsing GRE default preferences. Is this an old-style " "embedding app?");
}
}
#ifdef MOZ_WIDGET_COCOA // On macOS, channel-prefs.js is no longer bundled with the application and // the "app.update.channel" pref is now read from a Framework instead. // Previously, channel-prefs.js was read as one of the files in // NS_APP_PREF_DEFAULTS_50_DIR (see just above). See bug 1799332 for more // info.
nsAutoCString appUpdatePrefKey;
appUpdatePrefKey.Assign(kChannelPref);
nsAutoCString appUpdatePrefValue;
PrefValue channelPrefValue;
channelPrefValue.mStringVal = MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL); if (ChannelPrefsUtil::GetChannelPrefValue(appUpdatePrefValue)) {
channelPrefValue.mStringVal = appUpdatePrefValue.get();
}
pref_SetPref(appUpdatePrefKey, PrefType::String, PrefValueKind::Default,
channelPrefValue, /* isSticky */ false, /* isLocked */ true, /* fromInit */ true); #endif
// Load jar:$app/omni.jar!/defaults/preferences/*.js // or jar:$gre/omni.jar!/defaults/preferences/*.js.
RefPtr<nsZipArchive> appJarReader = Omnijar::GetReader(Omnijar::APP);
// GetReader(Omnijar::APP) returns null when `$app == $gre`, in // which case we look for app-specific default preferences in $gre. if (!appJarReader) {
appJarReader = Omnijar::GetReader(Omnijar::GRE);
}
if (appJarReader) {
rv = appJarReader->FindInit("defaults/preferences/*.js$",
getter_Transfers(find));
NS_ENSURE_SUCCESS(rv, rv);
prefEntries.Clear(); while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
prefEntries.AppendElement(Substring(entryName, entryNameLen));
}
prefEntries.Sort(); for (uint32_t i = prefEntries.Length(); i--;) {
rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get()); if (NS_FAILED(rv)) {
NS_WARNING("Error parsing preferences.");
}
}
#ifdef MOZ_BACKGROUNDTASKS if (BackgroundTasks::IsBackgroundTaskMode()) {
rv = appJarReader->FindInit("defaults/backgroundtasks/*.js$",
getter_Transfers(find));
NS_ENSURE_SUCCESS(rv, rv);
prefEntries.Clear(); while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
prefEntries.AppendElement(Substring(entryName, entryNameLen));
}
prefEntries.Sort(); for (uint32_t i = prefEntries.Length(); i--;) {
rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get()); if (NS_FAILED(rv)) {
NS_WARNING("Error parsing preferences.");
}
}
} #endif
}
nsCOMPtr<nsISimpleEnumerator> list;
dirSvc->Get(NS_APP_PREFS_DEFAULTS_DIR_LIST, NS_GET_IID(nsISimpleEnumerator),
getter_AddRefs(list)); if (list) { bool hasMore; while (NS_SUCCEEDED(list->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> elem;
list->GetNext(getter_AddRefs(elem)); if (!elem) { continue;
}
nsCOMPtr<nsIFile> path = do_QueryInterface(elem); if (!path) { continue;
}
// Do we care if a file provided by this process fails to load?
pref_LoadPrefsInDir(path);
}
}
#ifdefined(MOZ_WIDGET_GTK) // To ensure the system-wide preferences are not overwritten by // firefox/browser/defauts/preferences/*.js we need to load // the /etc/firefox/defaults/pref/*.js settings as last. // Under Flatpak, the NS_OS_SYSTEM_CONFIG_DIR points to /app/etc/firefox
nsCOMPtr<nsIFile> defaultSystemPrefDir;
rv = NS_GetSpecialDirectory(NS_OS_SYSTEM_CONFIG_DIR,
getter_AddRefs(defaultSystemPrefDir));
NS_ENSURE_SUCCESS(rv, rv);
defaultSystemPrefDir->AppendNative("defaults"_ns);
defaultSystemPrefDir->AppendNative("pref"_ns);
if (XRE_IsParentProcess()) {
SetupTelemetryPref();
}
if (aIsStartup) { // Now that all prefs have their initial values, install the callbacks for // `always`-mirrored static prefs. We do this now rather than in // StaticPrefs::InitAll() so that the callbacks don't need to be traversed // while we load prefs from data files.
StaticPrefs::StartObservingAlwaysPrefs();
}
constauto& prefName = nsDependentCString{aPrefName}; auto result = pref_LookupForModify(
prefName, [](const PrefWrapper& aPref) { return aPref.HasUserValue(); }); if (result.isErr()) { return NS_OK;
}
if (Pref* pref = result.unwrap()) {
pref->ClearUserValue();
if (!pref->HasDefaultValue()) {
MOZ_ASSERT(
!gSharedMap || !pref->IsSanitized() || !gSharedMap->Has(pref->Name()), "A sanitized pref should never be in the shared pref map."); if (!pref->IsSanitized() &&
(!gSharedMap || !gSharedMap->Has(pref->Name()))) {
HashTable()->remove(aPrefName);
} else {
pref->SetType(PrefType::None);
}
Maybe<PrefWrapper> pref = pref_Lookup(aPrefName); if (!pref.isSome()) { return PREF_INVALID;
}
switch (pref->Type()) { case PrefType::String: return PREF_STRING;
case PrefType::Int: return PREF_INT;
case PrefType::Bool: return PREF_BOOL;
case PrefType::None: if (IsPreferenceSanitized(aPrefName)) {
glean::security::pref_usage_content_process.Record(Some(
glean::security::PrefUsageContentProcessExtra{Some(aPrefName)}));
if (sCrashOnBlocklistedPref) {
MOZ_CRASH_UNSAFE_PRINTF( "Should not access the preference '%s' in the Content Processes",
aPrefName);
} else { return PREF_INVALID;
}
}
[[fallthrough]];
auto node = new CallbackNode(aPrefNode, aCallback, aData, aMatchKind);
if (aIsPriority) { // Add to the start of the list.
node->SetNext(gFirstCallback);
gFirstCallback = node; if (!gLastPriorityNode) {
gLastPriorityNode = node;
}
} else { // Add to the start of the non-priority part of the list. if (gLastPriorityNode) {
node->SetNext(gLastPriorityNode->Next());
gLastPriorityNode->SetNext(node);
} else {
node->SetNext(gFirstCallback);
gFirstCallback = node;
}
}
template <typename T> staticvoid InitAlwaysPref(const nsCString& aName, T* aCache,
StripAtomic<T> aDefaultValue) { // Only called in the parent process. Set/reset the pref value and the // `always` mirror to the default value. // `once` mirrors will be initialized lazily in InitOncePrefs().
InitPref(aName, aDefaultValue);
*aCache = aDefaultValue;
}
staticvoid InitAlwaysPref(const nsCString& aName, DataMutexString& aCache, const nsLiteralCString& aDefaultValue) { // Only called in the parent process. Set/reset the pref value and the // `always` mirror to the default value. // `once` mirrors will be initialized lazily in InitOncePrefs().
InitPref_String(aName, aDefaultValue.get());
Internals::AssignMirror(aCache, aDefaultValue);
}
void MaybeInitOncePrefs() { if (MOZ_LIKELY(sOncePrefRead)) { // `once`-mirrored prefs have already been initialized to their default // value. return;
}
StaticMutexAutoLock lock(sOncePrefMutex); if (NS_IsMainThread()) {
InitOncePrefs();
} else {
RefPtr<Runnable> runnable = NS_NewRunnableFunction( "Preferences::MaybeInitOncePrefs", [&]() { InitOncePrefs(); }); // This logic needs to run on the main thread
SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), runnable);
}
sOncePrefRead = true;
}
// For all prefs we generate some initialization code. // // The InitPref_*() functions have a type suffix to avoid ambiguity between // prefs having int32_t and float default values. That suffix is not needed // for the InitAlwaysPref() functions because they take a pointer parameter, // which prevents automatic int-to-float coercion. #define NEVER_PREF(name, cpp_type, value) \
InitPref_##cpp_type(name ""_ns, value); #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) \
InitAlwaysPref(name ""_ns, &sMirror_##full_id, value); #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value) \
InitAlwaysPref(name ""_ns, sMirror_##full_id, value); #define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
InitPref_##cpp_type(name ""_ns, value); #include"mozilla/StaticPrefListAll.h" #undef NEVER_PREF #undef ALWAYS_PREF #undef ALWAYS_DATAMUTEX_PREF #undef ONCE_PREF
}
// Call AddMirror so that our mirrors for `always` prefs will stay updated. // The call to AddMirror re-reads the current pref value into the mirror, so // our mirror will now be up-to-date even if some of the prefs have changed // since the call to InitAll(). #define NEVER_PREF(name, cpp_type, value) #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) \
AddMirror(&sMirror_##full_id, name ""_ns, sMirror_##full_id); #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value) \
AddMirror(sMirror_##full_id, name ""_ns); #define ONCE_PREF(name, base_id, full_id, cpp_type, value) #include"mozilla/StaticPrefListAll.h" #undef NEVER_PREF #undef ALWAYS_PREF #undef ALWAYS_DATAMUTEX_PREF #undef ONCE_PREF
}
staticvoid InitOncePrefs() { // For `once`-mirrored prefs we generate some initialization code. This is // done in case the pref value was updated when reading pref data files. It's // necessary because we don't have callbacks registered for `once`-mirrored // prefs. // // In debug builds, we also install a mechanism that can check if the // preference value is modified after `once`-mirrored prefs are initialized. // In tests this would indicate a likely misuse of a `once`-mirrored pref and // suggest that it should instead be `always`-mirrored. #define NEVER_PREF(name, cpp_type, value) #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value) #ifdef DEBUG # define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
{ \
MOZ_ASSERT(gOnceStaticPrefsAntiFootgun); \
sMirror_##full_id = Internals::GetPref(name, cpp_type(value)); \ auto checkPref = [&]() { \
MOZ_ASSERT(sOncePrefRead); \
cpp_type staticPrefValue = full_id(); \
cpp_type preferenceValue = \
Internals::GetPref(GetPrefName_##base_id(), cpp_type(value)); \
MOZ_ASSERT(staticPrefValue == preferenceValue, \ "Preference '" name \ "' got modified since StaticPrefs::"#full_id \ " was initialized. Consider using an `always` mirror kind " \ "instead"); \
}; \
gOnceStaticPrefsAntiFootgun->insert( \
std::pair<constchar*, AntiFootgunCallback>(GetPrefName_##base_id(), \
std::move(checkPref))); \
} #else # define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
sMirror_##full_id = Internals::GetPref(name, cpp_type(value)); #endif
static MOZ_MAYBE_UNUSED void SaveOncePrefToSharedMap(
SharedPrefMapBuilder& aBuilder, const nsACString& aName, float aValue) { auto oncePref = MakeUnique<Pref>(aName);
oncePref->SetType(PrefType::String);
oncePref->SetIsSkippedByIteration(true);
nsAutoCString value;
value.AppendFloat(aValue); bool valueChanged = false; // It's ok to stash a pointer to the temporary PromiseFlatCString's chars in // pref because pref_SetPref() duplicates those chars. const nsCString& flat = PromiseFlatCString(value);
MOZ_ALWAYS_SUCCEEDS(
oncePref->SetDefaultValue(PrefType::String, PrefValue(flat.get()), /* isSticky */ true, /* isLocked */ true, &valueChanged));
oncePref->AddToMap(aBuilder);
}
#define ONCE_PREF_NAME(name) "$$$" name "$$$"
namespace StaticPrefs {
staticvoid RegisterOncePrefs(SharedPrefMapBuilder& aBuilder) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_DIAGNOSTIC_ASSERT(!gSharedMap, "Must be called before gSharedMap has been created");
MaybeInitOncePrefs();
// For `once`-mirrored prefs we generate a save call, which saves the value // as it was at parent startup. It is stored in a special (hidden and locked) // entry in the global SharedPreferenceMap. In order for the entry to be // hidden and not appear in about:config nor ever be stored to disk, we set // its IsSkippedByIteration flag to true. We also distinguish it by adding a // "$$$" prefix and suffix to the preference name. #define NEVER_PREF(name, cpp_type, value) #define ALWAYS_PREF(name, base_id, full_id, cpp_type, value) #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, value) #define ONCE_PREF(name, base_id, full_id, cpp_type, value) \
SaveOncePrefToSharedMap(aBuilder, ONCE_PREF_NAME(name) ""_ns, \
cpp_type(sMirror_##full_id)); #include"mozilla/StaticPrefListAll.h" #undef NEVER_PREF #undef ALWAYS_PREF #undef ALWAYS_DATAMUTEX_PREF #undef ONCE_PREF
}
// Disable thread safety analysis on this function, because it explodes build // times and memory usage.
MOZ_NO_THREAD_SAFETY_ANALYSIS staticvoid InitStaticPrefsFromShared() {
MOZ_ASSERT(!XRE_IsParentProcess());
MOZ_DIAGNOSTIC_ASSERT(gSharedMap, "Must be called once gSharedMap has been created");
#ifdef DEBUG # define ASSERT_PREF_NOT_SANITIZED(name, cpp_type) \ if (IsString<cpp_type>::value && IsPreferenceSanitized(name)) { \
MOZ_CRASH("Unexpected sanitized string preference '" name \ "'. " \ "Static Preferences cannot be sanitized currently, because " \ "they expect to be initialized from the Static Map, and " \ "sanitized preferences are not present there."); \
} #else # define ASSERT_PREF_NOT_SANITIZED(name, cpp_type) #endif
// For mirrored static prefs we generate some initialization code. Each // mirror variable is already initialized in the binary with the default // value. If the pref value hasn't changed from the default in the main // process (the common case) then the overwriting here won't change the // mirror variable's value. // // Note that the MOZ_ASSERT calls below can fail in one obscure case: when a // Firefox update occurs and we get a main process from the old binary (with // static prefs {A,B,C,D}) plus a new content process from the new binary // (with static prefs {A,B,C,D,E}). The content process' call to // GetSharedPrefValue() for pref E will fail because the shared pref map was // created by the main process, which doesn't have pref E. // // This silent failure is safe. The mirror variable for pref E is already // initialized to the default value in the content process, and the main // process cannot have changed pref E because it doesn't know about it! // // Nonetheless, it's useful to have the MOZ_ASSERT here for testing of debug // builds, where this scenario involving inconsistent binaries should not // occur. #define NEVER_PREF(name, cpp_type, default_value) #define ALWAYS_PREF(name, base_id, full_id, cpp_type, default_value) \
{ \
StripAtomic<cpp_type> val; \
ASSERT_PREF_NOT_SANITIZED(name, cpp_type); \
DebugOnly<nsresult> rv = Internals::GetSharedPrefValue(name, &val); \
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \
StaticPrefs::sMirror_##full_id = val; \
} #define ALWAYS_DATAMUTEX_PREF(name, base_id, full_id, cpp_type, default_value) \
{ \
StripAtomic<cpp_type> val; \
ASSERT_PREF_NOT_SANITIZED(name, cpp_type); \
DebugOnly<nsresult> rv = Internals::GetSharedPrefValue(name, &val); \
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \
Internals::AssignMirror(StaticPrefs::sMirror_##full_id, \
std::forward<StripAtomic<cpp_type>>(val)); \
} #define ONCE_PREF(name, base_id, full_id, cpp_type, default_value) \
{ \
cpp_type val; \
ASSERT_PREF_NOT_SANITIZED(name, cpp_type); \
DebugOnly<nsresult> rv = \
Internals::GetSharedPrefValue(ONCE_PREF_NAME(name), &val); \
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \
StaticPrefs::sMirror_##full_id = val; \
} #include"mozilla/StaticPrefListAll.h" #undef NEVER_PREF #undef ALWAYS_PREF #undef ALWAYS_DATAMUTEX_PREF #undef ONCE_PREF #undef ASSERT_PREF_NOT_SANITIZED
// `once`-mirrored prefs have been set to their value in the step above and // outside the parent process they are immutable. We set sOncePrefRead so // that we can directly skip any lazy initializations.
sOncePrefRead = true;
}
} // namespace StaticPrefs
} // namespace mozilla
#undef ENSURE_PARENT_PROCESS
//=========================================================================== // Module and factory stuff //===========================================================================
NS_IMPL_COMPONENT_FACTORY(nsPrefLocalizedString) { auto str = MakeRefPtr<nsPrefLocalizedString>(); if (NS_SUCCEEDED(str->Init())) { return str.forget().downcast<nsISupports>();
} return nullptr;
}
// A preference is 'sanitized' (i.e. not sent to web content processes) if // one of two criteria are met: // 1. The pref name matches one of the prefixes in the following list // 2. The pref is dynamically named (i.e. not specified in all.js or // StaticPrefList.yml), a string pref, and it is NOT exempted in // sDynamicPrefOverrideList // // This behavior is codified in ShouldSanitizePreference() below. // Exclusions of preferences can be defined in sOverrideRestrictionsList[]. staticconst PrefListEntry sRestrictFromWebContentProcesses[] = { // Remove prefs with user data
PREF_LIST_ENTRY("datareporting.policy."),
PREF_LIST_ENTRY("browser.download.lastDir"),
PREF_LIST_ENTRY("browser.newtabpage.pinned"),
PREF_LIST_ENTRY("browser.uiCustomization.state"),
PREF_LIST_ENTRY("browser.urlbar"),
PREF_LIST_ENTRY("devtools.debugger.pending-selected-location"),
PREF_LIST_ENTRY("identity.fxaccounts.account.device.name"),
PREF_LIST_ENTRY("identity.fxaccounts.account.telemetry.sanitized_uid"),
PREF_LIST_ENTRY("identity.fxaccounts.lastSignedInUserHash"),
PREF_LIST_ENTRY("print_printer"),
PREF_LIST_ENTRY("services."),
// Remove IDs that could be used to correlate across origins
PREF_LIST_ENTRY("app.update.lastUpdateTime."),
PREF_LIST_ENTRY( "browser.contentblocking.cfr-milestone.milestone-shown-time"),
PREF_LIST_ENTRY("browser.contextual-services.contextId"),
PREF_LIST_ENTRY("browser.laterrun.bookkeeping.profileCreationTime"),
PREF_LIST_ENTRY("browser.newtabpage.activity-stream.discoverystream."),
PREF_LIST_ENTRY("browser.sessionstore.upgradeBackup.latestBuildID"),
PREF_LIST_ENTRY("browser.shell.mostRecentDateSetAsDefault"),
PREF_LIST_ENTRY("idle.lastDailyNotification"),
PREF_LIST_ENTRY("media.gmp-gmpopenh264.lastUpdate"),
PREF_LIST_ENTRY("media.gmp-manager.lastCheck"),
PREF_LIST_ENTRY("places.database.lastMaintenance"),
PREF_LIST_ENTRY("privacy.purge_trackers.last_purge"),
PREF_LIST_ENTRY("storage.vacuum.last.places.sqlite"),
PREF_LIST_ENTRY("toolkit.startup.last_success"),
// Remove fingerprintable things
PREF_LIST_ENTRY("browser.startup.homepage_override.buildID"),
PREF_LIST_ENTRY("extensions.lastAppBuildId"),
PREF_LIST_ENTRY("media.gmp-manager.buildID"),
PREF_LIST_ENTRY("toolkit.telemetry.previousBuildID"),
};
// Allowlist for prefs and branches blocklisted in // sRestrictFromWebContentProcesses[], including prefs from // StaticPrefList.yaml and *.js, to let them pass. staticconst PrefListEntry sOverrideRestrictionsList[]{
PREF_LIST_ENTRY("services.settings.clock_skew_seconds"),
PREF_LIST_ENTRY("services.settings.last_update_seconds"),
PREF_LIST_ENTRY("services.settings.loglevel"), // This is really a boolean dynamic pref, but one Nightly user // has it set as a string...
PREF_LIST_ENTRY("services.settings.preview_enabled"),
PREF_LIST_ENTRY("services.settings.server"),
};
// These prefs are dynamically-named (i.e. not specified in prefs.js or // StaticPrefList) and would normally by blocklisted but we allow them through // anyway, so this override list acts as an allowlist staticconst PrefListEntry sDynamicPrefOverrideList[]{
PREF_LIST_ENTRY("accessibility.tabfocus"),
PREF_LIST_ENTRY("app.update.channel"),
PREF_LIST_ENTRY("apz.subtest"),
PREF_LIST_ENTRY("browser.contentblocking.category"),
PREF_LIST_ENTRY("browser.dom.window.dump.file"),
PREF_LIST_ENTRY("browser.search.region"),
PREF_LIST_ENTRY( "browser.tabs.remote.testOnly.failPBrowserCreation.browsingContext"),
PREF_LIST_ENTRY("browser.uitour.testingOrigins"),
PREF_LIST_ENTRY("browser.urlbar.loglevel"),
PREF_LIST_ENTRY("browser.urlbar.opencompanionsearch.enabled"),
PREF_LIST_ENTRY("capability.policy"),
PREF_LIST_ENTRY("dom.securecontext.allowlist"),
PREF_LIST_ENTRY("extensions.foobaz"),
PREF_LIST_ENTRY( "extensions.formautofill.creditCards.heuristics.testConfidence"),
PREF_LIST_ENTRY("general.appversion.override"),
PREF_LIST_ENTRY("general.buildID.override"),
PREF_LIST_ENTRY("general.oscpu.override"),
PREF_LIST_ENTRY("general.useragent.override"),
PREF_LIST_ENTRY("general.platform.override"),
PREF_LIST_ENTRY("gfx.blacklist."),
PREF_LIST_ENTRY("font.system.whitelist"),
PREF_LIST_ENTRY("font.name."),
PREF_LIST_ENTRY("intl.date_time.pattern_override."),
PREF_LIST_ENTRY("intl.hyphenation-alias."),
PREF_LIST_ENTRY("logging.config.LOG_FILE"),
PREF_LIST_ENTRY("media.audio_loopback_dev"),
PREF_LIST_ENTRY("media.decoder-doctor."),
PREF_LIST_ENTRY("media.cubeb.backend"),
PREF_LIST_ENTRY("media.cubeb.output_device"),
PREF_LIST_ENTRY("media.getusermedia.fake-camera-name"),
PREF_LIST_ENTRY("media.hls.server.url"),
PREF_LIST_ENTRY("media.peerconnection.nat_simulator.filtering_type"),
PREF_LIST_ENTRY("media.peerconnection.nat_simulator.mapping_type"),
PREF_LIST_ENTRY("media.peerconnection.nat_simulator.redirect_address"),
PREF_LIST_ENTRY("media.peerconnection.nat_simulator.redirect_targets"),
PREF_LIST_ENTRY("media.peerconnection.nat_simulator.network_delay_ms"),
PREF_LIST_ENTRY("media.video_loopback_dev"),
PREF_LIST_ENTRY("media.webspeech.service.endpoint"),
PREF_LIST_ENTRY("network.gio.supported-protocols"),
PREF_LIST_ENTRY("network.protocol-handler.external."),
PREF_LIST_ENTRY("network.security.ports.banned"),
PREF_LIST_ENTRY("nimbus.syncdatastore."),
PREF_LIST_ENTRY("pdfjs."),
PREF_LIST_ENTRY("plugins.force.wmode"),
PREF_LIST_ENTRY("print.printer_"),
PREF_LIST_ENTRY("print_printer"),
PREF_LIST_ENTRY("places.interactions.customBlocklist"),
PREF_LIST_ENTRY("remote.log.level"), // services.* preferences should be added in sOverrideRestrictionsList[] - // the whole preference branch gets sanitized by default.
PREF_LIST_ENTRY("spellchecker.dictionary"),
PREF_LIST_ENTRY("test.char"),
PREF_LIST_ENTRY("Test.IPC."),
PREF_LIST_ENTRY("exists.thenDoesNot"),
PREF_LIST_ENTRY("type.String."),
PREF_LIST_ENTRY("toolkit.mozprotocol.url"),
PREF_LIST_ENTRY("toolkit.telemetry.log.level"),
PREF_LIST_ENTRY("ui."),
};
#undef PREF_LIST_ENTRY
staticbool ShouldSanitizePreference(const Pref* const aPref) { // In the parent process, we use a heuristic to decide if a pref // value should be sanitized before sending to subprocesses.
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
constchar* prefName = aPref->Name();
// If a pref starts with this magic string, it is a Once-Initialized pref // from Static Prefs. It should* not be in the above list and while it looks // like a dnyamically named pref, it is not. // * nothing enforces this if (strncmp(prefName, "$$$", 3) == 0) { returnfalse;
}
// First check against the denylist. // The services pref is an annoying one - it's much easier to blocklist // the whole branch and then add this one check to let this one annoying // pref through. for (constauto& entry : sRestrictFromWebContentProcesses) { if (strncmp(entry.mPrefBranch, prefName, entry.mLen) == 0) { for (constauto& pasEnt : sOverrideRestrictionsList) { if (strncmp(pasEnt.mPrefBranch, prefName, pasEnt.mLen) == 0) { returnfalse;
}
} returntrue;
}
}
// Then check if it's a dynamically named string preference and not // in the override list if (aPref->Type() == PrefType::String && !aPref->HasDefaultValue()) { for (constauto& entry : sDynamicPrefOverrideList) { if (strncmp(entry.mPrefBranch, prefName, entry.mLen) == 0) { returnfalse;
}
} returntrue;
}
returnfalse;
}
// Forward Declaration - it's not defined in the .h, because we don't need to; // it's only used here. template <class T> staticbool IsPreferenceSanitized_Impl(const T& aPref);
// This is the only Check Sanitization function exposed outside of // Preferences.cpp, because this is the only one ever called from // outside this file. bool IsPreferenceSanitized(constchar* aPrefName) { // Perform this comparison (see notes above) early to avoid a lookup // if we can avoid it. if (strncmp(aPrefName, "$$$", 3) == 0) { returnfalse;
}
if (!gContentProcessPrefsAreInited) { returnfalse;
}
if (Maybe<PrefWrapper> pref = pref_Lookup(aPrefName)) { if (pref.isNothing()) { returntrue;
} return IsPreferenceSanitized(pref.value());
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.