Backed out 2 changesets (bug 1853057) for causing mda failures on test_video_low_powe...
[gecko.git] / xpcom / components / nsCategoryManager.cpp
bloba12fd510f58d1ac16becef5c65102e8244bf27f9
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 "nsQuickSort.h"
26 #include "nsEnumeratorUtils.h"
27 #include "nsThreadUtils.h"
28 #include "mozilla/ArenaAllocatorExtensions.h"
29 #include "mozilla/MemoryReporting.h"
30 #include "mozilla/ProfilerLabels.h"
31 #include "mozilla/ProfilerMarkers.h"
32 #include "mozilla/Services.h"
33 #include "mozilla/SimpleEnumerator.h"
35 #include "ManifestParser.h"
36 #include "nsSimpleEnumerator.h"
38 using namespace mozilla;
39 class nsIComponentLoaderManager;
42 CategoryDatabase
43 contains 0 or more 1-1 mappings of string to Category
44 each Category contains 0 or more 1-1 mappings of string keys to string values
46 In other words, the CategoryDatabase is a tree, whose root is a hashtable.
47 Internal nodes (or Categories) are hashtables. Leaf nodes are strings.
49 The leaf strings are allocated in an arena, because we assume they're not
50 going to change much ;)
54 // CategoryEnumerator class
57 class CategoryEnumerator : public nsSimpleEnumerator,
58 private nsStringEnumeratorBase {
59 public:
60 NS_DECL_ISUPPORTS_INHERITED
61 NS_DECL_NSISIMPLEENUMERATOR
62 NS_DECL_NSIUTF8STRINGENUMERATOR
64 using nsStringEnumeratorBase::GetNext;
66 const nsID& DefaultInterface() override {
67 return NS_GET_IID(nsISupportsCString);
70 static CategoryEnumerator* Create(
71 nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable);
73 protected:
74 CategoryEnumerator()
75 : mArray(nullptr), mCount(0), mSimpleCurItem(0), mStringCurItem(0) {}
77 ~CategoryEnumerator() override { delete[] mArray; }
79 const char** mArray;
80 uint32_t mCount;
81 uint32_t mSimpleCurItem;
82 uint32_t mStringCurItem;
85 NS_IMPL_ISUPPORTS_INHERITED(CategoryEnumerator, nsSimpleEnumerator,
86 nsIUTF8StringEnumerator, nsIStringEnumerator)
88 NS_IMETHODIMP
89 CategoryEnumerator::HasMoreElements(bool* aResult) {
90 *aResult = (mSimpleCurItem < mCount);
92 return NS_OK;
95 NS_IMETHODIMP
96 CategoryEnumerator::GetNext(nsISupports** aResult) {
97 if (mSimpleCurItem >= mCount) {
98 return NS_ERROR_FAILURE;
101 auto* str = new nsSupportsDependentCString(mArray[mSimpleCurItem++]);
103 *aResult = str;
104 NS_ADDREF(*aResult);
105 return NS_OK;
108 NS_IMETHODIMP
109 CategoryEnumerator::HasMore(bool* aResult) {
110 *aResult = (mStringCurItem < mCount);
112 return NS_OK;
115 NS_IMETHODIMP
116 CategoryEnumerator::GetNext(nsACString& aResult) {
117 if (mStringCurItem >= mCount) {
118 return NS_ERROR_FAILURE;
121 aResult = nsDependentCString(mArray[mStringCurItem++]);
122 return NS_OK;
125 CategoryEnumerator* CategoryEnumerator::Create(
126 nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable) {
127 auto* enumObj = new CategoryEnumerator();
128 if (!enumObj) {
129 return nullptr;
132 enumObj->mArray = new const char*[aTable.Count()];
133 if (!enumObj->mArray) {
134 delete enumObj;
135 return nullptr;
138 for (const auto& entry : aTable) {
139 // if a category has no entries, we pretend it doesn't exist
140 CategoryNode* aNode = entry.GetWeak();
141 if (aNode->Count()) {
142 enumObj->mArray[enumObj->mCount++] = entry.GetKey();
146 return enumObj;
149 class CategoryEntry final : public nsICategoryEntry {
150 NS_DECL_ISUPPORTS
151 NS_DECL_NSICATEGORYENTRY
152 NS_DECL_NSISUPPORTSCSTRING
153 NS_DECL_NSISUPPORTSPRIMITIVE
155 CategoryEntry(const char* aKey, const char* aValue)
156 : mKey(aKey), mValue(aValue) {}
158 const char* Key() const { return mKey; }
160 static CategoryEntry* Cast(nsICategoryEntry* aEntry) {
161 return static_cast<CategoryEntry*>(aEntry);
164 private:
165 ~CategoryEntry() = default;
167 const char* mKey;
168 const char* mValue;
171 NS_IMPL_ISUPPORTS(CategoryEntry, nsICategoryEntry, nsISupportsCString)
173 nsresult CategoryEntry::ToString(char** aResult) {
174 *aResult = moz_xstrdup(mKey);
175 return NS_OK;
178 nsresult CategoryEntry::GetType(uint16_t* aType) {
179 *aType = TYPE_CSTRING;
180 return NS_OK;
183 nsresult CategoryEntry::GetData(nsACString& aData) {
184 aData = mKey;
185 return NS_OK;
188 nsresult CategoryEntry::SetData(const nsACString& aData) {
189 return NS_ERROR_NOT_IMPLEMENTED;
192 nsresult CategoryEntry::GetEntry(nsACString& aEntry) {
193 aEntry = mKey;
194 return NS_OK;
197 nsresult CategoryEntry::GetValue(nsACString& aValue) {
198 aValue = mValue;
199 return NS_OK;
202 static nsresult CreateEntryEnumerator(nsTHashtable<CategoryLeaf>& aTable,
203 nsISimpleEnumerator** aResult) {
204 nsCOMArray<nsICategoryEntry> entries(aTable.Count());
206 for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) {
207 CategoryLeaf* leaf = iter.Get();
208 if (leaf->value) {
209 entries.AppendElement(new CategoryEntry(leaf->GetKey(), leaf->value));
213 entries.Sort(
214 [](nsICategoryEntry* aA, nsICategoryEntry* aB, void*) {
215 return strcmp(CategoryEntry::Cast(aA)->Key(),
216 CategoryEntry::Cast(aB)->Key());
218 nullptr);
220 return NS_NewArrayEnumerator(aResult, entries, NS_GET_IID(nsICategoryEntry));
224 // CategoryNode implementations
227 CategoryNode* CategoryNode::Create(CategoryAllocator* aArena) {
228 return new (aArena) CategoryNode();
231 CategoryNode::~CategoryNode() = default;
233 void* CategoryNode::operator new(size_t aSize, CategoryAllocator* aArena) {
234 return aArena->Allocate(aSize, mozilla::fallible);
237 static inline const char* MaybeStrdup(const nsACString& aStr,
238 CategoryAllocator* aArena) {
239 if (aStr.IsLiteral()) {
240 return aStr.BeginReading();
242 return ArenaStrdup(PromiseFlatCString(aStr).get(), *aArena);
245 nsresult CategoryNode::GetLeaf(const nsACString& aEntryName,
246 nsACString& aResult) {
247 MutexAutoLock lock(mLock);
248 nsresult rv = NS_ERROR_NOT_AVAILABLE;
249 CategoryLeaf* ent = mTable.GetEntry(PromiseFlatCString(aEntryName).get());
251 if (ent && ent->value) {
252 aResult.Assign(ent->value);
253 return NS_OK;
256 return rv;
259 nsresult CategoryNode::AddLeaf(const nsACString& aEntryName,
260 const nsACString& aValue, bool aReplace,
261 nsACString& aResult, CategoryAllocator* aArena) {
262 aResult.SetIsVoid(true);
264 auto entryName = PromiseFlatCString(aEntryName);
266 MutexAutoLock lock(mLock);
267 CategoryLeaf* leaf = mTable.GetEntry(entryName.get());
269 if (!leaf) {
270 leaf = mTable.PutEntry(MaybeStrdup(aEntryName, aArena));
271 if (!leaf) {
272 return NS_ERROR_OUT_OF_MEMORY;
276 if (leaf->value && !aReplace) {
277 return NS_ERROR_INVALID_ARG;
280 if (leaf->value) {
281 aResult.AssignLiteral(leaf->value, strlen(leaf->value));
282 } else {
283 aResult.SetIsVoid(true);
285 leaf->value = MaybeStrdup(aValue, aArena);
286 return NS_OK;
289 void CategoryNode::DeleteLeaf(const nsACString& aEntryName) {
290 // we don't throw any errors, because it normally doesn't matter
291 // and it makes JS a lot cleaner
292 MutexAutoLock lock(mLock);
294 // we can just remove the entire hash entry without introspection
295 mTable.RemoveEntry(PromiseFlatCString(aEntryName).get());
298 nsresult CategoryNode::Enumerate(nsISimpleEnumerator** aResult) {
299 MutexAutoLock lock(mLock);
300 return CreateEntryEnumerator(mTable, aResult);
303 size_t CategoryNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
304 // We don't measure the strings pointed to by the entries because the
305 // pointers are non-owning.
306 return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
310 // nsCategoryManager implementations
313 NS_IMPL_QUERY_INTERFACE(nsCategoryManager, nsICategoryManager,
314 nsIMemoryReporter)
316 NS_IMETHODIMP_(MozExternalRefCountType)
317 nsCategoryManager::AddRef() { return 2; }
319 NS_IMETHODIMP_(MozExternalRefCountType)
320 nsCategoryManager::Release() { return 1; }
322 nsCategoryManager* nsCategoryManager::gCategoryManager;
324 /* static */
325 nsCategoryManager* nsCategoryManager::GetSingleton() {
326 if (!gCategoryManager) {
327 gCategoryManager = new nsCategoryManager();
329 return gCategoryManager;
332 /* static */
333 void nsCategoryManager::Destroy() {
334 // The nsMemoryReporterManager gets destroyed before the nsCategoryManager,
335 // so we don't need to unregister the nsCategoryManager as a memory reporter.
336 // In debug builds we assert that unregistering fails, as a way (imperfect
337 // but better than nothing) of testing the "destroyed before" part.
338 MOZ_ASSERT(NS_FAILED(UnregisterWeakMemoryReporter(gCategoryManager)));
340 delete gCategoryManager;
341 gCategoryManager = nullptr;
344 nsresult nsCategoryManager::Create(REFNSIID aIID, void** aResult) {
345 return GetSingleton()->QueryInterface(aIID, aResult);
348 nsCategoryManager::nsCategoryManager()
349 : mArena(),
350 mTable(),
351 mLock("nsCategoryManager"),
352 mSuppressNotifications(false) {}
354 void nsCategoryManager::InitMemoryReporter() {
355 RegisterWeakMemoryReporter(this);
358 nsCategoryManager::~nsCategoryManager() {
359 // the hashtable contains entries that must be deleted before the arena is
360 // destroyed, or else you will have PRLocks undestroyed and other Really
361 // Bad Stuff (TM)
362 mTable.Clear();
365 inline CategoryNode* nsCategoryManager::get_category(const nsACString& aName) {
366 CategoryNode* node;
367 if (!mTable.Get(PromiseFlatCString(aName).get(), &node)) {
368 return nullptr;
370 return node;
373 MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf)
375 NS_IMETHODIMP
376 nsCategoryManager::CollectReports(nsIHandleReportCallback* aHandleReport,
377 nsISupports* aData, bool aAnonymize) {
378 MOZ_COLLECT_REPORT("explicit/xpcom/category-manager", KIND_HEAP, UNITS_BYTES,
379 SizeOfIncludingThis(CategoryManagerMallocSizeOf),
380 "Memory used for the XPCOM category manager.");
382 return NS_OK;
385 size_t nsCategoryManager::SizeOfIncludingThis(
386 mozilla::MallocSizeOf aMallocSizeOf) {
387 size_t n = aMallocSizeOf(this);
389 n += mArena.SizeOfExcludingThis(aMallocSizeOf);
391 n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
392 for (const auto& data : mTable.Values()) {
393 // We don't measure the key string because it's a non-owning pointer.
394 n += data->SizeOfExcludingThis(aMallocSizeOf);
397 return n;
400 namespace {
402 class CategoryNotificationRunnable : public Runnable {
403 public:
404 CategoryNotificationRunnable(nsISupports* aSubject, const char* aTopic,
405 const nsACString& aData)
406 : Runnable("CategoryNotificationRunnable"),
407 mSubject(aSubject),
408 mTopic(aTopic),
409 mData(aData) {}
411 NS_DECL_NSIRUNNABLE
413 private:
414 nsCOMPtr<nsISupports> mSubject;
415 const char* mTopic;
416 NS_ConvertUTF8toUTF16 mData;
419 NS_IMETHODIMP
420 CategoryNotificationRunnable::Run() {
421 nsCOMPtr<nsIObserverService> observerService =
422 mozilla::services::GetObserverService();
423 if (observerService) {
424 observerService->NotifyObservers(mSubject, mTopic, mData.get());
427 return NS_OK;
430 } // namespace
432 void nsCategoryManager::NotifyObservers(const char* aTopic,
433 const nsACString& aCategoryName,
434 const nsACString& aEntryName) {
435 if (mSuppressNotifications) {
436 return;
439 RefPtr<CategoryNotificationRunnable> r;
441 if (aEntryName.Length()) {
442 nsCOMPtr<nsISupportsCString> entry =
443 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
444 if (!entry) {
445 return;
448 nsresult rv = entry->SetData(aEntryName);
449 if (NS_FAILED(rv)) {
450 return;
453 r = new CategoryNotificationRunnable(entry, aTopic, aCategoryName);
454 } else {
455 r = new CategoryNotificationRunnable(
456 NS_ISUPPORTS_CAST(nsICategoryManager*, this), aTopic, aCategoryName);
459 NS_DispatchToMainThread(r);
462 NS_IMETHODIMP
463 nsCategoryManager::GetCategoryEntry(const nsACString& aCategoryName,
464 const nsACString& aEntryName,
465 nsACString& aResult) {
466 nsresult status = NS_ERROR_NOT_AVAILABLE;
468 CategoryNode* category;
470 MutexAutoLock lock(mLock);
471 category = get_category(aCategoryName);
474 if (category) {
475 status = category->GetLeaf(aEntryName, aResult);
478 return status;
481 NS_IMETHODIMP
482 nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName,
483 const nsACString& aEntryName,
484 const nsACString& aValue, bool aPersist,
485 bool aReplace, nsACString& aResult) {
486 if (aPersist) {
487 NS_ERROR("Category manager doesn't support persistence.");
488 return NS_ERROR_INVALID_ARG;
491 AddCategoryEntry(aCategoryName, aEntryName, aValue, aReplace, aResult);
492 return NS_OK;
495 void nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName,
496 const nsACString& aEntryName,
497 const nsACString& aValue,
498 bool aReplace, nsACString& aOldValue) {
499 aOldValue.SetIsVoid(true);
501 // Before we can insert a new entry, we'll need to
502 // find the |CategoryNode| to put it in...
503 CategoryNode* category;
505 MutexAutoLock lock(mLock);
506 category = get_category(aCategoryName);
508 if (!category) {
509 // That category doesn't exist yet; let's make it.
510 category = mTable
511 .InsertOrUpdate(
512 MaybeStrdup(aCategoryName, &mArena),
513 UniquePtr<CategoryNode>{CategoryNode::Create(&mArena)})
514 .get();
518 if (!category) {
519 return;
522 nsresult rv =
523 category->AddLeaf(aEntryName, aValue, aReplace, aOldValue, &mArena);
525 if (NS_SUCCEEDED(rv)) {
526 if (!aOldValue.IsEmpty()) {
527 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID,
528 aCategoryName, aEntryName);
530 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, aCategoryName,
531 aEntryName);
535 NS_IMETHODIMP
536 nsCategoryManager::DeleteCategoryEntry(const nsACString& aCategoryName,
537 const nsACString& aEntryName,
538 bool aDontPersist) {
540 Note: no errors are reported since failure to delete
541 probably won't hurt you, and returning errors seriously
542 inconveniences JS clients
545 CategoryNode* category;
547 MutexAutoLock lock(mLock);
548 category = get_category(aCategoryName);
551 if (category) {
552 category->DeleteLeaf(aEntryName);
554 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, aCategoryName,
555 aEntryName);
558 return NS_OK;
561 NS_IMETHODIMP
562 nsCategoryManager::DeleteCategory(const nsACString& aCategoryName) {
563 // the categories are arena-allocated, so we don't
564 // actually delete them. We just remove all of the
565 // leaf nodes.
567 CategoryNode* category;
569 MutexAutoLock lock(mLock);
570 category = get_category(aCategoryName);
573 if (category) {
574 category->Clear();
575 NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, aCategoryName,
576 VoidCString());
579 return NS_OK;
582 NS_IMETHODIMP
583 nsCategoryManager::EnumerateCategory(const nsACString& aCategoryName,
584 nsISimpleEnumerator** aResult) {
585 CategoryNode* category;
587 MutexAutoLock lock(mLock);
588 category = get_category(aCategoryName);
591 if (!category) {
592 return NS_NewEmptyEnumerator(aResult);
595 return category->Enumerate(aResult);
598 NS_IMETHODIMP
599 nsCategoryManager::EnumerateCategories(nsISimpleEnumerator** aResult) {
600 if (NS_WARN_IF(!aResult)) {
601 return NS_ERROR_INVALID_ARG;
604 MutexAutoLock lock(mLock);
605 CategoryEnumerator* enumObj = CategoryEnumerator::Create(mTable);
607 if (!enumObj) {
608 return NS_ERROR_OUT_OF_MEMORY;
611 *aResult = enumObj;
612 NS_ADDREF(*aResult);
613 return NS_OK;
616 struct writecat_struct {
617 PRFileDesc* fd;
618 bool success;
621 nsresult nsCategoryManager::SuppressNotifications(bool aSuppress) {
622 mSuppressNotifications = aSuppress;
623 return NS_OK;
627 * CreateServicesFromCategory()
629 * Given a category, this convenience functions enumerates the category and
630 * creates a service of every CID or ContractID registered under the category.
631 * If observerTopic is non null and the service implements nsIObserver,
632 * this will attempt to notify the observer with the origin, observerTopic
633 * string as parameter.
635 void NS_CreateServicesFromCategory(const char* aCategory, nsISupports* aOrigin,
636 const char* aObserverTopic,
637 const char16_t* aObserverData) {
638 nsresult rv;
640 nsCOMPtr<nsICategoryManager> categoryManager =
641 do_GetService("@mozilla.org/categorymanager;1");
642 if (!categoryManager) {
643 return;
646 nsDependentCString category(aCategory);
648 nsCOMPtr<nsISimpleEnumerator> enumerator;
649 rv = categoryManager->EnumerateCategory(category, getter_AddRefs(enumerator));
650 if (NS_FAILED(rv)) {
651 return;
654 for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(enumerator)) {
655 // From here on just skip any error we get.
656 nsAutoCString entryString;
657 categoryEntry->GetEntry(entryString);
659 nsAutoCString contractID;
660 categoryEntry->GetValue(contractID);
662 nsCOMPtr<nsISupports> instance = do_GetService(contractID.get());
663 if (!instance) {
664 LogMessage(
665 "While creating services from category '%s', could not create "
666 "service for entry '%s', contract ID '%s'",
667 aCategory, entryString.get(), contractID.get());
668 continue;
671 if (aObserverTopic) {
672 // try an observer, if it implements it.
673 nsCOMPtr<nsIObserver> observer = do_QueryInterface(instance);
674 if (observer) {
675 nsPrintfCString profilerStr("%s (%s)", aObserverTopic,
676 entryString.get());
677 AUTO_PROFILER_MARKER_TEXT("Category observer notification", OTHER,
678 MarkerStack::Capture(), profilerStr);
679 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE(
680 "Category observer notification -", OTHER, profilerStr);
682 observer->Observe(aOrigin, aObserverTopic,
683 aObserverData ? aObserverData : u"");
684 } else {
685 LogMessage(
686 "While creating services from category '%s', service for entry "
687 "'%s', contract ID '%s' does not implement nsIObserver.",
688 aCategory, entryString.get(), contractID.get());