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/. */
6 
7 #include "nsICategoryManager.h"
8 #include "nsCategoryManager.h"
9 
10 #include "prio.h"
11 #include "prlock.h"
12 #include "nsCOMPtr.h"
13 #include "nsTHashtable.h"
14 #include "nsClassHashtable.h"
15 #include "nsIFactory.h"
16 #include "nsIStringEnumerator.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 "nsQuickSort.h"
25 #include "nsEnumeratorUtils.h"
26 #include "nsThreadUtils.h"
27 #include "mozilla/ArenaAllocatorExtensions.h"
28 #include "mozilla/MemoryReporting.h"
29 #include "mozilla/Services.h"
30 
31 #include "ManifestParser.h"
32 #include "nsISimpleEnumerator.h"
33 
34 using namespace mozilla;
35 class nsIComponentLoaderManager;
36 
37 /*
38   CategoryDatabase
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
41 
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.
44 
45   The leaf strings are allocated in an arena, because we assume they're not
46   going to change much ;)
47 */
48 
49 //
50 // BaseStringEnumerator is subclassed by EntryEnumerator and
51 // CategoryEnumerator
52 //
53 class BaseStringEnumerator : public nsISimpleEnumerator,
54                              private nsIUTF8StringEnumerator {
55  public:
56   NS_DECL_ISUPPORTS
57   NS_DECL_NSISIMPLEENUMERATOR
58   NS_DECL_NSIUTF8STRINGENUMERATOR
59 
60  protected:
61   // Callback function for NS_QuickSort to sort mArray
62   static int SortCallback(const void*, const void*, void*);
63 
BaseStringEnumerator()64   BaseStringEnumerator()
65       : mArray(nullptr), mCount(0), mSimpleCurItem(0), mStringCurItem(0) {}
66 
67   // A virtual destructor is needed here because subclasses of
68   // BaseStringEnumerator do not implement their own Release() method.
69 
~BaseStringEnumerator()70   virtual ~BaseStringEnumerator() { delete[] mArray; }
71 
72   void Sort();
73 
74   const char** mArray;
75   uint32_t mCount;
76   uint32_t mSimpleCurItem;
77   uint32_t mStringCurItem;
78 };
79 
NS_IMPL_ISUPPORTS(BaseStringEnumerator,nsISimpleEnumerator,nsIUTF8StringEnumerator)80 NS_IMPL_ISUPPORTS(BaseStringEnumerator, nsISimpleEnumerator,
81                   nsIUTF8StringEnumerator)
82 
83 NS_IMETHODIMP
84 BaseStringEnumerator::HasMoreElements(bool* aResult) {
85   *aResult = (mSimpleCurItem < mCount);
86 
87   return NS_OK;
88 }
89 
90 NS_IMETHODIMP
GetNext(nsISupports ** aResult)91 BaseStringEnumerator::GetNext(nsISupports** aResult) {
92   if (mSimpleCurItem >= mCount) {
93     return NS_ERROR_FAILURE;
94   }
95 
96   auto* str = new nsSupportsDependentCString(mArray[mSimpleCurItem++]);
97   if (!str) {
98     return NS_ERROR_OUT_OF_MEMORY;
99   }
100 
101   *aResult = str;
102   NS_ADDREF(*aResult);
103   return NS_OK;
104 }
105 
106 NS_IMETHODIMP
HasMore(bool * aResult)107 BaseStringEnumerator::HasMore(bool* aResult) {
108   *aResult = (mStringCurItem < mCount);
109 
110   return NS_OK;
111 }
112 
113 NS_IMETHODIMP
GetNext(nsACString & aResult)114 BaseStringEnumerator::GetNext(nsACString& aResult) {
115   if (mStringCurItem >= mCount) {
116     return NS_ERROR_FAILURE;
117   }
118 
119   aResult = nsDependentCString(mArray[mStringCurItem++]);
120   return NS_OK;
121 }
122 
SortCallback(const void * aE1,const void * aE2,void *)123 int BaseStringEnumerator::SortCallback(const void* aE1, const void* aE2,
124                                        void* /*unused*/) {
125   char const* const* s1 = reinterpret_cast<char const* const*>(aE1);
126   char const* const* s2 = reinterpret_cast<char const* const*>(aE2);
127 
128   return strcmp(*s1, *s2);
129 }
130 
Sort()131 void BaseStringEnumerator::Sort() {
132   NS_QuickSort(mArray, mCount, sizeof(mArray[0]), SortCallback, nullptr);
133 }
134 
135 //
136 // EntryEnumerator is the wrapper that allows
137 // nsICategoryManager::EnumerateCategory
138 //
139 class EntryEnumerator : public BaseStringEnumerator {
140  public:
141   static EntryEnumerator* Create(nsTHashtable<CategoryLeaf>& aTable);
142 };
143 
Create(nsTHashtable<CategoryLeaf> & aTable)144 EntryEnumerator* EntryEnumerator::Create(nsTHashtable<CategoryLeaf>& aTable) {
145   auto* enumObj = new EntryEnumerator();
146   if (!enumObj) {
147     return nullptr;
148   }
149 
150   enumObj->mArray = new char const*[aTable.Count()];
151   if (!enumObj->mArray) {
152     delete enumObj;
153     return nullptr;
154   }
155 
156   for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) {
157     CategoryLeaf* leaf = iter.Get();
158     if (leaf->value) {
159       enumObj->mArray[enumObj->mCount++] = leaf->GetKey();
160     }
161   }
162 
163   enumObj->Sort();
164 
165   return enumObj;
166 }
167 
168 //
169 // CategoryNode implementations
170 //
171 
Create(CategoryAllocator * aArena)172 CategoryNode* CategoryNode::Create(CategoryAllocator* aArena) {
173   return new (aArena) CategoryNode();
174 }
175 
176 CategoryNode::~CategoryNode() = default;
177 
operator new(size_t aSize,CategoryAllocator * aArena)178 void* CategoryNode::operator new(size_t aSize, CategoryAllocator* aArena) {
179   return aArena->Allocate(aSize, mozilla::fallible);
180 }
181 
GetLeaf(const char * aEntryName,char ** aResult)182 nsresult CategoryNode::GetLeaf(const char* aEntryName, char** aResult) {
183   MutexAutoLock lock(mLock);
184   nsresult rv = NS_ERROR_NOT_AVAILABLE;
185   CategoryLeaf* ent = mTable.GetEntry(aEntryName);
186 
187   if (ent && ent->value) {
188     *aResult = NS_strdup(ent->value);
189     if (*aResult) {
190       rv = NS_OK;
191     }
192   }
193 
194   return rv;
195 }
196 
AddLeaf(const char * aEntryName,const char * aValue,bool aReplace,char ** aResult,CategoryAllocator * aArena)197 nsresult CategoryNode::AddLeaf(const char* aEntryName, const char* aValue,
198                                bool aReplace, char** aResult,
199                                CategoryAllocator* aArena) {
200   if (aResult) {
201     *aResult = nullptr;
202   }
203 
204   MutexAutoLock lock(mLock);
205   CategoryLeaf* leaf = mTable.GetEntry(aEntryName);
206 
207   if (!leaf) {
208     const char* arenaEntryName = ArenaStrdup(aEntryName, *aArena);
209     if (!arenaEntryName) {
210       return NS_ERROR_OUT_OF_MEMORY;
211     }
212 
213     leaf = mTable.PutEntry(arenaEntryName);
214     if (!leaf) {
215       return NS_ERROR_OUT_OF_MEMORY;
216     }
217   }
218 
219   if (leaf->value && !aReplace) {
220     return NS_ERROR_INVALID_ARG;
221   }
222 
223   const char* arenaValue = ArenaStrdup(aValue, *aArena);
224   if (!arenaValue) {
225     return NS_ERROR_OUT_OF_MEMORY;
226   }
227 
228   if (aResult && leaf->value) {
229     *aResult = ToNewCString(nsDependentCString(leaf->value));
230     if (!*aResult) {
231       return NS_ERROR_OUT_OF_MEMORY;
232     }
233   }
234 
235   leaf->value = arenaValue;
236   return NS_OK;
237 }
238 
DeleteLeaf(const char * aEntryName)239 void CategoryNode::DeleteLeaf(const char* aEntryName) {
240   // we don't throw any errors, because it normally doesn't matter
241   // and it makes JS a lot cleaner
242   MutexAutoLock lock(mLock);
243 
244   // we can just remove the entire hash entry without introspection
245   mTable.RemoveEntry(aEntryName);
246 }
247 
Enumerate(nsISimpleEnumerator ** aResult)248 nsresult CategoryNode::Enumerate(nsISimpleEnumerator** aResult) {
249   if (NS_WARN_IF(!aResult)) {
250     return NS_ERROR_INVALID_ARG;
251   }
252 
253   MutexAutoLock lock(mLock);
254   EntryEnumerator* enumObj = EntryEnumerator::Create(mTable);
255 
256   if (!enumObj) {
257     return NS_ERROR_OUT_OF_MEMORY;
258   }
259 
260   *aResult = enumObj;
261   NS_ADDREF(*aResult);
262   return NS_OK;
263 }
264 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)265 size_t CategoryNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
266   // We don't measure the strings pointed to by the entries because the
267   // pointers are non-owning.
268   return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
269 }
270 
271 //
272 // CategoryEnumerator class
273 //
274 
275 class CategoryEnumerator : public BaseStringEnumerator {
276  public:
277   static CategoryEnumerator* Create(
278       nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable);
279 };
280 
Create(nsClassHashtable<nsDepCharHashKey,CategoryNode> & aTable)281 CategoryEnumerator* CategoryEnumerator::Create(
282     nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable) {
283   auto* enumObj = new CategoryEnumerator();
284   if (!enumObj) {
285     return nullptr;
286   }
287 
288   enumObj->mArray = new const char*[aTable.Count()];
289   if (!enumObj->mArray) {
290     delete enumObj;
291     return nullptr;
292   }
293 
294   for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) {
295     // if a category has no entries, we pretend it doesn't exist
296     CategoryNode* aNode = iter.UserData();
297     if (aNode->Count()) {
298       const char* str = iter.Key();
299       enumObj->mArray[enumObj->mCount++] = str;
300     }
301   }
302 
303   return enumObj;
304 }
305 
306 //
307 // nsCategoryManager implementations
308 //
309 
NS_IMPL_QUERY_INTERFACE(nsCategoryManager,nsICategoryManager,nsIMemoryReporter)310 NS_IMPL_QUERY_INTERFACE(nsCategoryManager, nsICategoryManager,
311                         nsIMemoryReporter)
312 
313 NS_IMETHODIMP_(MozExternalRefCountType)
314 nsCategoryManager::AddRef() { return 2; }
315 
NS_IMETHODIMP_(MozExternalRefCountType)316 NS_IMETHODIMP_(MozExternalRefCountType)
317 nsCategoryManager::Release() { return 1; }
318 
319 nsCategoryManager* nsCategoryManager::gCategoryManager;
320 
GetSingleton()321 /* static */ nsCategoryManager* nsCategoryManager::GetSingleton() {
322   if (!gCategoryManager) {
323     gCategoryManager = new nsCategoryManager();
324   }
325   return gCategoryManager;
326 }
327 
Destroy()328 /* static */ void nsCategoryManager::Destroy() {
329   // The nsMemoryReporterManager gets destroyed before the nsCategoryManager,
330   // so we don't need to unregister the nsCategoryManager as a memory reporter.
331   // In debug builds we assert that unregistering fails, as a way (imperfect
332   // but better than nothing) of testing the "destroyed before" part.
333   MOZ_ASSERT(NS_FAILED(UnregisterWeakMemoryReporter(gCategoryManager)));
334 
335   delete gCategoryManager;
336   gCategoryManager = nullptr;
337 }
338 
Create(nsISupports * aOuter,REFNSIID aIID,void ** aResult)339 nsresult nsCategoryManager::Create(nsISupports* aOuter, REFNSIID aIID,
340                                    void** aResult) {
341   if (aOuter) {
342     return NS_ERROR_NO_AGGREGATION;
343   }
344 
345   return GetSingleton()->QueryInterface(aIID, aResult);
346 }
347 
nsCategoryManager()348 nsCategoryManager::nsCategoryManager()
349     : mArena(),
350       mTable(),
351       mLock("nsCategoryManager"),
352       mSuppressNotifications(false) {}
353 
InitMemoryReporter()354 void nsCategoryManager::InitMemoryReporter() {
355   RegisterWeakMemoryReporter(this);
356 }
357 
~nsCategoryManager()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();
363 }
364 
get_category(const char * aName)365 inline CategoryNode* nsCategoryManager::get_category(const char* aName) {
366   CategoryNode* node;
367   if (!mTable.Get(aName, &node)) {
368     return nullptr;
369   }
370   return node;
371 }
372 
MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf)373 MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf)
374 
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.");
381 
382   return NS_OK;
383 }
384 
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)385 size_t nsCategoryManager::SizeOfIncludingThis(
386     mozilla::MallocSizeOf aMallocSizeOf) {
387   size_t n = aMallocSizeOf(this);
388 
389   n += mArena.SizeOfExcludingThis(aMallocSizeOf);
390 
391   n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
392   for (auto iter = mTable.ConstIter(); !iter.Done(); iter.Next()) {
393     // We don't measure the key string because it's a non-owning pointer.
394     n += iter.Data()->SizeOfExcludingThis(aMallocSizeOf);
395   }
396 
397   return n;
398 }
399 
400 namespace {
401 
402 class CategoryNotificationRunnable : public Runnable {
403  public:
CategoryNotificationRunnable(nsISupports * aSubject,const char * aTopic,const char * aData)404   CategoryNotificationRunnable(nsISupports* aSubject, const char* aTopic,
405                                const char* aData)
406       : Runnable("CategoryNotificationRunnable"),
407         mSubject(aSubject),
408         mTopic(aTopic),
409         mData(aData) {}
410 
411   NS_DECL_NSIRUNNABLE
412 
413  private:
414   nsCOMPtr<nsISupports> mSubject;
415   const char* mTopic;
416   NS_ConvertUTF8toUTF16 mData;
417 };
418 
419 NS_IMETHODIMP
Run()420 CategoryNotificationRunnable::Run() {
421   nsCOMPtr<nsIObserverService> observerService =
422       mozilla::services::GetObserverService();
423   if (observerService) {
424     observerService->NotifyObservers(mSubject, mTopic, mData.get());
425   }
426 
427   return NS_OK;
428 }
429 
430 }  // namespace
431 
NotifyObservers(const char * aTopic,const char * aCategoryName,const char * aEntryName)432 void nsCategoryManager::NotifyObservers(const char* aTopic,
433                                         const char* aCategoryName,
434                                         const char* aEntryName) {
435   if (mSuppressNotifications) {
436     return;
437   }
438 
439   RefPtr<CategoryNotificationRunnable> r;
440 
441   if (aEntryName) {
442     nsCOMPtr<nsISupportsCString> entry =
443         do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
444     if (!entry) {
445       return;
446     }
447 
448     nsresult rv = entry->SetData(nsDependentCString(aEntryName));
449     if (NS_FAILED(rv)) {
450       return;
451     }
452 
453     r = new CategoryNotificationRunnable(entry, aTopic, aCategoryName);
454   } else {
455     r = new CategoryNotificationRunnable(
456         NS_ISUPPORTS_CAST(nsICategoryManager*, this), aTopic, aCategoryName);
457   }
458 
459   NS_DispatchToMainThread(r);
460 }
461 
462 NS_IMETHODIMP
GetCategoryEntry(const char * aCategoryName,const char * aEntryName,char ** aResult)463 nsCategoryManager::GetCategoryEntry(const char* aCategoryName,
464                                     const char* aEntryName, char** aResult) {
465   if (NS_WARN_IF(!aCategoryName) || NS_WARN_IF(!aEntryName) ||
466       NS_WARN_IF(!aResult)) {
467     return NS_ERROR_INVALID_ARG;
468   }
469 
470   nsresult status = NS_ERROR_NOT_AVAILABLE;
471 
472   CategoryNode* category;
473   {
474     MutexAutoLock lock(mLock);
475     category = get_category(aCategoryName);
476   }
477 
478   if (category) {
479     status = category->GetLeaf(aEntryName, aResult);
480   }
481 
482   return status;
483 }
484 
485 NS_IMETHODIMP
AddCategoryEntry(const char * aCategoryName,const char * aEntryName,const char * aValue,bool aPersist,bool aReplace,char ** aResult)486 nsCategoryManager::AddCategoryEntry(const char* aCategoryName,
487                                     const char* aEntryName, const char* aValue,
488                                     bool aPersist, bool aReplace,
489                                     char** aResult) {
490   if (aPersist) {
491     NS_ERROR("Category manager doesn't support persistence.");
492     return NS_ERROR_INVALID_ARG;
493   }
494 
495   AddCategoryEntry(aCategoryName, aEntryName, aValue, aReplace, aResult);
496   return NS_OK;
497 }
498 
AddCategoryEntry(const char * aCategoryName,const char * aEntryName,const char * aValue,bool aReplace,char ** aOldValue)499 void nsCategoryManager::AddCategoryEntry(const char* aCategoryName,
500                                          const char* aEntryName,
501                                          const char* aValue, bool aReplace,
502                                          char** aOldValue) {
503   if (aOldValue) {
504     *aOldValue = nullptr;
505   }
506 
507   // Before we can insert a new entry, we'll need to
508   //  find the |CategoryNode| to put it in...
509   CategoryNode* category;
510   {
511     MutexAutoLock lock(mLock);
512     category = get_category(aCategoryName);
513 
514     if (!category) {
515       // That category doesn't exist yet; let's make it.
516       category = CategoryNode::Create(&mArena);
517 
518       char* categoryName = ArenaStrdup(aCategoryName, mArena);
519       mTable.Put(categoryName, category);
520     }
521   }
522 
523   if (!category) {
524     return;
525   }
526 
527   // We will need the return value of AddLeaf even if the called doesn't want it
528   char* oldEntry = nullptr;
529 
530   nsresult rv =
531       category->AddLeaf(aEntryName, aValue, aReplace, &oldEntry, &mArena);
532 
533   if (NS_SUCCEEDED(rv)) {
534     if (oldEntry) {
535       NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID,
536                       aCategoryName, aEntryName);
537     }
538     NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, aCategoryName,
539                     aEntryName);
540 
541     if (aOldValue) {
542       *aOldValue = oldEntry;
543     } else {
544       free(oldEntry);
545     }
546   }
547 }
548 
549 NS_IMETHODIMP
DeleteCategoryEntry(const char * aCategoryName,const char * aEntryName,bool aDontPersist)550 nsCategoryManager::DeleteCategoryEntry(const char* aCategoryName,
551                                        const char* aEntryName,
552                                        bool aDontPersist) {
553   if (NS_WARN_IF(!aCategoryName) || NS_WARN_IF(!aEntryName)) {
554     return NS_ERROR_INVALID_ARG;
555   }
556 
557   /*
558     Note: no errors are reported since failure to delete
559     probably won't hurt you, and returning errors seriously
560     inconveniences JS clients
561   */
562 
563   CategoryNode* category;
564   {
565     MutexAutoLock lock(mLock);
566     category = get_category(aCategoryName);
567   }
568 
569   if (category) {
570     category->DeleteLeaf(aEntryName);
571 
572     NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, aCategoryName,
573                     aEntryName);
574   }
575 
576   return NS_OK;
577 }
578 
579 NS_IMETHODIMP
DeleteCategory(const char * aCategoryName)580 nsCategoryManager::DeleteCategory(const char* aCategoryName) {
581   if (NS_WARN_IF(!aCategoryName)) {
582     return NS_ERROR_INVALID_ARG;
583   }
584 
585   // the categories are arena-allocated, so we don't
586   // actually delete them. We just remove all of the
587   // leaf nodes.
588 
589   CategoryNode* category;
590   {
591     MutexAutoLock lock(mLock);
592     category = get_category(aCategoryName);
593   }
594 
595   if (category) {
596     category->Clear();
597     NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, aCategoryName,
598                     nullptr);
599   }
600 
601   return NS_OK;
602 }
603 
604 NS_IMETHODIMP
EnumerateCategory(const char * aCategoryName,nsISimpleEnumerator ** aResult)605 nsCategoryManager::EnumerateCategory(const char* aCategoryName,
606                                      nsISimpleEnumerator** aResult) {
607   if (NS_WARN_IF(!aCategoryName) || NS_WARN_IF(!aResult)) {
608     return NS_ERROR_INVALID_ARG;
609   }
610 
611   CategoryNode* category;
612   {
613     MutexAutoLock lock(mLock);
614     category = get_category(aCategoryName);
615   }
616 
617   if (!category) {
618     return NS_NewEmptyEnumerator(aResult);
619   }
620 
621   return category->Enumerate(aResult);
622 }
623 
624 NS_IMETHODIMP
EnumerateCategories(nsISimpleEnumerator ** aResult)625 nsCategoryManager::EnumerateCategories(nsISimpleEnumerator** aResult) {
626   if (NS_WARN_IF(!aResult)) {
627     return NS_ERROR_INVALID_ARG;
628   }
629 
630   MutexAutoLock lock(mLock);
631   CategoryEnumerator* enumObj = CategoryEnumerator::Create(mTable);
632 
633   if (!enumObj) {
634     return NS_ERROR_OUT_OF_MEMORY;
635   }
636 
637   *aResult = enumObj;
638   NS_ADDREF(*aResult);
639   return NS_OK;
640 }
641 
642 struct writecat_struct {
643   PRFileDesc* fd;
644   bool success;
645 };
646 
SuppressNotifications(bool aSuppress)647 nsresult nsCategoryManager::SuppressNotifications(bool aSuppress) {
648   mSuppressNotifications = aSuppress;
649   return NS_OK;
650 }
651 
652 /*
653  * CreateServicesFromCategory()
654  *
655  * Given a category, this convenience functions enumerates the category and
656  * creates a service of every CID or ContractID registered under the category.
657  * If observerTopic is non null and the service implements nsIObserver,
658  * this will attempt to notify the observer with the origin, observerTopic
659  * string as parameter.
660  */
NS_CreateServicesFromCategory(const char * aCategory,nsISupports * aOrigin,const char * aObserverTopic,const char16_t * aObserverData)661 void NS_CreateServicesFromCategory(const char* aCategory, nsISupports* aOrigin,
662                                    const char* aObserverTopic,
663                                    const char16_t* aObserverData) {
664   nsresult rv;
665 
666   nsCOMPtr<nsICategoryManager> categoryManager =
667       do_GetService("@mozilla.org/categorymanager;1");
668   if (!categoryManager) {
669     return;
670   }
671 
672   nsCOMPtr<nsISimpleEnumerator> enumerator;
673   rv =
674       categoryManager->EnumerateCategory(aCategory, getter_AddRefs(enumerator));
675   if (NS_FAILED(rv)) {
676     return;
677   }
678 
679   nsCOMPtr<nsIUTF8StringEnumerator> senumerator = do_QueryInterface(enumerator);
680   if (!senumerator) {
681     NS_WARNING("Category enumerator doesn't support nsIUTF8StringEnumerator.");
682     return;
683   }
684 
685   bool hasMore;
686   while (NS_SUCCEEDED(senumerator->HasMore(&hasMore)) && hasMore) {
687     // From here on just skip any error we get.
688     nsAutoCString entryString;
689     if (NS_FAILED(senumerator->GetNext(entryString))) {
690       continue;
691     }
692 
693     nsCString contractID;
694     rv = categoryManager->GetCategoryEntry(aCategory, entryString.get(),
695                                            getter_Copies(contractID));
696     if (NS_FAILED(rv)) {
697       continue;
698     }
699 
700     nsCOMPtr<nsISupports> instance = do_GetService(contractID.get());
701     if (!instance) {
702       LogMessage(
703           "While creating services from category '%s', could not create "
704           "service for entry '%s', contract ID '%s'",
705           aCategory, entryString.get(), contractID.get());
706       continue;
707     }
708 
709     if (aObserverTopic) {
710       // try an observer, if it implements it.
711       nsCOMPtr<nsIObserver> observer = do_QueryInterface(instance);
712       if (observer) {
713         observer->Observe(aOrigin, aObserverTopic,
714                           aObserverData ? aObserverData : u"");
715       } else {
716         LogMessage(
717             "While creating services from category '%s', service for entry "
718             "'%s', contract ID '%s' does not implement nsIObserver.",
719             aCategory, entryString.get(), contractID.get());
720       }
721     }
722   }
723 }
724