summaryrefslogtreecommitdiffstats
path: root/modules/libpref/prefapi.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/libpref/prefapi.cpp')
-rw-r--r--modules/libpref/prefapi.cpp1013
1 files changed, 1013 insertions, 0 deletions
diff --git a/modules/libpref/prefapi.cpp b/modules/libpref/prefapi.cpp
new file mode 100644
index 000000000..046a69419
--- /dev/null
+++ b/modules/libpref/prefapi.cpp
@@ -0,0 +1,1013 @@
+/* -*- 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 <string>
+#include <vector>
+
+#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 "PLDHashTable.h"
+#include "plbase64.h"
+#include "mozilla/Logging.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<PrefHashEntry *>(entry);
+ if (pref->prefFlags.IsTypeString())
+ {
+ 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(const PLDHashEntryHdr* entry, const void* key)
+{
+ const PrefHashEntry *prefEntry =
+ static_cast<const PrefHashEntry*>(entry);
+
+ if (prefEntry->key == key) return true;
+
+ if (!prefEntry->key || !key) return false;
+
+ const char *otherKey = reinterpret_cast<const char*>(key);
+ return (strcmp(prefEntry->key, otherKey) == 0);
+}
+
+PLDHashTable* gHashTable;
+static PLArenaPool gPrefNameArena;
+
+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 = {
+ PLDHashTable::HashStringKey,
+ matchPrefEntry,
+ PLDHashTable::MoveEntryStub,
+ clearPrefEntry,
+ 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<char*>(mem);
+}
+
+static PrefsDirtyFunc gDirtyCallback = nullptr;
+
+inline void MakeDirtyCallback()
+{
+ // Right now the callback function is always set, so we don't need
+ // to complicate the code to cover the scenario where we set the callback
+ // after we've already tried to make it dirty. If this assert triggers
+ // we will add that code.
+ MOZ_ASSERT(gDirtyCallback);
+ if (gDirtyCallback) {
+ gDirtyCallback();
+ }
+}
+
+void PREF_SetDirtyCallback(PrefsDirtyFunc aFunc)
+{
+ gDirtyCallback = aFunc;
+}
+
+/*---------------------------------------------------------------------------*/
+
+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,
+ kPrefStickyDefault = 4,
+};
+static nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags);
+
+#define PREF_HASHTABLE_INITIAL_LENGTH 1024
+
+void PREF_Init()
+{
+ if (!gHashTable) {
+ gHashTable = new PLDHashTable(&pref_HashTableOps,
+ sizeof(PrefHashEntry),
+ PREF_HASHTABLE_INITIAL_LENGTH);
+
+ PL_INIT_ARENA_POOL(&gPrefNameArena, "PrefNameArena",
+ PREFNAME_ARENA_SIZE);
+ }
+}
+
+/* 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) {
+ delete gHashTable;
+ gHashTable = 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.AppendLiteral("\\n");
+ break;
+
+ case '\r':
+ aResult.AppendLiteral("\\r");
+ break;
+
+ case '\\':
+ aResult.AppendLiteral("\\\\");
+ break;
+
+ case '\"':
+ aResult.AppendLiteral("\\\"");
+ 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, PrefType::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, PrefType::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, PrefType::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;
+}
+
+UniquePtr<char*[]>
+pref_savePrefs(PLDHashTable* aTable, uint32_t* aPrefCount)
+{
+ // This function allocates the entries in the savedPrefs array it returns.
+ // It is the callers responsibility to go through the array and free
+ // all of them. The aPrefCount entries will be non-null. Any end padding
+ // is an implementation detail and may change.
+ MOZ_ASSERT(aPrefCount);
+ auto savedPrefs = MakeUnique<char*[]>(aTable->EntryCount());
+
+ // This is not necessary, but leaving it in for now
+ memset(savedPrefs.get(), 0, aTable->EntryCount() * sizeof(char*));
+
+ int32_t j = 0;
+ for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
+ auto pref = static_cast<PrefHashEntry*>(iter.Get());
+
+ nsAutoCString prefValue;
+ nsAutoCString prefPrefix;
+ prefPrefix.AssignLiteral("user_pref(\"");
+
+ // where we're getting our pref from
+ PrefValue* sourcePref;
+
+ if (pref->prefFlags.HasUserValue() &&
+ (pref_ValueChanged(pref->defaultPref,
+ pref->userPref,
+ pref->prefFlags.GetPrefType()) ||
+ !(pref->prefFlags.HasDefault()) ||
+ pref->prefFlags.HasStickyDefault())) {
+ sourcePref = &pref->userPref;
+ } else {
+ // do not save default prefs that haven't changed
+ continue;
+ }
+
+ // strings are in quotes!
+ if (pref->prefFlags.IsTypeString()) {
+ prefValue = '\"';
+ str_escape(sourcePref->stringVal, prefValue);
+ prefValue += '\"';
+
+ } else if (pref->prefFlags.IsTypeInt()) {
+ prefValue.AppendInt(sourcePref->intVal);
+
+ } else if (pref->prefFlags.IsTypeBool()) {
+ prefValue = (sourcePref->boolVal) ? "true" : "false";
+ }
+
+ nsAutoCString prefName;
+ str_escape(pref->key, prefName);
+
+ savedPrefs[j++] = ToNewCString(prefPrefix +
+ prefName +
+ NS_LITERAL_CSTRING("\", ") +
+ prefValue +
+ NS_LITERAL_CSTRING(");"));
+ }
+ *aPrefCount = j;
+
+ return savedPrefs;
+}
+
+bool
+pref_EntryHasAdvisablySizedValues(PrefHashEntry* aHashEntry)
+{
+ if (aHashEntry->prefFlags.GetPrefType() != PrefType::String) {
+ return true;
+ }
+
+ char* stringVal;
+ if (aHashEntry->prefFlags.HasDefault()) {
+ stringVal = aHashEntry->defaultPref.stringVal;
+ if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
+ return false;
+ }
+ }
+
+ if (aHashEntry->prefFlags.HasUserValue()) {
+ stringVal = aHashEntry->userPref.stringVal;
+ if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+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->prefFlags.GetPrefType()) {
+ case PrefType::String:
+ *settingValue = nsDependentCString(value->stringVal);
+ return;
+ case PrefType::Int:
+ *settingValue = value->intVal;
+ return;
+ case PrefType::Bool:
+ *settingValue = !!value->boolVal;
+ return;
+ default:
+ MOZ_CRASH();
+ }
+}
+
+void
+pref_GetPrefFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref)
+{
+ aPref->name() = aHashEntry->key;
+ if (aHashEntry->prefFlags.HasDefault()) {
+ GetPrefValueFromEntry(aHashEntry, aPref, DEFAULT_VALUE);
+ } else {
+ aPref->defaultValue() = null_t();
+ }
+ if (aHashEntry->prefFlags.HasUserValue()) {
+ 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)
+ return false;
+
+ PrefHashEntry *pref = pref_HashTableLookup(pref_name);
+ return pref && pref->prefFlags.HasUserValue();
+}
+
+nsresult
+PREF_CopyCharPref(const char *pref_name, char ** return_buffer, bool get_default)
+{
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ char* stringVal;
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+
+ if (pref && (pref->prefFlags.IsTypeString())) {
+ if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) {
+ 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)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+ if (pref && (pref->prefFlags.IsTypeInt())) {
+ if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) {
+ int32_t tempInt = pref->defaultPref.intVal;
+ /* check to see if we even had a default */
+ if (!pref->prefFlags.HasDefault()) {
+ 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)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+ //NS_ASSERTION(pref, pref_name);
+ if (pref && (pref->prefFlags.IsTypeBool())) {
+ if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) {
+ bool tempBool = pref->defaultPref.boolVal;
+ /* check to see if we even had a default */
+ if (pref->prefFlags.HasDefault()) {
+ *return_value = tempBool;
+ rv = NS_OK;
+ }
+ } else {
+ *return_value = pref->userPref.boolVal;
+ rv = NS_OK;
+ }
+ }
+ return rv;
+}
+
+nsresult
+PREF_DeleteBranch(const char *branch_name)
+{
+#ifndef MOZ_B2G
+ MOZ_ASSERT(NS_IsMainThread());
+#endif
+
+ int len = (int)strlen(branch_name);
+
+ if (!gHashTable)
+ 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 += '.';
+
+ /* Delete a branch. Used for deleting mime types */
+ const char *to_delete = branch_dot.get();
+ MOZ_ASSERT(to_delete);
+ len = strlen(to_delete);
+ for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PrefHashEntry*>(iter.Get());
+
+ /* 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 (PL_strncmp(entry->key, to_delete, (uint32_t) len) == 0 ||
+ (len-1 == (int)strlen(entry->key) &&
+ PL_strncmp(entry->key, to_delete, (uint32_t)(len-1)) == 0)) {
+ iter.Remove();
+ }
+ }
+
+ MakeDirtyCallback();
+ return NS_OK;
+}
+
+
+nsresult
+PREF_ClearUserPref(const char *pref_name)
+{
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+ if (pref && pref->prefFlags.HasUserValue()) {
+ pref->prefFlags.SetHasUserValue(false);
+
+ if (!pref->prefFlags.HasDefault()) {
+ gHashTable->RemoveEntry(pref);
+ }
+
+ pref_DoCallback(pref_name);
+ MakeDirtyCallback();
+ }
+ return NS_OK;
+}
+
+nsresult
+PREF_ClearAllUserPrefs()
+{
+#ifndef MOZ_B2G
+ MOZ_ASSERT(NS_IsMainThread());
+#endif
+
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ std::vector<std::string> prefStrings;
+ for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
+ auto pref = static_cast<PrefHashEntry*>(iter.Get());
+
+ if (pref->prefFlags.HasUserValue()) {
+ prefStrings.push_back(std::string(pref->key));
+
+ pref->prefFlags.SetHasUserValue(false);
+ if (!pref->prefFlags.HasDefault()) {
+ iter.Remove();
+ }
+ }
+ }
+
+ for (std::string& prefString : prefStrings) {
+ pref_DoCallback(prefString.c_str());
+ }
+
+ MakeDirtyCallback();
+ return NS_OK;
+}
+
+nsresult PREF_LockPref(const char *key, bool lockit)
+{
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ PrefHashEntry* pref = pref_HashTableLookup(key);
+ if (!pref)
+ return NS_ERROR_UNEXPECTED;
+
+ if (lockit) {
+ if (!pref->prefFlags.IsLocked()) {
+ pref->prefFlags.SetLocked(true);
+ gIsAnyPrefLocked = true;
+ pref_DoCallback(key);
+ }
+ } else {
+ if (pref->prefFlags.IsLocked()) {
+ pref->prefFlags.SetLocked(false);
+ pref_DoCallback(key);
+ }
+ }
+ return NS_OK;
+}
+
+/*
+ * Hash table functions
+ */
+static bool pref_ValueChanged(PrefValue oldValue, PrefValue newValue, PrefType type)
+{
+ bool changed = true;
+ switch(type) {
+ case PrefType::String:
+ if (oldValue.stringVal && newValue.stringVal) {
+ changed = (strcmp(oldValue.stringVal, newValue.stringVal) != 0);
+ }
+ break;
+ case PrefType::Int:
+ changed = oldValue.intVal != newValue.intVal;
+ break;
+ case PrefType::Bool:
+ changed = oldValue.boolVal != newValue.boolVal;
+ break;
+ case PrefType::Invalid:
+ default:
+ changed = false;
+ break;
+ }
+ 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 PrefTypeFlags pref_SetValue(PrefValue* existingValue, PrefTypeFlags flags,
+ PrefValue newValue, PrefType newType)
+{
+ if (flags.IsTypeString() && existingValue->stringVal) {
+ PL_strfree(existingValue->stringVal);
+ }
+ flags.SetPrefType(newType);
+ if (flags.IsTypeString()) {
+ MOZ_ASSERT(newValue.stringVal);
+ existingValue->stringVal = newValue.stringVal ? PL_strdup(newValue.stringVal) : nullptr;
+ }
+ else {
+ *existingValue = newValue;
+ }
+ return flags;
+}
+
+PrefHashEntry* pref_HashTableLookup(const char *key)
+{
+#ifndef MOZ_B2G
+ MOZ_ASSERT(NS_IsMainThread());
+#endif
+
+ return static_cast<PrefHashEntry*>(gHashTable->Search(key));
+}
+
+nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags)
+{
+#ifndef MOZ_B2G
+ MOZ_ASSERT(NS_IsMainThread());
+#endif
+
+ if (!gHashTable)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ auto pref = static_cast<PrefHashEntry*>(gHashTable->Add(key, fallible));
+ if (!pref)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // new entry, better initialize
+ if (!pref->key) {
+
+ // initialize the pref entry
+ pref->prefFlags.Reset().SetPrefType(type);
+ pref->key = ArenaStrDup(key, &gPrefNameArena);
+ memset(&pref->defaultPref, 0, sizeof(pref->defaultPref));
+ memset(&pref->userPref, 0, sizeof(pref->userPref));
+ } else if (pref->prefFlags.HasDefault() && !pref->prefFlags.IsPrefType(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->prefFlags.IsLocked()) {
+ /* ?? change of semantics? */
+ if (pref_ValueChanged(pref->defaultPref, value, type) ||
+ !pref->prefFlags.HasDefault()) {
+ pref->prefFlags = pref_SetValue(&pref->defaultPref, pref->prefFlags, value, type).SetHasDefault(true);
+ if (flags & kPrefStickyDefault) {
+ pref->prefFlags.SetHasStickyDefault(true);
+ }
+ if (!pref->prefFlags.HasUserValue()) {
+ valueChanged = true;
+ }
+ }
+ // What if we change the default to be the same as the user value?
+ // Should we clear the user value?
+ }
+ } else {
+ /* If new value is same as the default value and it's not a "sticky"
+ pref, then un-set the user value.
+ Otherwise, set the user value only if it has changed */
+ if ((pref->prefFlags.HasDefault()) &&
+ !(pref->prefFlags.HasStickyDefault()) &&
+ !pref_ValueChanged(pref->defaultPref, value, type) &&
+ !(flags & kPrefForceSet)) {
+ if (pref->prefFlags.HasUserValue()) {
+ /* XXX should we free a user-set string value if there is one? */
+ pref->prefFlags.SetHasUserValue(false);
+ if (!pref->prefFlags.IsLocked()) {
+ MakeDirtyCallback();
+ valueChanged = true;
+ }
+ }
+ } else if (!pref->prefFlags.HasUserValue() ||
+ !pref->prefFlags.IsPrefType(type) ||
+ pref_ValueChanged(pref->userPref, value, type) ) {
+ pref->prefFlags = pref_SetValue(&pref->userPref, pref->prefFlags, value, type).SetHasUserValue(true);
+ if (!pref->prefFlags.IsLocked()) {
+ MakeDirtyCallback();
+ valueChanged = true;
+ }
+ }
+ }
+
+ if (valueChanged) {
+ return pref_DoCallback(key);
+ }
+ return NS_OK;
+}
+
+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) {
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+ if (pref) {
+ return pref->prefFlags.GetPrefType();
+ }
+ }
+ return PrefType::Invalid;
+}
+
+/* -- */
+
+bool
+PREF_PrefIsLocked(const char *pref_name)
+{
+ bool result = false;
+ if (gIsAnyPrefLocked && gHashTable) {
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+ if (pref && pref->prefFlags.IsLocked()) {
+ 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,
+ bool isStickyDefault)
+
+{
+ uint32_t flags = 0;
+ if (isDefault) {
+ flags |= kPrefSetDefault;
+ if (isStickyDefault) {
+ flags |= kPrefStickyDefault;
+ }
+ } else {
+ flags |= kPrefForceSet;
+ }
+ pref_HashPref(pref, value, type, flags);
+}