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"
11 #include "nsIStringBundle.h"
12 #include "nsStringBundleService.h"
13 #include "nsArrayEnumerator.h"
15 #include "nsNetUtil.h"
16 #include "nsComponentManagerUtils.h"
17 #include "nsServiceManagerUtils.h"
18 #include "nsIChannel.h"
19 #include "nsIInputStream.h"
21 #include "nsIObserverService.h"
22 #include "nsCOMArray.h"
23 #include "nsTextFormatter.h"
24 #include "nsContentUtils.h"
25 #include "nsPersistentProperties.h"
26 #include "nsQueryObject.h"
27 #include "nsSimpleEnumerator.h"
28 #include "nsStringStream.h"
29 #include "mozilla/dom/txXSLTMsgsURL.h"
30 #include "mozilla/BinarySearch.h"
31 #include "mozilla/ClearOnShutdown.h"
32 #include "mozilla/ResultExtensions.h"
33 #include "mozilla/URLPreloader.h"
34 #include "mozilla/ResultExtensions.h"
35 #include "mozilla/dom/ContentParent.h"
36 #include "mozilla/dom/ipc/SharedStringMap.h"
40 # include "nsIBinaryInputStream.h"
41 # include "nsIStringStream.h"
44 using namespace mozilla
;
46 using mozilla::dom::ContentParent
;
47 using mozilla::dom::StringBundleDescriptor
;
48 using mozilla::dom::ipc::SharedStringMap
;
49 using mozilla::dom::ipc::SharedStringMapBuilder
;
50 using mozilla::ipc::FileDescriptor
;
53 * A set of string bundle URLs which are loaded by content processes, and
54 * should be allocated in a shared memory region, and then sent to content
57 * Note: This layout is chosen to avoid having to create a separate char*
58 * array pointing to the string constant values, which would require
59 * per-process relocations. The second array size is the length of the longest
60 * URL plus its null terminator. Shorter strings are null padded to this
63 * This should be kept in sync with the similar array in nsContentUtils.cpp,
64 * and updated with any other property files which need to be loaded in all
67 static const char kContentBundles
[][52] = {
68 "chrome://branding/locale/brand.properties",
69 "chrome://global/locale/commonDialogs.properties",
70 "chrome://global/locale/css.properties",
71 "chrome://global/locale/dom/dom.properties",
72 "chrome://global/locale/layout/HtmlForm.properties",
73 "chrome://global/locale/layout/htmlparser.properties",
74 "chrome://global/locale/layout_errors.properties",
75 "chrome://global/locale/mathml/mathml.properties",
76 "chrome://global/locale/printing.properties",
77 "chrome://global/locale/security/csp.properties",
78 "chrome://global/locale/security/security.properties",
79 "chrome://global/locale/svg/svg.properties",
80 "chrome://global/locale/xul.properties",
81 "chrome://necko/locale/necko.properties",
84 static bool IsContentBundle(const nsCString
& aUrl
) {
86 return BinarySearchIf(
87 kContentBundles
, 0, MOZ_ARRAY_LENGTH(kContentBundles
),
88 [&](const char* aElem
) {
89 return Compare(aUrl
, nsDependentCString(aElem
));
96 #define STRINGBUNDLEPROXY_IID \
98 0x537cf21b, 0x99fc, 0x4002, { \
99 0x9e, 0xec, 0x97, 0xbe, 0x4d, 0xe0, 0xb3, 0xdc \
104 * A simple proxy class for a string bundle instance which will be replaced by
105 * a different implementation later in the session.
107 * This is used when creating string bundles which should use shared memory,
108 * but the content process has not yet received their shared memory buffer.
109 * When the shared memory variant becomes available, this proxy is retarged to
110 * that instance, and the original non-shared instance is destroyed.
112 * At that point, the cache entry for the proxy is replaced with the shared
113 * memory instance, and callers which already have an instance of the proxy
114 * are redirected to the new instance.
116 class StringBundleProxy
: public nsIStringBundle
{
117 NS_DECL_THREADSAFE_ISUPPORTS
119 NS_DECLARE_STATIC_IID_ACCESSOR(STRINGBUNDLEPROXY_IID
)
121 explicit StringBundleProxy(already_AddRefed
<nsIStringBundle
> aTarget
)
122 : mMutex("StringBundleProxy::mMutex"), mTarget(aTarget
) {}
124 void Retarget(nsIStringBundle
* aTarget
) {
125 MutexAutoLock
automon(mMutex
);
129 // Forward nsIStringBundle methods (other than the `SizeOf*` methods) to
131 NS_IMETHOD
GetStringFromID(int32_t aID
, nsAString
& _retval
) override
{
132 return Target()->GetStringFromID(aID
, _retval
);
134 NS_IMETHOD
GetStringFromAUTF8Name(const nsACString
& aName
,
135 nsAString
& _retval
) override
{
136 return Target()->GetStringFromAUTF8Name(aName
, _retval
);
138 NS_IMETHOD
GetStringFromName(const char* aName
, nsAString
& _retval
) override
{
139 return Target()->GetStringFromName(aName
, _retval
);
141 NS_IMETHOD
FormatStringFromID(int32_t aID
, const nsTArray
<nsString
>& params
,
142 nsAString
& _retval
) override
{
143 return Target()->FormatStringFromID(aID
, params
, _retval
);
145 NS_IMETHOD
FormatStringFromAUTF8Name(const nsACString
& aName
,
146 const nsTArray
<nsString
>& params
,
147 nsAString
& _retval
) override
{
148 return Target()->FormatStringFromAUTF8Name(aName
, params
, _retval
);
150 NS_IMETHOD
FormatStringFromName(const char* aName
,
151 const nsTArray
<nsString
>& params
,
152 nsAString
& _retval
) override
{
153 return Target()->FormatStringFromName(aName
, params
, _retval
);
155 NS_IMETHOD
GetSimpleEnumeration(nsISimpleEnumerator
** _retval
) override
{
156 return Target()->GetSimpleEnumeration(_retval
);
158 NS_IMETHOD
AsyncPreload() override
{ return Target()->AsyncPreload(); }
160 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) override
{
161 return aMallocSizeOf(this);
164 size_t SizeOfIncludingThisIfUnshared(
165 mozilla::MallocSizeOf aMallocSizeOf
) override
{
166 return mRefCnt
== 1 ? SizeOfIncludingThis(aMallocSizeOf
) : 0;
170 virtual ~StringBundleProxy() = default;
173 Mutex mMutex MOZ_UNANNOTATED
;
174 nsCOMPtr
<nsIStringBundle
> mTarget
;
176 // Atomically reads mTarget and returns a strong reference to it. This
177 // allows for safe multi-threaded use when the proxy may be retargetted by
178 // the main thread during access.
179 nsCOMPtr
<nsIStringBundle
> Target() {
180 MutexAutoLock
automon(mMutex
);
185 NS_DEFINE_STATIC_IID_ACCESSOR(StringBundleProxy
, STRINGBUNDLEPROXY_IID
)
187 NS_IMPL_ISUPPORTS(StringBundleProxy
, nsIStringBundle
, StringBundleProxy
)
189 #define SHAREDSTRINGBUNDLE_IID \
191 0x7a8df5f7, 0x9e50, 0x44f6, { \
192 0xbf, 0x89, 0xc7, 0xad, 0x6c, 0x17, 0xf8, 0x5f \
197 * A string bundle backed by a read-only, shared memory buffer. This should
198 * only be used for string bundles which are used in child processes.
200 * Important: The memory allocated by these string bundles will never be freed
201 * before process shutdown, per the restrictions in SharedStringMap.h, so they
202 * should never be used for short-lived bundles.
204 class SharedStringBundle final
: public nsStringBundleBase
{
207 * Initialize the string bundle with a file descriptor pointing to a
208 * pre-populated key-value store for this string bundle. This should only be
209 * called in child processes, for bundles initially created in the parent
212 void SetMapFile(const FileDescriptor
& aFile
, size_t aSize
);
214 NS_DECL_ISUPPORTS_INHERITED
215 NS_DECLARE_STATIC_IID_ACCESSOR(SHAREDSTRINGBUNDLE_IID
)
217 nsresult
LoadProperties() override
;
220 * Returns a copy of the file descriptor pointing to the shared memory
221 * key-values tore for this string bundle. This should only be called in the
222 * parent process, and may be used to send shared string bundles to child
225 FileDescriptor
CloneFileDescriptor() const {
226 MOZ_ASSERT(XRE_IsParentProcess());
227 if (mMapFile
.isSome()) {
228 return mMapFile
.ref();
230 return mStringMap
->CloneFileDescriptor();
233 size_t MapSize() const {
234 if (mMapFile
.isSome()) {
238 return mStringMap
->MapSize();
243 bool Initialized() const { return mStringMap
|| mMapFile
.isSome(); }
245 StringBundleDescriptor
GetDescriptor() const {
246 MOZ_ASSERT(Initialized());
248 StringBundleDescriptor descriptor
;
249 descriptor
.bundleURL() = BundleURL();
250 descriptor
.mapFile() = CloneFileDescriptor();
251 descriptor
.mapSize() = MapSize();
255 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) override
;
257 static SharedStringBundle
* Cast(nsIStringBundle
* aStringBundle
) {
258 return static_cast<SharedStringBundle
*>(aStringBundle
);
262 friend class nsStringBundleBase
;
264 explicit SharedStringBundle(const char* aURLSpec
)
265 : nsStringBundleBase(aURLSpec
) {}
267 ~SharedStringBundle() override
= default;
269 nsresult
GetStringImpl(const nsACString
& aName
, nsAString
& aResult
) override
;
271 nsresult
GetSimpleEnumerationImpl(nsISimpleEnumerator
** elements
) override
;
274 RefPtr
<SharedStringMap
> mStringMap
;
276 Maybe
<FileDescriptor
> mMapFile
;
280 NS_DEFINE_STATIC_IID_ACCESSOR(SharedStringBundle
, SHAREDSTRINGBUNDLE_IID
)
282 class StringMapEnumerator final
: public nsSimpleEnumerator
{
284 NS_DECL_NSISIMPLEENUMERATOR
286 explicit StringMapEnumerator(SharedStringMap
* aStringMap
)
287 : mStringMap(aStringMap
) {}
289 const nsID
& DefaultInterface() override
{
290 return NS_GET_IID(nsIPropertyElement
);
294 virtual ~StringMapEnumerator() = default;
297 RefPtr
<SharedStringMap
> mStringMap
;
302 template <typename T
, typename
... Args
>
303 already_AddRefed
<T
> MakeBundle(Args
... args
) {
304 return nsStringBundleBase::Create
<T
>(args
...);
307 template <typename T
, typename
... Args
>
308 RefPtr
<T
> MakeBundleRefPtr(Args
... args
) {
309 return nsStringBundleBase::Create
<T
>(args
...);
312 } // anonymous namespace
314 NS_IMPL_ISUPPORTS(nsStringBundleBase
, nsIStringBundle
, nsIMemoryReporter
)
316 NS_IMPL_ISUPPORTS_INHERITED0(nsStringBundle
, nsStringBundleBase
)
317 NS_IMPL_ISUPPORTS_INHERITED(SharedStringBundle
, nsStringBundleBase
,
320 nsStringBundleBase::nsStringBundleBase(const char* aURLSpec
)
321 : mPropertiesURL(aURLSpec
),
322 mMutex("nsStringBundle.mMutex"),
323 mAttemptedLoad(false),
326 nsStringBundleBase::~nsStringBundleBase() {
327 UnregisterWeakMemoryReporter(this);
330 void nsStringBundleBase::RegisterMemoryReporter() {
331 RegisterWeakMemoryReporter(this);
334 template <typename T
, typename
... Args
>
336 already_AddRefed
<T
> nsStringBundleBase::Create(Args
... args
) {
337 RefPtr
<T
> bundle
= new T(args
...);
338 bundle
->RegisterMemoryReporter();
339 return bundle
.forget();
342 nsStringBundle::nsStringBundle(const char* aURLSpec
)
343 : nsStringBundleBase(aURLSpec
) {}
345 nsStringBundle::~nsStringBundle() = default;
348 nsStringBundleBase::AsyncPreload() {
349 return NS_DispatchToCurrentThreadQueue(
350 NewIdleRunnableMethod("nsStringBundleBase::LoadProperties", this,
351 &nsStringBundleBase::LoadProperties
),
352 EventQueuePriority::Idle
);
355 size_t nsStringBundle::SizeOfIncludingThis(
356 mozilla::MallocSizeOf aMallocSizeOf
) {
359 n
+= mProps
->SizeOfIncludingThis(aMallocSizeOf
);
361 return aMallocSizeOf(this) + n
;
364 size_t nsStringBundleBase::SizeOfIncludingThis(
365 mozilla::MallocSizeOf aMallocSizeOf
) {
369 size_t nsStringBundleBase::SizeOfIncludingThisIfUnshared(
370 mozilla::MallocSizeOf aMallocSizeOf
) {
372 return SizeOfIncludingThis(aMallocSizeOf
);
378 size_t SharedStringBundle::SizeOfIncludingThis(
379 mozilla::MallocSizeOf aMallocSizeOf
) {
382 n
+= aMallocSizeOf(mStringMap
);
384 return aMallocSizeOf(this) + n
;
388 nsStringBundleBase::CollectReports(nsIHandleReportCallback
* aHandleReport
,
389 nsISupports
* aData
, bool aAnonymize
) {
390 // String bundle URLs are always local, and part of the distribution.
391 // There's no need to anonymize.
392 nsAutoCStringN
<64> escapedURL(mPropertiesURL
);
393 escapedURL
.ReplaceChar('/', '\\');
395 size_t sharedSize
= 0;
396 size_t heapSize
= SizeOfIncludingThis(MallocSizeOf
);
398 nsAutoCStringN
<256> path("explicit/string-bundles/");
399 if (RefPtr
<SharedStringBundle
> shared
= do_QueryObject(this)) {
400 path
.AppendLiteral("SharedStringBundle");
401 if (XRE_IsParentProcess()) {
402 sharedSize
= shared
->MapSize();
405 path
.AppendLiteral("nsStringBundle");
408 path
.AppendLiteral("(url=\"");
409 path
.Append(escapedURL
);
411 // Note: The memory reporter service holds a strong reference to reporters
412 // while collecting reports, so we want to ignore the extra ref in reports.
413 path
.AppendLiteral("\", shared=");
414 path
.AppendASCII(mRefCnt
> 2 ? "true" : "false");
415 path
.AppendLiteral(", refCount=");
416 path
.AppendInt(uint32_t(mRefCnt
- 1));
419 path
.AppendLiteral(", sharedMemorySize=");
420 path
.AppendInt(uint32_t(sharedSize
));
423 path
.AppendLiteral(")");
425 constexpr auto desc
=
426 "A StringBundle instance representing the data in a (probably "
427 "localized) .properties file. Data may be shared between "
430 aHandleReport
->Callback(""_ns
, path
, KIND_HEAP
, UNITS_BYTES
, heapSize
, desc
,
434 path
.ReplaceLiteral(0, sizeof("explicit/") - 1, "shared-");
436 aHandleReport
->Callback(""_ns
, path
, KIND_OTHER
, UNITS_BYTES
, sharedSize
,
443 nsresult
nsStringBundleBase::ParseProperties(nsIPersistentProperties
** aProps
) {
444 // this is different than mLoaded, because we only want to attempt
446 // we only want to load once, but if we've tried once and failed,
447 // continue to throw an error!
448 if (mAttemptedLoad
) {
449 if (mLoaded
) return NS_OK
;
451 return NS_ERROR_UNEXPECTED
;
454 MOZ_ASSERT(NS_IsMainThread(),
455 "String bundles must be initialized on the main thread "
456 "before they may be used off-main-thread");
458 mAttemptedLoad
= true;
462 // do it synchronously
463 nsCOMPtr
<nsIURI
> uri
;
464 rv
= NS_NewURI(getter_AddRefs(uri
), mPropertiesURL
);
465 if (NS_FAILED(rv
)) return rv
;
467 // whitelist check for local schemes
469 uri
->GetScheme(scheme
);
470 if (!scheme
.EqualsLiteral("chrome") && !scheme
.EqualsLiteral("jar") &&
471 !scheme
.EqualsLiteral("resource") && !scheme
.EqualsLiteral("file") &&
472 !scheme
.EqualsLiteral("data")) {
473 return NS_ERROR_ABORT
;
476 nsCOMPtr
<nsIInputStream
> in
;
478 auto result
= URLPreloader::ReadURI(uri
);
480 MOZ_TRY(NS_NewCStringInputStream(getter_AddRefs(in
), result
.unwrap()));
482 nsCOMPtr
<nsIChannel
> channel
;
483 rv
= NS_NewChannel(getter_AddRefs(channel
), uri
,
484 nsContentUtils::GetSystemPrincipal(),
485 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
486 nsIContentPolicy::TYPE_OTHER
);
488 if (NS_FAILED(rv
)) return rv
;
490 // It's a string bundle. We expect a text/plain type, so set that as hint
491 channel
->SetContentType("text/plain"_ns
);
493 rv
= channel
->Open(getter_AddRefs(in
));
494 if (NS_FAILED(rv
)) return rv
;
497 auto props
= MakeRefPtr
<nsPersistentProperties
>();
499 mAttemptedLoad
= true;
501 MOZ_TRY(props
->Load(in
));
502 props
.forget(aProps
);
508 nsresult
nsStringBundle::LoadProperties() {
509 // Something such as Necko might use string bundle after ClearOnShutdown is
510 // called. LocaleService etc is already down, so we cannot get bundle data.
511 if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown
)) {
512 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
518 return ParseProperties(getter_AddRefs(mProps
));
521 nsresult
SharedStringBundle::LoadProperties() {
522 if (mStringMap
) return NS_OK
;
524 if (mMapFile
.isSome()) {
525 mStringMap
= new SharedStringMap(mMapFile
.ref(), mMapSize
);
530 MOZ_ASSERT(NS_IsMainThread(),
531 "String bundles must be initialized on the main thread "
532 "before they may be used off-main-thread");
534 // We can't access the locale service after shutdown has started, which
535 // means we can't attempt to load chrome: locale resources (which most of
536 // our string bundles come from). Since shared string bundles won't be
537 // useful after shutdown has started anyway (and we almost certainly got
538 // here from a pre-load attempt in an idle task), just bail out.
539 if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown
)) {
540 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
543 // We should only populate shared memory string bundles in the parent
544 // process. Instances in the child process should always be instantiated
545 // with a shared memory file descriptor sent from the parent.
546 MOZ_ASSERT(XRE_IsParentProcess());
548 nsCOMPtr
<nsIPersistentProperties
> props
;
549 MOZ_TRY(ParseProperties(getter_AddRefs(props
)));
551 SharedStringMapBuilder builder
;
553 nsCOMPtr
<nsISimpleEnumerator
> iter
;
554 MOZ_TRY(props
->Enumerate(getter_AddRefs(iter
)));
556 while (NS_SUCCEEDED(iter
->HasMoreElements(&hasMore
)) && hasMore
) {
557 nsCOMPtr
<nsISupports
> next
;
558 MOZ_TRY(iter
->GetNext(getter_AddRefs(next
)));
561 nsCOMPtr
<nsIPropertyElement
> elem
= do_QueryInterface(next
, &rv
);
566 MOZ_TRY(elem
->GetKey(key
));
567 MOZ_TRY(elem
->GetValue(value
));
569 builder
.Add(key
, value
);
572 mStringMap
= new SharedStringMap(std::move(builder
));
574 ContentParent::BroadcastStringBundle(GetDescriptor());
579 void SharedStringBundle::SetMapFile(const FileDescriptor
& aFile
, size_t aSize
) {
580 MOZ_ASSERT(XRE_IsContentProcess());
581 mStringMap
= nullptr;
582 mMapFile
.emplace(aFile
);
587 nsStringBundleBase::GetStringFromID(int32_t aID
, nsAString
& aResult
) {
589 idStr
.AppendInt(aID
, 10);
590 return GetStringFromName(idStr
.get(), aResult
);
594 nsStringBundleBase::GetStringFromAUTF8Name(const nsACString
& aName
,
595 nsAString
& aResult
) {
596 return GetStringFromName(PromiseFlatCString(aName
).get(), aResult
);
600 nsStringBundleBase::GetStringFromName(const char* aName
, nsAString
& aResult
) {
601 NS_ENSURE_ARG_POINTER(aName
);
603 MutexAutoLock
autolock(mMutex
);
605 return GetStringImpl(nsDependentCString(aName
), aResult
);
608 nsresult
nsStringBundle::GetStringImpl(const nsACString
& aName
,
609 nsAString
& aResult
) {
610 MOZ_TRY(LoadProperties());
612 return mProps
->GetStringProperty(aName
, aResult
);
615 nsresult
SharedStringBundle::GetStringImpl(const nsACString
& aName
,
616 nsAString
& aResult
) {
617 MOZ_TRY(LoadProperties());
619 if (mStringMap
->Get(PromiseFlatCString(aName
), aResult
)) {
622 return NS_ERROR_FAILURE
;
626 nsStringBundleBase::FormatStringFromID(int32_t aID
,
627 const nsTArray
<nsString
>& aParams
,
628 nsAString
& aResult
) {
630 idStr
.AppendInt(aID
, 10);
631 return FormatStringFromName(idStr
.get(), aParams
, aResult
);
634 // this function supports at most 10 parameters.. see below for why
636 nsStringBundleBase::FormatStringFromAUTF8Name(const nsACString
& aName
,
637 const nsTArray
<nsString
>& aParams
,
638 nsAString
& aResult
) {
639 return FormatStringFromName(PromiseFlatCString(aName
).get(), aParams
,
643 // this function supports at most 10 parameters.. see below for why
645 nsStringBundleBase::FormatStringFromName(const char* aName
,
646 const nsTArray
<nsString
>& aParams
,
647 nsAString
& aResult
) {
648 NS_ASSERTION(!aParams
.IsEmpty(),
649 "FormatStringFromName() without format parameters: use "
650 "GetStringFromName() instead");
652 nsAutoString formatStr
;
653 nsresult rv
= GetStringFromName(aName
, formatStr
);
654 if (NS_FAILED(rv
)) return rv
;
656 return FormatString(formatStr
.get(), aParams
, aResult
);
660 nsStringBundleBase::GetSimpleEnumeration(nsISimpleEnumerator
** aElements
) {
661 NS_ENSURE_ARG_POINTER(aElements
);
663 return GetSimpleEnumerationImpl(aElements
);
666 nsresult
nsStringBundle::GetSimpleEnumerationImpl(
667 nsISimpleEnumerator
** elements
) {
668 MOZ_TRY(LoadProperties());
670 return mProps
->Enumerate(elements
);
673 nsresult
SharedStringBundle::GetSimpleEnumerationImpl(
674 nsISimpleEnumerator
** aEnumerator
) {
675 MOZ_TRY(LoadProperties());
677 auto iter
= MakeRefPtr
<StringMapEnumerator
>(mStringMap
);
678 iter
.forget(aEnumerator
);
683 StringMapEnumerator::HasMoreElements(bool* aHasMore
) {
684 *aHasMore
= mIndex
< mStringMap
->Count();
689 StringMapEnumerator::GetNext(nsISupports
** aNext
) {
690 if (mIndex
>= mStringMap
->Count()) {
691 return NS_ERROR_FAILURE
;
694 auto elem
= MakeRefPtr
<nsPropertyElement
>(mStringMap
->GetKeyAt(mIndex
),
695 mStringMap
->GetValueAt(mIndex
));
703 nsresult
nsStringBundleBase::FormatString(const char16_t
* aFormatStr
,
704 const nsTArray
<nsString
>& aParams
,
705 nsAString
& aResult
) {
706 auto length
= aParams
.Length();
707 NS_ENSURE_ARG(length
<= 10); // enforce 10-parameter limit
709 // implementation note: you would think you could use vsmprintf
710 // to build up an arbitrary length array.. except that there
711 // is no way to build up a va_list at runtime!
712 // Don't believe me? See:
713 // http://www.eskimo.com/~scs/C-faq/q15.13.html
715 nsTextFormatter::ssprintf(aResult
, aFormatStr
,
716 length
>= 1 ? aParams
[0].get() : nullptr,
717 length
>= 2 ? aParams
[1].get() : nullptr,
718 length
>= 3 ? aParams
[2].get() : nullptr,
719 length
>= 4 ? aParams
[3].get() : nullptr,
720 length
>= 5 ? aParams
[4].get() : nullptr,
721 length
>= 6 ? aParams
[5].get() : nullptr,
722 length
>= 7 ? aParams
[6].get() : nullptr,
723 length
>= 8 ? aParams
[7].get() : nullptr,
724 length
>= 9 ? aParams
[8].get() : nullptr,
725 length
>= 10 ? aParams
[9].get() : nullptr);
730 /////////////////////////////////////////////////////////////////////////////////////////
732 #define MAX_CACHED_BUNDLES 16
734 struct bundleCacheEntry_t final
: public LinkedListElement
<bundleCacheEntry_t
> {
736 nsCOMPtr
<nsIStringBundle
> mBundle
;
738 MOZ_COUNTED_DEFAULT_CTOR(bundleCacheEntry_t
)
740 MOZ_COUNTED_DTOR(bundleCacheEntry_t
)
743 nsStringBundleService::nsStringBundleService()
744 : mBundleMap(MAX_CACHED_BUNDLES
) {}
746 NS_IMPL_ISUPPORTS(nsStringBundleService
, nsIStringBundleService
, nsIObserver
,
747 nsISupportsWeakReference
, nsIMemoryReporter
)
749 nsStringBundleService::~nsStringBundleService() {
750 UnregisterWeakMemoryReporter(this);
751 flushBundleCache(/* ignoreShared = */ false);
754 nsresult
nsStringBundleService::Init() {
755 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
757 os
->AddObserver(this, "memory-pressure", true);
758 os
->AddObserver(this, "profile-do-change", true);
759 os
->AddObserver(this, "chrome-flush-caches", true);
760 os
->AddObserver(this, "intl:app-locales-changed", true);
763 RegisterWeakMemoryReporter(this);
768 size_t nsStringBundleService::SizeOfIncludingThis(
769 mozilla::MallocSizeOf aMallocSizeOf
) {
770 size_t n
= mBundleMap
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
771 for (const auto& data
: mBundleMap
.Values()) {
772 n
+= aMallocSizeOf(data
);
773 n
+= data
->mHashKey
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
);
775 return aMallocSizeOf(this) + n
;
779 nsStringBundleService::Observe(nsISupports
* aSubject
, const char* aTopic
,
780 const char16_t
* aSomeData
) {
781 if (strcmp("profile-do-change", aTopic
) == 0 ||
782 strcmp("chrome-flush-caches", aTopic
) == 0 ||
783 strcmp("intl:app-locales-changed", aTopic
) == 0) {
784 flushBundleCache(/* ignoreShared = */ false);
785 } else if (strcmp("memory-pressure", aTopic
) == 0) {
786 flushBundleCache(/* ignoreShared = */ true);
792 void nsStringBundleService::flushBundleCache(bool ignoreShared
) {
793 LinkedList
<bundleCacheEntry_t
> newList
;
795 while (!mBundleCache
.isEmpty()) {
796 UniquePtr
<bundleCacheEntry_t
> entry(mBundleCache
.popFirst());
797 auto* bundle
= nsStringBundleBase::Cast(entry
->mBundle
);
799 if (ignoreShared
&& bundle
->IsShared()) {
800 newList
.insertBack(entry
.release());
802 mBundleMap
.Remove(entry
->mHashKey
);
806 mBundleCache
= std::move(newList
);
810 nsStringBundleService::FlushBundles() {
811 flushBundleCache(/* ignoreShared = */ false);
815 void nsStringBundleService::SendContentBundles(ContentParent
* aContentParent
) {
816 nsTArray
<StringBundleDescriptor
> bundles
;
818 for (auto* entry
: mSharedBundles
) {
819 auto bundle
= SharedStringBundle::Cast(entry
->mBundle
);
821 if (bundle
->Initialized()) {
822 bundles
.AppendElement(bundle
->GetDescriptor());
826 Unused
<< aContentParent
->SendRegisterStringBundles(std::move(bundles
));
829 void nsStringBundleService::RegisterContentBundle(
830 const nsACString
& aBundleURL
, const FileDescriptor
& aMapFile
,
832 RefPtr
<StringBundleProxy
> proxy
;
834 bundleCacheEntry_t
* cacheEntry
= mBundleMap
.Get(aBundleURL
);
836 if (RefPtr
<SharedStringBundle
> shared
=
837 do_QueryObject(cacheEntry
->mBundle
)) {
841 proxy
= do_QueryObject(cacheEntry
->mBundle
);
843 cacheEntry
->remove();
847 auto bundle
= MakeBundleRefPtr
<SharedStringBundle
>(
848 PromiseFlatCString(aBundleURL
).get());
849 bundle
->SetMapFile(aMapFile
, aMapSize
);
852 proxy
->Retarget(bundle
);
855 cacheEntry
= insertIntoCache(bundle
.forget(), aBundleURL
);
856 mSharedBundles
.insertBack(cacheEntry
);
859 void nsStringBundleService::getStringBundle(const char* aURLSpec
,
860 nsIStringBundle
** aResult
) {
861 nsDependentCString
key(aURLSpec
);
862 bundleCacheEntry_t
* cacheEntry
= mBundleMap
.Get(key
);
864 RefPtr
<SharedStringBundle
> shared
;
867 // Remove the entry from the list so it can be re-inserted at the back.
868 cacheEntry
->remove();
870 shared
= do_QueryObject(cacheEntry
->mBundle
);
872 nsCOMPtr
<nsIStringBundle
> bundle
;
873 bool isContent
= IsContentBundle(key
);
874 if (!isContent
|| !XRE_IsParentProcess()) {
875 bundle
= MakeBundle
<nsStringBundle
>(aURLSpec
);
878 // If this is a bundle which is used by the content processes, we want to
879 // load it into a shared memory region.
881 // If we're in the parent process, just create a new SharedStringBundle,
882 // and populate it from the properties file.
884 // If we're in a child process, the fact that the bundle is not already in
885 // the cache means that we haven't received its shared memory descriptor
886 // from the parent yet. There's not much we can do about that besides
887 // wait, but we need to return a bundle now. So instead of a shared memory
888 // bundle, we create a temporary proxy, which points to a non-shared
889 // bundle initially, and is retarged to a shared memory bundle when it
890 // becomes available.
892 if (XRE_IsParentProcess()) {
893 shared
= MakeBundle
<SharedStringBundle
>(aURLSpec
);
896 bundle
= new StringBundleProxy(bundle
.forget());
900 cacheEntry
= insertIntoCache(bundle
.forget(), key
);
904 mSharedBundles
.insertBack(cacheEntry
);
906 mBundleCache
.insertBack(cacheEntry
);
909 // finally, return the value
910 *aResult
= cacheEntry
->mBundle
;
914 UniquePtr
<bundleCacheEntry_t
> nsStringBundleService::evictOneEntry() {
915 for (auto* entry
: mBundleCache
) {
916 auto* bundle
= nsStringBundleBase::Cast(entry
->mBundle
);
917 if (!bundle
->IsShared()) {
919 mBundleMap
.Remove(entry
->mHashKey
);
920 return UniquePtr
<bundleCacheEntry_t
>(entry
);
926 bundleCacheEntry_t
* nsStringBundleService::insertIntoCache(
927 already_AddRefed
<nsIStringBundle
> aBundle
, const nsACString
& aHashKey
) {
928 UniquePtr
<bundleCacheEntry_t
> cacheEntry
;
930 if (mBundleMap
.Count() >= MAX_CACHED_BUNDLES
) {
931 cacheEntry
= evictOneEntry();
935 cacheEntry
.reset(new bundleCacheEntry_t());
938 cacheEntry
->mHashKey
= aHashKey
;
939 cacheEntry
->mBundle
= aBundle
;
941 mBundleMap
.InsertOrUpdate(cacheEntry
->mHashKey
, cacheEntry
.get());
943 return cacheEntry
.release();
947 nsStringBundleService::CreateBundle(const char* aURLSpec
,
948 nsIStringBundle
** aResult
) {
949 getStringBundle(aURLSpec
, aResult
);
953 #define GLOBAL_PROPERTIES "chrome://global/locale/global-strres.properties"
955 nsresult
nsStringBundleService::FormatWithBundle(
956 nsIStringBundle
* bundle
, nsresult aStatus
,
957 const nsTArray
<nsString
>& argArray
, nsAString
& result
) {
960 // try looking up the error message with the int key:
961 uint16_t code
= NS_ERROR_GET_CODE(aStatus
);
962 rv
= bundle
->FormatStringFromID(code
, argArray
, result
);
964 // If the int key fails, try looking up the default error message. E.g. print:
965 // An unknown error has occurred (0x804B0003).
967 AutoTArray
<nsString
, 1> otherArgArray
;
968 otherArgArray
.AppendElement()->AppendInt(static_cast<uint32_t>(aStatus
),
970 uint16_t code
= NS_ERROR_GET_CODE(NS_ERROR_FAILURE
);
971 rv
= bundle
->FormatStringFromID(code
, otherArgArray
, result
);
978 nsStringBundleService::FormatStatusMessage(nsresult aStatus
,
979 const char16_t
* aStatusArg
,
981 uint32_t i
, argCount
= 0;
982 nsCOMPtr
<nsIStringBundle
> bundle
;
984 // XXX hack for mailnews who has already formatted their messages:
985 if (aStatus
== NS_OK
&& aStatusArg
) {
986 result
.Assign(aStatusArg
);
990 if (aStatus
== NS_OK
) {
991 return NS_ERROR_FAILURE
; // no message to format
994 // format the arguments:
995 const nsDependentString
args(aStatusArg
);
996 argCount
= args
.CountChar(char16_t('\n')) + 1;
997 NS_ENSURE_ARG(argCount
<= 10); // enforce 10-parameter limit
998 AutoTArray
<nsString
, 10> argArray
;
1000 // convert the aStatusArg into an nsString array
1001 if (argCount
== 1) {
1002 argArray
.AppendElement(aStatusArg
);
1003 } else if (argCount
> 1) {
1005 for (i
= 0; i
< argCount
; i
++) {
1006 int32_t pos
= args
.FindChar('\n', offset
);
1007 if (pos
== -1) pos
= args
.Length();
1008 argArray
.AppendElement(Substring(args
, offset
, pos
- offset
));
1013 switch (NS_ERROR_GET_MODULE(aStatus
)) {
1014 case NS_ERROR_MODULE_XSLT
:
1015 getStringBundle(XSLT_MSGS_URL
, getter_AddRefs(bundle
));
1017 case NS_ERROR_MODULE_NETWORK
:
1018 getStringBundle(NECKO_MSGS_URL
, getter_AddRefs(bundle
));
1021 getStringBundle(GLOBAL_PROPERTIES
, getter_AddRefs(bundle
));
1025 return FormatWithBundle(bundle
, aStatus
, argArray
, result
);