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/. */
5
6 #include "nsStringBundle.h"
7 #include "nsID.h"
8 #include "nsString.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"
15 #include "nscore.h"
16 #include "nsMemory.h"
17 #include "nsNetUtil.h"
18 #include "nsComponentManagerUtils.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsIInputStream.h"
21 #include "nsIURI.h"
22 #include "nsIObserverService.h"
23 #include "nsCOMArray.h"
24 #include "nsTextFormatter.h"
25 #include "nsIErrorService.h"
26 #include "nsICategoryManager.h"
27 #include "nsContentUtils.h"
28 #include "nsStringStream.h"
29 #include "mozilla/ResultExtensions.h"
30 #include "mozilla/URLPreloader.h"
31
32 // for async loading
33 #ifdef ASYNC_LOADING
34 #include "nsIBinaryInputStream.h"
35 #include "nsIStringStream.h"
36 #endif
37
38 using namespace mozilla;
39
40 static NS_DEFINE_CID(kErrorServiceCID, NS_ERRORSERVICE_CID);
41
NS_IMPL_ISUPPORTS(nsStringBundle,nsIStringBundle)42 NS_IMPL_ISUPPORTS(nsStringBundle, nsIStringBundle)
43
44 nsStringBundle::nsStringBundle(const char* aURLSpec,
45 nsIStringBundleOverride* aOverrideStrings)
46 : mPropertiesURL(aURLSpec),
47 mOverrideStrings(aOverrideStrings),
48 mReentrantMonitor("nsStringBundle.mReentrantMonitor"),
49 mAttemptedLoad(false),
50 mLoaded(false) {}
51
~nsStringBundle()52 nsStringBundle::~nsStringBundle() {}
53
54 NS_IMETHODIMP
AsyncPreload()55 nsStringBundle::AsyncPreload() {
56 return NS_IdleDispatchToCurrentThread(NewIdleRunnableMethod(
57 "nsStringBundle::LoadProperties", this, &nsStringBundle::LoadProperties));
58 }
59
LoadProperties()60 nsresult nsStringBundle::LoadProperties() {
61 // this is different than mLoaded, because we only want to attempt
62 // to load once
63 // we only want to load once, but if we've tried once and failed,
64 // continue to throw an error!
65 if (mAttemptedLoad) {
66 if (mLoaded) return NS_OK;
67
68 return NS_ERROR_UNEXPECTED;
69 }
70
71 mAttemptedLoad = true;
72
73 nsresult rv;
74
75 // do it synchronously
76 nsCOMPtr<nsIURI> uri;
77 rv = NS_NewURI(getter_AddRefs(uri), mPropertiesURL);
78 if (NS_FAILED(rv)) return rv;
79
80 // whitelist check for local schemes
81 nsCString scheme;
82 uri->GetScheme(scheme);
83 if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("jar") &&
84 !scheme.EqualsLiteral("resource") && !scheme.EqualsLiteral("file") &&
85 !scheme.EqualsLiteral("data")) {
86 return NS_ERROR_ABORT;
87 }
88
89 nsCOMPtr<nsIInputStream> in;
90
91 auto result = URLPreloader::ReadURI(uri);
92 if (result.isOk()) {
93 MOZ_TRY(NS_NewCStringInputStream(getter_AddRefs(in), result.unwrap()));
94 } else {
95 nsCOMPtr<nsIChannel> channel;
96 rv = NS_NewChannel(getter_AddRefs(channel), uri,
97 nsContentUtils::GetSystemPrincipal(),
98 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
99 nsIContentPolicy::TYPE_OTHER);
100
101 if (NS_FAILED(rv)) return rv;
102
103 // It's a string bundle. We expect a text/plain type, so set that as hint
104 channel->SetContentType(NS_LITERAL_CSTRING("text/plain"));
105
106 rv = channel->Open2(getter_AddRefs(in));
107 if (NS_FAILED(rv)) return rv;
108 }
109
110 NS_ASSERTION(NS_SUCCEEDED(rv) && in, "Error in OpenBlockingStream");
111 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && in, NS_ERROR_FAILURE);
112
113 static NS_DEFINE_CID(kPersistentPropertiesCID, NS_IPERSISTENTPROPERTIES_CID);
114 mProps = do_CreateInstance(kPersistentPropertiesCID, &rv);
115 NS_ENSURE_SUCCESS(rv, rv);
116
117 mAttemptedLoad = mLoaded = true;
118 rv = mProps->Load(in);
119
120 mLoaded = NS_SUCCEEDED(rv);
121
122 return rv;
123 }
124
125 NS_IMETHODIMP
GetStringFromID(int32_t aID,nsAString & aResult)126 nsStringBundle::GetStringFromID(int32_t aID, nsAString& aResult) {
127 nsAutoCString idStr;
128 idStr.AppendInt(aID, 10);
129 return GetStringFromName(idStr.get(), aResult);
130 }
131
132 NS_IMETHODIMP
GetStringFromAUTF8Name(const nsACString & aName,nsAString & aResult)133 nsStringBundle::GetStringFromAUTF8Name(const nsACString& aName,
134 nsAString& aResult) {
135 return GetStringFromName(PromiseFlatCString(aName).get(), aResult);
136 }
137
138 NS_IMETHODIMP
GetStringFromName(const char * aName,nsAString & aResult)139 nsStringBundle::GetStringFromName(const char* aName, nsAString& aResult) {
140 NS_ENSURE_ARG_POINTER(aName);
141
142 nsresult rv = LoadProperties();
143 if (NS_FAILED(rv)) return rv;
144
145 ReentrantMonitorAutoEnter automon(mReentrantMonitor);
146
147 // try override first
148 if (mOverrideStrings) {
149 rv = mOverrideStrings->GetStringFromName(
150 mPropertiesURL, nsDependentCString(aName), aResult);
151 if (NS_SUCCEEDED(rv)) return rv;
152 }
153
154 return mProps->GetStringProperty(nsDependentCString(aName), aResult);
155 }
156
157 NS_IMETHODIMP
FormatStringFromID(int32_t aID,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)158 nsStringBundle::FormatStringFromID(int32_t aID, const char16_t** aParams,
159 uint32_t aLength, nsAString& aResult) {
160 nsAutoCString idStr;
161 idStr.AppendInt(aID, 10);
162 return FormatStringFromName(idStr.get(), aParams, aLength, aResult);
163 }
164
165 // this function supports at most 10 parameters.. see below for why
166 NS_IMETHODIMP
FormatStringFromAUTF8Name(const nsACString & aName,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)167 nsStringBundle::FormatStringFromAUTF8Name(const nsACString& aName,
168 const char16_t** aParams,
169 uint32_t aLength,
170 nsAString& aResult) {
171 return FormatStringFromName(PromiseFlatCString(aName).get(), aParams, aLength,
172 aResult);
173 }
174
175 // this function supports at most 10 parameters.. see below for why
176 NS_IMETHODIMP
FormatStringFromName(const char * aName,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)177 nsStringBundle::FormatStringFromName(const char* aName,
178 const char16_t** aParams, uint32_t aLength,
179 nsAString& aResult) {
180 NS_ASSERTION(aParams && aLength,
181 "FormatStringFromName() without format parameters: use "
182 "GetStringFromName() instead");
183
184 nsAutoString formatStr;
185 nsresult rv = GetStringFromName(aName, formatStr);
186 if (NS_FAILED(rv)) return rv;
187
188 return FormatString(formatStr.get(), aParams, aLength, aResult);
189 }
190
GetCombinedEnumeration(nsIStringBundleOverride * aOverrideStrings,nsISimpleEnumerator ** aResult)191 nsresult nsStringBundle::GetCombinedEnumeration(
192 nsIStringBundleOverride* aOverrideStrings, nsISimpleEnumerator** aResult) {
193 nsCOMPtr<nsISupports> supports;
194 nsCOMPtr<nsIPropertyElement> propElement;
195
196 nsresult rv;
197
198 nsCOMPtr<nsIMutableArray> resultArray =
199 do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
200 NS_ENSURE_SUCCESS(rv, rv);
201
202 // first, append the override elements
203 nsCOMPtr<nsISimpleEnumerator> overrideEnumerator;
204 rv = aOverrideStrings->EnumerateKeysInBundle(
205 mPropertiesURL, getter_AddRefs(overrideEnumerator));
206
207 bool hasMore;
208 rv = overrideEnumerator->HasMoreElements(&hasMore);
209 NS_ENSURE_SUCCESS(rv, rv);
210 while (hasMore) {
211 rv = overrideEnumerator->GetNext(getter_AddRefs(supports));
212 if (NS_SUCCEEDED(rv)) resultArray->AppendElement(supports);
213
214 rv = overrideEnumerator->HasMoreElements(&hasMore);
215 NS_ENSURE_SUCCESS(rv, rv);
216 }
217
218 // ok, now we have the override elements in resultArray
219 nsCOMPtr<nsISimpleEnumerator> propEnumerator;
220 rv = mProps->Enumerate(getter_AddRefs(propEnumerator));
221 if (NS_FAILED(rv)) {
222 // no elements in mProps anyway, just return what we have
223 return NS_NewArrayEnumerator(aResult, resultArray);
224 }
225
226 // second, append all the elements that are in mProps
227 do {
228 rv = propEnumerator->GetNext(getter_AddRefs(supports));
229 if (NS_SUCCEEDED(rv) && (propElement = do_QueryInterface(supports, &rv))) {
230 // now check if its in the override bundle
231 nsAutoCString key;
232 propElement->GetKey(key);
233
234 nsAutoString value;
235 rv = aOverrideStrings->GetStringFromName(mPropertiesURL, key, value);
236
237 // if it isn't there, then it is safe to append
238 if (NS_FAILED(rv)) resultArray->AppendElement(propElement);
239 }
240
241 rv = propEnumerator->HasMoreElements(&hasMore);
242 NS_ENSURE_SUCCESS(rv, rv);
243 } while (hasMore);
244
245 return resultArray->Enumerate(aResult);
246 }
247
248 NS_IMETHODIMP
GetSimpleEnumeration(nsISimpleEnumerator ** elements)249 nsStringBundle::GetSimpleEnumeration(nsISimpleEnumerator** elements) {
250 if (!elements) return NS_ERROR_INVALID_POINTER;
251
252 nsresult rv;
253 rv = LoadProperties();
254 if (NS_FAILED(rv)) return rv;
255
256 if (mOverrideStrings)
257 return GetCombinedEnumeration(mOverrideStrings, elements);
258
259 return mProps->Enumerate(elements);
260 }
261
FormatString(const char16_t * aFormatStr,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)262 nsresult nsStringBundle::FormatString(const char16_t* aFormatStr,
263 const char16_t** aParams,
264 uint32_t aLength, nsAString& aResult) {
265 NS_ENSURE_ARG(aLength <= 10); // enforce 10-parameter limit
266
267 // implementation note: you would think you could use vsmprintf
268 // to build up an arbitrary length array.. except that there
269 // is no way to build up a va_list at runtime!
270 // Don't believe me? See:
271 // http://www.eskimo.com/~scs/C-faq/q15.13.html
272 // -alecf
273 nsTextFormatter::ssprintf(
274 aResult, aFormatStr, aLength >= 1 ? aParams[0] : nullptr,
275 aLength >= 2 ? aParams[1] : nullptr, aLength >= 3 ? aParams[2] : nullptr,
276 aLength >= 4 ? aParams[3] : nullptr, aLength >= 5 ? aParams[4] : nullptr,
277 aLength >= 6 ? aParams[5] : nullptr, aLength >= 7 ? aParams[6] : nullptr,
278 aLength >= 8 ? aParams[7] : nullptr, aLength >= 9 ? aParams[8] : nullptr,
279 aLength >= 10 ? aParams[9] : nullptr);
280
281 return NS_OK;
282 }
283
NS_IMPL_ISUPPORTS(nsExtensibleStringBundle,nsIStringBundle)284 NS_IMPL_ISUPPORTS(nsExtensibleStringBundle, nsIStringBundle)
285
286 nsExtensibleStringBundle::nsExtensibleStringBundle() { mLoaded = false; }
287
Init(const char * aCategory,nsIStringBundleService * aBundleService)288 nsresult nsExtensibleStringBundle::Init(
289 const char* aCategory, nsIStringBundleService* aBundleService) {
290 nsresult rv;
291 nsCOMPtr<nsICategoryManager> catman =
292 do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
293 if (NS_FAILED(rv)) return rv;
294
295 nsCOMPtr<nsISimpleEnumerator> enumerator;
296 rv = catman->EnumerateCategory(aCategory, getter_AddRefs(enumerator));
297 if (NS_FAILED(rv)) return rv;
298
299 bool hasMore;
300 while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
301 nsCOMPtr<nsISupports> supports;
302 rv = enumerator->GetNext(getter_AddRefs(supports));
303 if (NS_FAILED(rv)) continue;
304
305 nsCOMPtr<nsISupportsCString> supStr = do_QueryInterface(supports, &rv);
306 if (NS_FAILED(rv)) continue;
307
308 nsAutoCString name;
309 rv = supStr->GetData(name);
310 if (NS_FAILED(rv)) continue;
311
312 nsCOMPtr<nsIStringBundle> bundle;
313 rv = aBundleService->CreateBundle(name.get(), getter_AddRefs(bundle));
314 if (NS_FAILED(rv)) continue;
315
316 mBundles.AppendObject(bundle);
317 }
318
319 return rv;
320 }
321
322 NS_IMETHODIMP
AsyncPreload()323 nsExtensibleStringBundle::AsyncPreload() {
324 nsresult rv = NS_OK;
325 const uint32_t size = mBundles.Count();
326 for (uint32_t i = 0; i < size; ++i) {
327 nsIStringBundle* bundle = mBundles[i];
328 if (bundle) {
329 nsresult rv2 = bundle->AsyncPreload();
330 rv = NS_FAILED(rv) ? rv : rv2;
331 }
332 }
333 return rv;
334 }
335
~nsExtensibleStringBundle()336 nsExtensibleStringBundle::~nsExtensibleStringBundle() {}
337
GetStringFromID(int32_t aID,nsAString & aResult)338 nsresult nsExtensibleStringBundle::GetStringFromID(int32_t aID,
339 nsAString& aResult) {
340 nsAutoCString idStr;
341 idStr.AppendInt(aID, 10);
342 return GetStringFromName(idStr.get(), aResult);
343 }
344
GetStringFromAUTF8Name(const nsACString & aName,nsAString & aResult)345 nsresult nsExtensibleStringBundle::GetStringFromAUTF8Name(
346 const nsACString& aName, nsAString& aResult) {
347 return GetStringFromName(PromiseFlatCString(aName).get(), aResult);
348 }
349
GetStringFromName(const char * aName,nsAString & aResult)350 nsresult nsExtensibleStringBundle::GetStringFromName(const char* aName,
351 nsAString& aResult) {
352 nsresult rv;
353 const uint32_t size = mBundles.Count();
354 for (uint32_t i = 0; i < size; ++i) {
355 nsIStringBundle* bundle = mBundles[i];
356 if (bundle) {
357 rv = bundle->GetStringFromName(aName, aResult);
358 if (NS_SUCCEEDED(rv)) return NS_OK;
359 }
360 }
361
362 return NS_ERROR_FAILURE;
363 }
364
365 NS_IMETHODIMP
FormatStringFromID(int32_t aID,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)366 nsExtensibleStringBundle::FormatStringFromID(int32_t aID,
367 const char16_t** aParams,
368 uint32_t aLength,
369 nsAString& aResult) {
370 nsAutoCString idStr;
371 idStr.AppendInt(aID, 10);
372 return FormatStringFromName(idStr.get(), aParams, aLength, aResult);
373 }
374
375 NS_IMETHODIMP
FormatStringFromAUTF8Name(const nsACString & aName,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)376 nsExtensibleStringBundle::FormatStringFromAUTF8Name(const nsACString& aName,
377 const char16_t** aParams,
378 uint32_t aLength,
379 nsAString& aResult) {
380 return FormatStringFromName(PromiseFlatCString(aName).get(), aParams, aLength,
381 aResult);
382 }
383
384 NS_IMETHODIMP
FormatStringFromName(const char * aName,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)385 nsExtensibleStringBundle::FormatStringFromName(const char* aName,
386 const char16_t** aParams,
387 uint32_t aLength,
388 nsAString& aResult) {
389 nsAutoString formatStr;
390 nsresult rv;
391 rv = GetStringFromName(aName, formatStr);
392 if (NS_FAILED(rv)) return rv;
393
394 return nsStringBundle::FormatString(formatStr.get(), aParams, aLength,
395 aResult);
396 }
397
GetSimpleEnumeration(nsISimpleEnumerator ** aResult)398 nsresult nsExtensibleStringBundle::GetSimpleEnumeration(
399 nsISimpleEnumerator** aResult) {
400 // XXX write me
401 *aResult = nullptr;
402 return NS_ERROR_NOT_IMPLEMENTED;
403 }
404
405 /////////////////////////////////////////////////////////////////////////////////////////
406
407 #define MAX_CACHED_BUNDLES 16
408
409 struct bundleCacheEntry_t final : public LinkedListElement<bundleCacheEntry_t> {
410 nsCString mHashKey;
411 nsCOMPtr<nsIStringBundle> mBundle;
412
bundleCacheEntry_tbundleCacheEntry_t413 bundleCacheEntry_t() { MOZ_COUNT_CTOR(bundleCacheEntry_t); }
414
~bundleCacheEntry_tbundleCacheEntry_t415 ~bundleCacheEntry_t() { MOZ_COUNT_DTOR(bundleCacheEntry_t); }
416 };
417
nsStringBundleService()418 nsStringBundleService::nsStringBundleService()
419 : mBundleMap(MAX_CACHED_BUNDLES) {
420 mErrorService = do_GetService(kErrorServiceCID);
421 NS_ASSERTION(mErrorService, "Couldn't get error service");
422 }
423
NS_IMPL_ISUPPORTS(nsStringBundleService,nsIStringBundleService,nsIObserver,nsISupportsWeakReference)424 NS_IMPL_ISUPPORTS(nsStringBundleService, nsIStringBundleService, nsIObserver,
425 nsISupportsWeakReference)
426
427 nsStringBundleService::~nsStringBundleService() { flushBundleCache(); }
428
Init()429 nsresult nsStringBundleService::Init() {
430 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
431 if (os) {
432 os->AddObserver(this, "memory-pressure", true);
433 os->AddObserver(this, "profile-do-change", true);
434 os->AddObserver(this, "chrome-flush-caches", true);
435 os->AddObserver(this, "xpcom-category-entry-added", true);
436 os->AddObserver(this, "intl:app-locales-changed", true);
437 }
438
439 // instantiate the override service, if there is any.
440 // at some point we probably want to make this a category, and
441 // support multiple overrides
442 mOverrideStrings = do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID);
443
444 return NS_OK;
445 }
446
447 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aSomeData)448 nsStringBundleService::Observe(nsISupports* aSubject, const char* aTopic,
449 const char16_t* aSomeData) {
450 if (strcmp("memory-pressure", aTopic) == 0 ||
451 strcmp("profile-do-change", aTopic) == 0 ||
452 strcmp("chrome-flush-caches", aTopic) == 0 ||
453 strcmp("intl:app-locales-changed", aTopic) == 0) {
454 flushBundleCache();
455 } else if (strcmp("xpcom-category-entry-added", aTopic) == 0 &&
456 NS_LITERAL_STRING("xpcom-autoregistration").Equals(aSomeData)) {
457 mOverrideStrings = do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID);
458 }
459
460 return NS_OK;
461 }
462
flushBundleCache()463 void nsStringBundleService::flushBundleCache() {
464 // release all bundles in the cache
465 mBundleMap.Clear();
466
467 while (!mBundleCache.isEmpty()) {
468 delete mBundleCache.popFirst();
469 }
470 }
471
472 NS_IMETHODIMP
FlushBundles()473 nsStringBundleService::FlushBundles() {
474 flushBundleCache();
475 return NS_OK;
476 }
477
getStringBundle(const char * aURLSpec,nsIStringBundle ** aResult)478 void nsStringBundleService::getStringBundle(const char* aURLSpec,
479 nsIStringBundle** aResult) {
480 nsDependentCString key(aURLSpec);
481 bundleCacheEntry_t* cacheEntry = mBundleMap.Get(key);
482
483 if (cacheEntry) {
484 // cache hit!
485 // remove it from the list, it will later be reinserted
486 // at the head of the list
487 cacheEntry->remove();
488
489 } else {
490 // hasn't been cached, so insert it into the hash table
491 RefPtr<nsStringBundle> bundle =
492 new nsStringBundle(aURLSpec, mOverrideStrings);
493 cacheEntry = insertIntoCache(bundle.forget(), key);
494 }
495
496 // at this point the cacheEntry should exist in the hashtable,
497 // but is not in the LRU cache.
498 // put the cache entry at the front of the list
499 mBundleCache.insertFront(cacheEntry);
500
501 // finally, return the value
502 *aResult = cacheEntry->mBundle;
503 NS_ADDREF(*aResult);
504 }
505
insertIntoCache(already_AddRefed<nsIStringBundle> aBundle,nsCString & aHashKey)506 bundleCacheEntry_t* nsStringBundleService::insertIntoCache(
507 already_AddRefed<nsIStringBundle> aBundle, nsCString& aHashKey) {
508 bundleCacheEntry_t* cacheEntry;
509
510 if (mBundleMap.Count() < MAX_CACHED_BUNDLES) {
511 // cache not full - create a new entry
512 cacheEntry = new bundleCacheEntry_t();
513 } else {
514 // cache is full
515 // take the last entry in the list, and recycle it.
516 cacheEntry = mBundleCache.getLast();
517
518 // remove it from the hash table and linked list
519 NS_ASSERTION(mBundleMap.Contains(cacheEntry->mHashKey),
520 "Element will not be removed!");
521 mBundleMap.Remove(cacheEntry->mHashKey);
522 cacheEntry->remove();
523 }
524
525 // at this point we have a new cacheEntry that doesn't exist
526 // in the hashtable, so set up the cacheEntry
527 cacheEntry->mHashKey = aHashKey;
528 cacheEntry->mBundle = aBundle;
529
530 // insert the entry into the cache and map, make it the MRU
531 mBundleMap.Put(cacheEntry->mHashKey, cacheEntry);
532
533 return cacheEntry;
534 }
535
536 NS_IMETHODIMP
CreateBundle(const char * aURLSpec,nsIStringBundle ** aResult)537 nsStringBundleService::CreateBundle(const char* aURLSpec,
538 nsIStringBundle** aResult) {
539 getStringBundle(aURLSpec, aResult);
540 return NS_OK;
541 }
542
543 NS_IMETHODIMP
CreateExtensibleBundle(const char * aCategory,nsIStringBundle ** aResult)544 nsStringBundleService::CreateExtensibleBundle(const char* aCategory,
545 nsIStringBundle** aResult) {
546 NS_ENSURE_ARG_POINTER(aResult);
547 *aResult = nullptr;
548
549 RefPtr<nsExtensibleStringBundle> bundle = new nsExtensibleStringBundle();
550
551 nsresult res = bundle->Init(aCategory, this);
552 if (NS_FAILED(res)) {
553 return res;
554 }
555
556 bundle.forget(aResult);
557 return NS_OK;
558 }
559
560 #define GLOBAL_PROPERTIES "chrome://global/locale/global-strres.properties"
561
FormatWithBundle(nsIStringBundle * bundle,nsresult aStatus,uint32_t argCount,char16_t ** argArray,nsAString & result)562 nsresult nsStringBundleService::FormatWithBundle(nsIStringBundle* bundle,
563 nsresult aStatus,
564 uint32_t argCount,
565 char16_t** argArray,
566 nsAString& result) {
567 nsresult rv;
568
569 // try looking up the error message with the int key:
570 uint16_t code = NS_ERROR_GET_CODE(aStatus);
571 rv = bundle->FormatStringFromID(code, (const char16_t**)argArray, argCount,
572 result);
573
574 // If the int key fails, try looking up the default error message. E.g. print:
575 // An unknown error has occurred (0x804B0003).
576 if (NS_FAILED(rv)) {
577 nsAutoString statusStr;
578 statusStr.AppendInt(static_cast<uint32_t>(aStatus), 16);
579 const char16_t* otherArgArray[1];
580 otherArgArray[0] = statusStr.get();
581 uint16_t code = NS_ERROR_GET_CODE(NS_ERROR_FAILURE);
582 rv = bundle->FormatStringFromID(code, otherArgArray, 1, result);
583 }
584
585 return rv;
586 }
587
588 NS_IMETHODIMP
FormatStatusMessage(nsresult aStatus,const char16_t * aStatusArg,nsAString & result)589 nsStringBundleService::FormatStatusMessage(nsresult aStatus,
590 const char16_t* aStatusArg,
591 nsAString& result) {
592 nsresult rv;
593 uint32_t i, argCount = 0;
594 nsCOMPtr<nsIStringBundle> bundle;
595 nsCString stringBundleURL;
596
597 // XXX hack for mailnews who has already formatted their messages:
598 if (aStatus == NS_OK && aStatusArg) {
599 result.Assign(aStatusArg);
600 return NS_OK;
601 }
602
603 if (aStatus == NS_OK) {
604 return NS_ERROR_FAILURE; // no message to format
605 }
606
607 // format the arguments:
608 const nsDependentString args(aStatusArg);
609 argCount = args.CountChar(char16_t('\n')) + 1;
610 NS_ENSURE_ARG(argCount <= 10); // enforce 10-parameter limit
611 char16_t* argArray[10];
612
613 // convert the aStatusArg into a char16_t array
614 if (argCount == 1) {
615 // avoid construction for the simple case:
616 argArray[0] = (char16_t*)aStatusArg;
617 } else if (argCount > 1) {
618 int32_t offset = 0;
619 for (i = 0; i < argCount; i++) {
620 int32_t pos = args.FindChar('\n', offset);
621 if (pos == -1) pos = args.Length();
622 argArray[i] = ToNewUnicode(Substring(args, offset, pos - offset));
623 if (argArray[i] == nullptr) {
624 rv = NS_ERROR_OUT_OF_MEMORY;
625 argCount = i - 1; // don't try to free uninitialized memory
626 goto done;
627 }
628 offset = pos + 1;
629 }
630 }
631
632 // find the string bundle for the error's module:
633 rv = mErrorService->GetErrorStringBundle(NS_ERROR_GET_MODULE(aStatus),
634 getter_Copies(stringBundleURL));
635 if (NS_SUCCEEDED(rv)) {
636 getStringBundle(stringBundleURL.get(), getter_AddRefs(bundle));
637 rv = FormatWithBundle(bundle, aStatus, argCount, argArray, result);
638 }
639 if (NS_FAILED(rv)) {
640 getStringBundle(GLOBAL_PROPERTIES, getter_AddRefs(bundle));
641 rv = FormatWithBundle(bundle, aStatus, argCount, argArray, result);
642 }
643
644 done:
645 if (argCount > 1) {
646 for (i = 0; i < argCount; i++) {
647 if (argArray[i]) free(argArray[i]);
648 }
649 }
650 return rv;
651 }
652