diff -r 000000000000 -r 6474c204b198 modules/libpref/src/prefapi.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/libpref/src/prefapi.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1011 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "base/basictypes.h" + +#include "prefapi.h" +#include "prefapi_private_data.h" +#include "prefread.h" +#include "MainThreadUtils.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" + +#define PL_ARENA_CONST_ALIGN_MASK 3 +#include "plarena.h" + +#ifdef _WIN32 + #include "windows.h" +#endif /* _WIN32 */ + +#include "plstr.h" +#include "pldhash.h" +#include "plbase64.h" +#include "prlog.h" +#include "prprf.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/PContent.h" +#include "nsQuickSort.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "prlink.h" + +using namespace mozilla; + +static void +clearPrefEntry(PLDHashTable *table, PLDHashEntryHdr *entry) +{ + PrefHashEntry *pref = static_cast(entry); + if (pref->flags & PREF_STRING) + { + if (pref->defaultPref.stringVal) + PL_strfree(pref->defaultPref.stringVal); + if (pref->userPref.stringVal) + PL_strfree(pref->userPref.stringVal); + } + // don't need to free this as it's allocated in memory owned by + // gPrefNameArena + pref->key = nullptr; + memset(entry, 0, table->entrySize); +} + +static bool +matchPrefEntry(PLDHashTable*, const PLDHashEntryHdr* entry, + const void* key) +{ + const PrefHashEntry *prefEntry = + static_cast(entry); + + if (prefEntry->key == key) return true; + + if (!prefEntry->key || !key) return false; + + const char *otherKey = reinterpret_cast(key); + return (strcmp(prefEntry->key, otherKey) == 0); +} + +PLDHashTable gHashTable = { nullptr }; +static PLArenaPool gPrefNameArena; +bool gDirty = false; + +static struct CallbackNode* gCallbacks = nullptr; +static bool gIsAnyPrefLocked = false; +// These are only used during the call to pref_DoCallback +static bool gCallbacksInProgress = false; +static bool gShouldCleanupDeadNodes = false; + + +static PLDHashTableOps pref_HashTableOps = { + PL_DHashAllocTable, + PL_DHashFreeTable, + PL_DHashStringKey, + matchPrefEntry, + PL_DHashMoveEntryStub, + clearPrefEntry, + PL_DHashFinalizeStub, + nullptr, +}; + +// PR_ALIGN_OF_WORD is only defined on some platforms. ALIGN_OF_WORD has +// already been defined to PR_ALIGN_OF_WORD everywhere +#ifndef PR_ALIGN_OF_WORD +#define PR_ALIGN_OF_WORD PR_ALIGN_OF_POINTER +#endif + +// making PrefName arena 8k for nice allocation +#define PREFNAME_ARENA_SIZE 8192 + +#define WORD_ALIGN_MASK (PR_ALIGN_OF_WORD - 1) + +// sanity checking +#if (PR_ALIGN_OF_WORD & WORD_ALIGN_MASK) != 0 +#error "PR_ALIGN_OF_WORD must be a power of 2!" +#endif + +// equivalent to strdup() - does no error checking, +// we're assuming we're only called with a valid pointer +static char *ArenaStrDup(const char* str, PLArenaPool* aArena) +{ + void* mem; + uint32_t len = strlen(str); + PL_ARENA_ALLOCATE(mem, aArena, len+1); + if (mem) + memcpy(mem, str, len+1); + return static_cast(mem); +} + +/*---------------------------------------------------------------------------*/ + +#define PREF_IS_LOCKED(pref) ((pref)->flags & PREF_LOCKED) +#define PREF_HAS_DEFAULT_VALUE(pref) ((pref)->flags & PREF_HAS_DEFAULT) +#define PREF_HAS_USER_VALUE(pref) ((pref)->flags & PREF_USERSET) +#define PREF_TYPE(pref) (PrefType)((pref)->flags & PREF_VALUETYPE_MASK) + +static bool pref_ValueChanged(PrefValue oldValue, PrefValue newValue, PrefType type); + +/* -- Privates */ +struct CallbackNode { + char* domain; + // If someone attempts to remove the node from the callback list while + // pref_DoCallback is running, |func| is set to nullptr. Such nodes will + // be removed at the end of pref_DoCallback. + PrefChangedFunc func; + void* data; + struct CallbackNode* next; +}; + +/* -- Prototypes */ +static nsresult pref_DoCallback(const char* changed_pref); + +enum { + kPrefSetDefault = 1, + kPrefForceSet = 2 +}; +static nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags); + +#define PREF_HASHTABLE_INITIAL_SIZE 2048 + +nsresult PREF_Init() +{ + if (!gHashTable.ops) { + if (!PL_DHashTableInit(&gHashTable, &pref_HashTableOps, nullptr, + sizeof(PrefHashEntry), PREF_HASHTABLE_INITIAL_SIZE, + fallible_t())) { + gHashTable.ops = nullptr; + return NS_ERROR_OUT_OF_MEMORY; + } + + PL_INIT_ARENA_POOL(&gPrefNameArena, "PrefNameArena", + PREFNAME_ARENA_SIZE); + } + return NS_OK; +} + +/* Frees the callback list. */ +void PREF_Cleanup() +{ + NS_ASSERTION(!gCallbacksInProgress, + "PREF_Cleanup was called while gCallbacksInProgress is true!"); + struct CallbackNode* node = gCallbacks; + struct CallbackNode* next_node; + + while (node) + { + next_node = node->next; + PL_strfree(node->domain); + free(node); + node = next_node; + } + gCallbacks = nullptr; + + PREF_CleanupPrefs(); +} + +/* Frees up all the objects except the callback list. */ +void PREF_CleanupPrefs() +{ + if (gHashTable.ops) { + PL_DHashTableFinish(&gHashTable); + gHashTable.ops = nullptr; + PL_FinishArenaPool(&gPrefNameArena); + } +} + +// note that this appends to aResult, and does not assign! +static void str_escape(const char * original, nsAFlatCString& aResult) +{ + /* 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 + */ + const char *p; + + if (original == nullptr) + return; + + /* Paranoid worst case all slashes will free quickly */ + for (p=original; *p; ++p) + { + switch (*p) + { + case '\n': + aResult.Append("\\n"); + break; + + case '\r': + aResult.Append("\\r"); + break; + + case '\\': + aResult.Append("\\\\"); + break; + + case '\"': + aResult.Append("\\\""); + break; + + default: + aResult.Append(*p); + break; + } + } +} + +/* +** External calls +*/ +nsresult +PREF_SetCharPref(const char *pref_name, const char *value, bool set_default) +{ + if ((uint32_t)strlen(value) > MAX_PREF_LENGTH) { + return NS_ERROR_ILLEGAL_VALUE; + } + + PrefValue pref; + pref.stringVal = (char*)value; + + return pref_HashPref(pref_name, pref, PREF_STRING, set_default ? kPrefSetDefault : 0); +} + +nsresult +PREF_SetIntPref(const char *pref_name, int32_t value, bool set_default) +{ + PrefValue pref; + pref.intVal = value; + + return pref_HashPref(pref_name, pref, PREF_INT, set_default ? kPrefSetDefault : 0); +} + +nsresult +PREF_SetBoolPref(const char *pref_name, bool value, bool set_default) +{ + PrefValue pref; + pref.boolVal = value; + + return pref_HashPref(pref_name, pref, PREF_BOOL, set_default ? kPrefSetDefault : 0); +} + +enum WhichValue { DEFAULT_VALUE, USER_VALUE }; +static nsresult +SetPrefValue(const char* aPrefName, const dom::PrefValue& aValue, + WhichValue aWhich) +{ + bool setDefault = (aWhich == DEFAULT_VALUE); + switch (aValue.type()) { + case dom::PrefValue::TnsCString: + return PREF_SetCharPref(aPrefName, aValue.get_nsCString().get(), + setDefault); + case dom::PrefValue::Tint32_t: + return PREF_SetIntPref(aPrefName, aValue.get_int32_t(), + setDefault); + case dom::PrefValue::Tbool: + return PREF_SetBoolPref(aPrefName, aValue.get_bool(), + setDefault); + default: + MOZ_CRASH(); + } +} + +nsresult +pref_SetPref(const dom::PrefSetting& aPref) +{ + const char* prefName = aPref.name().get(); + const dom::MaybePrefValue& defaultValue = aPref.defaultValue(); + const dom::MaybePrefValue& userValue = aPref.userValue(); + + nsresult rv; + if (defaultValue.type() == dom::MaybePrefValue::TPrefValue) { + rv = SetPrefValue(prefName, defaultValue.get_PrefValue(), DEFAULT_VALUE); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (userValue.type() == dom::MaybePrefValue::TPrefValue) { + rv = SetPrefValue(prefName, userValue.get_PrefValue(), USER_VALUE); + } else { + rv = PREF_ClearUserPref(prefName); + } + + // NB: we should never try to clear a default value, that doesn't + // make sense + + return rv; +} + +PLDHashOperator +pref_savePref(PLDHashTable *table, PLDHashEntryHdr *heh, uint32_t i, void *arg) +{ + pref_saveArgs *argData = static_cast(arg); + PrefHashEntry *pref = static_cast(heh); + + PR_ASSERT(pref); + if (!pref) + return PL_DHASH_NEXT; + + nsAutoCString prefValue; + nsAutoCString prefPrefix; + prefPrefix.Assign(NS_LITERAL_CSTRING("user_pref(\"")); + + // where we're getting our pref from + PrefValue* sourcePref; + + if (PREF_HAS_USER_VALUE(pref) && + (pref_ValueChanged(pref->defaultPref, + pref->userPref, + (PrefType) PREF_TYPE(pref)) || + !(pref->flags & PREF_HAS_DEFAULT))) { + sourcePref = &pref->userPref; + } else { + if (argData->saveTypes == SAVE_ALL_AND_DEFAULTS) { + prefPrefix.Assign(NS_LITERAL_CSTRING("pref(\"")); + sourcePref = &pref->defaultPref; + } + else + // do not save default prefs that haven't changed + return PL_DHASH_NEXT; + } + + // strings are in quotes! + if (pref->flags & PREF_STRING) { + prefValue = '\"'; + str_escape(sourcePref->stringVal, prefValue); + prefValue += '\"'; + } + + else if (pref->flags & PREF_INT) + prefValue.AppendInt(sourcePref->intVal); + + else if (pref->flags & PREF_BOOL) + prefValue = (sourcePref->boolVal) ? "true" : "false"; + + nsAutoCString prefName; + str_escape(pref->key, prefName); + + argData->prefArray[i] = ToNewCString(prefPrefix + + prefName + + NS_LITERAL_CSTRING("\", ") + + prefValue + + NS_LITERAL_CSTRING(");")); + + return PL_DHASH_NEXT; +} + +PLDHashOperator +pref_GetPrefs(PLDHashTable *table, + PLDHashEntryHdr *heh, + uint32_t i, + void *arg) +{ + if (heh) { + PrefHashEntry *entry = static_cast(heh); + dom::PrefSetting *pref = + static_cast*>(arg)->AppendElement(); + + pref_GetPrefFromEntry(entry, pref); + } + return PL_DHASH_NEXT; +} + +static void +GetPrefValueFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref, + WhichValue aWhich) +{ + PrefValue* value; + dom::PrefValue* settingValue; + if (aWhich == USER_VALUE) { + value = &aHashEntry->userPref; + aPref->userValue() = dom::PrefValue(); + settingValue = &aPref->userValue().get_PrefValue(); + } else { + value = &aHashEntry->defaultPref; + aPref->defaultValue() = dom::PrefValue(); + settingValue = &aPref->defaultValue().get_PrefValue(); + } + + switch (aHashEntry->flags & PREF_VALUETYPE_MASK) { + case PREF_STRING: + *settingValue = nsDependentCString(value->stringVal); + return; + case PREF_INT: + *settingValue = value->intVal; + return; + case PREF_BOOL: + *settingValue = !!value->boolVal; + return; + default: + MOZ_CRASH(); + } +} + +void +pref_GetPrefFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref) +{ + aPref->name() = aHashEntry->key; + if (PREF_HAS_DEFAULT_VALUE(aHashEntry)) { + GetPrefValueFromEntry(aHashEntry, aPref, DEFAULT_VALUE); + } else { + aPref->defaultValue() = null_t(); + } + if (PREF_HAS_USER_VALUE(aHashEntry)) { + GetPrefValueFromEntry(aHashEntry, aPref, USER_VALUE); + } else { + aPref->userValue() = null_t(); + } + + MOZ_ASSERT(aPref->defaultValue().type() == dom::MaybePrefValue::Tnull_t || + aPref->userValue().type() == dom::MaybePrefValue::Tnull_t || + (aPref->defaultValue().get_PrefValue().type() == + aPref->userValue().get_PrefValue().type())); +} + + +int +pref_CompareStrings(const void *v1, const void *v2, void *unused) +{ + char *s1 = *(char**) v1; + char *s2 = *(char**) v2; + + if (!s1) + { + if (!s2) + return 0; + else + return -1; + } + else if (!s2) + return 1; + else + return strcmp(s1, s2); +} + +bool PREF_HasUserPref(const char *pref_name) +{ + if (!gHashTable.ops) + return false; + + PrefHashEntry *pref = pref_HashTableLookup(pref_name); + if (!pref) return false; + + /* convert PREF_HAS_USER_VALUE to bool */ + return (PREF_HAS_USER_VALUE(pref) != 0); + +} + +nsresult +PREF_CopyCharPref(const char *pref_name, char ** return_buffer, bool get_default) +{ + if (!gHashTable.ops) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_ERROR_UNEXPECTED; + char* stringVal; + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + + if (pref && (pref->flags & PREF_STRING)) + { + if (get_default || PREF_IS_LOCKED(pref) || !PREF_HAS_USER_VALUE(pref)) + stringVal = pref->defaultPref.stringVal; + else + stringVal = pref->userPref.stringVal; + + if (stringVal) { + *return_buffer = NS_strdup(stringVal); + rv = NS_OK; + } + } + return rv; +} + +nsresult PREF_GetIntPref(const char *pref_name,int32_t * return_int, bool get_default) +{ + if (!gHashTable.ops) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_ERROR_UNEXPECTED; + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + if (pref && (pref->flags & PREF_INT)) + { + if (get_default || PREF_IS_LOCKED(pref) || !PREF_HAS_USER_VALUE(pref)) + { + int32_t tempInt = pref->defaultPref.intVal; + /* check to see if we even had a default */ + if (!(pref->flags & PREF_HAS_DEFAULT)) + return NS_ERROR_UNEXPECTED; + *return_int = tempInt; + } + else + *return_int = pref->userPref.intVal; + rv = NS_OK; + } + return rv; +} + +nsresult PREF_GetBoolPref(const char *pref_name, bool * return_value, bool get_default) +{ + if (!gHashTable.ops) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_ERROR_UNEXPECTED; + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + //NS_ASSERTION(pref, pref_name); + if (pref && (pref->flags & PREF_BOOL)) + { + if (get_default || PREF_IS_LOCKED(pref) || !PREF_HAS_USER_VALUE(pref)) + { + bool tempBool = pref->defaultPref.boolVal; + /* check to see if we even had a default */ + if (pref->flags & PREF_HAS_DEFAULT) { + *return_value = tempBool; + rv = NS_OK; + } + } + else { + *return_value = pref->userPref.boolVal; + rv = NS_OK; + } + } + return rv; +} + +/* Delete a branch. Used for deleting mime types */ +static PLDHashOperator +pref_DeleteItem(PLDHashTable *table, PLDHashEntryHdr *heh, uint32_t i, void *arg) +{ + PrefHashEntry* he = static_cast(heh); + const char *to_delete = (const char *) arg; + int len = strlen(to_delete); + + /* note if we're deleting "ldap" then we want to delete "ldap.xxx" + and "ldap" (if such a leaf node exists) but not "ldap_1.xxx" */ + if (to_delete && (PL_strncmp(he->key, to_delete, (uint32_t) len) == 0 || + (len-1 == (int)strlen(he->key) && PL_strncmp(he->key, to_delete, (uint32_t)(len-1)) == 0))) + return PL_DHASH_REMOVE; + + return PL_DHASH_NEXT; +} + +nsresult +PREF_DeleteBranch(const char *branch_name) +{ +#ifndef MOZ_B2G + MOZ_ASSERT(NS_IsMainThread()); +#endif + + int len = (int)strlen(branch_name); + + if (!gHashTable.ops) + return NS_ERROR_NOT_INITIALIZED; + + /* The following check insures that if the branch name already has a "." + * at the end, we don't end up with a "..". This fixes an incompatibility + * between nsIPref, which needs the period added, and nsIPrefBranch which + * does not. When nsIPref goes away this function should be fixed to + * never add the period at all. + */ + nsAutoCString branch_dot(branch_name); + if ((len > 1) && branch_name[len - 1] != '.') + branch_dot += '.'; + + PL_DHashTableEnumerate(&gHashTable, pref_DeleteItem, + (void*) branch_dot.get()); + gDirty = true; + return NS_OK; +} + + +nsresult +PREF_ClearUserPref(const char *pref_name) +{ + if (!gHashTable.ops) + return NS_ERROR_NOT_INITIALIZED; + + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + if (pref && PREF_HAS_USER_VALUE(pref)) + { + pref->flags &= ~PREF_USERSET; + + if (!(pref->flags & PREF_HAS_DEFAULT)) { + PL_DHashTableOperate(&gHashTable, pref_name, PL_DHASH_REMOVE); + } + + pref_DoCallback(pref_name); + gDirty = true; + } + return NS_OK; +} + +static PLDHashOperator +pref_ClearUserPref(PLDHashTable *table, PLDHashEntryHdr *he, uint32_t, + void *arg) +{ + PrefHashEntry *pref = static_cast(he); + + PLDHashOperator nextOp = PL_DHASH_NEXT; + + if (PREF_HAS_USER_VALUE(pref)) + { + pref->flags &= ~PREF_USERSET; + + if (!(pref->flags & PREF_HAS_DEFAULT)) { + nextOp = PL_DHASH_REMOVE; + } + + pref_DoCallback(pref->key); + } + return nextOp; +} + +nsresult +PREF_ClearAllUserPrefs() +{ +#ifndef MOZ_B2G + MOZ_ASSERT(NS_IsMainThread()); +#endif + + if (!gHashTable.ops) + return NS_ERROR_NOT_INITIALIZED; + + PL_DHashTableEnumerate(&gHashTable, pref_ClearUserPref, nullptr); + + gDirty = true; + return NS_OK; +} + +nsresult PREF_LockPref(const char *key, bool lockit) +{ + if (!gHashTable.ops) + return NS_ERROR_NOT_INITIALIZED; + + PrefHashEntry* pref = pref_HashTableLookup(key); + if (!pref) + return NS_ERROR_UNEXPECTED; + + if (lockit) { + if (!PREF_IS_LOCKED(pref)) + { + pref->flags |= PREF_LOCKED; + gIsAnyPrefLocked = true; + pref_DoCallback(key); + } + } + else + { + if (PREF_IS_LOCKED(pref)) + { + pref->flags &= ~PREF_LOCKED; + pref_DoCallback(key); + } + } + return NS_OK; +} + +/* + * Hash table functions + */ +static bool pref_ValueChanged(PrefValue oldValue, PrefValue newValue, PrefType type) +{ + bool changed = true; + if (type & PREF_STRING) + { + if (oldValue.stringVal && newValue.stringVal) + changed = (strcmp(oldValue.stringVal, newValue.stringVal) != 0); + } + else if (type & PREF_INT) + changed = oldValue.intVal != newValue.intVal; + else if (type & PREF_BOOL) + changed = oldValue.boolVal != newValue.boolVal; + return changed; +} + +/* + * Overwrite the type and value of an existing preference. Caller must + * ensure that they are not changing the type of a preference that has + * a default value. + */ +static void pref_SetValue(PrefValue* existingValue, uint16_t *existingFlags, + PrefValue newValue, PrefType newType) +{ + if ((*existingFlags & PREF_STRING) && existingValue->stringVal) { + PL_strfree(existingValue->stringVal); + } + *existingFlags = (*existingFlags & ~PREF_VALUETYPE_MASK) | newType; + if (newType & PREF_STRING) { + PR_ASSERT(newValue.stringVal); + existingValue->stringVal = newValue.stringVal ? PL_strdup(newValue.stringVal) : nullptr; + } + else { + *existingValue = newValue; + } + gDirty = true; +} + +PrefHashEntry* pref_HashTableLookup(const void *key) +{ +#ifndef MOZ_B2G + MOZ_ASSERT(NS_IsMainThread()); +#endif + + PrefHashEntry* result = + static_cast(PL_DHashTableOperate(&gHashTable, key, PL_DHASH_LOOKUP)); + + if (PL_DHASH_ENTRY_IS_FREE(result)) + return nullptr; + + return result; +} + +nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags) +{ +#ifndef MOZ_B2G + MOZ_ASSERT(NS_IsMainThread()); +#endif + + if (!gHashTable.ops) + return NS_ERROR_OUT_OF_MEMORY; + + PrefHashEntry* pref = static_cast(PL_DHashTableOperate(&gHashTable, key, PL_DHASH_ADD)); + + if (!pref) + return NS_ERROR_OUT_OF_MEMORY; + + // new entry, better initialize + if (!pref->key) { + + // initialize the pref entry + pref->flags = type; + pref->key = ArenaStrDup(key, &gPrefNameArena); + memset(&pref->defaultPref, 0, sizeof(pref->defaultPref)); + memset(&pref->userPref, 0, sizeof(pref->userPref)); + } + else if ((pref->flags & PREF_HAS_DEFAULT) && PREF_TYPE(pref) != type) + { + NS_WARNING(nsPrintfCString("Trying to overwrite value of default pref %s with the wrong type!", key).get()); + return NS_ERROR_UNEXPECTED; + } + + bool valueChanged = false; + if (flags & kPrefSetDefault) + { + if (!PREF_IS_LOCKED(pref)) + { /* ?? change of semantics? */ + if (pref_ValueChanged(pref->defaultPref, value, type) || + !(pref->flags & PREF_HAS_DEFAULT)) + { + pref_SetValue(&pref->defaultPref, &pref->flags, value, type); + pref->flags |= PREF_HAS_DEFAULT; + if (!PREF_HAS_USER_VALUE(pref)) + valueChanged = true; + } + } + } + else + { + /* If new value is same as the default value, then un-set the user value. + Otherwise, set the user value only if it has changed */ + if ((pref->flags & PREF_HAS_DEFAULT) && + !pref_ValueChanged(pref->defaultPref, value, type) && + !(flags & kPrefForceSet)) + { + if (PREF_HAS_USER_VALUE(pref)) + { + /* XXX should we free a user-set string value if there is one? */ + pref->flags &= ~PREF_USERSET; + if (!PREF_IS_LOCKED(pref)) + valueChanged = true; + } + } + else if (!PREF_HAS_USER_VALUE(pref) || + PREF_TYPE(pref) != type || + pref_ValueChanged(pref->userPref, value, type) ) + { + pref_SetValue(&pref->userPref, &pref->flags, value, type); + pref->flags |= PREF_USERSET; + if (!PREF_IS_LOCKED(pref)) + valueChanged = true; + } + } + + nsresult rv = NS_OK; + if (valueChanged) { + gDirty = true; + + nsresult rv2 = pref_DoCallback(key); + if (NS_FAILED(rv2)) + rv = rv2; + } + return rv; +} + +size_t +pref_SizeOfPrivateData(MallocSizeOf aMallocSizeOf) +{ + size_t n = PL_SizeOfArenaPoolExcludingPool(&gPrefNameArena, aMallocSizeOf); + for (struct CallbackNode* node = gCallbacks; node; node = node->next) { + n += aMallocSizeOf(node); + n += aMallocSizeOf(node->domain); + } + return n; +} + +PrefType +PREF_GetPrefType(const char *pref_name) +{ + if (gHashTable.ops) + { + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + if (pref) + { + if (pref->flags & PREF_STRING) + return PREF_STRING; + else if (pref->flags & PREF_INT) + return PREF_INT; + else if (pref->flags & PREF_BOOL) + return PREF_BOOL; + } + } + return PREF_INVALID; +} + +/* -- */ + +bool +PREF_PrefIsLocked(const char *pref_name) +{ + bool result = false; + if (gIsAnyPrefLocked && gHashTable.ops) { + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + if (pref && PREF_IS_LOCKED(pref)) + result = true; + } + + return result; +} + +/* Adds a node to the beginning of the callback list. */ +void +PREF_RegisterCallback(const char *pref_node, + PrefChangedFunc callback, + void * instance_data) +{ + NS_PRECONDITION(pref_node, "pref_node must not be nullptr"); + NS_PRECONDITION(callback, "callback must not be nullptr"); + + struct CallbackNode* node = (struct CallbackNode*) malloc(sizeof(struct CallbackNode)); + if (node) + { + node->domain = PL_strdup(pref_node); + node->func = callback; + node->data = instance_data; + node->next = gCallbacks; + gCallbacks = node; + } + return; +} + +/* Removes |node| from gCallbacks list. + Returns the node after the deleted one. */ +struct CallbackNode* +pref_RemoveCallbackNode(struct CallbackNode* node, + struct CallbackNode* prev_node) +{ + NS_PRECONDITION(!prev_node || prev_node->next == node, "invalid params"); + NS_PRECONDITION(prev_node || gCallbacks == node, "invalid params"); + + NS_ASSERTION(!gCallbacksInProgress, + "modifying the callback list while gCallbacksInProgress is true"); + + struct CallbackNode* next_node = node->next; + if (prev_node) + prev_node->next = next_node; + else + gCallbacks = next_node; + PL_strfree(node->domain); + free(node); + return next_node; +} + +/* Deletes a node from the callback list or marks it for deletion. */ +nsresult +PREF_UnregisterCallback(const char *pref_node, + PrefChangedFunc callback, + void * instance_data) +{ + nsresult rv = NS_ERROR_FAILURE; + struct CallbackNode* node = gCallbacks; + struct CallbackNode* prev_node = nullptr; + + while (node != nullptr) + { + if ( node->func == callback && + node->data == instance_data && + strcmp(node->domain, pref_node) == 0) + { + if (gCallbacksInProgress) + { + // postpone the node removal until after + // gCallbacks enumeration is finished. + node->func = nullptr; + gShouldCleanupDeadNodes = true; + prev_node = node; + node = node->next; + } + else + { + node = pref_RemoveCallbackNode(node, prev_node); + } + rv = NS_OK; + } + else + { + prev_node = node; + node = node->next; + } + } + return rv; +} + +static nsresult pref_DoCallback(const char* changed_pref) +{ + nsresult rv = NS_OK; + struct CallbackNode* node; + + bool reentered = gCallbacksInProgress; + gCallbacksInProgress = true; + // 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. + + for (node = gCallbacks; node != nullptr; node = node->next) + { + if ( node->func && + PL_strncmp(changed_pref, + node->domain, + strlen(node->domain)) == 0 ) + { + (*node->func) (changed_pref, node->data); + } + } + + gCallbacksInProgress = reentered; + + if (gShouldCleanupDeadNodes && !gCallbacksInProgress) + { + struct CallbackNode* prev_node = nullptr; + node = gCallbacks; + + while (node != nullptr) + { + if (!node->func) + { + node = pref_RemoveCallbackNode(node, prev_node); + } + else + { + prev_node = node; + node = node->next; + } + } + gShouldCleanupDeadNodes = false; + } + + return rv; +} + +void PREF_ReaderCallback(void *closure, + const char *pref, + PrefValue value, + PrefType type, + bool isDefault) +{ + pref_HashPref(pref, value, type, isDefault ? kPrefSetDefault : kPrefForceSet); +}