/* -*- 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() { MOZ_ASSERT(NS_IsMainThread()); 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" #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