diff options
Diffstat (limited to 'xpcom/components/nsCategoryManager.cpp')
-rw-r--r-- | xpcom/components/nsCategoryManager.cpp | 696 |
1 files changed, 696 insertions, 0 deletions
diff --git a/xpcom/components/nsCategoryManager.cpp b/xpcom/components/nsCategoryManager.cpp new file mode 100644 index 0000000000..e620067219 --- /dev/null +++ b/xpcom/components/nsCategoryManager.cpp @@ -0,0 +1,696 @@ +/* -*- 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 "nsCategoryManager.h" +#include "nsCategoryManagerUtils.h" + +#include "prio.h" +#include "prlock.h" +#include "nsArrayEnumerator.h" +#include "nsCOMPtr.h" +#include "nsTHashtable.h" +#include "nsClassHashtable.h" +#include "nsStringEnumerator.h" +#include "nsSupportsPrimitives.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsPrintfCString.h" +#include "nsQuickSort.h" +#include "nsEnumeratorUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/ArenaAllocatorExtensions.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Services.h" +#include "mozilla/SimpleEnumerator.h" + +#include "GeckoProfiler.h" +#include "ManifestParser.h" +#include "nsSimpleEnumerator.h" + +using namespace mozilla; +class nsIComponentLoaderManager; + +/* + CategoryDatabase + contains 0 or more 1-1 mappings of string to Category + each Category contains 0 or more 1-1 mappings of string keys to string values + + In other words, the CategoryDatabase is a tree, whose root is a hashtable. + Internal nodes (or Categories) are hashtables. Leaf nodes are strings. + + The leaf strings are allocated in an arena, because we assume they're not + going to change much ;) +*/ + +// +// CategoryEnumerator class +// + +class CategoryEnumerator : public nsSimpleEnumerator, + private nsStringEnumeratorBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIUTF8STRINGENUMERATOR + + using nsStringEnumeratorBase::GetNext; + + const nsID& DefaultInterface() override { + return NS_GET_IID(nsISupportsCString); + } + + static CategoryEnumerator* Create( + nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable); + + protected: + CategoryEnumerator() + : mArray(nullptr), mCount(0), mSimpleCurItem(0), mStringCurItem(0) {} + + ~CategoryEnumerator() override { delete[] mArray; } + + const char** mArray; + uint32_t mCount; + uint32_t mSimpleCurItem; + uint32_t mStringCurItem; +}; + +NS_IMPL_ISUPPORTS_INHERITED(CategoryEnumerator, nsSimpleEnumerator, + nsIUTF8StringEnumerator, nsIStringEnumerator) + +NS_IMETHODIMP +CategoryEnumerator::HasMoreElements(bool* aResult) { + *aResult = (mSimpleCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::GetNext(nsISupports** aResult) { + if (mSimpleCurItem >= mCount) { + return NS_ERROR_FAILURE; + } + + auto* str = new nsSupportsDependentCString(mArray[mSimpleCurItem++]); + + *aResult = str; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::HasMore(bool* aResult) { + *aResult = (mStringCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::GetNext(nsACString& aResult) { + if (mStringCurItem >= mCount) { + return NS_ERROR_FAILURE; + } + + aResult = nsDependentCString(mArray[mStringCurItem++]); + return NS_OK; +} + +CategoryEnumerator* CategoryEnumerator::Create( + nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable) { + auto* enumObj = new CategoryEnumerator(); + if (!enumObj) { + return nullptr; + } + + enumObj->mArray = new const char*[aTable.Count()]; + if (!enumObj->mArray) { + delete enumObj; + return nullptr; + } + + for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) { + // if a category has no entries, we pretend it doesn't exist + CategoryNode* aNode = iter.UserData(); + if (aNode->Count()) { + enumObj->mArray[enumObj->mCount++] = iter.Key(); + } + } + + return enumObj; +} + +class CategoryEntry final : public nsICategoryEntry { + NS_DECL_ISUPPORTS + NS_DECL_NSICATEGORYENTRY + NS_DECL_NSISUPPORTSCSTRING + NS_DECL_NSISUPPORTSPRIMITIVE + + CategoryEntry(const char* aKey, const char* aValue) + : mKey(aKey), mValue(aValue) {} + + const char* Key() const { return mKey; } + + static CategoryEntry* Cast(nsICategoryEntry* aEntry) { + return static_cast<CategoryEntry*>(aEntry); + } + + private: + ~CategoryEntry() = default; + + const char* mKey; + const char* mValue; +}; + +NS_IMPL_ISUPPORTS(CategoryEntry, nsICategoryEntry, nsISupportsCString) + +nsresult CategoryEntry::ToString(char** aResult) { + *aResult = moz_xstrdup(mKey); + return NS_OK; +} + +nsresult CategoryEntry::GetType(uint16_t* aType) { + *aType = TYPE_CSTRING; + return NS_OK; +} + +nsresult CategoryEntry::GetData(nsACString& aData) { + aData = mKey; + return NS_OK; +} + +nsresult CategoryEntry::SetData(const nsACString& aData) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult CategoryEntry::GetEntry(nsACString& aEntry) { + aEntry = mKey; + return NS_OK; +} + +nsresult CategoryEntry::GetValue(nsACString& aValue) { + aValue = mValue; + return NS_OK; +} + +static nsresult CreateEntryEnumerator(nsTHashtable<CategoryLeaf>& aTable, + nsISimpleEnumerator** aResult) { + nsCOMArray<nsICategoryEntry> entries(aTable.Count()); + + for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) { + CategoryLeaf* leaf = iter.Get(); + if (leaf->value) { + entries.AppendElement(new CategoryEntry(leaf->GetKey(), leaf->value)); + } + } + + entries.Sort( + [](nsICategoryEntry* aA, nsICategoryEntry* aB, void*) { + return strcmp(CategoryEntry::Cast(aA)->Key(), + CategoryEntry::Cast(aB)->Key()); + }, + nullptr); + + return NS_NewArrayEnumerator(aResult, entries, NS_GET_IID(nsICategoryEntry)); +} + +// +// CategoryNode implementations +// + +CategoryNode* CategoryNode::Create(CategoryAllocator* aArena) { + return new (aArena) CategoryNode(); +} + +CategoryNode::~CategoryNode() = default; + +void* CategoryNode::operator new(size_t aSize, CategoryAllocator* aArena) { + return aArena->Allocate(aSize, mozilla::fallible); +} + +static inline const char* MaybeStrdup(const nsACString& aStr, + CategoryAllocator* aArena) { + if (aStr.IsLiteral()) { + return aStr.BeginReading(); + } + return ArenaStrdup(PromiseFlatCString(aStr).get(), *aArena); +} + +nsresult CategoryNode::GetLeaf(const nsACString& aEntryName, + nsACString& aResult) { + MutexAutoLock lock(mLock); + nsresult rv = NS_ERROR_NOT_AVAILABLE; + CategoryLeaf* ent = mTable.GetEntry(PromiseFlatCString(aEntryName).get()); + + if (ent && ent->value) { + aResult.Assign(ent->value); + return NS_OK; + } + + return rv; +} + +nsresult CategoryNode::AddLeaf(const nsACString& aEntryName, + const nsACString& aValue, bool aReplace, + nsACString& aResult, CategoryAllocator* aArena) { + aResult.SetIsVoid(true); + + auto entryName = PromiseFlatCString(aEntryName); + + MutexAutoLock lock(mLock); + CategoryLeaf* leaf = mTable.GetEntry(entryName.get()); + + if (!leaf) { + leaf = mTable.PutEntry(MaybeStrdup(aEntryName, aArena)); + if (!leaf) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (leaf->value && !aReplace) { + return NS_ERROR_INVALID_ARG; + } + + if (leaf->value) { + aResult.AssignLiteral(leaf->value, strlen(leaf->value)); + } else { + aResult.SetIsVoid(true); + } + leaf->value = MaybeStrdup(aValue, aArena); + return NS_OK; +} + +void CategoryNode::DeleteLeaf(const nsACString& aEntryName) { + // we don't throw any errors, because it normally doesn't matter + // and it makes JS a lot cleaner + MutexAutoLock lock(mLock); + + // we can just remove the entire hash entry without introspection + mTable.RemoveEntry(PromiseFlatCString(aEntryName).get()); +} + +nsresult CategoryNode::Enumerate(nsISimpleEnumerator** aResult) { + MutexAutoLock lock(mLock); + return CreateEntryEnumerator(mTable, aResult); +} + +size_t CategoryNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) { + // We don't measure the strings pointed to by the entries because the + // pointers are non-owning. + return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +// +// nsCategoryManager implementations +// + +NS_IMPL_QUERY_INTERFACE(nsCategoryManager, nsICategoryManager, + nsIMemoryReporter) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsCategoryManager::AddRef() { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +nsCategoryManager::Release() { return 1; } + +nsCategoryManager* nsCategoryManager::gCategoryManager; + +/* static */ +nsCategoryManager* nsCategoryManager::GetSingleton() { + if (!gCategoryManager) { + gCategoryManager = new nsCategoryManager(); + } + return gCategoryManager; +} + +/* static */ +void nsCategoryManager::Destroy() { + // The nsMemoryReporterManager gets destroyed before the nsCategoryManager, + // so we don't need to unregister the nsCategoryManager as a memory reporter. + // In debug builds we assert that unregistering fails, as a way (imperfect + // but better than nothing) of testing the "destroyed before" part. + MOZ_ASSERT(NS_FAILED(UnregisterWeakMemoryReporter(gCategoryManager))); + + delete gCategoryManager; + gCategoryManager = nullptr; +} + +nsresult nsCategoryManager::Create(nsISupports* aOuter, REFNSIID aIID, + void** aResult) { + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + return GetSingleton()->QueryInterface(aIID, aResult); +} + +nsCategoryManager::nsCategoryManager() + : mArena(), + mTable(), + mLock("nsCategoryManager"), + mSuppressNotifications(false) {} + +void nsCategoryManager::InitMemoryReporter() { + RegisterWeakMemoryReporter(this); +} + +nsCategoryManager::~nsCategoryManager() { + // the hashtable contains entries that must be deleted before the arena is + // destroyed, or else you will have PRLocks undestroyed and other Really + // Bad Stuff (TM) + mTable.Clear(); +} + +inline CategoryNode* nsCategoryManager::get_category(const nsACString& aName) { + CategoryNode* node; + if (!mTable.Get(PromiseFlatCString(aName).get(), &node)) { + return nullptr; + } + return node; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf) + +NS_IMETHODIMP +nsCategoryManager::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_COLLECT_REPORT("explicit/xpcom/category-manager", KIND_HEAP, UNITS_BYTES, + SizeOfIncludingThis(CategoryManagerMallocSizeOf), + "Memory used for the XPCOM category manager."); + + return NS_OK; +} + +size_t nsCategoryManager::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + size_t n = aMallocSizeOf(this); + + n += mArena.SizeOfExcludingThis(aMallocSizeOf); + + n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mTable.ConstIter(); !iter.Done(); iter.Next()) { + // We don't measure the key string because it's a non-owning pointer. + n += iter.Data()->SizeOfExcludingThis(aMallocSizeOf); + } + + return n; +} + +namespace { + +class CategoryNotificationRunnable : public Runnable { + public: + CategoryNotificationRunnable(nsISupports* aSubject, const char* aTopic, + const nsACString& aData) + : Runnable("CategoryNotificationRunnable"), + mSubject(aSubject), + mTopic(aTopic), + mData(aData) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr<nsISupports> mSubject; + const char* mTopic; + NS_ConvertUTF8toUTF16 mData; +}; + +NS_IMETHODIMP +CategoryNotificationRunnable::Run() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(mSubject, mTopic, mData.get()); + } + + return NS_OK; +} + +} // namespace + +void nsCategoryManager::NotifyObservers(const char* aTopic, + const nsACString& aCategoryName, + const nsACString& aEntryName) { + if (mSuppressNotifications) { + return; + } + + RefPtr<CategoryNotificationRunnable> r; + + if (aEntryName.Length()) { + nsCOMPtr<nsISupportsCString> entry = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); + if (!entry) { + return; + } + + nsresult rv = entry->SetData(aEntryName); + if (NS_FAILED(rv)) { + return; + } + + r = new CategoryNotificationRunnable(entry, aTopic, aCategoryName); + } else { + r = new CategoryNotificationRunnable( + NS_ISUPPORTS_CAST(nsICategoryManager*, this), aTopic, aCategoryName); + } + + NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +nsCategoryManager::GetCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + nsACString& aResult) { + nsresult status = NS_ERROR_NOT_AVAILABLE; + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + status = category->GetLeaf(aEntryName, aResult); + } + + return status; +} + +NS_IMETHODIMP +nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + const nsACString& aValue, bool aPersist, + bool aReplace, nsACString& aResult) { + if (aPersist) { + NS_ERROR("Category manager doesn't support persistence."); + return NS_ERROR_INVALID_ARG; + } + + AddCategoryEntry(aCategoryName, aEntryName, aValue, aReplace, aResult); + return NS_OK; +} + +void nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + const nsACString& aValue, + bool aReplace, nsACString& aOldValue) { + aOldValue.SetIsVoid(true); + + // Before we can insert a new entry, we'll need to + // find the |CategoryNode| to put it in... + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + + if (!category) { + // That category doesn't exist yet; let's make it. + category = CategoryNode::Create(&mArena); + + mTable.Put(MaybeStrdup(aCategoryName, &mArena), category); + } + } + + if (!category) { + return; + } + + nsresult rv = + category->AddLeaf(aEntryName, aValue, aReplace, aOldValue, &mArena); + + if (NS_SUCCEEDED(rv)) { + if (!aOldValue.IsEmpty()) { + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, + aCategoryName, aEntryName); + } + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, aCategoryName, + aEntryName); + } +} + +NS_IMETHODIMP +nsCategoryManager::DeleteCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + bool aDontPersist) { + /* + Note: no errors are reported since failure to delete + probably won't hurt you, and returning errors seriously + inconveniences JS clients + */ + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + category->DeleteLeaf(aEntryName); + + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, aCategoryName, + aEntryName); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCategoryManager::DeleteCategory(const nsACString& aCategoryName) { + // the categories are arena-allocated, so we don't + // actually delete them. We just remove all of the + // leaf nodes. + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + category->Clear(); + NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, aCategoryName, + VoidCString()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategory(const nsACString& aCategoryName, + nsISimpleEnumerator** aResult) { + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (!category) { + return NS_NewEmptyEnumerator(aResult); + } + + return category->Enumerate(aResult); +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategories(nsISimpleEnumerator** aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mLock); + CategoryEnumerator* enumObj = CategoryEnumerator::Create(mTable); + + if (!enumObj) { + return NS_ERROR_OUT_OF_MEMORY; + } + + *aResult = enumObj; + NS_ADDREF(*aResult); + return NS_OK; +} + +struct writecat_struct { + PRFileDesc* fd; + bool success; +}; + +nsresult nsCategoryManager::SuppressNotifications(bool aSuppress) { + mSuppressNotifications = aSuppress; + return NS_OK; +} + +/* + * CreateServicesFromCategory() + * + * Given a category, this convenience functions enumerates the category and + * creates a service of every CID or ContractID registered under the category. + * If observerTopic is non null and the service implements nsIObserver, + * this will attempt to notify the observer with the origin, observerTopic + * string as parameter. + */ +void NS_CreateServicesFromCategory(const char* aCategory, nsISupports* aOrigin, + const char* aObserverTopic, + const char16_t* aObserverData) { + nsresult rv; + + nsCOMPtr<nsICategoryManager> categoryManager = + do_GetService("@mozilla.org/categorymanager;1"); + if (!categoryManager) { + return; + } + + nsDependentCString category(aCategory); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = categoryManager->EnumerateCategory(category, getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) { + return; + } + + for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(enumerator)) { + // From here on just skip any error we get. + nsAutoCString entryString; + categoryEntry->GetEntry(entryString); + + nsAutoCString contractID; + categoryEntry->GetValue(contractID); + + nsCOMPtr<nsISupports> instance = do_GetService(contractID.get()); + if (!instance) { + LogMessage( + "While creating services from category '%s', could not create " + "service for entry '%s', contract ID '%s'", + aCategory, entryString.get(), contractID.get()); + continue; + } + + if (aObserverTopic) { + // try an observer, if it implements it. + nsCOMPtr<nsIObserver> observer = do_QueryInterface(instance); + if (observer) { +#ifdef MOZ_GECKO_PROFILER + nsPrintfCString profilerStr("%s (%s)", aObserverTopic, + entryString.get()); + AUTO_PROFILER_MARKER_TEXT("Category observer notification", OTHER, + MarkerStack::Capture(), profilerStr); + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE( + "Category observer notification -", OTHER, profilerStr); +#endif + + observer->Observe(aOrigin, aObserverTopic, + aObserverData ? aObserverData : u""); + } else { + LogMessage( + "While creating services from category '%s', service for entry " + "'%s', contract ID '%s' does not implement nsIObserver.", + aCategory, entryString.get(), contractID.get()); + } + } + } +} |