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