diff -r 000000000000 -r 6474c204b198 xpcom/components/nsCategoryManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xpcom/components/nsCategoryManager.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,844 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#define PL_ARENA_CONST_ALIGN_MASK 7 + +#include "nsICategoryManager.h" +#include "nsCategoryManager.h" + +#include "plarena.h" +#include "prio.h" +#include "prprf.h" +#include "prlock.h" +#include "nsCOMPtr.h" +#include "nsTHashtable.h" +#include "nsClassHashtable.h" +#include "nsIFactory.h" +#include "nsIStringEnumerator.h" +#include "nsSupportsPrimitives.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsQuickSort.h" +#include "nsEnumeratorUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Services.h" + +#include "ManifestParser.h" +#include "nsISimpleEnumerator.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 ;) +*/ + +#define NS_CATEGORYMANAGER_ARENA_SIZE (1024 * 8) + +// pulled in from nsComponentManager.cpp +char* ArenaStrdup(const char* s, PLArenaPool* aArena); + +// +// BaseStringEnumerator is subclassed by EntryEnumerator and +// CategoryEnumerator +// +class BaseStringEnumerator + : public nsISimpleEnumerator, + private nsIUTF8StringEnumerator +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIUTF8STRINGENUMERATOR + +protected: + // Callback function for NS_QuickSort to sort mArray + static int SortCallback(const void *, const void *, void *); + + BaseStringEnumerator() + : mArray(nullptr), + mCount(0), + mSimpleCurItem(0), + mStringCurItem(0) { } + + // A virtual destructor is needed here because subclasses of + // BaseStringEnumerator do not implement their own Release() method. + + virtual ~BaseStringEnumerator() + { + if (mArray) + delete[] mArray; + } + + void Sort(); + + const char** mArray; + uint32_t mCount; + uint32_t mSimpleCurItem; + uint32_t mStringCurItem; +}; + +NS_IMPL_ISUPPORTS(BaseStringEnumerator, nsISimpleEnumerator, nsIUTF8StringEnumerator) + +NS_IMETHODIMP +BaseStringEnumerator::HasMoreElements(bool *_retval) +{ + *_retval = (mSimpleCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +BaseStringEnumerator::GetNext(nsISupports **_retval) +{ + if (mSimpleCurItem >= mCount) + return NS_ERROR_FAILURE; + + nsSupportsDependentCString* str = + new nsSupportsDependentCString(mArray[mSimpleCurItem++]); + if (!str) + return NS_ERROR_OUT_OF_MEMORY; + + *_retval = str; + NS_ADDREF(*_retval); + return NS_OK; +} + +NS_IMETHODIMP +BaseStringEnumerator::HasMore(bool *_retval) +{ + *_retval = (mStringCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +BaseStringEnumerator::GetNext(nsACString& _retval) +{ + if (mStringCurItem >= mCount) + return NS_ERROR_FAILURE; + + _retval = nsDependentCString(mArray[mStringCurItem++]); + return NS_OK; +} + +int +BaseStringEnumerator::SortCallback(const void *e1, const void *e2, + void * /*unused*/) +{ + char const *const *s1 = reinterpret_cast(e1); + char const *const *s2 = reinterpret_cast(e2); + + return strcmp(*s1, *s2); +} + +void +BaseStringEnumerator::Sort() +{ + NS_QuickSort(mArray, mCount, sizeof(mArray[0]), SortCallback, nullptr); +} + +// +// EntryEnumerator is the wrapper that allows nsICategoryManager::EnumerateCategory +// +class EntryEnumerator + : public BaseStringEnumerator +{ +public: + static EntryEnumerator* Create(nsTHashtable& aTable); + +private: + static PLDHashOperator + enumfunc_createenumerator(CategoryLeaf* aLeaf, void* userArg); +}; + + +PLDHashOperator +EntryEnumerator::enumfunc_createenumerator(CategoryLeaf* aLeaf, void* userArg) +{ + EntryEnumerator* mythis = static_cast(userArg); + if (aLeaf->value) + mythis->mArray[mythis->mCount++] = aLeaf->GetKey(); + + return PL_DHASH_NEXT; +} + +EntryEnumerator* +EntryEnumerator::Create(nsTHashtable& aTable) +{ + EntryEnumerator* enumObj = new EntryEnumerator(); + if (!enumObj) + return nullptr; + + enumObj->mArray = new char const* [aTable.Count()]; + if (!enumObj->mArray) { + delete enumObj; + return nullptr; + } + + aTable.EnumerateEntries(enumfunc_createenumerator, enumObj); + + enumObj->Sort(); + + return enumObj; +} + + +// +// CategoryNode implementations +// + +CategoryNode* +CategoryNode::Create(PLArenaPool* aArena) +{ + CategoryNode* node = new(aArena) CategoryNode(); + if (!node) + return nullptr; + + return node; +} + +CategoryNode::~CategoryNode() +{ +} + +void* +CategoryNode::operator new(size_t aSize, PLArenaPool* aArena) +{ + void* p; + PL_ARENA_ALLOCATE(p, aArena, aSize); + return p; +} + +NS_METHOD +CategoryNode::GetLeaf(const char* aEntryName, + char** _retval) +{ + MutexAutoLock lock(mLock); + nsresult rv = NS_ERROR_NOT_AVAILABLE; + CategoryLeaf* ent = + mTable.GetEntry(aEntryName); + + if (ent && ent->value) { + *_retval = NS_strdup(ent->value); + if (*_retval) + rv = NS_OK; + } + + return rv; +} + +NS_METHOD +CategoryNode::AddLeaf(const char* aEntryName, + const char* aValue, + bool aReplace, + char** _retval, + PLArenaPool* aArena) +{ + if (_retval) + *_retval = nullptr; + + MutexAutoLock lock(mLock); + CategoryLeaf* leaf = + mTable.GetEntry(aEntryName); + + if (!leaf) { + const char* arenaEntryName = ArenaStrdup(aEntryName, aArena); + if (!arenaEntryName) + return NS_ERROR_OUT_OF_MEMORY; + + leaf = mTable.PutEntry(arenaEntryName); + if (!leaf) + return NS_ERROR_OUT_OF_MEMORY; + } + + if (leaf->value && !aReplace) + return NS_ERROR_INVALID_ARG; + + const char* arenaValue = ArenaStrdup(aValue, aArena); + if (!arenaValue) + return NS_ERROR_OUT_OF_MEMORY; + + if (_retval && leaf->value) { + *_retval = ToNewCString(nsDependentCString(leaf->value)); + if (!*_retval) + return NS_ERROR_OUT_OF_MEMORY; + } + + leaf->value = arenaValue; + return NS_OK; +} + +void +CategoryNode::DeleteLeaf(const char* 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(aEntryName); +} + +NS_METHOD +CategoryNode::Enumerate(nsISimpleEnumerator **_retval) +{ + if (NS_WARN_IF(!_retval)) + return NS_ERROR_INVALID_ARG; + + MutexAutoLock lock(mLock); + EntryEnumerator* enumObj = EntryEnumerator::Create(mTable); + + if (!enumObj) + return NS_ERROR_OUT_OF_MEMORY; + + *_retval = enumObj; + NS_ADDREF(*_retval); + return NS_OK; +} + +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.SizeOfExcludingThis(nullptr, aMallocSizeOf); +} + +struct persistent_userstruct { + PRFileDesc* fd; + const char* categoryName; + bool success; +}; + +PLDHashOperator +enumfunc_pentries(CategoryLeaf* aLeaf, void* userArg) +{ + persistent_userstruct* args = + static_cast(userArg); + + PLDHashOperator status = PL_DHASH_NEXT; + + if (aLeaf->value) { + if (PR_fprintf(args->fd, + "%s,%s,%s\n", + args->categoryName, + aLeaf->GetKey(), + aLeaf->value) == (uint32_t) -1) { + args->success = false; + status = PL_DHASH_STOP; + } + } + + return status; +} + +// +// CategoryEnumerator class +// + +class CategoryEnumerator + : public BaseStringEnumerator +{ +public: + static CategoryEnumerator* Create(nsClassHashtable& aTable); + +private: + static PLDHashOperator + enumfunc_createenumerator(const char* aStr, + CategoryNode* aNode, + void* userArg); +}; + +CategoryEnumerator* +CategoryEnumerator::Create(nsClassHashtable& aTable) +{ + CategoryEnumerator* enumObj = new CategoryEnumerator(); + if (!enumObj) + return nullptr; + + enumObj->mArray = new const char* [aTable.Count()]; + if (!enumObj->mArray) { + delete enumObj; + return nullptr; + } + + aTable.EnumerateRead(enumfunc_createenumerator, enumObj); + + return enumObj; +} + +PLDHashOperator +CategoryEnumerator::enumfunc_createenumerator(const char* aStr, CategoryNode* aNode, void* userArg) +{ + CategoryEnumerator* mythis = static_cast(userArg); + + // if a category has no entries, we pretend it doesn't exist + if (aNode->Count()) + mythis->mArray[mythis->mCount++] = aStr; + + return PL_DHASH_NEXT; +} + + +// +// 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() +{ + 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() + : mLock("nsCategoryManager") + , mSuppressNotifications(false) +{ + PL_INIT_ARENA_POOL(&mArena, "CategoryManagerArena", + NS_CATEGORYMANAGER_ARENA_SIZE); +} + +void +nsCategoryManager::InitMemoryReporter() +{ + RegisterWeakMemoryReporter(this); +} + +nsCategoryManager::~nsCategoryManager() +{ + UnregisterWeakMemoryReporter(this); + + // 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(); + + PL_FinishArenaPool(&mArena); +} + +inline CategoryNode* +nsCategoryManager::get_category(const char* aName) { + CategoryNode* node; + if (!mTable.Get(aName, &node)) { + return nullptr; + } + return node; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf) + +NS_IMETHODIMP +nsCategoryManager::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData) +{ + return MOZ_COLLECT_REPORT( + "explicit/xpcom/category-manager", KIND_HEAP, UNITS_BYTES, + SizeOfIncludingThis(CategoryManagerMallocSizeOf), + "Memory used for the XPCOM category manager."); +} + +static size_t +SizeOfCategoryManagerTableEntryExcludingThis(nsDepCharHashKey::KeyType aKey, + const nsAutoPtr &aData, + MallocSizeOf aMallocSizeOf, + void* aUserArg) +{ + // We don't measure the string pointed to by aKey because it's a non-owning + // pointer. + return aData.get()->SizeOfExcludingThis(aMallocSizeOf); +} + +size_t +nsCategoryManager::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + + n += PL_SizeOfArenaPoolExcludingPool(&mArena, aMallocSizeOf); + + n += mTable.SizeOfExcludingThis(SizeOfCategoryManagerTableEntryExcludingThis, + aMallocSizeOf); + + return n; +} + +namespace { + +class CategoryNotificationRunnable : public nsRunnable +{ +public: + CategoryNotificationRunnable(nsISupports* aSubject, + const char* aTopic, + const char* aData) + : mSubject(aSubject) + , mTopic(aTopic) + , mData(aData) + { } + + NS_DECL_NSIRUNNABLE + +private: + nsCOMPtr mSubject; + const char* mTopic; + NS_ConvertUTF8toUTF16 mData; +}; + +NS_IMETHODIMP +CategoryNotificationRunnable::Run() +{ + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) + observerService->NotifyObservers(mSubject, mTopic, mData.get()); + + return NS_OK; +} + +} // anonymous namespace + + +void +nsCategoryManager::NotifyObservers( const char *aTopic, + const char *aCategoryName, + const char *aEntryName ) +{ + if (mSuppressNotifications) + return; + + nsRefPtr r; + + if (aEntryName) { + nsCOMPtr entry + (do_CreateInstance (NS_SUPPORTS_CSTRING_CONTRACTID)); + if (!entry) + return; + + nsresult rv = entry->SetData(nsDependentCString(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 char *aCategoryName, + const char *aEntryName, + char **_retval ) +{ + if (NS_WARN_IF(!aCategoryName) || + NS_WARN_IF(!aEntryName) || + NS_WARN_IF(!_retval)) + return NS_ERROR_INVALID_ARG;; + + nsresult status = NS_ERROR_NOT_AVAILABLE; + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + status = category->GetLeaf(aEntryName, _retval); + } + + return status; +} + +NS_IMETHODIMP +nsCategoryManager::AddCategoryEntry( const char *aCategoryName, + const char *aEntryName, + const char *aValue, + bool aPersist, + bool aReplace, + char **_retval ) +{ + if (aPersist) { + NS_ERROR("Category manager doesn't support persistence."); + return NS_ERROR_INVALID_ARG; + } + + AddCategoryEntry(aCategoryName, aEntryName, aValue, aReplace, _retval); + return NS_OK; +} + +void +nsCategoryManager::AddCategoryEntry(const char *aCategoryName, + const char *aEntryName, + const char *aValue, + bool aReplace, + char** aOldValue) +{ + if (aOldValue) + *aOldValue = nullptr; + + // 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); + + char* categoryName = ArenaStrdup(aCategoryName, &mArena); + mTable.Put(categoryName, category); + } + } + + if (!category) + return; + + // We will need the return value of AddLeaf even if the called doesn't want it + char *oldEntry = nullptr; + + nsresult rv = category->AddLeaf(aEntryName, + aValue, + aReplace, + &oldEntry, + &mArena); + + if (NS_SUCCEEDED(rv)) { + if (oldEntry) { + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, + aCategoryName, aEntryName); + } + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, + aCategoryName, aEntryName); + + if (aOldValue) + *aOldValue = oldEntry; + else + NS_Free(oldEntry); + } +} + +NS_IMETHODIMP +nsCategoryManager::DeleteCategoryEntry( const char *aCategoryName, + const char *aEntryName, + bool aDontPersist) +{ + if (NS_WARN_IF(!aCategoryName) || + NS_WARN_IF(!aEntryName)) + return NS_ERROR_INVALID_ARG; + + /* + 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 char *aCategoryName ) +{ + if (NS_WARN_IF(!aCategoryName)) + return NS_ERROR_INVALID_ARG; + + // 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, nullptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategory( const char *aCategoryName, + nsISimpleEnumerator **_retval ) +{ + if (NS_WARN_IF(!aCategoryName) || + NS_WARN_IF(!_retval)) + return NS_ERROR_INVALID_ARG; + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (!category) { + return NS_NewEmptyEnumerator(_retval); + } + + return category->Enumerate(_retval); +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategories(nsISimpleEnumerator **_retval) +{ + if (NS_WARN_IF(!_retval)) + return NS_ERROR_INVALID_ARG; + + MutexAutoLock lock(mLock); + CategoryEnumerator* enumObj = CategoryEnumerator::Create(mTable); + + if (!enumObj) + return NS_ERROR_OUT_OF_MEMORY; + + *_retval = enumObj; + NS_ADDREF(*_retval); + return NS_OK; +} + +struct writecat_struct { + PRFileDesc* fd; + bool success; +}; + +NS_METHOD +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 *category, + nsISupports *origin, + const char *observerTopic) +{ + nsresult rv; + + nsCOMPtr categoryManager = + do_GetService("@mozilla.org/categorymanager;1"); + if (!categoryManager) + return; + + nsCOMPtr enumerator; + rv = categoryManager->EnumerateCategory(category, + getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr senumerator = + do_QueryInterface(enumerator); + if (!senumerator) { + NS_WARNING("Category enumerator doesn't support nsIUTF8StringEnumerator."); + return; + } + + bool hasMore; + while (NS_SUCCEEDED(senumerator->HasMore(&hasMore)) && hasMore) { + // From here on just skip any error we get. + nsAutoCString entryString; + if (NS_FAILED(senumerator->GetNext(entryString))) + continue; + + nsXPIDLCString contractID; + rv = categoryManager->GetCategoryEntry(category,entryString.get(), + getter_Copies(contractID)); + if (NS_FAILED(rv)) + continue; + + nsCOMPtr instance = do_GetService(contractID); + if (!instance) { + LogMessage("While creating services from category '%s', could not create service for entry '%s', contract ID '%s'", + category, entryString.get(), contractID.get()); + continue; + } + + if (observerTopic) { + // try an observer, if it implements it. + nsCOMPtr observer = do_QueryInterface(instance); + if (observer) + observer->Observe(origin, observerTopic, EmptyString().get()); + else + LogMessage("While creating services from category '%s', service for entry '%s', contract ID '%s' does not implement nsIObserver.", + category, entryString.get(), contractID.get()); + } + } +}