summaryrefslogtreecommitdiffstats
path: root/xpcom/components/nsCategoryManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/components/nsCategoryManager.cpp')
-rw-r--r--xpcom/components/nsCategoryManager.cpp692
1 files changed, 692 insertions, 0 deletions
diff --git a/xpcom/components/nsCategoryManager.cpp b/xpcom/components/nsCategoryManager.cpp
new file mode 100644
index 0000000000..a12fd510f5
--- /dev/null
+++ b/xpcom/components/nsCategoryManager.cpp
@@ -0,0 +1,692 @@
+/* -*- 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/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/Services.h"
+#include "mozilla/SimpleEnumerator.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 (const auto& entry : aTable) {
+ // if a category has no entries, we pretend it doesn't exist
+ CategoryNode* aNode = entry.GetWeak();
+ if (aNode->Count()) {
+ enumObj->mArray[enumObj->mCount++] = entry.GetKey();
+ }
+ }
+
+ 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(REFNSIID aIID, void** aResult) {
+ 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 (const auto& data : mTable.Values()) {
+ // We don't measure the key string because it's a non-owning pointer.
+ n += 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 = mTable
+ .InsertOrUpdate(
+ MaybeStrdup(aCategoryName, &mArena),
+ UniquePtr<CategoryNode>{CategoryNode::Create(&mArena)})
+ .get();
+ }
+ }
+
+ 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) {
+ 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);
+
+ 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());
+ }
+ }
+ }
+}