no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / intl / strres / nsStringBundle.cpp
blob31ecbc3d001953b701a8538dcfe4519efd5b752d
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"
8 #include "netCore.h"
9 #include "nsID.h"
10 #include "nsString.h"
11 #include "nsIStringBundle.h"
12 #include "nsStringBundleService.h"
13 #include "nsArrayEnumerator.h"
14 #include "nscore.h"
15 #include "nsNetUtil.h"
16 #include "nsComponentManagerUtils.h"
17 #include "nsServiceManagerUtils.h"
18 #include "nsIChannel.h"
19 #include "nsIInputStream.h"
20 #include "nsIURI.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/Try.h"
35 #include "mozilla/dom/ContentParent.h"
36 #include "mozilla/dom/ipc/SharedStringMap.h"
38 // for async loading
39 #ifdef ASYNC_LOADING
40 # include "nsIBinaryInputStream.h"
41 # include "nsIStringStream.h"
42 #endif
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;
52 /**
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
55 * processes.
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
61 * length.
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
65 * content processes.
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) {
85 size_t index;
86 return BinarySearchIf(
87 kContentBundles, 0, MOZ_ARRAY_LENGTH(kContentBundles),
88 [&](const char* aElem) {
89 return Compare(aUrl, nsDependentCString(aElem));
91 &index);
94 namespace {
96 #define STRINGBUNDLEPROXY_IID \
97 { \
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);
126 mTarget = aTarget;
129 // Forward nsIStringBundle methods (other than the `SizeOf*` methods) to
130 // `Target()`.
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;
169 protected:
170 virtual ~StringBundleProxy() = default;
172 private:
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);
181 return mTarget;
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 {
205 public:
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
210 * process.
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
223 * processes.
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()) {
235 return mMapSize;
237 if (mStringMap) {
238 return mStringMap->MapSize();
240 return 0;
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();
252 return descriptor;
255 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) override;
257 static SharedStringBundle* Cast(nsIStringBundle* aStringBundle) {
258 return static_cast<SharedStringBundle*>(aStringBundle);
261 protected:
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;
273 private:
274 RefPtr<SharedStringMap> mStringMap;
276 Maybe<FileDescriptor> mMapFile;
277 size_t mMapSize;
280 NS_DEFINE_STATIC_IID_ACCESSOR(SharedStringBundle, SHAREDSTRINGBUNDLE_IID)
282 class StringMapEnumerator final : public nsSimpleEnumerator {
283 public:
284 NS_DECL_NSISIMPLEENUMERATOR
286 explicit StringMapEnumerator(SharedStringMap* aStringMap)
287 : mStringMap(aStringMap) {}
289 const nsID& DefaultInterface() override {
290 return NS_GET_IID(nsIPropertyElement);
293 protected:
294 virtual ~StringMapEnumerator() = default;
296 private:
297 RefPtr<SharedStringMap> mStringMap;
299 uint32_t mIndex = 0;
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,
318 SharedStringBundle)
320 nsStringBundleBase::nsStringBundleBase(const char* aURLSpec)
321 : mPropertiesURL(aURLSpec),
322 mMutex("nsStringBundle.mMutex"),
323 mAttemptedLoad(false),
324 mLoaded(false) {}
326 nsStringBundleBase::~nsStringBundleBase() {
327 UnregisterWeakMemoryReporter(this);
330 void nsStringBundleBase::RegisterMemoryReporter() {
331 RegisterWeakMemoryReporter(this);
334 template <typename T, typename... Args>
335 /* static */
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;
347 NS_IMETHODIMP
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) {
357 size_t n = 0;
358 if (mProps) {
359 n += mProps->SizeOfIncludingThis(aMallocSizeOf);
361 return aMallocSizeOf(this) + n;
364 size_t nsStringBundleBase::SizeOfIncludingThis(
365 mozilla::MallocSizeOf aMallocSizeOf) {
366 return 0;
369 size_t nsStringBundleBase::SizeOfIncludingThisIfUnshared(
370 mozilla::MallocSizeOf aMallocSizeOf) {
371 if (mRefCnt == 1) {
372 return SizeOfIncludingThis(aMallocSizeOf);
373 } else {
374 return 0;
378 size_t SharedStringBundle::SizeOfIncludingThis(
379 mozilla::MallocSizeOf aMallocSizeOf) {
380 size_t n = 0;
381 if (mStringMap) {
382 n += aMallocSizeOf(mStringMap);
384 return aMallocSizeOf(this) + n;
387 NS_IMETHODIMP
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();
404 } else {
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));
418 if (sharedSize) {
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 "
428 "processes."_ns;
430 aHandleReport->Callback(""_ns, path, KIND_HEAP, UNITS_BYTES, heapSize, desc,
431 aData);
433 if (sharedSize) {
434 path.ReplaceLiteral(0, sizeof("explicit/") - 1, "shared-");
436 aHandleReport->Callback(""_ns, path, KIND_OTHER, UNITS_BYTES, sharedSize,
437 desc, aData);
440 return NS_OK;
443 nsresult nsStringBundleBase::ParseProperties(nsIPersistentProperties** aProps) {
444 // this is different than mLoaded, because we only want to attempt
445 // to load once
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;
460 nsresult rv;
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
468 nsCString scheme;
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);
479 if (result.isOk()) {
480 MOZ_TRY(NS_NewCStringInputStream(getter_AddRefs(in), result.unwrap()));
481 } else {
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);
504 mLoaded = true;
505 return NS_OK;
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;
515 if (mProps) {
516 return NS_OK;
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);
526 mMapFile.reset();
527 return NS_OK;
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)));
555 bool hasMore;
556 while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
557 nsCOMPtr<nsISupports> next;
558 MOZ_TRY(iter->GetNext(getter_AddRefs(next)));
560 nsresult rv;
561 nsCOMPtr<nsIPropertyElement> elem = do_QueryInterface(next, &rv);
562 MOZ_TRY(rv);
564 nsCString key;
565 nsString value;
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());
576 return NS_OK;
579 void SharedStringBundle::SetMapFile(const FileDescriptor& aFile, size_t aSize) {
580 MOZ_ASSERT(XRE_IsContentProcess());
581 mStringMap = nullptr;
582 mMapFile.emplace(aFile);
583 mMapSize = aSize;
586 NS_IMETHODIMP
587 nsStringBundleBase::GetStringFromID(int32_t aID, nsAString& aResult) {
588 nsAutoCString idStr;
589 idStr.AppendInt(aID, 10);
590 return GetStringFromName(idStr.get(), aResult);
593 NS_IMETHODIMP
594 nsStringBundleBase::GetStringFromAUTF8Name(const nsACString& aName,
595 nsAString& aResult) {
596 return GetStringFromName(PromiseFlatCString(aName).get(), aResult);
599 NS_IMETHODIMP
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)) {
620 return NS_OK;
622 return NS_ERROR_FAILURE;
625 NS_IMETHODIMP
626 nsStringBundleBase::FormatStringFromID(int32_t aID,
627 const nsTArray<nsString>& aParams,
628 nsAString& aResult) {
629 nsAutoCString idStr;
630 idStr.AppendInt(aID, 10);
631 return FormatStringFromName(idStr.get(), aParams, aResult);
634 // this function supports at most 10 parameters.. see below for why
635 NS_IMETHODIMP
636 nsStringBundleBase::FormatStringFromAUTF8Name(const nsACString& aName,
637 const nsTArray<nsString>& aParams,
638 nsAString& aResult) {
639 return FormatStringFromName(PromiseFlatCString(aName).get(), aParams,
640 aResult);
643 // this function supports at most 10 parameters.. see below for why
644 NS_IMETHODIMP
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);
659 NS_IMETHODIMP
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);
679 return NS_OK;
682 NS_IMETHODIMP
683 StringMapEnumerator::HasMoreElements(bool* aHasMore) {
684 *aHasMore = mIndex < mStringMap->Count();
685 return NS_OK;
688 NS_IMETHODIMP
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));
697 elem.forget(aNext);
699 mIndex++;
700 return NS_OK;
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
714 // -alecf
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);
727 return NS_OK;
730 /////////////////////////////////////////////////////////////////////////////////////////
732 #define MAX_CACHED_BUNDLES 16
734 struct bundleCacheEntry_t final : public LinkedListElement<bundleCacheEntry_t> {
735 nsCString mHashKey;
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();
756 if (os) {
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);
765 return NS_OK;
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;
778 NS_IMETHODIMP
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);
789 return NS_OK;
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());
801 } else {
802 mBundleMap.Remove(entry->mHashKey);
806 mBundleCache = std::move(newList);
809 NS_IMETHODIMP
810 nsStringBundleService::FlushBundles() {
811 flushBundleCache(/* ignoreShared = */ false);
812 return NS_OK;
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,
831 size_t aMapSize) {
832 RefPtr<StringBundleProxy> proxy;
834 bundleCacheEntry_t* cacheEntry = mBundleMap.Get(aBundleURL);
835 if (cacheEntry) {
836 if (RefPtr<SharedStringBundle> shared =
837 do_QueryObject(cacheEntry->mBundle)) {
838 return;
841 proxy = do_QueryObject(cacheEntry->mBundle);
842 MOZ_ASSERT(proxy);
843 cacheEntry->remove();
844 delete cacheEntry;
847 auto bundle = MakeBundleRefPtr<SharedStringBundle>(
848 PromiseFlatCString(aBundleURL).get());
849 bundle->SetMapFile(aMapFile, aMapSize);
851 if (proxy) {
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;
866 if (cacheEntry) {
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);
871 } else {
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.
891 if (isContent) {
892 if (XRE_IsParentProcess()) {
893 shared = MakeBundle<SharedStringBundle>(aURLSpec);
894 bundle = shared;
895 } else {
896 bundle = new StringBundleProxy(bundle.forget());
900 cacheEntry = insertIntoCache(bundle.forget(), key);
903 if (shared) {
904 mSharedBundles.insertBack(cacheEntry);
905 } else {
906 mBundleCache.insertBack(cacheEntry);
909 // finally, return the value
910 *aResult = cacheEntry->mBundle;
911 NS_ADDREF(*aResult);
914 UniquePtr<bundleCacheEntry_t> nsStringBundleService::evictOneEntry() {
915 for (auto* entry : mBundleCache) {
916 auto* bundle = nsStringBundleBase::Cast(entry->mBundle);
917 if (!bundle->IsShared()) {
918 entry->remove();
919 mBundleMap.Remove(entry->mHashKey);
920 return UniquePtr<bundleCacheEntry_t>(entry);
923 return nullptr;
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();
934 if (!cacheEntry) {
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();
946 NS_IMETHODIMP
947 nsStringBundleService::CreateBundle(const char* aURLSpec,
948 nsIStringBundle** aResult) {
949 getStringBundle(aURLSpec, aResult);
950 return NS_OK;
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) {
958 nsresult rv;
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).
966 if (NS_FAILED(rv)) {
967 AutoTArray<nsString, 1> otherArgArray;
968 otherArgArray.AppendElement()->AppendInt(static_cast<uint32_t>(aStatus),
969 16);
970 uint16_t code = NS_ERROR_GET_CODE(NS_ERROR_FAILURE);
971 rv = bundle->FormatStringFromID(code, otherArgArray, result);
974 return rv;
977 NS_IMETHODIMP
978 nsStringBundleService::FormatStatusMessage(nsresult aStatus,
979 const char16_t* aStatusArg,
980 nsAString& result) {
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);
987 return NS_OK;
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) {
1004 int32_t offset = 0;
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));
1009 offset = pos + 1;
1013 switch (NS_ERROR_GET_MODULE(aStatus)) {
1014 case NS_ERROR_MODULE_XSLT:
1015 getStringBundle(XSLT_MSGS_URL, getter_AddRefs(bundle));
1016 break;
1017 case NS_ERROR_MODULE_NETWORK:
1018 getStringBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
1019 break;
1020 default:
1021 getStringBundle(GLOBAL_PROPERTIES, getter_AddRefs(bundle));
1022 break;
1025 return FormatWithBundle(bundle, aStatus, argArray, result);