1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #define PL_ARENA_CONST_ALIGN_MASK 7
8 #include "nsICategoryManager.h"
9 #include "nsCategoryManager.h"
16 #include "nsTHashtable.h"
17 #include "nsClassHashtable.h"
18 #include "nsIFactory.h"
19 #include "nsIStringEnumerator.h"
20 #include "nsSupportsPrimitives.h"
21 #include "nsComponentManagerUtils.h"
22 #include "nsServiceManagerUtils.h"
23 #include "nsIObserver.h"
24 #include "nsIObserverService.h"
25 #include "nsReadableUtils.h"
27 #include "nsQuickSort.h"
28 #include "nsEnumeratorUtils.h"
29 #include "nsThreadUtils.h"
30 #include "mozilla/Services.h"
32 #include "ManifestParser.h"
34 using namespace mozilla
;
35 class nsIComponentLoaderManager
;
39 contains 0 or more 1-1 mappings of string to Category
40 each Category contains 0 or more 1-1 mappings of string keys to string values
42 In other words, the CategoryDatabase is a tree, whose root is a hashtable.
43 Internal nodes (or Categories) are hashtables. Leaf nodes are strings.
45 The leaf strings are allocated in an arena, because we assume they're not
46 going to change much ;)
49 #define NS_CATEGORYMANAGER_ARENA_SIZE (1024 * 8)
51 // pulled in from nsComponentManager.cpp
52 char* ArenaStrdup(const char* s
, PLArenaPool
* aArena
);
55 // BaseStringEnumerator is subclassed by EntryEnumerator and
58 class BaseStringEnumerator
59 : public nsISimpleEnumerator
,
60 private nsIUTF8StringEnumerator
64 NS_DECL_NSISIMPLEENUMERATOR
65 NS_DECL_NSIUTF8STRINGENUMERATOR
68 // Callback function for NS_QuickSort to sort mArray
69 static int SortCallback(const void *, const void *, void *);
71 BaseStringEnumerator()
77 // A virtual destructor is needed here because subclasses of
78 // BaseStringEnumerator do not implement their own Release() method.
80 virtual ~BaseStringEnumerator()
90 uint32_t mSimpleCurItem
;
91 uint32_t mStringCurItem
;
94 NS_IMPL_ISUPPORTS2(BaseStringEnumerator
, nsISimpleEnumerator
, nsIUTF8StringEnumerator
)
97 BaseStringEnumerator::HasMoreElements(bool *_retval
)
99 *_retval
= (mSimpleCurItem
< mCount
);
105 BaseStringEnumerator::GetNext(nsISupports
**_retval
)
107 if (mSimpleCurItem
>= mCount
)
108 return NS_ERROR_FAILURE
;
110 nsSupportsDependentCString
* str
=
111 new nsSupportsDependentCString(mArray
[mSimpleCurItem
++]);
113 return NS_ERROR_OUT_OF_MEMORY
;
121 BaseStringEnumerator::HasMore(bool *_retval
)
123 *_retval
= (mStringCurItem
< mCount
);
129 BaseStringEnumerator::GetNext(nsACString
& _retval
)
131 if (mStringCurItem
>= mCount
)
132 return NS_ERROR_FAILURE
;
134 _retval
= nsDependentCString(mArray
[mStringCurItem
++]);
139 BaseStringEnumerator::SortCallback(const void *e1
, const void *e2
,
142 char const *const *s1
= reinterpret_cast<char const *const *>(e1
);
143 char const *const *s2
= reinterpret_cast<char const *const *>(e2
);
145 return strcmp(*s1
, *s2
);
149 BaseStringEnumerator::Sort()
151 NS_QuickSort(mArray
, mCount
, sizeof(mArray
[0]), SortCallback
, nullptr);
155 // EntryEnumerator is the wrapper that allows nsICategoryManager::EnumerateCategory
157 class EntryEnumerator
158 : public BaseStringEnumerator
161 static EntryEnumerator
* Create(nsTHashtable
<CategoryLeaf
>& aTable
);
164 static PLDHashOperator
165 enumfunc_createenumerator(CategoryLeaf
* aLeaf
, void* userArg
);
170 EntryEnumerator::enumfunc_createenumerator(CategoryLeaf
* aLeaf
, void* userArg
)
172 EntryEnumerator
* mythis
= static_cast<EntryEnumerator
*>(userArg
);
174 mythis
->mArray
[mythis
->mCount
++] = aLeaf
->GetKey();
176 return PL_DHASH_NEXT
;
180 EntryEnumerator::Create(nsTHashtable
<CategoryLeaf
>& aTable
)
182 EntryEnumerator
* enumObj
= new EntryEnumerator();
186 enumObj
->mArray
= new char const* [aTable
.Count()];
187 if (!enumObj
->mArray
) {
192 aTable
.EnumerateEntries(enumfunc_createenumerator
, enumObj
);
201 // CategoryNode implementations
205 CategoryNode::Create(PLArenaPool
* aArena
)
207 CategoryNode
* node
= new(aArena
) CategoryNode();
215 CategoryNode::~CategoryNode()
220 CategoryNode::operator new(size_t aSize
, PLArenaPool
* aArena
)
223 PL_ARENA_ALLOCATE(p
, aArena
, aSize
);
228 CategoryNode::GetLeaf(const char* aEntryName
,
231 MutexAutoLock
lock(mLock
);
232 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
234 mTable
.GetEntry(aEntryName
);
236 if (ent
&& ent
->value
) {
237 *_retval
= NS_strdup(ent
->value
);
246 CategoryNode::AddLeaf(const char* aEntryName
,
255 MutexAutoLock
lock(mLock
);
257 mTable
.GetEntry(aEntryName
);
260 const char* arenaEntryName
= ArenaStrdup(aEntryName
, aArena
);
262 return NS_ERROR_OUT_OF_MEMORY
;
264 leaf
= mTable
.PutEntry(arenaEntryName
);
266 return NS_ERROR_OUT_OF_MEMORY
;
269 if (leaf
->value
&& !aReplace
)
270 return NS_ERROR_INVALID_ARG
;
272 const char* arenaValue
= ArenaStrdup(aValue
, aArena
);
274 return NS_ERROR_OUT_OF_MEMORY
;
276 if (_retval
&& leaf
->value
) {
277 *_retval
= ToNewCString(nsDependentCString(leaf
->value
));
279 return NS_ERROR_OUT_OF_MEMORY
;
282 leaf
->value
= arenaValue
;
287 CategoryNode::DeleteLeaf(const char* aEntryName
)
289 // we don't throw any errors, because it normally doesn't matter
290 // and it makes JS a lot cleaner
291 MutexAutoLock
lock(mLock
);
293 // we can just remove the entire hash entry without introspection
294 mTable
.RemoveEntry(aEntryName
);
298 CategoryNode::Enumerate(nsISimpleEnumerator
**_retval
)
300 NS_ENSURE_ARG_POINTER(_retval
);
302 MutexAutoLock
lock(mLock
);
303 EntryEnumerator
* enumObj
= EntryEnumerator::Create(mTable
);
306 return NS_ERROR_OUT_OF_MEMORY
;
313 struct persistent_userstruct
{
315 const char* categoryName
;
320 enumfunc_pentries(CategoryLeaf
* aLeaf
, void* userArg
)
322 persistent_userstruct
* args
=
323 static_cast<persistent_userstruct
*>(userArg
);
325 PLDHashOperator status
= PL_DHASH_NEXT
;
328 if (PR_fprintf(args
->fd
,
332 aLeaf
->value
) == (uint32_t) -1) {
333 args
->success
= false;
334 status
= PL_DHASH_STOP
;
342 // CategoryEnumerator class
345 class CategoryEnumerator
346 : public BaseStringEnumerator
349 static CategoryEnumerator
* Create(nsClassHashtable
<nsDepCharHashKey
, CategoryNode
>& aTable
);
352 static PLDHashOperator
353 enumfunc_createenumerator(const char* aStr
,
359 CategoryEnumerator::Create(nsClassHashtable
<nsDepCharHashKey
, CategoryNode
>& aTable
)
361 CategoryEnumerator
* enumObj
= new CategoryEnumerator();
365 enumObj
->mArray
= new const char* [aTable
.Count()];
366 if (!enumObj
->mArray
) {
371 aTable
.EnumerateRead(enumfunc_createenumerator
, enumObj
);
377 CategoryEnumerator::enumfunc_createenumerator(const char* aStr
, CategoryNode
* aNode
, void* userArg
)
379 CategoryEnumerator
* mythis
= static_cast<CategoryEnumerator
*>(userArg
);
381 // if a category has no entries, we pretend it doesn't exist
383 mythis
->mArray
[mythis
->mCount
++] = aStr
;
385 return PL_DHASH_NEXT
;
390 // nsCategoryManager implementations
393 NS_IMPL_QUERY_INTERFACE1(nsCategoryManager
, nsICategoryManager
)
395 NS_IMETHODIMP_(nsrefcnt
)
396 nsCategoryManager::AddRef()
401 NS_IMETHODIMP_(nsrefcnt
)
402 nsCategoryManager::Release()
407 nsCategoryManager
* nsCategoryManager::gCategoryManager
;
409 /* static */ nsCategoryManager
*
410 nsCategoryManager::GetSingleton()
412 if (!gCategoryManager
)
413 gCategoryManager
= new nsCategoryManager();
414 return gCategoryManager
;
418 nsCategoryManager::Destroy()
420 delete gCategoryManager
;
424 nsCategoryManager::Create(nsISupports
* aOuter
, REFNSIID aIID
, void** aResult
)
427 return NS_ERROR_NO_AGGREGATION
;
429 return GetSingleton()->QueryInterface(aIID
, aResult
);
432 nsCategoryManager::nsCategoryManager()
433 : mLock("nsCategoryManager")
434 , mSuppressNotifications(false)
436 PL_INIT_ARENA_POOL(&mArena
, "CategoryManagerArena",
437 NS_CATEGORYMANAGER_ARENA_SIZE
);
442 nsCategoryManager::~nsCategoryManager()
444 // the hashtable contains entries that must be deleted before the arena is
445 // destroyed, or else you will have PRLocks undestroyed and other Really
449 PL_FinishArenaPool(&mArena
);
453 nsCategoryManager::get_category(const char* aName
) {
455 if (!mTable
.Get(aName
, &node
)) {
463 class CategoryNotificationRunnable
: public nsRunnable
466 CategoryNotificationRunnable(nsISupports
* aSubject
,
477 nsCOMPtr
<nsISupports
> mSubject
;
479 NS_ConvertUTF8toUTF16 mData
;
483 CategoryNotificationRunnable::Run()
485 nsCOMPtr
<nsIObserverService
> observerService
=
486 mozilla::services::GetObserverService();
488 observerService
->NotifyObservers(mSubject
, mTopic
, mData
.get());
493 } // anonymous namespace
497 nsCategoryManager::NotifyObservers( const char *aTopic
,
498 const char *aCategoryName
,
499 const char *aEntryName
)
501 if (mSuppressNotifications
)
504 nsRefPtr
<CategoryNotificationRunnable
> r
;
507 nsCOMPtr
<nsISupportsCString
> entry
508 (do_CreateInstance (NS_SUPPORTS_CSTRING_CONTRACTID
));
512 nsresult rv
= entry
->SetData(nsDependentCString(aEntryName
));
516 r
= new CategoryNotificationRunnable(entry
, aTopic
, aCategoryName
);
518 r
= new CategoryNotificationRunnable(this, aTopic
, aCategoryName
);
521 NS_DispatchToMainThread(r
);
525 nsCategoryManager::GetCategoryEntry( const char *aCategoryName
,
526 const char *aEntryName
,
529 NS_ENSURE_ARG_POINTER(aCategoryName
);
530 NS_ENSURE_ARG_POINTER(aEntryName
);
531 NS_ENSURE_ARG_POINTER(_retval
);
533 nsresult status
= NS_ERROR_NOT_AVAILABLE
;
535 CategoryNode
* category
;
537 MutexAutoLock
lock(mLock
);
538 category
= get_category(aCategoryName
);
542 status
= category
->GetLeaf(aEntryName
, _retval
);
549 nsCategoryManager::AddCategoryEntry( const char *aCategoryName
,
550 const char *aEntryName
,
557 NS_ERROR("Category manager doesn't support persistence.");
558 return NS_ERROR_INVALID_ARG
;
561 AddCategoryEntry(aCategoryName
, aEntryName
, aValue
, aReplace
, _retval
);
566 nsCategoryManager::AddCategoryEntry(const char *aCategoryName
,
567 const char *aEntryName
,
575 // Before we can insert a new entry, we'll need to
576 // find the |CategoryNode| to put it in...
577 CategoryNode
* category
;
579 MutexAutoLock
lock(mLock
);
580 category
= get_category(aCategoryName
);
583 // That category doesn't exist yet; let's make it.
584 category
= CategoryNode::Create(&mArena
);
586 char* categoryName
= ArenaStrdup(aCategoryName
, &mArena
);
587 mTable
.Put(categoryName
, category
);
594 // We will need the return value of AddLeaf even if the called doesn't want it
595 char *oldEntry
= nullptr;
597 nsresult rv
= category
->AddLeaf(aEntryName
,
603 if (NS_SUCCEEDED(rv
)) {
605 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID
,
606 aCategoryName
, oldEntry
);
608 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID
,
609 aCategoryName
, aEntryName
);
612 *aOldValue
= oldEntry
;
619 nsCategoryManager::DeleteCategoryEntry( const char *aCategoryName
,
620 const char *aEntryName
,
623 NS_ENSURE_ARG_POINTER(aCategoryName
);
624 NS_ENSURE_ARG_POINTER(aEntryName
);
627 Note: no errors are reported since failure to delete
628 probably won't hurt you, and returning errors seriously
629 inconveniences JS clients
632 CategoryNode
* category
;
634 MutexAutoLock
lock(mLock
);
635 category
= get_category(aCategoryName
);
639 category
->DeleteLeaf(aEntryName
);
641 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID
,
642 aCategoryName
, aEntryName
);
649 nsCategoryManager::DeleteCategory( const char *aCategoryName
)
651 NS_ENSURE_ARG_POINTER(aCategoryName
);
653 // the categories are arena-allocated, so we don't
654 // actually delete them. We just remove all of the
657 CategoryNode
* category
;
659 MutexAutoLock
lock(mLock
);
660 category
= get_category(aCategoryName
);
665 NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID
,
666 aCategoryName
, nullptr);
673 nsCategoryManager::EnumerateCategory( const char *aCategoryName
,
674 nsISimpleEnumerator
**_retval
)
676 NS_ENSURE_ARG_POINTER(aCategoryName
);
677 NS_ENSURE_ARG_POINTER(_retval
);
679 CategoryNode
* category
;
681 MutexAutoLock
lock(mLock
);
682 category
= get_category(aCategoryName
);
686 return NS_NewEmptyEnumerator(_retval
);
689 return category
->Enumerate(_retval
);
693 nsCategoryManager::EnumerateCategories(nsISimpleEnumerator
**_retval
)
695 NS_ENSURE_ARG_POINTER(_retval
);
697 MutexAutoLock
lock(mLock
);
698 CategoryEnumerator
* enumObj
= CategoryEnumerator::Create(mTable
);
701 return NS_ERROR_OUT_OF_MEMORY
;
708 struct writecat_struct
{
714 nsCategoryManager::SuppressNotifications(bool aSuppress
)
716 mSuppressNotifications
= aSuppress
;
721 * CreateServicesFromCategory()
723 * Given a category, this convenience functions enumerates the category and
724 * creates a service of every CID or ContractID registered under the category.
725 * If observerTopic is non null and the service implements nsIObserver,
726 * this will attempt to notify the observer with the origin, observerTopic string
730 NS_CreateServicesFromCategory(const char *category
,
732 const char *observerTopic
)
736 nsCOMPtr
<nsICategoryManager
> categoryManager
=
737 do_GetService("@mozilla.org/categorymanager;1");
738 if (!categoryManager
)
741 nsCOMPtr
<nsISimpleEnumerator
> enumerator
;
742 rv
= categoryManager
->EnumerateCategory(category
,
743 getter_AddRefs(enumerator
));
747 nsCOMPtr
<nsIUTF8StringEnumerator
> senumerator
=
748 do_QueryInterface(enumerator
);
750 NS_WARNING("Category enumerator doesn't support nsIUTF8StringEnumerator.");
755 while (NS_SUCCEEDED(senumerator
->HasMore(&hasMore
)) && hasMore
) {
756 // From here on just skip any error we get.
757 nsAutoCString entryString
;
758 if (NS_FAILED(senumerator
->GetNext(entryString
)))
761 nsXPIDLCString contractID
;
762 rv
= categoryManager
->GetCategoryEntry(category
,entryString
.get(),
763 getter_Copies(contractID
));
767 nsCOMPtr
<nsISupports
> instance
= do_GetService(contractID
);
769 LogMessage("While creating services from category '%s', could not create service for entry '%s', contract ID '%s'",
770 category
, entryString
.get(), contractID
.get());
775 // try an observer, if it implements it.
776 nsCOMPtr
<nsIObserver
> observer
= do_QueryInterface(instance
);
778 observer
->Observe(origin
, observerTopic
, EmptyString().get());
780 LogMessage("While creating services from category '%s', service for entry '%s', contract ID '%s' does not implement nsIObserver.",
781 category
, entryString
.get(), contractID
.get());