/* -*- 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;
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 ist noch experimentell.