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 "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
;
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
{
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
);
74 : mArray(nullptr), mCount(0), mSimpleCurItem(0), mStringCurItem(0) {}
76 ~CategoryEnumerator() override
{ delete[] mArray
; }
80 uint32_t mSimpleCurItem
;
81 uint32_t mStringCurItem
;
84 NS_IMPL_ISUPPORTS_INHERITED(CategoryEnumerator
, nsSimpleEnumerator
,
85 nsIUTF8StringEnumerator
, nsIStringEnumerator
)
88 CategoryEnumerator::HasMoreElements(bool* aResult
) {
89 *aResult
= (mSimpleCurItem
< mCount
);
95 CategoryEnumerator::GetNext(nsISupports
** aResult
) {
96 if (mSimpleCurItem
>= mCount
) {
97 return NS_ERROR_FAILURE
;
100 auto* str
= new nsSupportsDependentCString(mArray
[mSimpleCurItem
++]);
108 CategoryEnumerator::HasMore(bool* aResult
) {
109 *aResult
= (mStringCurItem
< mCount
);
115 CategoryEnumerator::GetNext(nsACString
& aResult
) {
116 if (mStringCurItem
>= mCount
) {
117 return NS_ERROR_FAILURE
;
120 aResult
= nsDependentCString(mArray
[mStringCurItem
++]);
124 CategoryEnumerator
* CategoryEnumerator::Create(
125 nsClassHashtable
<nsDepCharHashKey
, CategoryNode
>& aTable
) {
126 auto* enumObj
= new CategoryEnumerator();
131 enumObj
->mArray
= new const char*[aTable
.Count()];
132 if (!enumObj
->mArray
) {
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();
148 class CategoryEntry final
: public nsICategoryEntry
{
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
);
164 ~CategoryEntry() = default;
170 NS_IMPL_ISUPPORTS(CategoryEntry
, nsICategoryEntry
, nsISupportsCString
)
172 nsresult
CategoryEntry::ToString(char** aResult
) {
173 *aResult
= moz_xstrdup(mKey
);
177 nsresult
CategoryEntry::GetType(uint16_t* aType
) {
178 *aType
= TYPE_CSTRING
;
182 nsresult
CategoryEntry::GetData(nsACString
& aData
) {
187 nsresult
CategoryEntry::SetData(const nsACString
& aData
) {
188 return NS_ERROR_NOT_IMPLEMENTED
;
191 nsresult
CategoryEntry::GetEntry(nsACString
& aEntry
) {
196 nsresult
CategoryEntry::GetValue(nsACString
& aValue
) {
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();
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
);
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());
267 leaf
= mTable
.PutEntry(MaybeStrdup(aEntryName
, aArena
));
269 return NS_ERROR_OUT_OF_MEMORY
;
273 if (leaf
->value
&& !aReplace
) {
274 return NS_ERROR_INVALID_ARG
;
278 aResult
.AssignLiteral(leaf
->value
, strlen(leaf
->value
));
280 aResult
.SetIsVoid(true);
282 leaf
->value
= MaybeStrdup(aValue
, aArena
);
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
,
314 NS_IMETHODIMP_(MozExternalRefCountType
)
315 nsCategoryManager::AddRef() { return 2; }
317 NS_IMETHODIMP_(MozExternalRefCountType
)
318 nsCategoryManager::Release() { return 1; }
320 nsCategoryManager
* nsCategoryManager::gCategoryManager
;
323 nsCategoryManager
* nsCategoryManager::GetSingleton() {
324 if (!gCategoryManager
) {
325 gCategoryManager
= new nsCategoryManager();
327 return gCategoryManager
;
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
360 inline CategoryNode
* nsCategoryManager::get_category(const nsACString
& aName
) {
362 if (!mTable
.Get(PromiseFlatCString(aName
).get(), &node
)) {
368 MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf
)
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.");
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
);
399 class CategoryNotificationRunnable
: public Runnable
{
401 CategoryNotificationRunnable(nsISupports
* aSubject
, const char* aTopic
,
402 const nsACString
& aData
)
403 : Runnable("CategoryNotificationRunnable"),
411 nsCOMPtr
<nsISupports
> mSubject
;
413 NS_ConvertUTF8toUTF16 mData
;
417 CategoryNotificationRunnable::Run() {
418 nsCOMPtr
<nsIObserverService
> observerService
=
419 mozilla::services::GetObserverService();
420 if (observerService
) {
421 observerService
->NotifyObservers(mSubject
, mTopic
, mData
.get());
429 void nsCategoryManager::NotifyObservers(const char* aTopic
,
430 const nsACString
& aCategoryName
,
431 const nsACString
& aEntryName
) {
432 if (mSuppressNotifications
) {
436 RefPtr
<CategoryNotificationRunnable
> r
;
438 if (aEntryName
.Length()) {
439 nsCOMPtr
<nsISupportsCString
> entry
=
440 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID
);
445 nsresult rv
= entry
->SetData(aEntryName
);
450 r
= new CategoryNotificationRunnable(entry
, aTopic
, aCategoryName
);
452 r
= new CategoryNotificationRunnable(
453 NS_ISUPPORTS_CAST(nsICategoryManager
*, this), aTopic
, aCategoryName
);
456 NS_DispatchToMainThread(r
);
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
);
472 status
= category
->GetLeaf(aEntryName
, aResult
);
479 nsCategoryManager::AddCategoryEntry(const nsACString
& aCategoryName
,
480 const nsACString
& aEntryName
,
481 const nsACString
& aValue
, bool aPersist
,
482 bool aReplace
, nsACString
& aResult
) {
484 NS_ERROR("Category manager doesn't support persistence.");
485 return NS_ERROR_INVALID_ARG
;
488 AddCategoryEntry(aCategoryName
, aEntryName
, aValue
, aReplace
, aResult
);
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
);
507 // That category doesn't exist yet; let's make it.
510 MaybeStrdup(aCategoryName
, &mArena
),
511 UniquePtr
<CategoryNode
>{CategoryNode::Create(&mArena
)})
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
,
534 nsCategoryManager::DeleteCategoryEntry(const nsACString
& aCategoryName
,
535 const nsACString
& aEntryName
,
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
);
550 category
->DeleteLeaf(aEntryName
);
552 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID
, aCategoryName
,
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
565 CategoryNode
* category
;
567 MutexAutoLock
lock(mLock
);
568 category
= get_category(aCategoryName
);
573 NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID
, aCategoryName
,
581 nsCategoryManager::EnumerateCategory(const nsACString
& aCategoryName
,
582 nsISimpleEnumerator
** aResult
) {
583 CategoryNode
* category
;
585 MutexAutoLock
lock(mLock
);
586 category
= get_category(aCategoryName
);
590 return NS_NewEmptyEnumerator(aResult
);
593 return category
->Enumerate(aResult
);
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
);
606 return NS_ERROR_OUT_OF_MEMORY
;
614 struct writecat_struct
{
619 nsresult
nsCategoryManager::SuppressNotifications(bool aSuppress
) {
620 mSuppressNotifications
= aSuppress
;
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
) {
638 nsCOMPtr
<nsICategoryManager
> categoryManager
=
639 do_GetService("@mozilla.org/categorymanager;1");
640 if (!categoryManager
) {
644 nsDependentCString
category(aCategory
);
646 nsCOMPtr
<nsISimpleEnumerator
> enumerator
;
647 rv
= categoryManager
->EnumerateCategory(category
, getter_AddRefs(enumerator
));
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());
663 "While creating services from category '%s', could not create "
664 "service for entry '%s', contract ID '%s'",
665 aCategory
, entryString
.get(), contractID
.get());
669 if (aObserverTopic
) {
670 // try an observer, if it implements it.
671 nsCOMPtr
<nsIObserver
> observer
= do_QueryInterface(instance
);
673 nsPrintfCString
profilerStr("%s (%s)", aObserverTopic
,
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
"");
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());