diff options
Diffstat (limited to 'modules/libpref/Preferences.cpp')
-rw-r--r-- | modules/libpref/Preferences.cpp | 2023 |
1 files changed, 2023 insertions, 0 deletions
diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp new file mode 100644 index 000000000..f36819236 --- /dev/null +++ b/modules/libpref/Preferences.cpp @@ -0,0 +1,2023 @@ +/* -*- 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/. */ + +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/ContentChild.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/UniquePtrExtensions.h" + +#include "nsXULAppAPI.h" + +#include "mozilla/Preferences.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDataHashtable.h" +#include "nsDirectoryServiceDefs.h" +#include "nsICategoryManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsNetUtil.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsIObserverService.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsISimpleEnumerator.h" +#include "nsIStringEnumerator.h" +#include "nsIZipReader.h" +#include "nsPrefBranch.h" +#include "nsXPIDLString.h" +#include "nsCRT.h" +#include "nsCOMArray.h" +#include "nsXPCOMCID.h" +#include "nsAutoPtr.h" +#include "nsPrintfCString.h" + +#include "nsQuickSort.h" +#include "PLDHashTable.h" + +#include "prefapi.h" +#include "prefread.h" +#include "prefapi_private_data.h" + +#include "mozilla/Omnijar.h" +#include "nsZipArchive.h" + +#include "nsTArray.h" +#include "nsRefPtrHashtable.h" +#include "nsIMemoryReporter.h" +#include "nsThreadUtils.h" + +#ifdef DEBUG +#define ENSURE_MAIN_PROCESS(message, pref) do { \ + if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \ + nsPrintfCString msg("ENSURE_MAIN_PROCESS failed. %s %s", message, pref); \ + NS_WARNING(msg.get()); \ + return NS_ERROR_NOT_AVAILABLE; \ + } \ +} while (0); +#else +#define ENSURE_MAIN_PROCESS(message, pref) \ + if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \ + return NS_ERROR_NOT_AVAILABLE; \ + } +#endif + +class PrefCallback; + +namespace mozilla { + +// Definitions +#define INITIAL_PREF_FILES 10 +static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); + +void +Preferences::DirtyCallback() +{ + if (gHashTable && sPreferences && !sPreferences->mDirty) { + sPreferences->mDirty = true; + } +} + +// Prototypes +static nsresult openPrefFile(nsIFile* aFile); +static nsresult pref_InitInitialObjects(void); +static nsresult pref_LoadPrefsInDirList(const char *listId); +static nsresult ReadExtensionPrefs(nsIFile *aFile); + +static const char kTelemetryPref[] = "toolkit.telemetry.enabled"; +static const char kOldTelemetryPref[] = "toolkit.telemetry.enabledPreRelease"; +static const char kChannelPref[] = "app.update.channel"; + +static const char kPrefFileHeader[] = + "# Mozilla User Preferences" + NS_LINEBREAK + NS_LINEBREAK + "/* Do not edit this file." + NS_LINEBREAK + " *" + NS_LINEBREAK + " * If you make changes to this file while the application is running," + NS_LINEBREAK + " * the changes will be overwritten when the application exits." + NS_LINEBREAK + " *" + NS_LINEBREAK + " * To make a manual change to preferences, you can visit the URL about:config" + NS_LINEBREAK + " */" + NS_LINEBREAK + NS_LINEBREAK; + +Preferences* Preferences::sPreferences = nullptr; +nsIPrefBranch* Preferences::sRootBranch = nullptr; +nsIPrefBranch* Preferences::sDefaultRootBranch = nullptr; +bool Preferences::sShutdown = false; + +class ValueObserverHashKey : public PLDHashEntryHdr { +public: + typedef ValueObserverHashKey* KeyType; + typedef const ValueObserverHashKey* KeyTypePointer; + + static const ValueObserverHashKey* KeyToPointer(ValueObserverHashKey *aKey) + { + return aKey; + } + + static PLDHashNumber HashKey(const ValueObserverHashKey *aKey) + { + PLDHashNumber hash = HashString(aKey->mPrefName); + hash = AddToHash(hash, aKey->mMatchKind); + return AddToHash(hash, aKey->mCallback); + } + + ValueObserverHashKey(const char *aPref, PrefChangedFunc aCallback, Preferences::MatchKind aMatchKind) : + mPrefName(aPref), mCallback(aCallback), mMatchKind(aMatchKind) { } + + explicit ValueObserverHashKey(const ValueObserverHashKey *aOther) : + mPrefName(aOther->mPrefName), + mCallback(aOther->mCallback), + mMatchKind(aOther->mMatchKind) + { } + + bool KeyEquals(const ValueObserverHashKey *aOther) const + { + return mCallback == aOther->mCallback && + mPrefName == aOther->mPrefName && + mMatchKind == aOther->mMatchKind; + } + + ValueObserverHashKey *GetKey() const + { + return const_cast<ValueObserverHashKey*>(this); + } + + enum { ALLOW_MEMMOVE = true }; + + nsCString mPrefName; + PrefChangedFunc mCallback; + Preferences::MatchKind mMatchKind; +}; + +class ValueObserver final : public nsIObserver, + public ValueObserverHashKey +{ + ~ValueObserver() { + Preferences::RemoveObserver(this, mPrefName.get()); + } + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + ValueObserver(const char *aPref, PrefChangedFunc aCallback, Preferences::MatchKind aMatchKind) + : ValueObserverHashKey(aPref, aCallback, aMatchKind) { } + + void AppendClosure(void *aClosure) { + mClosures.AppendElement(aClosure); + } + + void RemoveClosure(void *aClosure) { + mClosures.RemoveElement(aClosure); + } + + bool HasNoClosures() { + return mClosures.Length() == 0; + } + + nsTArray<void*> mClosures; +}; + +NS_IMPL_ISUPPORTS(ValueObserver, nsIObserver) + +NS_IMETHODIMP +ValueObserver::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + NS_ASSERTION(!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID), + "invalid topic"); + NS_ConvertUTF16toUTF8 data(aData); + if (mMatchKind == Preferences::ExactMatch && !mPrefName.EqualsASCII(data.get())) { + return NS_OK; + } + for (uint32_t i = 0; i < mClosures.Length(); i++) { + mCallback(data.get(), mClosures.ElementAt(i)); + } + + return NS_OK; +} + +struct CacheData { + void* cacheLocation; + union { + bool defaultValueBool; + int32_t defaultValueInt; + uint32_t defaultValueUint; + float defaultValueFloat; + }; +}; + +static nsTArray<nsAutoPtr<CacheData> >* gCacheData = nullptr; +static nsRefPtrHashtable<ValueObserverHashKey, + ValueObserver>* gObserverTable = nullptr; + +#ifdef DEBUG +static bool +HaveExistingCacheFor(void* aPtr) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (gCacheData) { + for (size_t i = 0, count = gCacheData->Length(); i < count; ++i) { + if ((*gCacheData)[i]->cacheLocation == aPtr) { + return true; + } + } + } + return false; +} + +static void +AssertNotAlreadyCached(const char* aPrefType, + const char* aPref, + void* aPtr) +{ + if (HaveExistingCacheFor(aPtr)) { + fprintf_stderr(stderr, + "Attempt to add a %s pref cache for preference '%s' at address '%p'" + "was made. However, a pref was already cached at this address.\n", + aPrefType, aPref, aPtr); + MOZ_ASSERT(false, "Should not have an existing pref cache for this address"); + } +} +#endif + +static void +ReportToConsole(const char* aMessage, int aLine, bool aError) +{ + nsPrintfCString message("** Preference parsing %s (line %d) = %s **\n", + (aError ? "error" : "warning"), aLine, aMessage); + nsPrefBranch::ReportToConsole(NS_ConvertUTF8toUTF16(message.get())); +} + +// Although this is a member of Preferences, it measures sPreferences and +// several other global structures. +/* static */ int64_t +Preferences::SizeOfIncludingThisAndOtherStuff(mozilla::MallocSizeOf aMallocSizeOf) +{ + NS_ENSURE_TRUE(InitStaticMembers(), 0); + + size_t n = aMallocSizeOf(sPreferences); + if (gHashTable) { + // pref keys are allocated in a private arena, which we count elsewhere. + // pref stringvals are allocated out of the same private arena. + n += gHashTable->ShallowSizeOfIncludingThis(aMallocSizeOf); + } + if (gCacheData) { + n += gCacheData->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (uint32_t i = 0, count = gCacheData->Length(); i < count; ++i) { + n += aMallocSizeOf((*gCacheData)[i]); + } + } + if (gObserverTable) { + n += gObserverTable->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (auto iter = gObserverTable->Iter(); !iter.Done(); iter.Next()) { + n += iter.Key()->mPrefName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + n += iter.Data()->mClosures.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + } + if (sRootBranch) { + n += reinterpret_cast<nsPrefBranch*>(sRootBranch)->SizeOfIncludingThis(aMallocSizeOf); + } + if (sDefaultRootBranch) { + n += reinterpret_cast<nsPrefBranch*>(sDefaultRootBranch)->SizeOfIncludingThis(aMallocSizeOf); + } + n += pref_SizeOfPrivateData(aMallocSizeOf); + return n; +} + +class PreferenceServiceReporter final : public nsIMemoryReporter +{ + ~PreferenceServiceReporter() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + +protected: + static const uint32_t kSuspectReferentCount = 1000; +}; + +NS_IMPL_ISUPPORTS(PreferenceServiceReporter, nsIMemoryReporter) + +MOZ_DEFINE_MALLOC_SIZE_OF(PreferenceServiceMallocSizeOf) + +NS_IMETHODIMP +PreferenceServiceReporter::CollectReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) +{ + MOZ_COLLECT_REPORT( + "explicit/preferences", KIND_HEAP, UNITS_BYTES, + Preferences::SizeOfIncludingThisAndOtherStuff(PreferenceServiceMallocSizeOf), + "Memory used by the preferences system."); + + nsPrefBranch* rootBranch = + static_cast<nsPrefBranch*>(Preferences::GetRootBranch()); + if (!rootBranch) { + return NS_OK; + } + + size_t numStrong = 0; + size_t numWeakAlive = 0; + size_t numWeakDead = 0; + nsTArray<nsCString> suspectPreferences; + // Count of the number of referents for each preference. + nsDataHashtable<nsCStringHashKey, uint32_t> prefCounter; + + for (auto iter = rootBranch->mObservers.Iter(); !iter.Done(); iter.Next()) { + nsAutoPtr<PrefCallback>& callback = iter.Data(); + nsPrefBranch* prefBranch = callback->GetPrefBranch(); + const char* pref = prefBranch->getPrefName(callback->GetDomain().get()); + + if (callback->IsWeak()) { + nsCOMPtr<nsIObserver> callbackRef = do_QueryReferent(callback->mWeakRef); + if (callbackRef) { + numWeakAlive++; + } else { + numWeakDead++; + } + } else { + numStrong++; + } + + nsDependentCString prefString(pref); + uint32_t oldCount = 0; + prefCounter.Get(prefString, &oldCount); + uint32_t currentCount = oldCount + 1; + prefCounter.Put(prefString, currentCount); + + // Keep track of preferences that have a suspiciously large number of + // referents (a symptom of a leak). + if (currentCount == kSuspectReferentCount) { + suspectPreferences.AppendElement(prefString); + } + } + + for (uint32_t i = 0; i < suspectPreferences.Length(); i++) { + nsCString& suspect = suspectPreferences[i]; + uint32_t totalReferentCount = 0; + prefCounter.Get(suspect, &totalReferentCount); + + nsPrintfCString suspectPath("preference-service-suspect/" + "referent(pref=%s)", suspect.get()); + + aHandleReport->Callback( + /* process = */ EmptyCString(), + suspectPath, KIND_OTHER, UNITS_COUNT, totalReferentCount, + NS_LITERAL_CSTRING( + "A preference with a suspiciously large number referents (symptom of a " + "leak)."), + aData); + } + + MOZ_COLLECT_REPORT( + "preference-service/referent/strong", KIND_OTHER, UNITS_COUNT, + numStrong, + "The number of strong referents held by the preference service."); + + MOZ_COLLECT_REPORT( + "preference-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT, + numWeakAlive, + "The number of weak referents held by the preference service that are " + "still alive."); + + MOZ_COLLECT_REPORT( + "preference-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT, + numWeakDead, + "The number of weak referents held by the preference service that are " + "dead."); + + return NS_OK; +} + +namespace { +class AddPreferencesMemoryReporterRunnable : public Runnable +{ + NS_IMETHOD Run() override + { + return RegisterStrongMemoryReporter(new PreferenceServiceReporter()); + } +}; +} // namespace + +// static +Preferences* +Preferences::GetInstanceForService() +{ + if (sPreferences) { + NS_ADDREF(sPreferences); + return sPreferences; + } + + NS_ENSURE_TRUE(!sShutdown, nullptr); + + sRootBranch = new nsPrefBranch("", false); + NS_ADDREF(sRootBranch); + sDefaultRootBranch = new nsPrefBranch("", true); + NS_ADDREF(sDefaultRootBranch); + + sPreferences = new Preferences(); + NS_ADDREF(sPreferences); + + if (NS_FAILED(sPreferences->Init())) { + // The singleton instance will delete sRootBranch and sDefaultRootBranch. + NS_RELEASE(sPreferences); + return nullptr; + } + + gCacheData = new nsTArray<nsAutoPtr<CacheData> >(); + + gObserverTable = new nsRefPtrHashtable<ValueObserverHashKey, ValueObserver>(); + + // Preferences::GetInstanceForService() can be called from GetService(), and + // RegisterStrongMemoryReporter calls GetService(nsIMemoryReporter). To + // avoid a potential recursive GetService() call, we can't register the + // memory reporter here; instead, do it off a runnable. + RefPtr<AddPreferencesMemoryReporterRunnable> runnable = + new AddPreferencesMemoryReporterRunnable(); + NS_DispatchToMainThread(runnable); + + NS_ADDREF(sPreferences); + return sPreferences; +} + +// static +bool +Preferences::IsServiceAvailable() +{ + return !!sPreferences; +} + +// static +bool +Preferences::InitStaticMembers() +{ +#ifndef MOZ_B2G + MOZ_ASSERT(NS_IsMainThread()); +#endif + + if (!sShutdown && !sPreferences) { + nsCOMPtr<nsIPrefService> prefService = + do_GetService(NS_PREFSERVICE_CONTRACTID); + } + + return sPreferences != nullptr; +} + +// static +void +Preferences::Shutdown() +{ + if (!sShutdown) { + sShutdown = true; // Don't create the singleton instance after here. + + // Don't set sPreferences to nullptr here. The instance may be grabbed by + // other modules. The utility methods of Preferences should be available + // until the singleton instance actually released. + if (sPreferences) { + sPreferences->Release(); + } + } +} + +//----------------------------------------------------------------------------- + +/* + * Constructor/Destructor + */ + +Preferences::Preferences() + : mDirty(false) +{ +} + +Preferences::~Preferences() +{ + NS_ASSERTION(sPreferences == this, "Isn't this the singleton instance?"); + + delete gObserverTable; + gObserverTable = nullptr; + + delete gCacheData; + gCacheData = nullptr; + + NS_RELEASE(sRootBranch); + NS_RELEASE(sDefaultRootBranch); + + sPreferences = nullptr; + + PREF_Cleanup(); +} + + +/* + * nsISupports Implementation + */ + +NS_IMPL_ADDREF(Preferences) +NS_IMPL_RELEASE(Preferences) + +NS_INTERFACE_MAP_BEGIN(Preferences) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefService) + NS_INTERFACE_MAP_ENTRY(nsIPrefService) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIPrefBranch) + NS_INTERFACE_MAP_ENTRY(nsIPrefBranch2) + NS_INTERFACE_MAP_ENTRY(nsIPrefBranchInternal) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + + +/* + * nsIPrefService Implementation + */ + +nsresult +Preferences::Init() +{ + nsresult rv; + + PREF_SetDirtyCallback(&DirtyCallback); + PREF_Init(); + + rv = pref_InitInitialObjects(); + NS_ENSURE_SUCCESS(rv, rv); + + using mozilla::dom::ContentChild; + if (XRE_IsContentProcess()) { + InfallibleTArray<PrefSetting> prefs; + ContentChild::GetSingleton()->SendReadPrefsArray(&prefs); + + // Store the array + for (uint32_t i = 0; i < prefs.Length(); ++i) { + pref_SetPref(prefs[i]); + } + return NS_OK; + } + + nsXPIDLCString lockFileName; + /* + * The following is a small hack which will allow us to only load the library + * which supports the netscape.cfg file if the preference is defined. We + * test for the existence of the pref, set in the all.js (mozilla) or + * all-ns.js (netscape 6), and if it exists we startup the pref config + * category which will do the rest. + */ + + rv = PREF_CopyCharPref("general.config.filename", getter_Copies(lockFileName), false); + if (NS_SUCCEEDED(rv)) + NS_CreateServicesFromCategory("pref-config-startup", + static_cast<nsISupports *>(static_cast<void *>(this)), + "pref-config-startup"); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + rv = observerService->AddObserver(this, "profile-before-change", true); + + observerService->AddObserver(this, "load-extension-defaults", true); + observerService->AddObserver(this, "suspend_process_notification", true); + + return(rv); +} + +// static +nsresult +Preferences::ResetAndReadUserPrefs() +{ + sPreferences->ResetUserPrefs(); + return sPreferences->ReadUserPrefs(nullptr); +} + +NS_IMETHODIMP +Preferences::Observe(nsISupports *aSubject, const char *aTopic, + const char16_t *someData) +{ + if (XRE_IsContentProcess()) + return NS_ERROR_NOT_AVAILABLE; + + nsresult rv = NS_OK; + + if (!nsCRT::strcmp(aTopic, "profile-before-change")) { + rv = SavePrefFile(nullptr); + } else if (!strcmp(aTopic, "load-extension-defaults")) { + pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST); + } else if (!nsCRT::strcmp(aTopic, "reload-default-prefs")) { + // Reload the default prefs from file. + pref_InitInitialObjects(); + } else if (!nsCRT::strcmp(aTopic, "suspend_process_notification")) { + // Our process is being suspended. The OS may wake our process later, + // or it may kill the process. In case our process is going to be killed + // from the suspended state, we save preferences before suspending. + rv = SavePrefFile(nullptr); + } + return rv; +} + + +NS_IMETHODIMP +Preferences::ReadUserPrefs(nsIFile *aFile) +{ + if (XRE_IsContentProcess()) { + NS_ERROR("cannot load prefs from content process"); + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv; + + if (nullptr == aFile) { + rv = UseDefaultPrefFile(); + // A user pref file is optional. + // Ignore all errors related to it, so we retain 'rv' value :-| + (void) UseUserPrefFile(); + + // Migrate the old prerelease telemetry pref + if (!Preferences::GetBool(kOldTelemetryPref, true)) { + Preferences::SetBool(kTelemetryPref, false); + Preferences::ClearUser(kOldTelemetryPref); + } + + NotifyServiceObservers(NS_PREFSERVICE_READ_TOPIC_ID); + } else { + rv = ReadAndOwnUserPrefFile(aFile); + } + + return rv; +} + +NS_IMETHODIMP +Preferences::ResetPrefs() +{ + if (XRE_IsContentProcess()) { + NS_ERROR("cannot reset prefs from content process"); + return NS_ERROR_NOT_AVAILABLE; + } + + NotifyServiceObservers(NS_PREFSERVICE_RESET_TOPIC_ID); + PREF_CleanupPrefs(); + + PREF_Init(); + + return pref_InitInitialObjects(); +} + +NS_IMETHODIMP +Preferences::ResetUserPrefs() +{ + if (XRE_IsContentProcess()) { + NS_ERROR("cannot reset user prefs from content process"); + return NS_ERROR_NOT_AVAILABLE; + } + + PREF_ClearAllUserPrefs(); + return NS_OK; +} + +NS_IMETHODIMP +Preferences::SavePrefFile(nsIFile *aFile) +{ + if (XRE_IsContentProcess()) { + NS_ERROR("cannot save pref file from content process"); + return NS_ERROR_NOT_AVAILABLE; + } + + return SavePrefFileInternal(aFile); +} + +static nsresult +ReadExtensionPrefs(nsIFile *aFile) +{ + nsresult rv; + nsCOMPtr<nsIZipReader> reader = do_CreateInstance(kZipReaderCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = reader->Open(aFile); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIUTF8StringEnumerator> files; + rv = reader->FindEntries(nsDependentCString("defaults/preferences/*.(J|j)(S|s)$"), + getter_AddRefs(files)); + NS_ENSURE_SUCCESS(rv, rv); + + char buffer[4096]; + + bool more; + while (NS_SUCCEEDED(rv = files->HasMore(&more)) && more) { + nsAutoCString entry; + rv = files->GetNext(entry); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> stream; + rv = reader->GetInputStream(entry, getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, rv); + + uint64_t avail; + uint32_t read; + + PrefParseState ps; + PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr); + while (NS_SUCCEEDED(rv = stream->Available(&avail)) && avail) { + rv = stream->Read(buffer, 4096, &read); + if (NS_FAILED(rv)) { + NS_WARNING("Pref stream read failed"); + break; + } + + PREF_ParseBuf(&ps, buffer, read); + } + PREF_FinalizeParseState(&ps); + } + return rv; +} + +void +Preferences::SetPreference(const PrefSetting& aPref) +{ + pref_SetPref(aPref); +} + +void +Preferences::GetPreference(PrefSetting* aPref) +{ + PrefHashEntry *entry = pref_HashTableLookup(aPref->name().get()); + if (!entry) + return; + + if (pref_EntryHasAdvisablySizedValues(entry)) { + pref_GetPrefFromEntry(entry, aPref); + } +} + +void +Preferences::GetPreferences(InfallibleTArray<PrefSetting>* aPrefs) +{ + aPrefs->SetCapacity(gHashTable->Capacity()); + for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<PrefHashEntry*>(iter.Get()); + + if (!pref_EntryHasAdvisablySizedValues(entry)) { + continue; + } + + dom::PrefSetting *pref = aPrefs->AppendElement(); + pref_GetPrefFromEntry(entry, pref); + } +} + +NS_IMETHODIMP +Preferences::GetBranch(const char *aPrefRoot, nsIPrefBranch **_retval) +{ + nsresult rv; + + if ((nullptr != aPrefRoot) && (*aPrefRoot != '\0')) { + // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think) + RefPtr<nsPrefBranch> prefBranch = new nsPrefBranch(aPrefRoot, false); + prefBranch.forget(_retval); + rv = NS_OK; + } else { + // special case caching the default root + nsCOMPtr<nsIPrefBranch> root(sRootBranch); + root.forget(_retval); + rv = NS_OK; + } + return rv; +} + +NS_IMETHODIMP +Preferences::GetDefaultBranch(const char *aPrefRoot, nsIPrefBranch **_retval) +{ + if (!aPrefRoot || !aPrefRoot[0]) { + nsCOMPtr<nsIPrefBranch> root(sDefaultRootBranch); + root.forget(_retval); + return NS_OK; + } + + // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think) + RefPtr<nsPrefBranch> prefBranch = new nsPrefBranch(aPrefRoot, true); + if (!prefBranch) + return NS_ERROR_OUT_OF_MEMORY; + + prefBranch.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +Preferences::GetDirty(bool *_retval) { + *_retval = mDirty; + return NS_OK; +} + +nsresult +Preferences::NotifyServiceObservers(const char *aTopic) +{ + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + nsISupports *subject = (nsISupports *)((nsIPrefService *)this); + observerService->NotifyObservers(subject, aTopic, nullptr); + + return NS_OK; +} + +nsresult +Preferences::UseDefaultPrefFile() +{ + nsCOMPtr<nsIFile> aFile; + nsresult rv = NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE, getter_AddRefs(aFile)); + + if (NS_SUCCEEDED(rv)) { + rv = ReadAndOwnUserPrefFile(aFile); + // Most likely cause of failure here is that the file didn't + // exist, so save a new one. mUserPrefReadFailed will be + // used to catch an error in actually reading the file. + if (NS_FAILED(rv)) { + if (NS_FAILED(SavePrefFileInternal(aFile))) + NS_ERROR("Failed to save new shared pref file"); + else + rv = NS_OK; + } + } + + return rv; +} + +nsresult +Preferences::UseUserPrefFile() +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIFile> aFile; + nsDependentCString prefsDirProp(NS_APP_PREFS_50_DIR); + + rv = NS_GetSpecialDirectory(prefsDirProp.get(), getter_AddRefs(aFile)); + if (NS_SUCCEEDED(rv) && aFile) { + rv = aFile->AppendNative(NS_LITERAL_CSTRING("user.js")); + if (NS_SUCCEEDED(rv)) { + bool exists = false; + aFile->Exists(&exists); + if (exists) { + rv = openPrefFile(aFile); + } else { + rv = NS_ERROR_FILE_NOT_FOUND; + } + } + } + return rv; +} + +nsresult +Preferences::MakeBackupPrefFile(nsIFile *aFile) +{ + // Example: this copies "prefs.js" to "Invalidprefs.js" in the same directory. + // "Invalidprefs.js" is removed if it exists, prior to making the copy. + nsAutoString newFilename; + nsresult rv = aFile->GetLeafName(newFilename); + NS_ENSURE_SUCCESS(rv, rv); + newFilename.Insert(NS_LITERAL_STRING("Invalid"), 0); + nsCOMPtr<nsIFile> newFile; + rv = aFile->GetParent(getter_AddRefs(newFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = newFile->Append(newFilename); + NS_ENSURE_SUCCESS(rv, rv); + bool exists = false; + newFile->Exists(&exists); + if (exists) { + rv = newFile->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + } + rv = aFile->CopyTo(nullptr, newFilename); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +nsresult +Preferences::ReadAndOwnUserPrefFile(nsIFile *aFile) +{ + NS_ENSURE_ARG(aFile); + + if (mCurrentFile == aFile) + return NS_OK; + mCurrentFile = aFile; + + nsresult rv = NS_OK; + bool exists = false; + mCurrentFile->Exists(&exists); + if (exists) { + rv = openPrefFile(mCurrentFile); + if (NS_FAILED(rv)) { + // Save a backup copy of the current (invalid) prefs file, since all prefs + // from the error line to the end of the file will be lost (bug 361102). + // TODO we should notify the user about it (bug 523725). + MakeBackupPrefFile(mCurrentFile); + } + } else { + rv = NS_ERROR_FILE_NOT_FOUND; + } + + return rv; +} + +nsresult +Preferences::SavePrefFileInternal(nsIFile *aFile) +{ + if (nullptr == aFile) { + // the mDirty flag tells us if we should write to mCurrentFile + // we only check this flag when the caller wants to write to the default + if (!mDirty) { + return NS_OK; + } + + // It's possible that we never got a prefs file. + nsresult rv = NS_OK; + if (mCurrentFile) + rv = WritePrefFile(mCurrentFile); + + return rv; + } else { + return WritePrefFile(aFile); + } +} + +nsresult +Preferences::WritePrefFile(nsIFile* aFile) +{ + nsCOMPtr<nsIOutputStream> outStreamSink; + nsCOMPtr<nsIOutputStream> outStream; + uint32_t writeAmount; + nsresult rv; + + if (!gHashTable) + return NS_ERROR_NOT_INITIALIZED; + + // execute a "safe" save by saving through a tempfile + rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink), + aFile, + -1, + 0600); + if (NS_FAILED(rv)) + return rv; + rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream), outStreamSink, 4096); + if (NS_FAILED(rv)) + return rv; + + // get the lines that we're supposed to be writing to the file + uint32_t prefCount; + UniquePtr<char*[]> valueArray = pref_savePrefs(gHashTable, &prefCount); + + /* Sort the preferences to make a readable file on disk */ + NS_QuickSort(valueArray.get(), prefCount, sizeof(char *), + pref_CompareStrings, nullptr); + + // write out the file header + outStream->Write(kPrefFileHeader, sizeof(kPrefFileHeader) - 1, &writeAmount); + + for (uint32_t valueIdx = 0; valueIdx < prefCount; valueIdx++) { + char*& pref = valueArray[valueIdx]; + MOZ_ASSERT(pref); + outStream->Write(pref, strlen(pref), &writeAmount); + outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount); + free(pref); + pref = nullptr; + } + + // tell the safe output stream to overwrite the real prefs file + // (it'll abort if there were any errors during writing) + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream); + NS_ASSERTION(safeStream, "expected a safe output stream!"); + if (safeStream) { + rv = safeStream->Finish(); + if (NS_FAILED(rv)) { + NS_WARNING("failed to save prefs file! possible data loss"); + return rv; + } + } + + mDirty = false; + return NS_OK; +} + +static nsresult openPrefFile(nsIFile* aFile) +{ + nsCOMPtr<nsIInputStream> inStr; + + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), aFile); + if (NS_FAILED(rv)) + return rv; + + int64_t fileSize64; + rv = aFile->GetFileSize(&fileSize64); + if (NS_FAILED(rv)) + return rv; + NS_ENSURE_TRUE(fileSize64 <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG); + + uint32_t fileSize = (uint32_t)fileSize64; + auto fileBuffer = MakeUniqueFallible<char[]>(fileSize); + if (fileBuffer == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + PrefParseState ps; + PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr); + + // Read is not guaranteed to return a buf the size of fileSize, + // but usually will. + nsresult rv2 = NS_OK; + uint32_t offset = 0; + for (;;) { + uint32_t amtRead = 0; + rv = inStr->Read(fileBuffer.get(), fileSize, &amtRead); + if (NS_FAILED(rv) || amtRead == 0) + break; + if (!PREF_ParseBuf(&ps, fileBuffer.get(), amtRead)) + rv2 = NS_ERROR_FILE_CORRUPTED; + offset += amtRead; + if (offset == fileSize) { + break; + } + } + + PREF_FinalizeParseState(&ps); + + return NS_FAILED(rv) ? rv : rv2; +} + +/* + * some stuff that gets called from Pref_Init() + */ + +static int +pref_CompareFileNames(nsIFile* aFile1, nsIFile* aFile2, void* /*unused*/) +{ + nsAutoCString filename1, filename2; + aFile1->GetNativeLeafName(filename1); + aFile2->GetNativeLeafName(filename2); + + return Compare(filename2, filename1); +} + +/** + * Load default pref files from a directory. The files in the + * directory are sorted reverse-alphabetically; a set of "special file + * names" may be specified which are loaded after all the others. + */ +static nsresult +pref_LoadPrefsInDir(nsIFile* aDir, char const *const *aSpecialFiles, uint32_t aSpecialFilesCount) +{ + nsresult rv, rv2; + bool hasMoreElements; + + nsCOMPtr<nsISimpleEnumerator> dirIterator; + + // this may fail in some normal cases, such as embedders who do not use a GRE + rv = aDir->GetDirectoryEntries(getter_AddRefs(dirIterator)); + if (NS_FAILED(rv)) { + // If the directory doesn't exist, then we have no reason to complain. We + // loaded everything (and nothing) successfully. + if (rv == NS_ERROR_FILE_NOT_FOUND || rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) + rv = NS_OK; + return rv; + } + + rv = dirIterator->HasMoreElements(&hasMoreElements); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMArray<nsIFile> prefFiles(INITIAL_PREF_FILES); + nsCOMArray<nsIFile> specialFiles(aSpecialFilesCount); + nsCOMPtr<nsIFile> prefFile; + + while (hasMoreElements && NS_SUCCEEDED(rv)) { + nsAutoCString leafName; + + nsCOMPtr<nsISupports> supports; + rv = dirIterator->GetNext(getter_AddRefs(supports)); + prefFile = do_QueryInterface(supports); + if (NS_FAILED(rv)) { + break; + } + + prefFile->GetNativeLeafName(leafName); + NS_ASSERTION(!leafName.IsEmpty(), "Failure in default prefs: directory enumerator returned empty file?"); + + // Skip non-js files + if (StringEndsWith(leafName, NS_LITERAL_CSTRING(".js"), + nsCaseInsensitiveCStringComparator())) { + bool shouldParse = true; + // separate out special files + for (uint32_t i = 0; i < aSpecialFilesCount; ++i) { + if (leafName.Equals(nsDependentCString(aSpecialFiles[i]))) { + shouldParse = false; + // special files should be process in order; we put them into + // the array by index; this can make the array sparse + specialFiles.ReplaceObjectAt(prefFile, i); + } + } + + if (shouldParse) { + prefFiles.AppendObject(prefFile); + } + } + + rv = dirIterator->HasMoreElements(&hasMoreElements); + } + + if (prefFiles.Count() + specialFiles.Count() == 0) { + NS_WARNING("No default pref files found."); + if (NS_SUCCEEDED(rv)) { + rv = NS_SUCCESS_FILE_DIRECTORY_EMPTY; + } + return rv; + } + + prefFiles.Sort(pref_CompareFileNames, nullptr); + + uint32_t arrayCount = prefFiles.Count(); + uint32_t i; + for (i = 0; i < arrayCount; ++i) { + rv2 = openPrefFile(prefFiles[i]); + if (NS_FAILED(rv2)) { + NS_ERROR("Default pref file not parsed successfully."); + rv = rv2; + } + } + + arrayCount = specialFiles.Count(); + for (i = 0; i < arrayCount; ++i) { + // this may be a sparse array; test before parsing + nsIFile* file = specialFiles[i]; + if (file) { + rv2 = openPrefFile(file); + if (NS_FAILED(rv2)) { + NS_ERROR("Special default pref file not parsed successfully."); + rv = rv2; + } + } + } + + return rv; +} + +static nsresult pref_LoadPrefsInDirList(const char *listId) +{ + nsresult rv; + nsCOMPtr<nsIProperties> dirSvc(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsISimpleEnumerator> list; + dirSvc->Get(listId, + NS_GET_IID(nsISimpleEnumerator), + getter_AddRefs(list)); + if (!list) + return NS_OK; + + bool hasMore; + while (NS_SUCCEEDED(list->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> elem; + list->GetNext(getter_AddRefs(elem)); + if (!elem) + continue; + + nsCOMPtr<nsIFile> path = do_QueryInterface(elem); + if (!path) + continue; + + nsAutoCString leaf; + path->GetNativeLeafName(leaf); + + // Do we care if a file provided by this process fails to load? + if (Substring(leaf, leaf.Length() - 4).EqualsLiteral(".xpi")) + ReadExtensionPrefs(path); + else + pref_LoadPrefsInDir(path, nullptr, 0); + } + return NS_OK; +} + +static nsresult pref_ReadPrefFromJar(nsZipArchive* jarReader, const char *name) +{ + nsZipItemPtr<char> manifest(jarReader, name, true); + NS_ENSURE_TRUE(manifest.Buffer(), NS_ERROR_NOT_AVAILABLE); + + PrefParseState ps; + PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr); + PREF_ParseBuf(&ps, manifest, manifest.Length()); + PREF_FinalizeParseState(&ps); + + return NS_OK; +} + +//---------------------------------------------------------------------------------------- +// Initialize default preference JavaScript buffers from +// appropriate TEXT resources +//---------------------------------------------------------------------------------------- +static nsresult pref_InitInitialObjects() +{ + nsresult rv; + + // In omni.jar case, we load the following prefs: + // - jar:$gre/omni.jar!/greprefs.js + // - jar:$gre/omni.jar!/defaults/pref/*.js + // In non omni.jar case, we load: + // - $gre/greprefs.js + // + // In both cases, we also load: + // - $gre/defaults/pref/*.js + // This is kept for bug 591866 (channel-prefs.js should not be in omni.jar) + // on $app == $gre case ; we load all files instead of channel-prefs.js only + // to have the same behaviour as $app != $gre, where this is required as + // a supported location for GRE preferences. + // + // When $app != $gre, we additionally load, in omni.jar case: + // - jar:$app/omni.jar!/defaults/preferences/*.js + // - $app/defaults/preferences/*.js + // and in non omni.jar case: + // - $app/defaults/preferences/*.js + // When $app == $gre, we additionally load, in omni.jar case: + // - jar:$gre/omni.jar!/defaults/preferences/*.js + // Thus, in omni.jar case, we always load app-specific default preferences + // from omni.jar, whether or not $app == $gre. + + nsZipFind *findPtr; + nsAutoPtr<nsZipFind> find; + nsTArray<nsCString> prefEntries; + const char *entryName; + uint16_t entryNameLen; + + RefPtr<nsZipArchive> jarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); + if (jarReader) { + // Load jar:$gre/omni.jar!/greprefs.js + rv = pref_ReadPrefFromJar(jarReader, "greprefs.js"); + NS_ENSURE_SUCCESS(rv, rv); + + // Load jar:$gre/omni.jar!/defaults/pref/*.js + rv = jarReader->FindInit("defaults/pref/*.js$", &findPtr); + NS_ENSURE_SUCCESS(rv, rv); + + find = findPtr; + while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) { + prefEntries.AppendElement(Substring(entryName, entryNameLen)); + } + + prefEntries.Sort(); + for (uint32_t i = prefEntries.Length(); i--; ) { + rv = pref_ReadPrefFromJar(jarReader, prefEntries[i].get()); + if (NS_FAILED(rv)) + NS_WARNING("Error parsing preferences."); + } + } else { + // Load $gre/greprefs.js + nsCOMPtr<nsIFile> greprefsFile; + rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greprefsFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = greprefsFile->AppendNative(NS_LITERAL_CSTRING("greprefs.js")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = openPrefFile(greprefsFile); + if (NS_FAILED(rv)) + NS_WARNING("Error parsing GRE default preferences. Is this an old-style embedding app?"); + } + + // Load $gre/defaults/pref/*.js + nsCOMPtr<nsIFile> defaultPrefDir; + + rv = NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR, getter_AddRefs(defaultPrefDir)); + NS_ENSURE_SUCCESS(rv, rv); + + /* these pref file names should not be used: we process them after all other application pref files for backwards compatibility */ + static const char* specialFiles[] = { +#if defined(XP_MACOSX) + "macprefs.js" +#elif defined(XP_WIN) + "winpref.js" +#elif defined(XP_UNIX) + "unix.js" +#if defined(_AIX) + , "aix.js" +#endif +#elif defined(XP_BEOS) + "beos.js" +#endif + }; + + rv = pref_LoadPrefsInDir(defaultPrefDir, specialFiles, ArrayLength(specialFiles)); + if (NS_FAILED(rv)) + NS_WARNING("Error parsing application default preferences."); + + // Load jar:$app/omni.jar!/defaults/preferences/*.js + // or jar:$gre/omni.jar!/defaults/preferences/*.js. + RefPtr<nsZipArchive> appJarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); + // GetReader(mozilla::Omnijar::APP) returns null when $app == $gre, in which + // case we look for app-specific default preferences in $gre. + if (!appJarReader) + appJarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); + if (appJarReader) { + rv = appJarReader->FindInit("defaults/preferences/*.js$", &findPtr); + NS_ENSURE_SUCCESS(rv, rv); + find = findPtr; + prefEntries.Clear(); + while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) { + prefEntries.AppendElement(Substring(entryName, entryNameLen)); + } + prefEntries.Sort(); + for (uint32_t i = prefEntries.Length(); i--; ) { + rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get()); + if (NS_FAILED(rv)) + NS_WARNING("Error parsing preferences."); + } + } + + rv = pref_LoadPrefsInDirList(NS_APP_PREFS_DEFAULTS_DIR_LIST); + NS_ENSURE_SUCCESS(rv, rv); + + // Set up the correct default for toolkit.telemetry.enabled. + // If this build has MOZ_TELEMETRY_ON_BY_DEFAULT *or* we're on the beta + // channel, telemetry is on by default, otherwise not. This is necessary + // so that beta users who are testing final release builds don't flipflop + // defaults. + if (Preferences::GetDefaultType(kTelemetryPref) == nsIPrefBranch::PREF_INVALID) { + bool prerelease = false; +#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT + prerelease = true; +#else + if (Preferences::GetDefaultCString(kChannelPref).EqualsLiteral("beta")) { + prerelease = true; + } +#endif + PREF_SetBoolPref(kTelemetryPref, prerelease, true); + } + + NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, + nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + observerService->NotifyObservers(nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, nullptr); + + return pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST); +} + + +/****************************************************************************** + * + * static utilities + * + ******************************************************************************/ + +// static +nsresult +Preferences::GetBool(const char* aPref, bool* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_GetBoolPref(aPref, aResult, false); +} + +// static +nsresult +Preferences::GetInt(const char* aPref, int32_t* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_GetIntPref(aPref, aResult, false); +} + +// static +nsresult +Preferences::GetFloat(const char* aPref, float* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsAutoCString result; + nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), false); + if (NS_SUCCEEDED(rv)) { + *aResult = result.ToFloat(&rv); + } + + return rv; +} + +// static +nsAdoptingCString +Preferences::GetCString(const char* aPref) +{ + nsAdoptingCString result; + PREF_CopyCharPref(aPref, getter_Copies(result), false); + return result; +} + +// static +nsAdoptingString +Preferences::GetString(const char* aPref) +{ + nsAdoptingString result; + GetString(aPref, &result); + return result; +} + +// static +nsresult +Preferences::GetCString(const char* aPref, nsACString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsAutoCString result; + nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), false); + if (NS_SUCCEEDED(rv)) { + *aResult = result; + } + return rv; +} + +// static +nsresult +Preferences::GetString(const char* aPref, nsAString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsAutoCString result; + nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), false); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(result, *aResult); + } + return rv; +} + +// static +nsAdoptingCString +Preferences::GetLocalizedCString(const char* aPref) +{ + nsAdoptingCString result; + GetLocalizedCString(aPref, &result); + return result; +} + +// static +nsAdoptingString +Preferences::GetLocalizedString(const char* aPref) +{ + nsAdoptingString result; + GetLocalizedString(aPref, &result); + return result; +} + +// static +nsresult +Preferences::GetLocalizedCString(const char* aPref, nsACString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + nsAutoString result; + nsresult rv = GetLocalizedString(aPref, &result); + if (NS_SUCCEEDED(rv)) { + CopyUTF16toUTF8(result, *aResult); + } + return rv; +} + +// static +nsresult +Preferences::GetLocalizedString(const char* aPref, nsAString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsCOMPtr<nsIPrefLocalizedString> prefLocalString; + nsresult rv = sRootBranch->GetComplexValue(aPref, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(prefLocalString)); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(prefLocalString, "Succeeded but the result is NULL"); + prefLocalString->GetData(getter_Copies(*aResult)); + } + return rv; +} + +// static +nsresult +Preferences::GetComplex(const char* aPref, const nsIID &aType, void** aResult) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return sRootBranch->GetComplexValue(aPref, aType, aResult); +} + +// static +nsresult +Preferences::SetCString(const char* aPref, const char* aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetCString from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetCharPref(aPref, aValue, false); +} + +// static +nsresult +Preferences::SetCString(const char* aPref, const nsACString &aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetCString from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetCharPref(aPref, PromiseFlatCString(aValue).get(), false); +} + +// static +nsresult +Preferences::SetString(const char* aPref, const char16ptr_t aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetString from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetCharPref(aPref, NS_ConvertUTF16toUTF8(aValue).get(), false); +} + +// static +nsresult +Preferences::SetString(const char* aPref, const nsAString &aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetString from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetCharPref(aPref, NS_ConvertUTF16toUTF8(aValue).get(), false); +} + +// static +nsresult +Preferences::SetBool(const char* aPref, bool aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetBool from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetBoolPref(aPref, aValue, false); +} + +// static +nsresult +Preferences::SetInt(const char* aPref, int32_t aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetInt from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetIntPref(aPref, aValue, false); +} + +// static +nsresult +Preferences::SetFloat(const char* aPref, float aValue) +{ + return SetCString(aPref, nsPrintfCString("%f", aValue).get()); +} + +// static +nsresult +Preferences::SetComplex(const char* aPref, const nsIID &aType, + nsISupports* aValue) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return sRootBranch->SetComplexValue(aPref, aType, aValue); +} + +// static +nsresult +Preferences::ClearUser(const char* aPref) +{ + ENSURE_MAIN_PROCESS("Cannot ClearUser from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_ClearUserPref(aPref); +} + +// static +bool +Preferences::HasUserValue(const char* aPref) +{ + NS_ENSURE_TRUE(InitStaticMembers(), false); + return PREF_HasUserPref(aPref); +} + +// static +int32_t +Preferences::GetType(const char* aPref) +{ + NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID); + int32_t result; + return NS_SUCCEEDED(sRootBranch->GetPrefType(aPref, &result)) ? + result : nsIPrefBranch::PREF_INVALID; +} + +// static +nsresult +Preferences::AddStrongObserver(nsIObserver* aObserver, + const char* aPref) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return sRootBranch->AddObserver(aPref, aObserver, false); +} + +// static +nsresult +Preferences::AddWeakObserver(nsIObserver* aObserver, + const char* aPref) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return sRootBranch->AddObserver(aPref, aObserver, true); +} + +// static +nsresult +Preferences::RemoveObserver(nsIObserver* aObserver, + const char* aPref) +{ + if (!sPreferences && sShutdown) { + return NS_OK; // Observers have been released automatically. + } + NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE); + return sRootBranch->RemoveObserver(aPref, aObserver); +} + +// static +nsresult +Preferences::AddStrongObservers(nsIObserver* aObserver, + const char** aPrefs) +{ + for (uint32_t i = 0; aPrefs[i]; i++) { + nsresult rv = AddStrongObserver(aObserver, aPrefs[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +// static +nsresult +Preferences::AddWeakObservers(nsIObserver* aObserver, + const char** aPrefs) +{ + for (uint32_t i = 0; aPrefs[i]; i++) { + nsresult rv = AddWeakObserver(aObserver, aPrefs[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +// static +nsresult +Preferences::RemoveObservers(nsIObserver* aObserver, + const char** aPrefs) +{ + if (!sPreferences && sShutdown) { + return NS_OK; // Observers have been released automatically. + } + NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE); + + for (uint32_t i = 0; aPrefs[i]; i++) { + nsresult rv = RemoveObserver(aObserver, aPrefs[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +// static +nsresult +Preferences::RegisterCallback(PrefChangedFunc aCallback, + const char* aPref, + void* aClosure, + MatchKind aMatchKind) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + + ValueObserverHashKey hashKey(aPref, aCallback, aMatchKind); + RefPtr<ValueObserver> observer; + gObserverTable->Get(&hashKey, getter_AddRefs(observer)); + if (observer) { + observer->AppendClosure(aClosure); + return NS_OK; + } + + observer = new ValueObserver(aPref, aCallback, aMatchKind); + observer->AppendClosure(aClosure); + nsresult rv = AddStrongObserver(observer, aPref); + NS_ENSURE_SUCCESS(rv, rv); + gObserverTable->Put(observer, observer); + return NS_OK; +} + +// static +nsresult +Preferences::RegisterCallbackAndCall(PrefChangedFunc aCallback, + const char* aPref, + void* aClosure, + MatchKind aMatchKind) +{ + nsresult rv = RegisterCallback(aCallback, aPref, aClosure, aMatchKind); + if (NS_SUCCEEDED(rv)) { + (*aCallback)(aPref, aClosure); + } + return rv; +} + +// static +nsresult +Preferences::UnregisterCallback(PrefChangedFunc aCallback, + const char* aPref, + void* aClosure, + MatchKind aMatchKind) +{ + if (!sPreferences && sShutdown) { + return NS_OK; // Observers have been released automatically. + } + NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE); + + ValueObserverHashKey hashKey(aPref, aCallback, aMatchKind); + RefPtr<ValueObserver> observer; + gObserverTable->Get(&hashKey, getter_AddRefs(observer)); + if (!observer) { + return NS_OK; + } + + observer->RemoveClosure(aClosure); + if (observer->HasNoClosures()) { + // Delete the callback since its list of closures is empty. + gObserverTable->Remove(observer); + } + return NS_OK; +} + +static void BoolVarChanged(const char* aPref, void* aClosure) +{ + CacheData* cache = static_cast<CacheData*>(aClosure); + *((bool*)cache->cacheLocation) = + Preferences::GetBool(aPref, cache->defaultValueBool); +} + +// static +nsresult +Preferences::AddBoolVarCache(bool* aCache, + const char* aPref, + bool aDefault) +{ + NS_ASSERTION(aCache, "aCache must not be NULL"); +#ifdef DEBUG + AssertNotAlreadyCached("bool", aPref, aCache); +#endif + *aCache = GetBool(aPref, aDefault); + CacheData* data = new CacheData(); + data->cacheLocation = aCache; + data->defaultValueBool = aDefault; + gCacheData->AppendElement(data); + return RegisterCallback(BoolVarChanged, aPref, data, ExactMatch); +} + +static void IntVarChanged(const char* aPref, void* aClosure) +{ + CacheData* cache = static_cast<CacheData*>(aClosure); + *((int32_t*)cache->cacheLocation) = + Preferences::GetInt(aPref, cache->defaultValueInt); +} + +// static +nsresult +Preferences::AddIntVarCache(int32_t* aCache, + const char* aPref, + int32_t aDefault) +{ + NS_ASSERTION(aCache, "aCache must not be NULL"); +#ifdef DEBUG + AssertNotAlreadyCached("int", aPref, aCache); +#endif + *aCache = Preferences::GetInt(aPref, aDefault); + CacheData* data = new CacheData(); + data->cacheLocation = aCache; + data->defaultValueInt = aDefault; + gCacheData->AppendElement(data); + return RegisterCallback(IntVarChanged, aPref, data, ExactMatch); +} + +static void UintVarChanged(const char* aPref, void* aClosure) +{ + CacheData* cache = static_cast<CacheData*>(aClosure); + *((uint32_t*)cache->cacheLocation) = + Preferences::GetUint(aPref, cache->defaultValueUint); +} + +// static +nsresult +Preferences::AddUintVarCache(uint32_t* aCache, + const char* aPref, + uint32_t aDefault) +{ + NS_ASSERTION(aCache, "aCache must not be NULL"); +#ifdef DEBUG + AssertNotAlreadyCached("uint", aPref, aCache); +#endif + *aCache = Preferences::GetUint(aPref, aDefault); + CacheData* data = new CacheData(); + data->cacheLocation = aCache; + data->defaultValueUint = aDefault; + gCacheData->AppendElement(data); + return RegisterCallback(UintVarChanged, aPref, data, ExactMatch); +} + +template <MemoryOrdering Order> +static void AtomicUintVarChanged(const char* aPref, void* aClosure) +{ + CacheData* cache = static_cast<CacheData*>(aClosure); + *((Atomic<uint32_t, Order>*)cache->cacheLocation) = + Preferences::GetUint(aPref, cache->defaultValueUint); +} + +template <MemoryOrdering Order> +// static +nsresult +Preferences::AddAtomicUintVarCache(Atomic<uint32_t, Order>* aCache, + const char* aPref, + uint32_t aDefault) +{ + NS_ASSERTION(aCache, "aCache must not be NULL"); +#ifdef DEBUG + AssertNotAlreadyCached("uint", aPref, aCache); +#endif + *aCache = Preferences::GetUint(aPref, aDefault); + CacheData* data = new CacheData(); + data->cacheLocation = aCache; + data->defaultValueUint = aDefault; + gCacheData->AppendElement(data); + return RegisterCallback(AtomicUintVarChanged<Order>, aPref, data, ExactMatch); +} + +// Since the definition of this template function is not in a header file, +// we need to explicitly specify the instantiations that are required. +// Currently only the order=Relaxed variant is needed. +template +nsresult Preferences::AddAtomicUintVarCache(Atomic<uint32_t,Relaxed>*, + const char*, uint32_t); + +static void FloatVarChanged(const char* aPref, void* aClosure) +{ + CacheData* cache = static_cast<CacheData*>(aClosure); + *((float*)cache->cacheLocation) = + Preferences::GetFloat(aPref, cache->defaultValueFloat); +} + +// static +nsresult +Preferences::AddFloatVarCache(float* aCache, + const char* aPref, + float aDefault) +{ + NS_ASSERTION(aCache, "aCache must not be NULL"); +#ifdef DEBUG + AssertNotAlreadyCached("float", aPref, aCache); +#endif + *aCache = Preferences::GetFloat(aPref, aDefault); + CacheData* data = new CacheData(); + data->cacheLocation = aCache; + data->defaultValueFloat = aDefault; + gCacheData->AppendElement(data); + return RegisterCallback(FloatVarChanged, aPref, data, ExactMatch); +} + +// static +nsresult +Preferences::GetDefaultBool(const char* aPref, bool* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_GetBoolPref(aPref, aResult, true); +} + +// static +nsresult +Preferences::GetDefaultInt(const char* aPref, int32_t* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_GetIntPref(aPref, aResult, true); +} + +// static +nsresult +Preferences::GetDefaultCString(const char* aPref, nsACString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsAutoCString result; + nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), true); + if (NS_SUCCEEDED(rv)) { + *aResult = result; + } + return rv; +} + +// static +nsresult +Preferences::GetDefaultString(const char* aPref, nsAString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsAutoCString result; + nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), true); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(result, *aResult); + } + return rv; +} + +// static +nsresult +Preferences::GetDefaultLocalizedCString(const char* aPref, + nsACString* aResult) +{ + nsAutoString result; + nsresult rv = GetDefaultLocalizedString(aPref, &result); + if (NS_SUCCEEDED(rv)) { + CopyUTF16toUTF8(result, *aResult); + } + return rv; +} + +// static +nsresult +Preferences::GetDefaultLocalizedString(const char* aPref, + nsAString* aResult) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsCOMPtr<nsIPrefLocalizedString> prefLocalString; + nsresult rv = + sDefaultRootBranch->GetComplexValue(aPref, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(prefLocalString)); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(prefLocalString, "Succeeded but the result is NULL"); + prefLocalString->GetData(getter_Copies(*aResult)); + } + return rv; +} + +// static +nsAdoptingString +Preferences::GetDefaultString(const char* aPref) +{ + nsAdoptingString result; + GetDefaultString(aPref, &result); + return result; +} + +// static +nsAdoptingCString +Preferences::GetDefaultCString(const char* aPref) +{ + nsAdoptingCString result; + PREF_CopyCharPref(aPref, getter_Copies(result), true); + return result; +} + +// static +nsAdoptingString +Preferences::GetDefaultLocalizedString(const char* aPref) +{ + nsAdoptingString result; + GetDefaultLocalizedString(aPref, &result); + return result; +} + +// static +nsAdoptingCString +Preferences::GetDefaultLocalizedCString(const char* aPref) +{ + nsAdoptingCString result; + GetDefaultLocalizedCString(aPref, &result); + return result; +} + +// static +nsresult +Preferences::GetDefaultComplex(const char* aPref, const nsIID &aType, + void** aResult) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return sDefaultRootBranch->GetComplexValue(aPref, aType, aResult); +} + +// static +int32_t +Preferences::GetDefaultType(const char* aPref) +{ + NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID); + int32_t result; + return NS_SUCCEEDED(sDefaultRootBranch->GetPrefType(aPref, &result)) ? + result : nsIPrefBranch::PREF_INVALID; +} + +} // namespace mozilla + +#undef ENSURE_MAIN_PROCESS |