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