Bug 1919083 - [ci] Enable os-integration variant for more suites, r=jmaher
[gecko.git] / xpcom / components / nsCategoryManager.cpp
blob5602f0001fac06811c79bdd63384ea78ad2f271c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsCategoryManager.h"
8 #include "nsCategoryManagerUtils.h"
10 #include "prio.h"
11 #include "prlock.h"
12 #include "nsArrayEnumerator.h"
13 #include "nsCOMPtr.h"
14 #include "nsTHashtable.h"
15 #include "nsClassHashtable.h"
16 #include "nsStringEnumerator.h"
17 #include "nsSupportsPrimitives.h"
18 #include "nsComponentManagerUtils.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsIObserver.h"
21 #include "nsIObserverService.h"
22 #include "nsReadableUtils.h"
23 #include "nsCRT.h"
24 #include "nsPrintfCString.h"
25 #include "nsEnumeratorUtils.h"
26 #include "nsThreadUtils.h"
27 #include "mozilla/ArenaAllocatorExtensions.h"
28 #include "mozilla/MemoryReporting.h"
29 #include "mozilla/ProfilerLabels.h"
30 #include "mozilla/ProfilerMarkers.h"
31 #include "mozilla/Services.h"
32 #include "mozilla/SimpleEnumerator.h"
34 #include "ManifestParser.h"
35 #include "nsSimpleEnumerator.h"
37 using namespace mozilla;
38 class nsIComponentLoaderManager;
41 CategoryDatabase
42 contains 0 or more 1-1 mappings of string to Category
43 each Category contains 0 or more 1-1 mappings of string keys to string values
45 In other words, the CategoryDatabase is a tree, whose root is a hashtable.
46 Internal nodes (or Categories) are hashtables. Leaf nodes are strings.
48 The leaf strings are allocated in an arena, because we assume they're not
49 going to change much ;)
53 // CategoryEnumerator class
56 class CategoryEnumerator : public nsSimpleEnumerator,
57 private nsStringEnumeratorBase {
58 public:
59 NS_DECL_ISUPPORTS_INHERITED
60 NS_DECL_NSISIMPLEENUMERATOR
61 NS_DECL_NSIUTF8STRINGENUMERATOR
63 using nsStringEnumeratorBase::GetNext;
65 const nsID& DefaultInterface() override {
66 return NS_GET_IID(nsISupportsCString);
69 static CategoryEnumerator* Create(
70 nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable);
72 protected:
73 CategoryEnumerator()
74 : mArray(nullptr), mCount(0), mSimpleCurItem(0), mStringCurItem(0) {}
76 ~CategoryEnumerator() override { delete[] mArray; }
78 const char** mArray;
79 uint32_t mCount;
80 uint32_t mSimpleCurItem;
81 uint32_t mStringCurItem;
84 NS_IMPL_ISUPPORTS_INHERITED(CategoryEnumerator, nsSimpleEnumerator,
85 nsIUTF8StringEnumerator, nsIStringEnumerator)
87 NS_IMETHODIMP
88 CategoryEnumerator::HasMoreElements(bool* aResult) {
89 *aResult = (mSimpleCurItem < mCount);
91 return NS_OK;
94 NS_IMETHODIMP
95 CategoryEnumerator::GetNext(nsISupports** aResult) {
96 if (mSimpleCurItem >= mCount) {
97 return NS_ERROR_FAILURE;
100 auto* str = new nsSupportsDependentCString(mArray[mSimpleCurItem++]);
102 *aResult = str;
103 NS_ADDREF(*aResult);
104 return NS_OK;
107 NS_IMETHODIMP
108 CategoryEnumerator::HasMore(bool* aResult) {
109 *aResult = (mStringCurItem < mCount);
111 return NS_OK;
114 NS_IMETHODIMP
115 CategoryEnumerator::GetNext(nsACString& aResult) {
116 if (mStringCurItem >= mCount) {
117 return NS_ERROR_FAILURE;
120 aResult = nsDependentCString(mArray[mStringCurItem++]);
121 return NS_OK;
124 CategoryEnumerator* CategoryEnumerator::Create(
125 nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable) {
126 auto* enumObj = new CategoryEnumerator();
127 if (!enumObj) {
128 return nullptr;
131 enumObj->mArray = new const char*[aTable.Count()];
132 if (!enumObj->mArray) {
133 delete enumObj;
134 return nullptr;
137 for (const auto& entry : aTable) {
138 // if a category has no entries, we pretend it doesn't exist
139 CategoryNode* aNode = entry.GetWeak();
140 if (aNode->Count()) {
141 enumObj->mArray[enumObj->mCount++] = entry.GetKey();
145 return enumObj;
148 class CategoryEntry final : public nsICategoryEntry {
149 NS_DECL_ISUPPORTS
150 NS_DECL_NSICATEGORYENTRY
151 NS_DECL_NSISUPPORTSCSTRING
152 NS_DECL_NSISUPPORTSPRIMITIVE
154 CategoryEntry(const char* aKey, const char* aValue)
155 : mKey(aKey), mValue(aValue) {}
157 const char* Key() const { return mKey; }
159 static CategoryEntry* Cast(nsICategoryEntry* aEntry) {
160 return static_cast<CategoryEntry*>(aEntry);
163 private:
164 ~CategoryEntry() = default;
166 const char* mKey;
167 const char* mValue;
170 NS_IMPL_ISUPPORTS(CategoryEntry, nsICategoryEntry, nsISupportsCString)
172 nsresult CategoryEntry::ToString(char** aResult) {
173 *aResult = moz_xstrdup(mKey);
174 return NS_OK;
177 nsresult CategoryEntry::GetType(uint16_t* aType) {
178 *aType = TYPE_CSTRING;
179 return NS_OK;
182 nsresult CategoryEntry::GetData(nsACString& aData) {
183 aData = mKey;
184 return NS_OK;
187 nsresult CategoryEntry::SetData(const nsACString& aData) {
188 return NS_ERROR_NOT_IMPLEMENTED;
191 nsresult CategoryEntry::GetEntry(nsACString& aEntry) {
192 aEntry = mKey;
193 return NS_OK;
196 nsresult CategoryEntry::GetValue(nsACString& aValue) {
197 aValue = mValue;
198 return NS_OK;
201 static nsresult CreateEntryEnumerator(nsTHashtable<CategoryLeaf>& aTable,
202 nsISimpleEnumerator** aResult) {
203 nsCOMArray<nsICategoryEntry> entries(aTable.Count());
205 for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) {
206 CategoryLeaf* leaf = iter.Get();
207 if (leaf->value) {
208 entries.AppendElement(new CategoryEntry(leaf->GetKey(), leaf->value));
212 entries.Sort([](nsICategoryEntry* aA, nsICategoryEntry* aB) {
213 return strcmp(CategoryEntry::Cast(aA)->Key(),
214 CategoryEntry::Cast(aB)->Key());
217 return NS_NewArrayEnumerator(aResult, entries, NS_GET_IID(nsICategoryEntry));
221 // CategoryNode implementations
224 CategoryNode* CategoryNode::Create(CategoryAllocator* aArena) {
225 return new (aArena) CategoryNode();
228 CategoryNode::~CategoryNode() = default;
230 void* CategoryNode::operator new(size_t aSize, CategoryAllocator* aArena) {
231 return aArena->Allocate(aSize, mozilla::fallible);
234 static inline const char* MaybeStrdup(const nsACString& aStr,
235 CategoryAllocator* aArena) {
236 if (aStr.IsLiteral()) {
237 return aStr.BeginReading();
239 return ArenaStrdup(PromiseFlatCString(aStr).get(), *aArena);
242 nsresult CategoryNode::GetLeaf(const nsACString& aEntryName,
243 nsACString& aResult) {
244 MutexAutoLock lock(mLock);
245 nsresult rv = NS_ERROR_NOT_AVAILABLE;
246 CategoryLeaf* ent = mTable.GetEntry(PromiseFlatCString(aEntryName).get());
248 if (ent && ent->value) {
249 aResult.Assign(ent->value);
250 return NS_OK;
253 return rv;
256 nsresult CategoryNode::AddLeaf(const nsACString& aEntryName,
257 const nsACString& aValue, bool aReplace,
258 nsACString& aResult, CategoryAllocator* aArena) {
259 aResult.SetIsVoid(true);
261 auto entryName = PromiseFlatCString(aEntryName);
263 MutexAutoLock lock(mLock);
264 CategoryLeaf* leaf = mTable.GetEntry(entryName.get());
266 if (!leaf) {
267 leaf = mTable.PutEntry(MaybeStrdup(aEntryName, aArena));
268 if (!leaf) {
269 return NS_ERROR_OUT_OF_MEMORY;
273 if (leaf->value && !aReplace) {
274 return NS_ERROR_INVALID_ARG;
277 if (leaf->value) {
278 aResult.AssignLiteral(leaf->value, strlen(leaf->value));
279 } else {
280 aResult.SetIsVoid(true);
282 leaf->value = MaybeStrdup(aValue, aArena);
283 return NS_OK;
286 void CategoryNode::DeleteLeaf(const nsACString& aEntryName) {
287 // we don't throw any errors, because it normally doesn't matter
288 // and it makes JS a lot cleaner
289 MutexAutoLock lock(mLock);
291 // we can just remove the entire hash entry without introspection
292 mTable.RemoveEntry(PromiseFlatCString(aEntryName).get());
295 nsresult CategoryNode::Enumerate(nsISimpleEnumerator** aResult) {
296 MutexAutoLock lock(mLock);
297 return CreateEntryEnumerator(mTable, aResult);
300 size_t CategoryNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
301 // We don't measure the strings pointed to by the entries because the
302 // pointers are non-owning.
303 MutexAutoLock lock(mLock);
304 return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
308 // nsCategoryManager implementations
311 NS_IMPL_QUERY_INTERFACE(nsCategoryManager, nsICategoryManager,
312 nsIMemoryReporter)
314 NS_IMETHODIMP_(MozExternalRefCountType)
315 nsCategoryManager::AddRef() { return 2; }
317 NS_IMETHODIMP_(MozExternalRefCountType)
318 nsCategoryManager::Release() { return 1; }
320 nsCategoryManager* nsCategoryManager::gCategoryManager;
322 /* static */
323 nsCategoryManager* nsCategoryManager::GetSingleton() {
324 if (!gCategoryManager) {
325 gCategoryManager = new nsCategoryManager();
327 return gCategoryManager;
330 /* static */
331 void nsCategoryManager::Destroy() {
332 // The nsMemoryReporterManager gets destroyed before the nsCategoryManager,
333 // so we don't need to unregister the nsCategoryManager as a memory reporter.
334 // In debug builds we assert that unregistering fails, as a way (imperfect
335 // but better than nothing) of testing the "destroyed before" part.
336 MOZ_ASSERT(NS_FAILED(UnregisterWeakMemoryReporter(gCategoryManager)));
338 delete gCategoryManager;
339 gCategoryManager = nullptr;
342 nsresult nsCategoryManager::Create(REFNSIID aIID, void** aResult) {
343 return GetSingleton()->QueryInterface(aIID, aResult);
346 nsCategoryManager::nsCategoryManager()
347 : mLock("nsCategoryManager"), mSuppressNotifications(false) {}
349 void nsCategoryManager::InitMemoryReporter() {
350 RegisterWeakMemoryReporter(this);
353 nsCategoryManager::~nsCategoryManager() {
354 // the hashtable contains entries that must be deleted before the arena is
355 // destroyed, or else you will have PRLocks undestroyed and other Really
356 // Bad Stuff (TM)
357 mTable.Clear();
360 inline CategoryNode* nsCategoryManager::get_category(const nsACString& aName) {
361 CategoryNode* node;
362 if (!mTable.Get(PromiseFlatCString(aName).get(), &node)) {
363 return nullptr;
365 return node;
368 MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf)
370 NS_IMETHODIMP
371 nsCategoryManager::CollectReports(nsIHandleReportCallback* aHandleReport,
372 nsISupports* aData, bool aAnonymize) {
373 MOZ_COLLECT_REPORT("explicit/xpcom/category-manager", KIND_HEAP, UNITS_BYTES,
374 SizeOfIncludingThis(CategoryManagerMallocSizeOf),
375 "Memory used for the XPCOM category manager.");
377 return NS_OK;
380 size_t nsCategoryManager::SizeOfIncludingThis(
381 mozilla::MallocSizeOf aMallocSizeOf) {
382 MOZ_ASSERT(NS_IsMainThread());
383 MutexAutoLock lock(mLock);
384 size_t n = aMallocSizeOf(this);
386 n += mArena.SizeOfExcludingThis(aMallocSizeOf);
388 n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
389 for (const auto& data : mTable.Values()) {
390 // We don't measure the key string because it's a non-owning pointer.
391 n += data->SizeOfExcludingThis(aMallocSizeOf);
394 return n;
397 namespace {
399 class CategoryNotificationRunnable : public Runnable {
400 public:
401 CategoryNotificationRunnable(nsISupports* aSubject, const char* aTopic,
402 const nsACString& aData)
403 : Runnable("CategoryNotificationRunnable"),
404 mSubject(aSubject),
405 mTopic(aTopic),
406 mData(aData) {}
408 NS_DECL_NSIRUNNABLE
410 private:
411 nsCOMPtr<nsISupports> mSubject;
412 const char* mTopic;
413 NS_ConvertUTF8toUTF16 mData;
416 NS_IMETHODIMP
417 CategoryNotificationRunnable::Run() {
418 nsCOMPtr<nsIObserverService> observerService =
419 mozilla::services::GetObserverService();
420 if (observerService) {
421 observerService->NotifyObservers(mSubject, mTopic, mData.get());
424 return NS_OK;
427 } // namespace
429 void nsCategoryManager::NotifyObservers(const char* aTopic,
430 const nsACString& aCategoryName,
431 const nsACString& aEntryName) {
432 if (mSuppressNotifications) {
433 return;
436 RefPtr<CategoryNotificationRunnable> r;
438 if (aEntryName.Length()) {
439 nsCOMPtr<nsISupportsCString> entry =
440 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
441 if (!entry) {
442 return;
445 nsresult rv = entry->SetData(aEntryName);
446 if (NS_FAILED(rv)) {
447 return;
450 r = new CategoryNotificationRunnable(entry, aTopic, aCategoryName);
451 } else {
452 r = new CategoryNotificationRunnable(
453 NS_ISUPPORTS_CAST(nsICategoryManager*, this), aTopic, aCategoryName);
456 NS_DispatchToMainThread(r);
459 NS_IMETHODIMP
460 nsCategoryManager::GetCategoryEntry(const nsACString& aCategoryName,
461 const nsACString& aEntryName,
462 nsACString& aResult) {
463 nsresult status = NS_ERROR_NOT_AVAILABLE;
465 CategoryNode* category;
467 MutexAutoLock lock(mLock);
468 category = get_category(aCategoryName);
471 if (category) {
472 status = category->GetLeaf(aEntryName, aResult);
475 return status;
478 NS_IMETHODIMP
479 nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName,
480 const nsACString& aEntryName,
481 const nsACString& aValue, bool aPersist,
482 bool aReplace, nsACString& aResult) {
483 if (aPersist) {
484 NS_ERROR("Category manager doesn't support persistence.");
485 return NS_ERROR_INVALID_ARG;
488 AddCategoryEntry(aCategoryName, aEntryName, aValue, aReplace, aResult);
489 return NS_OK;
492 void nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName,
493 const nsACString& aEntryName,
494 const nsACString& aValue,
495 bool aReplace, nsACString& aOldValue) {
496 MOZ_ASSERT(NS_IsMainThread());
497 aOldValue.SetIsVoid(true);
499 // Before we can insert a new entry, we'll need to
500 // find the |CategoryNode| to put it in...
501 CategoryNode* category;
503 MutexAutoLock lock(mLock);
504 category = get_category(aCategoryName);
506 if (!category) {
507 // That category doesn't exist yet; let's make it.
508 category = mTable
509 .InsertOrUpdate(
510 MaybeStrdup(aCategoryName, &mArena),
511 UniquePtr<CategoryNode>{CategoryNode::Create(&mArena)})
512 .get();
516 if (!category) {
517 return;
520 nsresult rv =
521 category->AddLeaf(aEntryName, aValue, aReplace, aOldValue, &mArena);
523 if (NS_SUCCEEDED(rv)) {
524 if (!aOldValue.IsEmpty()) {
525 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID,
526 aCategoryName, aEntryName);
528 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, aCategoryName,
529 aEntryName);
533 NS_IMETHODIMP
534 nsCategoryManager::DeleteCategoryEntry(const nsACString& aCategoryName,
535 const nsACString& aEntryName,
536 bool aDontPersist) {
538 Note: no errors are reported since failure to delete
539 probably won't hurt you, and returning errors seriously
540 inconveniences JS clients
543 CategoryNode* category;
545 MutexAutoLock lock(mLock);
546 category = get_category(aCategoryName);
549 if (category) {
550 category->DeleteLeaf(aEntryName);
552 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, aCategoryName,
553 aEntryName);
556 return NS_OK;
559 NS_IMETHODIMP
560 nsCategoryManager::DeleteCategory(const nsACString& aCategoryName) {
561 // the categories are arena-allocated, so we don't
562 // actually delete them. We just remove all of the
563 // leaf nodes.
565 CategoryNode* category;
567 MutexAutoLock lock(mLock);
568 category = get_category(aCategoryName);
571 if (category) {
572 category->Clear();
573 NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, aCategoryName,
574 VoidCString());
577 return NS_OK;
580 NS_IMETHODIMP
581 nsCategoryManager::EnumerateCategory(const nsACString& aCategoryName,
582 nsISimpleEnumerator** aResult) {
583 CategoryNode* category;
585 MutexAutoLock lock(mLock);
586 category = get_category(aCategoryName);
589 if (!category) {
590 return NS_NewEmptyEnumerator(aResult);
593 return category->Enumerate(aResult);
596 NS_IMETHODIMP
597 nsCategoryManager::EnumerateCategories(nsISimpleEnumerator** aResult) {
598 if (NS_WARN_IF(!aResult)) {
599 return NS_ERROR_INVALID_ARG;
602 MutexAutoLock lock(mLock);
603 CategoryEnumerator* enumObj = CategoryEnumerator::Create(mTable);
605 if (!enumObj) {
606 return NS_ERROR_OUT_OF_MEMORY;
609 *aResult = enumObj;
610 NS_ADDREF(*aResult);
611 return NS_OK;
614 struct writecat_struct {
615 PRFileDesc* fd;
616 bool success;
619 nsresult nsCategoryManager::SuppressNotifications(bool aSuppress) {
620 mSuppressNotifications = aSuppress;
621 return NS_OK;
625 * CreateServicesFromCategory()
627 * Given a category, this convenience functions enumerates the category and
628 * creates a service of every CID or ContractID registered under the category.
629 * If observerTopic is non null and the service implements nsIObserver,
630 * this will attempt to notify the observer with the origin, observerTopic
631 * string as parameter.
633 void NS_CreateServicesFromCategory(const char* aCategory, nsISupports* aOrigin,
634 const char* aObserverTopic,
635 const char16_t* aObserverData) {
636 nsresult rv;
638 nsCOMPtr<nsICategoryManager> categoryManager =
639 do_GetService("@mozilla.org/categorymanager;1");
640 if (!categoryManager) {
641 return;
644 nsDependentCString category(aCategory);
646 nsCOMPtr<nsISimpleEnumerator> enumerator;
647 rv = categoryManager->EnumerateCategory(category, getter_AddRefs(enumerator));
648 if (NS_FAILED(rv)) {
649 return;
652 for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(enumerator)) {
653 // From here on just skip any error we get.
654 nsAutoCString entryString;
655 categoryEntry->GetEntry(entryString);
657 nsAutoCString contractID;
658 categoryEntry->GetValue(contractID);
660 nsCOMPtr<nsISupports> instance = do_GetService(contractID.get());
661 if (!instance) {
662 LogMessage(
663 "While creating services from category '%s', could not create "
664 "service for entry '%s', contract ID '%s'",
665 aCategory, entryString.get(), contractID.get());
666 continue;
669 if (aObserverTopic) {
670 // try an observer, if it implements it.
671 nsCOMPtr<nsIObserver> observer = do_QueryInterface(instance);
672 if (observer) {
673 nsPrintfCString profilerStr("%s (%s)", aObserverTopic,
674 entryString.get());
675 AUTO_PROFILER_MARKER_TEXT("Category observer notification", OTHER,
676 MarkerStack::Capture(), profilerStr);
677 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE(
678 "Category observer notification -", OTHER, profilerStr);
680 observer->Observe(aOrigin, aObserverTopic,
681 aObserverData ? aObserverData : u"");
682 } else {
683 LogMessage(
684 "While creating services from category '%s', service for entry "
685 "'%s', contract ID '%s' does not implement nsIObserver.",
686 aCategory, entryString.get(), contractID.get());