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 #include "nsStringBundle.h"
9 #include "nsIStringBundle.h"
10 #include "nsStringBundleService.h"
11 #include "nsStringBundleTextOverride.h"
12 #include "nsISupportsPrimitives.h"
13 #include "nsIMutableArray.h"
14 #include "nsArrayEnumerator.h"
17 #include "nsNetUtil.h"
18 #include "nsIObserverService.h"
19 #include "nsCOMArray.h"
20 #include "nsTextFormatter.h"
21 #include "nsIErrorService.h"
22 #include "nsICategoryManager.h"
23 #include "nsContentUtils.h"
27 #include "nsIBinaryInputStream.h"
28 #include "nsIStringStream.h"
31 using namespace mozilla
;
33 static NS_DEFINE_CID(kErrorServiceCID
, NS_ERRORSERVICE_CID
);
35 nsStringBundle::~nsStringBundle()
39 nsStringBundle::nsStringBundle(const char* aURLSpec
,
40 nsIStringBundleOverride
* aOverrideStrings
) :
41 mPropertiesURL(aURLSpec
),
42 mOverrideStrings(aOverrideStrings
),
43 mReentrantMonitor("nsStringBundle.mReentrantMonitor"),
44 mAttemptedLoad(false),
50 nsStringBundle::LoadProperties()
52 // this is different than mLoaded, because we only want to attempt
54 // we only want to load once, but if we've tried once and failed,
55 // continue to throw an error!
60 return NS_ERROR_UNEXPECTED
;
63 mAttemptedLoad
= true;
67 // do it synchronously
69 rv
= NS_NewURI(getter_AddRefs(uri
), mPropertiesURL
);
70 if (NS_FAILED(rv
)) return rv
;
72 nsCOMPtr
<nsIChannel
> channel
;
73 rv
= NS_NewChannel(getter_AddRefs(channel
),
75 nsContentUtils::GetSystemPrincipal(),
76 nsILoadInfo::SEC_NORMAL
,
77 nsIContentPolicy::TYPE_OTHER
);
79 if (NS_FAILED(rv
)) return rv
;
81 // It's a string bundle. We expect a text/plain type, so set that as hint
82 channel
->SetContentType(NS_LITERAL_CSTRING("text/plain"));
84 nsCOMPtr
<nsIInputStream
> in
;
85 rv
= channel
->Open(getter_AddRefs(in
));
86 if (NS_FAILED(rv
)) return rv
;
88 NS_ASSERTION(NS_SUCCEEDED(rv
) && in
, "Error in OpenBlockingStream");
89 NS_ENSURE_TRUE(NS_SUCCEEDED(rv
) && in
, NS_ERROR_FAILURE
);
91 static NS_DEFINE_CID(kPersistentPropertiesCID
, NS_IPERSISTENTPROPERTIES_CID
);
92 mProps
= do_CreateInstance(kPersistentPropertiesCID
, &rv
);
93 NS_ENSURE_SUCCESS(rv
, rv
);
95 mAttemptedLoad
= mLoaded
= true;
96 rv
= mProps
->Load(in
);
98 mLoaded
= NS_SUCCEEDED(rv
);
105 nsStringBundle::GetStringFromID(int32_t aID
, nsAString
& aResult
)
107 ReentrantMonitorAutoEnter
automon(mReentrantMonitor
);
109 name
.AppendInt(aID
, 10);
113 // try override first
114 if (mOverrideStrings
) {
115 rv
= mOverrideStrings
->GetStringFromName(mPropertiesURL
,
118 if (NS_SUCCEEDED(rv
)) return rv
;
121 rv
= mProps
->GetStringProperty(name
, aResult
);
127 nsStringBundle::GetStringFromName(const nsAString
& aName
,
132 // try override first
133 if (mOverrideStrings
) {
134 rv
= mOverrideStrings
->GetStringFromName(mPropertiesURL
,
135 NS_ConvertUTF16toUTF8(aName
),
137 if (NS_SUCCEEDED(rv
)) return rv
;
140 rv
= mProps
->GetStringProperty(NS_ConvertUTF16toUTF8(aName
), aResult
);
145 nsStringBundle::FormatStringFromID(int32_t aID
,
146 const char16_t
**aParams
,
151 idStr
.AppendInt(aID
, 10);
153 return FormatStringFromName(idStr
.get(), aParams
, aLength
, aResult
);
156 // this function supports at most 10 parameters.. see below for why
158 nsStringBundle::FormatStringFromName(const char16_t
*aName
,
159 const char16_t
**aParams
,
163 NS_ENSURE_ARG_POINTER(aName
);
164 NS_ASSERTION(aParams
&& aLength
, "FormatStringFromName() without format parameters: use GetStringFromName() instead");
165 NS_ENSURE_ARG_POINTER(aResult
);
168 rv
= LoadProperties();
169 if (NS_FAILED(rv
)) return rv
;
171 nsAutoString formatStr
;
172 rv
= GetStringFromName(nsDependentString(aName
), formatStr
);
173 if (NS_FAILED(rv
)) return rv
;
175 return FormatString(formatStr
.get(), aParams
, aLength
, aResult
);
179 NS_IMPL_ISUPPORTS(nsStringBundle
, nsIStringBundle
)
181 /* void GetStringFromID (in long aID, out wstring aResult); */
183 nsStringBundle::GetStringFromID(int32_t aID
, char16_t
**aResult
)
186 rv
= LoadProperties();
187 if (NS_FAILED(rv
)) return rv
;
192 rv
= GetStringFromID(aID
, tmpstr
);
193 NS_ENSURE_SUCCESS(rv
, rv
);
195 *aResult
= ToNewUnicode(tmpstr
);
196 NS_ENSURE_TRUE(*aResult
, NS_ERROR_OUT_OF_MEMORY
);
201 /* void GetStringFromName (in wstring aName, out wstring aResult); */
203 nsStringBundle::GetStringFromName(const char16_t
*aName
, char16_t
**aResult
)
205 NS_ENSURE_ARG_POINTER(aName
);
206 NS_ENSURE_ARG_POINTER(aResult
);
209 rv
= LoadProperties();
210 if (NS_FAILED(rv
)) return rv
;
212 ReentrantMonitorAutoEnter
automon(mReentrantMonitor
);
215 rv
= GetStringFromName(nsDependentString(aName
), tmpstr
);
219 // it is not uncommon for apps to request a string name which may not exist
220 // so be quiet about it.
221 NS_WARNING("String missing from string bundle");
222 printf(" '%s' missing from bundle %s\n", NS_ConvertUTF16toUTF8(aName
).get(), mPropertiesURL
.get());
227 *aResult
= ToNewUnicode(tmpstr
);
228 NS_ENSURE_TRUE(*aResult
, NS_ERROR_OUT_OF_MEMORY
);
234 nsStringBundle::GetCombinedEnumeration(nsIStringBundleOverride
* aOverrideStrings
,
235 nsISimpleEnumerator
** aResult
)
237 nsCOMPtr
<nsISupports
> supports
;
238 nsCOMPtr
<nsIPropertyElement
> propElement
;
242 nsCOMPtr
<nsIMutableArray
> resultArray
=
243 do_CreateInstance(NS_ARRAY_CONTRACTID
, &rv
);
244 NS_ENSURE_SUCCESS(rv
, rv
);
246 // first, append the override elements
247 nsCOMPtr
<nsISimpleEnumerator
> overrideEnumerator
;
248 rv
= aOverrideStrings
->EnumerateKeysInBundle(mPropertiesURL
,
249 getter_AddRefs(overrideEnumerator
));
252 rv
= overrideEnumerator
->HasMoreElements(&hasMore
);
253 NS_ENSURE_SUCCESS(rv
, rv
);
256 rv
= overrideEnumerator
->GetNext(getter_AddRefs(supports
));
257 if (NS_SUCCEEDED(rv
))
258 resultArray
->AppendElement(supports
, false);
260 rv
= overrideEnumerator
->HasMoreElements(&hasMore
);
261 NS_ENSURE_SUCCESS(rv
, rv
);
264 // ok, now we have the override elements in resultArray
265 nsCOMPtr
<nsISimpleEnumerator
> propEnumerator
;
266 rv
= mProps
->Enumerate(getter_AddRefs(propEnumerator
));
268 // no elements in mProps anyway, just return what we have
269 return NS_NewArrayEnumerator(aResult
, resultArray
);
272 // second, append all the elements that are in mProps
274 rv
= propEnumerator
->GetNext(getter_AddRefs(supports
));
275 if (NS_SUCCEEDED(rv
) &&
276 (propElement
= do_QueryInterface(supports
, &rv
))) {
278 // now check if its in the override bundle
280 propElement
->GetKey(key
);
283 rv
= aOverrideStrings
->GetStringFromName(mPropertiesURL
, key
, value
);
285 // if it isn't there, then it is safe to append
287 resultArray
->AppendElement(propElement
, false);
290 rv
= propEnumerator
->HasMoreElements(&hasMore
);
291 NS_ENSURE_SUCCESS(rv
, rv
);
294 return resultArray
->Enumerate(aResult
);
299 nsStringBundle::GetSimpleEnumeration(nsISimpleEnumerator
** elements
)
302 return NS_ERROR_INVALID_POINTER
;
305 rv
= LoadProperties();
306 if (NS_FAILED(rv
)) return rv
;
308 if (mOverrideStrings
)
309 return GetCombinedEnumeration(mOverrideStrings
, elements
);
311 return mProps
->Enumerate(elements
);
315 nsStringBundle::FormatString(const char16_t
*aFormatStr
,
316 const char16_t
**aParams
, uint32_t aLength
,
319 NS_ENSURE_ARG_POINTER(aResult
);
320 NS_ENSURE_ARG(aLength
<= 10); // enforce 10-parameter limit
322 // implementation note: you would think you could use vsmprintf
323 // to build up an arbitrary length array.. except that there
324 // is no way to build up a va_list at runtime!
325 // Don't believe me? See:
326 // http://www.eskimo.com/~scs/C-faq/q15.13.html
329 nsTextFormatter::smprintf(aFormatStr
,
330 aLength
>= 1 ? aParams
[0] : nullptr,
331 aLength
>= 2 ? aParams
[1] : nullptr,
332 aLength
>= 3 ? aParams
[2] : nullptr,
333 aLength
>= 4 ? aParams
[3] : nullptr,
334 aLength
>= 5 ? aParams
[4] : nullptr,
335 aLength
>= 6 ? aParams
[5] : nullptr,
336 aLength
>= 7 ? aParams
[6] : nullptr,
337 aLength
>= 8 ? aParams
[7] : nullptr,
338 aLength
>= 9 ? aParams
[8] : nullptr,
339 aLength
>= 10 ? aParams
[9] : nullptr);
343 return NS_ERROR_OUT_OF_MEMORY
;
346 // nsTextFormatter does not use the shared nsMemory allocator.
347 // Instead it is required to free the memory it allocates using
348 // nsTextFormatter::smprintf_free. Let's instead use nsMemory based
349 // allocation for the result that we give out and free the string
350 // returned by smprintf ourselves!
351 *aResult
= NS_strdup(text
);
352 nsTextFormatter::smprintf_free(text
);
354 return *aResult
? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
357 NS_IMPL_ISUPPORTS(nsExtensibleStringBundle
, nsIStringBundle
)
359 nsExtensibleStringBundle::nsExtensibleStringBundle()
365 nsExtensibleStringBundle::Init(const char * aCategory
,
366 nsIStringBundleService
* aBundleService
)
370 nsCOMPtr
<nsICategoryManager
> catman
=
371 do_GetService(NS_CATEGORYMANAGER_CONTRACTID
, &rv
);
372 if (NS_FAILED(rv
)) return rv
;
374 nsCOMPtr
<nsISimpleEnumerator
> enumerator
;
375 rv
= catman
->EnumerateCategory(aCategory
, getter_AddRefs(enumerator
));
376 if (NS_FAILED(rv
)) return rv
;
379 while (NS_SUCCEEDED(enumerator
->HasMoreElements(&hasMore
)) && hasMore
) {
380 nsCOMPtr
<nsISupports
> supports
;
381 rv
= enumerator
->GetNext(getter_AddRefs(supports
));
385 nsCOMPtr
<nsISupportsCString
> supStr
= do_QueryInterface(supports
, &rv
);
390 rv
= supStr
->GetData(name
);
394 nsCOMPtr
<nsIStringBundle
> bundle
;
395 rv
= aBundleService
->CreateBundle(name
.get(), getter_AddRefs(bundle
));
399 mBundles
.AppendObject(bundle
);
405 nsExtensibleStringBundle::~nsExtensibleStringBundle()
409 nsresult
nsExtensibleStringBundle::GetStringFromID(int32_t aID
, char16_t
** aResult
)
412 const uint32_t size
= mBundles
.Count();
413 for (uint32_t i
= 0; i
< size
; ++i
) {
414 nsIStringBundle
*bundle
= mBundles
[i
];
416 rv
= bundle
->GetStringFromID(aID
, aResult
);
417 if (NS_SUCCEEDED(rv
))
422 return NS_ERROR_FAILURE
;
425 nsresult
nsExtensibleStringBundle::GetStringFromName(const char16_t
*aName
,
429 const uint32_t size
= mBundles
.Count();
430 for (uint32_t i
= 0; i
< size
; ++i
) {
431 nsIStringBundle
* bundle
= mBundles
[i
];
433 rv
= bundle
->GetStringFromName(aName
, aResult
);
434 if (NS_SUCCEEDED(rv
))
439 return NS_ERROR_FAILURE
;
443 nsExtensibleStringBundle::FormatStringFromID(int32_t aID
,
444 const char16_t
** aParams
,
449 idStr
.AppendInt(aID
, 10);
450 return FormatStringFromName(idStr
.get(), aParams
, aLength
, aResult
);
454 nsExtensibleStringBundle::FormatStringFromName(const char16_t
*aName
,
455 const char16_t
** aParams
,
459 nsXPIDLString formatStr
;
461 rv
= GetStringFromName(aName
, getter_Copies(formatStr
));
465 return nsStringBundle::FormatString(formatStr
, aParams
, aLength
, aResult
);
468 nsresult
nsExtensibleStringBundle::GetSimpleEnumeration(nsISimpleEnumerator
** aResult
)
472 return NS_ERROR_NOT_IMPLEMENTED
;
475 /////////////////////////////////////////////////////////////////////////////////////////
477 #define MAX_CACHED_BUNDLES 16
479 struct bundleCacheEntry_t MOZ_FINAL
: public LinkedListElement
<bundleCacheEntry_t
> {
481 nsCOMPtr
<nsIStringBundle
> mBundle
;
485 MOZ_COUNT_CTOR(bundleCacheEntry_t
);
488 ~bundleCacheEntry_t()
490 MOZ_COUNT_DTOR(bundleCacheEntry_t
);
495 nsStringBundleService::nsStringBundleService() :
496 mBundleMap(MAX_CACHED_BUNDLES
)
498 mErrorService
= do_GetService(kErrorServiceCID
);
499 NS_ASSERTION(mErrorService
, "Couldn't get error service");
502 NS_IMPL_ISUPPORTS(nsStringBundleService
,
503 nsIStringBundleService
,
505 nsISupportsWeakReference
)
507 nsStringBundleService::~nsStringBundleService()
513 nsStringBundleService::Init()
515 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
517 os
->AddObserver(this, "memory-pressure", true);
518 os
->AddObserver(this, "profile-do-change", true);
519 os
->AddObserver(this, "chrome-flush-caches", true);
520 os
->AddObserver(this, "xpcom-category-entry-added", true);
523 // instantiate the override service, if there is any.
524 // at some point we probably want to make this a category, and
525 // support multiple overrides
526 mOverrideStrings
= do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID
);
532 nsStringBundleService::Observe(nsISupports
* aSubject
,
534 const char16_t
* aSomeData
)
536 if (strcmp("memory-pressure", aTopic
) == 0 ||
537 strcmp("profile-do-change", aTopic
) == 0 ||
538 strcmp("chrome-flush-caches", aTopic
) == 0)
542 else if (strcmp("xpcom-category-entry-added", aTopic
) == 0 &&
543 NS_LITERAL_STRING("xpcom-autoregistration").Equals(aSomeData
))
545 mOverrideStrings
= do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID
);
552 nsStringBundleService::flushBundleCache()
554 // release all bundles in the cache
557 while (!mBundleCache
.isEmpty()) {
558 delete mBundleCache
.popFirst();
563 nsStringBundleService::FlushBundles()
570 nsStringBundleService::getStringBundle(const char *aURLSpec
,
571 nsIStringBundle
**aResult
)
573 nsDependentCString
key(aURLSpec
);
574 bundleCacheEntry_t
* cacheEntry
= mBundleMap
.Get(key
);
578 // remove it from the list, it will later be reinserted
579 // at the head of the list
580 cacheEntry
->remove();
584 // hasn't been cached, so insert it into the hash table
585 nsRefPtr
<nsStringBundle
> bundle
= new nsStringBundle(aURLSpec
, mOverrideStrings
);
586 cacheEntry
= insertIntoCache(bundle
.forget(), key
);
589 // at this point the cacheEntry should exist in the hashtable,
590 // but is not in the LRU cache.
591 // put the cache entry at the front of the list
592 mBundleCache
.insertFront(cacheEntry
);
594 // finally, return the value
595 *aResult
= cacheEntry
->mBundle
;
602 nsStringBundleService::insertIntoCache(already_AddRefed
<nsIStringBundle
> aBundle
,
605 bundleCacheEntry_t
*cacheEntry
;
607 if (mBundleMap
.Count() < MAX_CACHED_BUNDLES
) {
608 // cache not full - create a new entry
609 cacheEntry
= new bundleCacheEntry_t();
612 // take the last entry in the list, and recycle it.
613 cacheEntry
= mBundleCache
.getLast();
615 // remove it from the hash table and linked list
616 NS_ASSERTION(mBundleMap
.Contains(cacheEntry
->mHashKey
),
617 "Element will not be removed!");
618 mBundleMap
.Remove(cacheEntry
->mHashKey
);
619 cacheEntry
->remove();
622 // at this point we have a new cacheEntry that doesn't exist
623 // in the hashtable, so set up the cacheEntry
624 cacheEntry
->mHashKey
= aHashKey
;
625 cacheEntry
->mBundle
= aBundle
;
627 // insert the entry into the cache and map, make it the MRU
628 mBundleMap
.Put(cacheEntry
->mHashKey
, cacheEntry
);
634 nsStringBundleService::CreateBundle(const char* aURLSpec
,
635 nsIStringBundle
** aResult
)
637 return getStringBundle(aURLSpec
,aResult
);
641 nsStringBundleService::CreateExtensibleBundle(const char* aCategory
,
642 nsIStringBundle
** aResult
)
644 NS_ENSURE_ARG_POINTER(aResult
);
647 nsRefPtr
<nsExtensibleStringBundle
> bundle
= new nsExtensibleStringBundle();
649 nsresult res
= bundle
->Init(aCategory
, this);
650 if (NS_FAILED(res
)) {
654 res
= bundle
->QueryInterface(NS_GET_IID(nsIStringBundle
), (void**) aResult
);
659 #define GLOBAL_PROPERTIES "chrome://global/locale/global-strres.properties"
662 nsStringBundleService::FormatWithBundle(nsIStringBundle
* bundle
, nsresult aStatus
,
663 uint32_t argCount
, char16_t
** argArray
,
669 // try looking up the error message with the int key:
670 uint16_t code
= NS_ERROR_GET_CODE(aStatus
);
671 rv
= bundle
->FormatStringFromID(code
, (const char16_t
**)argArray
, argCount
, result
);
673 // If the int key fails, try looking up the default error message. E.g. print:
674 // An unknown error has occurred (0x804B0003).
676 nsAutoString statusStr
;
677 statusStr
.AppendInt(static_cast<uint32_t>(aStatus
), 16);
678 const char16_t
* otherArgArray
[1];
679 otherArgArray
[0] = statusStr
.get();
680 uint16_t code
= NS_ERROR_GET_CODE(NS_ERROR_FAILURE
);
681 rv
= bundle
->FormatStringFromID(code
, otherArgArray
, 1, result
);
688 nsStringBundleService::FormatStatusMessage(nsresult aStatus
,
689 const char16_t
* aStatusArg
,
693 uint32_t i
, argCount
= 0;
694 nsCOMPtr
<nsIStringBundle
> bundle
;
695 nsXPIDLCString stringBundleURL
;
697 // XXX hack for mailnews who has already formatted their messages:
698 if (aStatus
== NS_OK
&& aStatusArg
) {
699 *result
= NS_strdup(aStatusArg
);
700 NS_ENSURE_TRUE(*result
, NS_ERROR_OUT_OF_MEMORY
);
704 if (aStatus
== NS_OK
) {
705 return NS_ERROR_FAILURE
; // no message to format
708 // format the arguments:
709 const nsDependentString
args(aStatusArg
);
710 argCount
= args
.CountChar(char16_t('\n')) + 1;
711 NS_ENSURE_ARG(argCount
<= 10); // enforce 10-parameter limit
712 char16_t
* argArray
[10];
714 // convert the aStatusArg into a char16_t array
716 // avoid construction for the simple case:
717 argArray
[0] = (char16_t
*)aStatusArg
;
719 else if (argCount
> 1) {
721 for (i
= 0; i
< argCount
; i
++) {
722 int32_t pos
= args
.FindChar('\n', offset
);
725 argArray
[i
] = ToNewUnicode(Substring(args
, offset
, pos
- offset
));
726 if (argArray
[i
] == nullptr) {
727 rv
= NS_ERROR_OUT_OF_MEMORY
;
728 argCount
= i
- 1; // don't try to free uninitialized memory
735 // find the string bundle for the error's module:
736 rv
= mErrorService
->GetErrorStringBundle(NS_ERROR_GET_MODULE(aStatus
),
737 getter_Copies(stringBundleURL
));
738 if (NS_SUCCEEDED(rv
)) {
739 rv
= getStringBundle(stringBundleURL
, getter_AddRefs(bundle
));
740 if (NS_SUCCEEDED(rv
)) {
741 rv
= FormatWithBundle(bundle
, aStatus
, argCount
, argArray
, result
);
745 rv
= getStringBundle(GLOBAL_PROPERTIES
, getter_AddRefs(bundle
));
746 if (NS_SUCCEEDED(rv
)) {
747 rv
= FormatWithBundle(bundle
, aStatus
, argCount
, argArray
, result
);
753 for (i
= 0; i
< argCount
; i
++) {
755 nsMemory::Free(argArray
[i
]);