VirtualBox

source: vbox/trunk/src/libs/xpcom18a4/xpcom/components/nsCategoryManager.cpp@ 71370

最後變更 在這個檔案從71370是 1,由 vboxsync 提交於 55 年 前

import

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 19.2 KB
 
1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is mozilla.org code.
16 *
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2000
20 * the Initial Developer. All Rights Reserved.
21 *
22 * Contributor(s):
23 * Scott Collins <[email protected]>
24 *
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
36 *
37 * ***** END LICENSE BLOCK ***** */
38
39#define PL_ARENA_CONST_ALIGN_MASK 7
40
41#include "nsICategoryManager.h"
42#include "nsCategoryManager.h"
43
44#include "plarena.h"
45#include "prio.h"
46#include "prprf.h"
47#include "prlock.h"
48#include "nsCOMPtr.h"
49#include "nsTHashtable.h"
50#include "nsClassHashtable.h"
51#include "nsIFactory.h"
52#include "nsIStringEnumerator.h"
53#include "nsSupportsPrimitives.h"
54#include "nsIServiceManagerUtils.h"
55#include "nsIObserver.h"
56#include "nsReadableUtils.h"
57#include "nsCRT.h"
58#include "nsEnumeratorUtils.h"
59
60class nsIComponentLoaderManager;
61
62/*
63 CategoryDatabase
64 contains 0 or more 1-1 mappings of string to Category
65 each Category contains 0 or more 1-1 mappings of string keys to string values
66
67 In other words, the CategoryDatabase is a tree, whose root is a hashtable.
68 Internal nodes (or Categories) are hashtables. Leaf nodes are strings.
69
70 The leaf strings are allocated in an arena, because we assume they're not
71 going to change much ;)
72*/
73
74#define NS_CATEGORYMANAGER_ARENA_SIZE (1024 * 8)
75
76// pulled in from nsComponentManager.cpp
77char* ArenaStrdup(const char* s, PLArenaPool* aArena);
78
79//
80// BaseStringEnumerator is subclassed by EntryEnumerator and
81// CategoryEnumerator
82//
83class BaseStringEnumerator
84 : public nsISimpleEnumerator,
85 nsIUTF8StringEnumerator
86{
87public:
88 NS_DECL_ISUPPORTS
89 NS_DECL_NSISIMPLEENUMERATOR
90 NS_DECL_NSIUTF8STRINGENUMERATOR
91
92protected:
93 BaseStringEnumerator()
94 : mArray(nsnull),
95 mCount(0),
96 mSimpleCurItem(0),
97 mStringCurItem(0) { }
98
99 // A virtual destructor is needed here because subclasses of
100 // BaseStringEnumerator do not implement their own Release() method.
101
102 virtual ~BaseStringEnumerator()
103 {
104 if (mArray)
105 delete[] mArray;
106 }
107
108 const char** mArray;
109 PRUint32 mCount;
110 PRUint32 mSimpleCurItem;
111 PRUint32 mStringCurItem;
112};
113
114NS_IMPL_ISUPPORTS2(BaseStringEnumerator, nsISimpleEnumerator, nsIUTF8StringEnumerator)
115
116NS_IMETHODIMP
117BaseStringEnumerator::HasMoreElements(PRBool *_retval)
118{
119 *_retval = (mSimpleCurItem < mCount);
120
121 return NS_OK;
122}
123
124NS_IMETHODIMP
125BaseStringEnumerator::GetNext(nsISupports **_retval)
126{
127 if (mSimpleCurItem >= mCount)
128 return NS_ERROR_FAILURE;
129
130 nsSupportsDependentCString* str =
131 new nsSupportsDependentCString(mArray[mSimpleCurItem++]);
132 if (!str)
133 return NS_ERROR_OUT_OF_MEMORY;
134
135 *_retval = str;
136 NS_ADDREF(*_retval);
137 return NS_OK;
138}
139
140NS_IMETHODIMP
141BaseStringEnumerator::HasMore(PRBool *_retval)
142{
143 *_retval = (mStringCurItem < mCount);
144
145 return NS_OK;
146}
147
148NS_IMETHODIMP
149BaseStringEnumerator::GetNext(nsACString& _retval)
150{
151 if (mStringCurItem >= mCount)
152 return NS_ERROR_FAILURE;
153
154 _retval = nsDependentCString(mArray[mStringCurItem++]);
155 return NS_OK;
156}
157
158
159//
160// EntryEnumerator is the wrapper that allows nsICategoryManager::EnumerateCategory
161//
162class EntryEnumerator
163 : public BaseStringEnumerator
164{
165public:
166 static EntryEnumerator* Create(nsTHashtable<CategoryLeaf>& aTable);
167
168private:
169 static PLDHashOperator PR_CALLBACK
170 enumfunc_createenumerator(CategoryLeaf* aLeaf, void* userArg);
171};
172
173
174PLDHashOperator PR_CALLBACK
175EntryEnumerator::enumfunc_createenumerator(CategoryLeaf* aLeaf, void* userArg)
176{
177 EntryEnumerator* mythis = NS_STATIC_CAST(EntryEnumerator*, userArg);
178 mythis->mArray[mythis->mCount++] = aLeaf->GetKey();
179
180 return PL_DHASH_NEXT;
181}
182
183EntryEnumerator*
184EntryEnumerator::Create(nsTHashtable<CategoryLeaf>& aTable)
185{
186 EntryEnumerator* enumObj = new EntryEnumerator();
187 if (!enumObj)
188 return nsnull;
189
190 enumObj->mArray = new char const* [aTable.Count()];
191 if (!enumObj->mArray) {
192 delete enumObj;
193 return nsnull;
194 }
195
196 aTable.EnumerateEntries(enumfunc_createenumerator, enumObj);
197
198 return enumObj;
199}
200
201
202//
203// CategoryNode implementations
204//
205
206CategoryNode*
207CategoryNode::Create(PLArenaPool* aArena)
208{
209 CategoryNode* node = new(aArena) CategoryNode();
210 if (!node)
211 return nsnull;
212
213 if (!node->mTable.Init()) {
214 delete node;
215 return nsnull;
216 }
217
218 node->mLock = PR_NewLock();
219 if (!node->mLock) {
220 delete node;
221 return nsnull;
222 }
223
224 return node;
225}
226
227CategoryNode::~CategoryNode()
228{
229 if (mLock)
230 PR_DestroyLock(mLock);
231}
232
233void*
234CategoryNode::operator new(size_t aSize, PLArenaPool* aArena)
235{
236 void* p;
237 PL_ARENA_ALLOCATE(p, aArena, aSize);
238 return p;
239}
240
241NS_METHOD
242CategoryNode::GetLeaf(const char* aEntryName,
243 char** _retval)
244{
245 PR_Lock(mLock);
246 nsresult rv = NS_ERROR_NOT_AVAILABLE;
247 CategoryLeaf* ent =
248 mTable.GetEntry(aEntryName);
249
250 // we only want the non-persistent value
251 if (ent && ent->nonpValue) {
252 *_retval = nsCRT::strdup(ent->nonpValue);
253 if (*_retval)
254 rv = NS_OK;
255 }
256 PR_Unlock(mLock);
257
258 return rv;
259}
260
261NS_METHOD
262CategoryNode::AddLeaf(const char* aEntryName,
263 const char* aValue,
264 PRBool aPersist,
265 PRBool aReplace,
266 char** _retval,
267 PLArenaPool* aArena)
268{
269 PR_Lock(mLock);
270 CategoryLeaf* leaf =
271 mTable.GetEntry(aEntryName);
272
273 nsresult rv = NS_OK;
274 if (leaf) {
275 //if the entry was found, aReplace must be specified
276 if (!aReplace && (leaf->nonpValue || (aPersist && leaf->pValue )))
277 rv = NS_ERROR_INVALID_ARG;
278 } else {
279 const char* arenaEntryName = ArenaStrdup(aEntryName, aArena);
280 if (!arenaEntryName) {
281 rv = NS_ERROR_OUT_OF_MEMORY;
282 } else {
283 leaf = mTable.PutEntry(arenaEntryName);
284 if (!leaf)
285 rv = NS_ERROR_OUT_OF_MEMORY;
286 }
287 }
288
289 if (NS_SUCCEEDED(rv)) {
290 const char* arenaValue = ArenaStrdup(aValue, aArena);
291 if (!arenaValue) {
292 rv = NS_ERROR_OUT_OF_MEMORY;
293 } else {
294 leaf->nonpValue = arenaValue;
295 if (aPersist)
296 leaf->pValue = arenaValue;
297 }
298 }
299
300 PR_Unlock(mLock);
301 return rv;
302}
303
304NS_METHOD
305CategoryNode::DeleteLeaf(const char* aEntryName,
306 PRBool aDontPersist)
307{
308 // we don't throw any errors, because it normally doesn't matter
309 // and it makes JS a lot cleaner
310 PR_Lock(mLock);
311
312 if (aDontPersist) {
313 // we can just remove the entire hash entry without introspection
314 mTable.RemoveEntry(aEntryName);
315 } else {
316 // if we are keeping the persistent value, we need to look at
317 // the contents of the current entry
318 CategoryLeaf* leaf = mTable.GetEntry(aEntryName);
319 if (leaf) {
320 if (leaf->pValue) {
321 leaf->nonpValue = nsnull;
322 } else {
323 // if there is no persistent value, just remove the entry
324 mTable.RawRemoveEntry(leaf);
325 }
326 }
327 }
328 PR_Unlock(mLock);
329
330 return NS_OK;
331}
332
333NS_METHOD
334CategoryNode::Enumerate(nsISimpleEnumerator **_retval)
335{
336 NS_ENSURE_ARG_POINTER(_retval);
337
338 PR_Lock(mLock);
339 EntryEnumerator* enumObj = EntryEnumerator::Create(mTable);
340 PR_Unlock(mLock);
341
342 if (!enumObj)
343 return NS_ERROR_OUT_OF_MEMORY;
344
345 *_retval = enumObj;
346 NS_ADDREF(*_retval);
347 return NS_OK;
348}
349
350struct persistent_userstruct {
351 PRFileDesc* fd;
352 const char* categoryName;
353 PRBool success;
354};
355
356PLDHashOperator PR_CALLBACK
357enumfunc_pentries(CategoryLeaf* aLeaf, void* userArg)
358{
359 persistent_userstruct* args =
360 NS_STATIC_CAST(persistent_userstruct*, userArg);
361
362 PLDHashOperator status = PL_DHASH_NEXT;
363
364 if (aLeaf->pValue) {
365 if (PR_fprintf(args->fd,
366 "%s,%s,%s\n",
367 args->categoryName,
368 aLeaf->GetKey(),
369 aLeaf->pValue) == (PRUint32) -1) {
370 args->success = PR_FALSE;
371 status = PL_DHASH_STOP;
372 }
373 }
374
375 return status;
376}
377
378PRBool
379CategoryNode::WritePersistentEntries(PRFileDesc* fd, const char* aCategoryName)
380{
381 persistent_userstruct args = {
382 fd,
383 aCategoryName,
384 PR_TRUE
385 };
386
387 PR_Lock(mLock);
388 mTable.EnumerateEntries(enumfunc_pentries, &args);
389 PR_Unlock(mLock);
390
391 return args.success;
392}
393
394
395//
396// CategoryEnumerator class
397//
398
399class CategoryEnumerator
400 : public BaseStringEnumerator
401{
402public:
403 static CategoryEnumerator* Create(nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable);
404
405private:
406 static PLDHashOperator PR_CALLBACK
407 enumfunc_createenumerator(const char* aStr,
408 CategoryNode* aNode,
409 void* userArg);
410};
411
412CategoryEnumerator*
413CategoryEnumerator::Create(nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable)
414{
415 CategoryEnumerator* enumObj = new CategoryEnumerator();
416 if (!enumObj)
417 return nsnull;
418
419 enumObj->mArray = new const char* [aTable.Count()];
420 if (!enumObj->mArray) {
421 delete enumObj;
422 return nsnull;
423 }
424
425 aTable.EnumerateRead(enumfunc_createenumerator, enumObj);
426
427 return enumObj;
428}
429
430PLDHashOperator PR_CALLBACK
431CategoryEnumerator::enumfunc_createenumerator(const char* aStr, CategoryNode* aNode, void* userArg)
432{
433 CategoryEnumerator* mythis = NS_STATIC_CAST(CategoryEnumerator*, userArg);
434
435 // if a category has no entries, we pretend it doesn't exist
436 if (aNode->Count())
437 mythis->mArray[mythis->mCount++] = aStr;
438
439 return PL_DHASH_NEXT;
440}
441
442
443//
444// nsCategoryManager implementations
445//
446
447NS_IMPL_THREADSAFE_ISUPPORTS1(nsCategoryManager, nsICategoryManager)
448
449nsCategoryManager*
450nsCategoryManager::Create()
451{
452 nsCategoryManager* manager = new nsCategoryManager();
453
454 if (!manager)
455 return nsnull;
456
457 PL_INIT_ARENA_POOL(&(manager->mArena), "CategoryManagerArena",
458 NS_CATEGORYMANAGER_ARENA_SIZE); // this never fails
459
460 if (!manager->mTable.Init()) {
461 delete manager;
462 return nsnull;
463 }
464
465 manager->mLock = PR_NewLock();
466
467 if (!manager->mLock) {
468 delete manager;
469 return nsnull;
470 }
471
472 return manager;
473}
474
475nsCategoryManager::~nsCategoryManager()
476{
477 if (mLock)
478 PR_DestroyLock(mLock);
479
480 // the hashtable contains entries that must be deleted before the arena is
481 // destroyed, or else you will have PRLocks undestroyed and other Really
482 // Bad Stuff (TM)
483 mTable.Clear();
484
485 PL_FinishArenaPool(&mArena);
486}
487
488inline CategoryNode*
489nsCategoryManager::get_category(const char* aName) {
490 CategoryNode* node;
491 if (!mTable.Get(aName, &node)) {
492 return nsnull;
493 }
494 return node;
495}
496
497NS_IMETHODIMP
498nsCategoryManager::GetCategoryEntry( const char *aCategoryName,
499 const char *aEntryName,
500 char **_retval )
501{
502 NS_ENSURE_ARG_POINTER(aCategoryName);
503 NS_ENSURE_ARG_POINTER(aEntryName);
504 NS_ENSURE_ARG_POINTER(_retval);
505
506 nsresult status = NS_ERROR_NOT_AVAILABLE;
507
508 PR_Lock(mLock);
509 CategoryNode* category = get_category(aCategoryName);
510 PR_Unlock(mLock);
511
512 if (category) {
513 status = category->GetLeaf(aEntryName, _retval);
514 }
515
516 return status;
517}
518
519NS_IMETHODIMP
520nsCategoryManager::AddCategoryEntry( const char *aCategoryName,
521 const char *aEntryName,
522 const char *aValue,
523 PRBool aPersist,
524 PRBool aReplace,
525 char **_retval )
526{
527 NS_ENSURE_ARG_POINTER(aCategoryName);
528 NS_ENSURE_ARG_POINTER(aEntryName);
529 NS_ENSURE_ARG_POINTER(aValue);
530
531 // Before we can insert a new entry, we'll need to
532 // find the |CategoryNode| to put it in...
533 PR_Lock(mLock);
534 CategoryNode* category = get_category(aCategoryName);
535
536 if (!category) {
537 // That category doesn't exist yet; let's make it.
538 category = CategoryNode::Create(&mArena);
539
540 char* categoryName = ArenaStrdup(aCategoryName, &mArena);
541 mTable.Put(categoryName, category);
542 }
543 PR_Unlock(mLock);
544
545 if (!category)
546 return NS_ERROR_OUT_OF_MEMORY;
547
548 return category->AddLeaf(aEntryName,
549 aValue,
550 aPersist,
551 aReplace,
552 _retval,
553 &mArena);
554}
555
556NS_IMETHODIMP
557nsCategoryManager::DeleteCategoryEntry( const char *aCategoryName,
558 const char *aEntryName,
559 PRBool aDontPersist)
560{
561 NS_ENSURE_ARG_POINTER(aCategoryName);
562 NS_ENSURE_ARG_POINTER(aEntryName);
563
564 /*
565 Note: no errors are reported since failure to delete
566 probably won't hurt you, and returning errors seriously
567 inconveniences JS clients
568 */
569
570 PR_Lock(mLock);
571 CategoryNode* category = get_category(aCategoryName);
572 PR_Unlock(mLock);
573
574 if (!category)
575 return NS_OK;
576
577 return category->DeleteLeaf(aEntryName,
578 aDontPersist);
579}
580
581NS_IMETHODIMP
582nsCategoryManager::DeleteCategory( const char *aCategoryName )
583{
584 NS_ENSURE_ARG_POINTER(aCategoryName);
585
586 // the categories are arena-allocated, so we don't
587 // actually delete them. We just remove all of the
588 // leaf nodes.
589
590 PR_Lock(mLock);
591 CategoryNode* category = get_category(aCategoryName);
592 PR_Unlock(mLock);
593
594 if (category)
595 category->Clear();
596
597 return NS_OK;
598}
599
600NS_IMETHODIMP
601nsCategoryManager::EnumerateCategory( const char *aCategoryName,
602 nsISimpleEnumerator **_retval )
603{
604 NS_ENSURE_ARG_POINTER(aCategoryName);
605 NS_ENSURE_ARG_POINTER(_retval);
606
607 PR_Lock(mLock);
608 CategoryNode* category = get_category(aCategoryName);
609 PR_Unlock(mLock);
610
611 if (!category) {
612 return NS_NewEmptyEnumerator(_retval);
613 }
614
615 return category->Enumerate(_retval);
616}
617
618NS_IMETHODIMP
619nsCategoryManager::EnumerateCategories(nsISimpleEnumerator **_retval)
620{
621 NS_ENSURE_ARG_POINTER(_retval);
622
623 PR_Lock(mLock);
624 CategoryEnumerator* enumObj = CategoryEnumerator::Create(mTable);
625 PR_Unlock(mLock);
626
627 if (!enumObj)
628 return NS_ERROR_OUT_OF_MEMORY;
629
630 *_retval = enumObj;
631 NS_ADDREF(*_retval);
632 return NS_OK;
633}
634
635struct writecat_struct {
636 PRFileDesc* fd;
637 PRBool success;
638};
639
640PLDHashOperator PR_CALLBACK
641enumfunc_categories(const char* aKey, CategoryNode* aCategory, void* userArg)
642{
643 writecat_struct* args = NS_STATIC_CAST(writecat_struct*, userArg);
644
645 PLDHashOperator result = PL_DHASH_NEXT;
646
647 if (!aCategory->WritePersistentEntries(args->fd, aKey)) {
648 args->success = PR_FALSE;
649 result = PL_DHASH_STOP;
650 }
651
652 return result;
653}
654
655NS_METHOD
656nsCategoryManager::WriteCategoryManagerToRegistry(PRFileDesc* fd)
657{
658 writecat_struct args = {
659 fd,
660 PR_TRUE
661 };
662
663 PR_Lock(mLock);
664 mTable.EnumerateRead(enumfunc_categories, &args);
665 PR_Unlock(mLock);
666
667 if (!args.success) {
668 return NS_ERROR_UNEXPECTED;
669 }
670
671 return NS_OK;
672}
673
674class nsCategoryManagerFactory : public nsIFactory
675 {
676 public:
677 nsCategoryManagerFactory() { }
678
679 NS_DECL_ISUPPORTS
680 NS_DECL_NSIFACTORY
681 };
682
683NS_IMPL_ISUPPORTS1(nsCategoryManagerFactory, nsIFactory)
684
685NS_IMETHODIMP
686nsCategoryManagerFactory::CreateInstance( nsISupports* aOuter, const nsIID& aIID, void** aResult )
687 {
688 NS_ENSURE_ARG_POINTER(aResult);
689
690 *aResult = 0;
691
692 nsresult status = NS_OK;
693 if ( aOuter )
694 status = NS_ERROR_NO_AGGREGATION;
695 else
696 {
697 nsCategoryManager* raw_category_manager = nsCategoryManager::Create();
698 nsCOMPtr<nsICategoryManager> new_category_manager = raw_category_manager;
699 if ( new_category_manager )
700 status = new_category_manager->QueryInterface(aIID, aResult);
701 else
702 status = NS_ERROR_OUT_OF_MEMORY;
703 }
704
705 return status;
706 }
707
708NS_IMETHODIMP
709nsCategoryManagerFactory::LockFactory( PRBool )
710 {
711 // Not implemented...
712 return NS_OK;
713 }
714
715nsresult
716NS_CategoryManagerGetFactory( nsIFactory** aFactory )
717 {
718 // assert(aFactory);
719
720 nsresult status;
721
722 *aFactory = 0;
723 nsIFactory* new_factory = NS_STATIC_CAST(nsIFactory*, new nsCategoryManagerFactory);
724 if (new_factory)
725 {
726 *aFactory = new_factory;
727 NS_ADDREF(*aFactory);
728 status = NS_OK;
729 }
730 else
731 status = NS_ERROR_OUT_OF_MEMORY;
732
733 return status;
734 }
735
736
737
738/*
739 * CreateServicesFromCategory()
740 *
741 * Given a category, this convenience functions enumerates the category and
742 * creates a service of every CID or ContractID registered under the category.
743 * If observerTopic is non null and the service implements nsIObserver,
744 * this will attempt to notify the observer with the origin, observerTopic string
745 * as parameter.
746 */
747NS_COM nsresult
748NS_CreateServicesFromCategory(const char *category,
749 nsISupports *origin,
750 const char *observerTopic)
751{
752 nsresult rv = NS_OK;
753
754 int nFailed = 0;
755 nsCOMPtr<nsICategoryManager> categoryManager =
756 do_GetService("@mozilla.org/categorymanager;1", &rv);
757 if (!categoryManager) return rv;
758
759 nsCOMPtr<nsISimpleEnumerator> enumerator;
760 rv = categoryManager->EnumerateCategory(category,
761 getter_AddRefs(enumerator));
762 if (NS_FAILED(rv)) return rv;
763
764 nsCOMPtr<nsISupports> entry;
765 while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(entry)))) {
766 // From here on just skip any error we get.
767 nsCOMPtr<nsISupportsCString> catEntry = do_QueryInterface(entry, &rv);
768 if (NS_FAILED(rv)) {
769 nFailed++;
770 continue;
771 }
772 nsCAutoString entryString;
773 rv = catEntry->GetData(entryString);
774 if (NS_FAILED(rv)) {
775 nFailed++;
776 continue;
777 }
778 nsXPIDLCString contractID;
779 rv = categoryManager->GetCategoryEntry(category,entryString.get(), getter_Copies(contractID));
780 if (NS_FAILED(rv)) {
781 nFailed++;
782 continue;
783 }
784
785 nsCOMPtr<nsISupports> instance = do_GetService(contractID, &rv);
786 if (NS_FAILED(rv)) {
787 nFailed++;
788 continue;
789 }
790
791 if (observerTopic) {
792 // try an observer, if it implements it.
793 nsCOMPtr<nsIObserver> observer = do_QueryInterface(instance, &rv);
794 if (NS_SUCCEEDED(rv) && observer)
795 observer->Observe(origin, observerTopic, EmptyString().get());
796 }
797 }
798 return (nFailed ? NS_ERROR_FAILURE : NS_OK);
799}
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette