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"
12 #include "nsArrayEnumerator.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"
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
;
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
{
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
);
75 : mArray(nullptr), mCount(0), mSimpleCurItem(0), mStringCurItem(0) {}
77 ~CategoryEnumerator() override
{ delete[] mArray
; }
81 uint32_t mSimpleCurItem
;
82 uint32_t mStringCurItem
;
85 NS_IMPL_ISUPPORTS_INHERITED(CategoryEnumerator
, nsSimpleEnumerator
,
86 nsIUTF8StringEnumerator
, nsIStringEnumerator
)
89 CategoryEnumerator::HasMoreElements(bool* aResult
) {
90 *aResult
= (mSimpleCurItem
< mCount
);
96 CategoryEnumerator::GetNext(nsISupports
** aResult
) {
97 if (mSimpleCurItem
>= mCount
) {
98 return NS_ERROR_FAILURE
;
101 auto* str
= new nsSupportsDependentCString(mArray
[mSimpleCurItem
++]);
109 CategoryEnumerator::HasMore(bool* aResult
) {
110 *aResult
= (mStringCurItem
< mCount
);
116 CategoryEnumerator::GetNext(nsACString
& aResult
) {
117 if (mStringCurItem
>= mCount
) {
118 return NS_ERROR_FAILURE
;
121 aResult
= nsDependentCString(mArray
[mStringCurItem
++]);
125 CategoryEnumerator
* CategoryEnumerator::Create(
126 nsClassHashtable
<nsDepCharHashKey
, CategoryNode
>& aTable
) {
127 auto* enumObj
= new CategoryEnumerator();
132 enumObj
->mArray
= new const char*[aTable
.Count()];
133 if (!enumObj
->mArray
) {
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();
149 class CategoryEntry final
: public nsICategoryEntry
{
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
);
165 ~CategoryEntry() = default;
171 NS_IMPL_ISUPPORTS(CategoryEntry
, nsICategoryEntry
, nsISupportsCString
)
173 nsresult
CategoryEntry::ToString(char** aResult
) {
174 *aResult
= moz_xstrdup(mKey
);
178 nsresult
CategoryEntry::GetType(uint16_t* aType
) {
179 *aType
= TYPE_CSTRING
;
183 nsresult
CategoryEntry::GetData(nsACString
& aData
) {
188 nsresult
CategoryEntry::SetData(const nsACString
& aData
) {
189 return NS_ERROR_NOT_IMPLEMENTED
;
192 nsresult
CategoryEntry::GetEntry(nsACString
& aEntry
) {
197 nsresult
CategoryEntry::GetValue(nsACString
& aValue
) {
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();
209 entries
.AppendElement(new CategoryEntry(leaf
->GetKey(), leaf
->value
));
214 [](nsICategoryEntry
* aA
, nsICategoryEntry
* aB
, void*) {
215 return strcmp(CategoryEntry::Cast(aA
)->Key(),
216 CategoryEntry::Cast(aB
)->Key());
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
);
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());
270 leaf
= mTable
.PutEntry(MaybeStrdup(aEntryName
, aArena
));
272 return NS_ERROR_OUT_OF_MEMORY
;
276 if (leaf
->value
&& !aReplace
) {
277 return NS_ERROR_INVALID_ARG
;
281 aResult
.AssignLiteral(leaf
->value
, strlen(leaf
->value
));
283 aResult
.SetIsVoid(true);
285 leaf
->value
= MaybeStrdup(aValue
, aArena
);
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
,
316 NS_IMETHODIMP_(MozExternalRefCountType
)
317 nsCategoryManager::AddRef() { return 2; }
319 NS_IMETHODIMP_(MozExternalRefCountType
)
320 nsCategoryManager::Release() { return 1; }
322 nsCategoryManager
* nsCategoryManager::gCategoryManager
;
325 nsCategoryManager
* nsCategoryManager::GetSingleton() {
326 if (!gCategoryManager
) {
327 gCategoryManager
= new nsCategoryManager();
329 return gCategoryManager
;
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()
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
365 inline CategoryNode
* nsCategoryManager::get_category(const nsACString
& aName
) {
367 if (!mTable
.Get(PromiseFlatCString(aName
).get(), &node
)) {
373 MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf
)
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.");
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
);
402 class CategoryNotificationRunnable
: public Runnable
{
404 CategoryNotificationRunnable(nsISupports
* aSubject
, const char* aTopic
,
405 const nsACString
& aData
)
406 : Runnable("CategoryNotificationRunnable"),
414 nsCOMPtr
<nsISupports
> mSubject
;
416 NS_ConvertUTF8toUTF16 mData
;
420 CategoryNotificationRunnable::Run() {
421 nsCOMPtr
<nsIObserverService
> observerService
=
422 mozilla::services::GetObserverService();
423 if (observerService
) {
424 observerService
->NotifyObservers(mSubject
, mTopic
, mData
.get());
432 void nsCategoryManager::NotifyObservers(const char* aTopic
,
433 const nsACString
& aCategoryName
,
434 const nsACString
& aEntryName
) {
435 if (mSuppressNotifications
) {
439 RefPtr
<CategoryNotificationRunnable
> r
;
441 if (aEntryName
.Length()) {
442 nsCOMPtr
<nsISupportsCString
> entry
=
443 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID
);
448 nsresult rv
= entry
->SetData(aEntryName
);
453 r
= new CategoryNotificationRunnable(entry
, aTopic
, aCategoryName
);
455 r
= new CategoryNotificationRunnable(
456 NS_ISUPPORTS_CAST(nsICategoryManager
*, this), aTopic
, aCategoryName
);
459 NS_DispatchToMainThread(r
);
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
);
475 status
= category
->GetLeaf(aEntryName
, aResult
);
482 nsCategoryManager::AddCategoryEntry(const nsACString
& aCategoryName
,
483 const nsACString
& aEntryName
,
484 const nsACString
& aValue
, bool aPersist
,
485 bool aReplace
, nsACString
& aResult
) {
487 NS_ERROR("Category manager doesn't support persistence.");
488 return NS_ERROR_INVALID_ARG
;
491 AddCategoryEntry(aCategoryName
, aEntryName
, aValue
, aReplace
, aResult
);
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
);
509 // That category doesn't exist yet; let's make it.
512 MaybeStrdup(aCategoryName
, &mArena
),
513 UniquePtr
<CategoryNode
>{CategoryNode::Create(&mArena
)})
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
,
536 nsCategoryManager::DeleteCategoryEntry(const nsACString
& aCategoryName
,
537 const nsACString
& aEntryName
,
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
);
552 category
->DeleteLeaf(aEntryName
);
554 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID
, aCategoryName
,
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
567 CategoryNode
* category
;
569 MutexAutoLock
lock(mLock
);
570 category
= get_category(aCategoryName
);
575 NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID
, aCategoryName
,
583 nsCategoryManager::EnumerateCategory(const nsACString
& aCategoryName
,
584 nsISimpleEnumerator
** aResult
) {
585 CategoryNode
* category
;
587 MutexAutoLock
lock(mLock
);
588 category
= get_category(aCategoryName
);
592 return NS_NewEmptyEnumerator(aResult
);
595 return category
->Enumerate(aResult
);
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
);
608 return NS_ERROR_OUT_OF_MEMORY
;
616 struct writecat_struct
{
621 nsresult
nsCategoryManager::SuppressNotifications(bool aSuppress
) {
622 mSuppressNotifications
= aSuppress
;
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
) {
640 nsCOMPtr
<nsICategoryManager
> categoryManager
=
641 do_GetService("@mozilla.org/categorymanager;1");
642 if (!categoryManager
) {
646 nsDependentCString
category(aCategory
);
648 nsCOMPtr
<nsISimpleEnumerator
> enumerator
;
649 rv
= categoryManager
->EnumerateCategory(category
, getter_AddRefs(enumerator
));
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());
665 "While creating services from category '%s', could not create "
666 "service for entry '%s', contract ID '%s'",
667 aCategory
, entryString
.get(), contractID
.get());
671 if (aObserverTopic
) {
672 // try an observer, if it implements it.
673 nsCOMPtr
<nsIObserver
> observer
= do_QueryInterface(instance
);
675 nsPrintfCString
profilerStr("%s (%s)", aObserverTopic
,
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
"");
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());