summaryrefslogtreecommitdiffstats
path: root/intl/strres
diff options
context:
space:
mode:
Diffstat (limited to 'intl/strres')
-rw-r--r--intl/strres/moz.build24
-rw-r--r--intl/strres/nsIStringBundle.idl78
-rw-r--r--intl/strres/nsIStringBundleOverride.idl25
-rw-r--r--intl/strres/nsStringBundle.cpp769
-rw-r--r--intl/strres/nsStringBundle.h81
-rw-r--r--intl/strres/nsStringBundleService.h56
-rw-r--r--intl/strres/nsStringBundleTextOverride.cpp286
-rw-r--r--intl/strres/nsStringBundleTextOverride.h41
-rw-r--r--intl/strres/tests/unit/397093.properties4
-rw-r--r--intl/strres/tests/unit/strres.properties14
-rw-r--r--intl/strres/tests/unit/test_bug378839.js64
-rw-r--r--intl/strres/tests/unit/test_bug397093.js43
-rw-r--r--intl/strres/tests/unit/xpcshell.ini9
13 files changed, 1494 insertions, 0 deletions
diff --git a/intl/strres/moz.build b/intl/strres/moz.build
new file mode 100644
index 000000000..1a10ef82f
--- /dev/null
+++ b/intl/strres/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+XPIDL_SOURCES += [
+ 'nsIStringBundle.idl',
+ 'nsIStringBundleOverride.idl',
+]
+
+XPIDL_MODULE = 'intl'
+
+UNIFIED_SOURCES += [
+ 'nsStringBundle.cpp',
+ 'nsStringBundleTextOverride.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/intl/strres/nsIStringBundle.idl b/intl/strres/nsIStringBundle.idl
new file mode 100644
index 000000000..ca734849a
--- /dev/null
+++ b/intl/strres/nsIStringBundle.idl
@@ -0,0 +1,78 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+
+%{C++
+
+// Define Contractid and CID
+// {D85A17C1-AA7C-11d2-9B8C-00805F8A16D9}
+#define NS_STRINGBUNDLESERVICE_CID \
+{ 0xd85a17c1, 0xaa7c, 0x11d2, \
+ { 0x9b, 0x8c, 0x0, 0x80, 0x5f, 0x8a, 0x16, 0xd9 } }
+
+#define NS_STRINGBUNDLE_CONTRACTID "@mozilla.org/intl/stringbundle;1"
+
+/**
+ * observer needs to check if the bundle handle matches
+ */
+#define NS_STRBUNDLE_LOADED_TOPIC "strbundle-loaded"
+
+%}
+
+native nsStrBundleLoadedFunc(nsStrBundleLoadedFunc);
+
+[scriptable, uuid(D85A17C2-AA7C-11d2-9B8C-00805F8A16D9)]
+interface nsIStringBundle : nsISupports
+{
+ wstring GetStringFromID(in long aID);
+ wstring GetStringFromName(in wstring aName);
+
+ // this is kind of like smprintf - except that you can
+ // only pass it unicode strings, using the %S formatting character.
+ // the id or name should refer to a string in the bundle that
+ // uses %S.. do NOT try to use any other types.
+ // this uses nsTextFormatter::smprintf to do the dirty work.
+ wstring formatStringFromID(in long aID,
+ [array, size_is(length)] in wstring params,
+ in unsigned long length);
+ wstring formatStringFromName(in wstring aName,
+ [array, size_is(length)] in wstring params,
+ in unsigned long length);
+ /*
+ Implements nsISimpleEnumerator, replaces nsIEnumerator
+ */
+ nsISimpleEnumerator getSimpleEnumeration();
+
+};
+
+[scriptable, uuid(D85A17C0-AA7C-11d2-9B8C-00805F8A16D9)]
+interface nsIStringBundleService : nsISupports
+{
+ nsIStringBundle createBundle(in string aURLSpec);
+ nsIStringBundle createExtensibleBundle(in string aRegistryKey);
+
+ /**
+ * Formats a message string from a status code and status arguments.
+ * @param aStatus - The status code. This is mapped into a string ID and
+ * and used in the string lookup process (see nsIErrorService).
+ * @param aStatusArg - The status message argument(s). Multiple arguments
+ * can be separated by newline ('\n') characters.
+ * @return the formatted message
+ */
+ wstring formatStatusMessage(in nsresult aStatus, in wstring aStatusArg);
+
+ /**
+ * flushes the string bundle cache - useful when the locale changes or
+ * when we need to get some extra memory back
+ *
+ * at some point, we might want to make this flush all the bundles,
+ * because any bundles that are floating around when the locale changes
+ * will suddenly contain bad data
+ *
+ */
+ void flushBundles();
+};
diff --git a/intl/strres/nsIStringBundleOverride.idl b/intl/strres/nsIStringBundleOverride.idl
new file mode 100644
index 000000000..280717107
--- /dev/null
+++ b/intl/strres/nsIStringBundleOverride.idl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsISimpleEnumerator;
+
+// to be implemented by an embeddor that wants to override some strings
+[scriptable, uuid(965eb278-5678-456b-82a7-20a0c86a803c)]
+interface nsIStringBundleOverride : nsISupports
+{
+ /**
+ * get the override value for a particular key in a particular
+ * string bundle
+ */
+ AString getStringFromName(in AUTF8String url,
+ in ACString key);
+
+ /**
+ * get all override keys for a given string bundle
+ */
+ nsISimpleEnumerator enumerateKeysInBundle(in AUTF8String url);
+};
diff --git a/intl/strres/nsStringBundle.cpp b/intl/strres/nsStringBundle.cpp
new file mode 100644
index 000000000..ab840a469
--- /dev/null
+++ b/intl/strres/nsStringBundle.cpp
@@ -0,0 +1,769 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStringBundle.h"
+#include "nsID.h"
+#include "nsString.h"
+#include "nsIStringBundle.h"
+#include "nsStringBundleService.h"
+#include "nsStringBundleTextOverride.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIMutableArray.h"
+#include "nsArrayEnumerator.h"
+#include "nscore.h"
+#include "nsMemory.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIInputStream.h"
+#include "nsIURI.h"
+#include "nsIObserverService.h"
+#include "nsCOMArray.h"
+#include "nsTextFormatter.h"
+#include "nsIErrorService.h"
+#include "nsICategoryManager.h"
+#include "nsContentUtils.h"
+
+// for async loading
+#ifdef ASYNC_LOADING
+#include "nsIBinaryInputStream.h"
+#include "nsIStringStream.h"
+#endif
+
+using namespace mozilla;
+
+static NS_DEFINE_CID(kErrorServiceCID, NS_ERRORSERVICE_CID);
+
+nsStringBundle::~nsStringBundle()
+{
+}
+
+nsStringBundle::nsStringBundle(const char* aURLSpec,
+ nsIStringBundleOverride* aOverrideStrings) :
+ mPropertiesURL(aURLSpec),
+ mOverrideStrings(aOverrideStrings),
+ mReentrantMonitor("nsStringBundle.mReentrantMonitor"),
+ mAttemptedLoad(false),
+ mLoaded(false)
+{
+}
+
+nsresult
+nsStringBundle::LoadProperties()
+{
+ // this is different than mLoaded, because we only want to attempt
+ // to load once
+ // we only want to load once, but if we've tried once and failed,
+ // continue to throw an error!
+ if (mAttemptedLoad) {
+ if (mLoaded)
+ return NS_OK;
+
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mAttemptedLoad = true;
+
+ nsresult rv;
+
+ // do it synchronously
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), mPropertiesURL);
+ if (NS_FAILED(rv)) return rv;
+
+ // whitelist check for local schemes
+ nsCString scheme;
+ uri->GetScheme(scheme);
+ if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("jar") &&
+ !scheme.EqualsLiteral("resource") && !scheme.EqualsLiteral("file") &&
+ !scheme.EqualsLiteral("data")) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (NS_FAILED(rv)) return rv;
+
+ // It's a string bundle. We expect a text/plain type, so set that as hint
+ channel->SetContentType(NS_LITERAL_CSTRING("text/plain"));
+
+ nsCOMPtr<nsIInputStream> in;
+ rv = channel->Open2(getter_AddRefs(in));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(NS_SUCCEEDED(rv) && in, "Error in OpenBlockingStream");
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && in, NS_ERROR_FAILURE);
+
+ static NS_DEFINE_CID(kPersistentPropertiesCID, NS_IPERSISTENTPROPERTIES_CID);
+ mProps = do_CreateInstance(kPersistentPropertiesCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mAttemptedLoad = mLoaded = true;
+ rv = mProps->Load(in);
+
+ mLoaded = NS_SUCCEEDED(rv);
+
+ return rv;
+}
+
+
+nsresult
+nsStringBundle::GetStringFromID(int32_t aID, nsAString& aResult)
+{
+ ReentrantMonitorAutoEnter automon(mReentrantMonitor);
+ nsAutoCString name;
+ name.AppendInt(aID, 10);
+
+ nsresult rv;
+
+ // try override first
+ if (mOverrideStrings) {
+ rv = mOverrideStrings->GetStringFromName(mPropertiesURL,
+ name,
+ aResult);
+ if (NS_SUCCEEDED(rv)) return rv;
+ }
+
+ rv = mProps->GetStringProperty(name, aResult);
+
+ return rv;
+}
+
+nsresult
+nsStringBundle::GetStringFromName(const nsAString& aName,
+ nsAString& aResult)
+{
+ nsresult rv;
+
+ // try override first
+ if (mOverrideStrings) {
+ rv = mOverrideStrings->GetStringFromName(mPropertiesURL,
+ NS_ConvertUTF16toUTF8(aName),
+ aResult);
+ if (NS_SUCCEEDED(rv)) return rv;
+ }
+
+ rv = mProps->GetStringProperty(NS_ConvertUTF16toUTF8(aName), aResult);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStringBundle::FormatStringFromID(int32_t aID,
+ const char16_t **aParams,
+ uint32_t aLength,
+ char16_t ** aResult)
+{
+ nsAutoString idStr;
+ idStr.AppendInt(aID, 10);
+
+ return FormatStringFromName(idStr.get(), aParams, aLength, aResult);
+}
+
+// this function supports at most 10 parameters.. see below for why
+NS_IMETHODIMP
+nsStringBundle::FormatStringFromName(const char16_t *aName,
+ const char16_t **aParams,
+ uint32_t aLength,
+ char16_t **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ASSERTION(aParams && aLength, "FormatStringFromName() without format parameters: use GetStringFromName() instead");
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ rv = LoadProperties();
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString formatStr;
+ rv = GetStringFromName(nsDependentString(aName), formatStr);
+ if (NS_FAILED(rv)) return rv;
+
+ return FormatString(formatStr.get(), aParams, aLength, aResult);
+}
+
+
+NS_IMPL_ISUPPORTS(nsStringBundle, nsIStringBundle)
+
+NS_IMETHODIMP
+nsStringBundle::GetStringFromID(int32_t aID, char16_t **aResult)
+{
+ nsresult rv;
+ rv = LoadProperties();
+ if (NS_FAILED(rv)) return rv;
+
+ *aResult = nullptr;
+ nsAutoString tmpstr;
+
+ rv = GetStringFromID(aID, tmpstr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = ToNewUnicode(tmpstr);
+ NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringBundle::GetStringFromName(const char16_t *aName, char16_t **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ rv = LoadProperties();
+ if (NS_FAILED(rv)) return rv;
+
+ ReentrantMonitorAutoEnter automon(mReentrantMonitor);
+ *aResult = nullptr;
+ nsAutoString tmpstr;
+ rv = GetStringFromName(nsDependentString(aName), tmpstr);
+ if (NS_FAILED(rv))
+ {
+#if 0
+ // it is not uncommon for apps to request a string name which may not exist
+ // so be quiet about it.
+ NS_WARNING("String missing from string bundle");
+ printf(" '%s' missing from bundle %s\n", NS_ConvertUTF16toUTF8(aName).get(), mPropertiesURL.get());
+#endif
+ return rv;
+ }
+
+ *aResult = ToNewUnicode(tmpstr);
+ NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
+
+ return NS_OK;
+}
+
+nsresult
+nsStringBundle::GetCombinedEnumeration(nsIStringBundleOverride* aOverrideStrings,
+ nsISimpleEnumerator** aResult)
+{
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIPropertyElement> propElement;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIMutableArray> resultArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // first, append the override elements
+ nsCOMPtr<nsISimpleEnumerator> overrideEnumerator;
+ rv = aOverrideStrings->EnumerateKeysInBundle(mPropertiesURL,
+ getter_AddRefs(overrideEnumerator));
+
+ bool hasMore;
+ rv = overrideEnumerator->HasMoreElements(&hasMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (hasMore) {
+
+ rv = overrideEnumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv))
+ resultArray->AppendElement(supports, false);
+
+ rv = overrideEnumerator->HasMoreElements(&hasMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // ok, now we have the override elements in resultArray
+ nsCOMPtr<nsISimpleEnumerator> propEnumerator;
+ rv = mProps->Enumerate(getter_AddRefs(propEnumerator));
+ if (NS_FAILED(rv)) {
+ // no elements in mProps anyway, just return what we have
+ return NS_NewArrayEnumerator(aResult, resultArray);
+ }
+
+ // second, append all the elements that are in mProps
+ do {
+ rv = propEnumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv) &&
+ (propElement = do_QueryInterface(supports, &rv))) {
+
+ // now check if its in the override bundle
+ nsAutoCString key;
+ propElement->GetKey(key);
+
+ nsAutoString value;
+ rv = aOverrideStrings->GetStringFromName(mPropertiesURL, key, value);
+
+ // if it isn't there, then it is safe to append
+ if (NS_FAILED(rv))
+ resultArray->AppendElement(propElement, false);
+ }
+
+ rv = propEnumerator->HasMoreElements(&hasMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (hasMore);
+
+ return resultArray->Enumerate(aResult);
+}
+
+
+NS_IMETHODIMP
+nsStringBundle::GetSimpleEnumeration(nsISimpleEnumerator** elements)
+{
+ if (!elements)
+ return NS_ERROR_INVALID_POINTER;
+
+ nsresult rv;
+ rv = LoadProperties();
+ if (NS_FAILED(rv)) return rv;
+
+ if (mOverrideStrings)
+ return GetCombinedEnumeration(mOverrideStrings, elements);
+
+ return mProps->Enumerate(elements);
+}
+
+nsresult
+nsStringBundle::FormatString(const char16_t *aFormatStr,
+ const char16_t **aParams, uint32_t aLength,
+ char16_t **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG(aLength <= 10); // enforce 10-parameter limit
+
+ // implementation note: you would think you could use vsmprintf
+ // to build up an arbitrary length array.. except that there
+ // is no way to build up a va_list at runtime!
+ // Don't believe me? See:
+ // http://www.eskimo.com/~scs/C-faq/q15.13.html
+ // -alecf
+ char16_t *text =
+ nsTextFormatter::smprintf(aFormatStr,
+ aLength >= 1 ? aParams[0] : nullptr,
+ aLength >= 2 ? aParams[1] : nullptr,
+ aLength >= 3 ? aParams[2] : nullptr,
+ aLength >= 4 ? aParams[3] : nullptr,
+ aLength >= 5 ? aParams[4] : nullptr,
+ aLength >= 6 ? aParams[5] : nullptr,
+ aLength >= 7 ? aParams[6] : nullptr,
+ aLength >= 8 ? aParams[7] : nullptr,
+ aLength >= 9 ? aParams[8] : nullptr,
+ aLength >= 10 ? aParams[9] : nullptr);
+
+ if (!text) {
+ *aResult = nullptr;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // nsTextFormatter does not use the shared nsMemory allocator.
+ // Instead it is required to free the memory it allocates using
+ // nsTextFormatter::smprintf_free. Let's instead use nsMemory based
+ // allocation for the result that we give out and free the string
+ // returned by smprintf ourselves!
+ *aResult = NS_strdup(text);
+ nsTextFormatter::smprintf_free(text);
+
+ return *aResult ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMPL_ISUPPORTS(nsExtensibleStringBundle, nsIStringBundle)
+
+nsExtensibleStringBundle::nsExtensibleStringBundle()
+{
+ mLoaded = false;
+}
+
+nsresult
+nsExtensibleStringBundle::Init(const char * aCategory,
+ nsIStringBundleService* aBundleService)
+{
+
+ nsresult rv;
+ nsCOMPtr<nsICategoryManager> catman =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = catman->EnumerateCategory(aCategory, getter_AddRefs(enumerator));
+ if (NS_FAILED(rv)) return rv;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsISupportsCString> supStr = do_QueryInterface(supports, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsAutoCString name;
+ rv = supStr->GetData(name);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = aBundleService->CreateBundle(name.get(), getter_AddRefs(bundle));
+ if (NS_FAILED(rv))
+ continue;
+
+ mBundles.AppendObject(bundle);
+ }
+
+ return rv;
+}
+
+nsExtensibleStringBundle::~nsExtensibleStringBundle()
+{
+}
+
+nsresult nsExtensibleStringBundle::GetStringFromID(int32_t aID, char16_t ** aResult)
+{
+ nsresult rv;
+ const uint32_t size = mBundles.Count();
+ for (uint32_t i = 0; i < size; ++i) {
+ nsIStringBundle *bundle = mBundles[i];
+ if (bundle) {
+ rv = bundle->GetStringFromID(aID, aResult);
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsExtensibleStringBundle::GetStringFromName(const char16_t *aName,
+ char16_t ** aResult)
+{
+ nsresult rv;
+ const uint32_t size = mBundles.Count();
+ for (uint32_t i = 0; i < size; ++i) {
+ nsIStringBundle* bundle = mBundles[i];
+ if (bundle) {
+ rv = bundle->GetStringFromName(aName, aResult);
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsExtensibleStringBundle::FormatStringFromID(int32_t aID,
+ const char16_t ** aParams,
+ uint32_t aLength,
+ char16_t ** aResult)
+{
+ nsAutoString idStr;
+ idStr.AppendInt(aID, 10);
+ return FormatStringFromName(idStr.get(), aParams, aLength, aResult);
+}
+
+NS_IMETHODIMP
+nsExtensibleStringBundle::FormatStringFromName(const char16_t *aName,
+ const char16_t ** aParams,
+ uint32_t aLength,
+ char16_t ** aResult)
+{
+ nsXPIDLString formatStr;
+ nsresult rv;
+ rv = GetStringFromName(aName, getter_Copies(formatStr));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return nsStringBundle::FormatString(formatStr, aParams, aLength, aResult);
+}
+
+nsresult nsExtensibleStringBundle::GetSimpleEnumeration(nsISimpleEnumerator ** aResult)
+{
+ // XXX write me
+ *aResult = nullptr;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#define MAX_CACHED_BUNDLES 16
+
+struct bundleCacheEntry_t final : public LinkedListElement<bundleCacheEntry_t> {
+ nsCString mHashKey;
+ nsCOMPtr<nsIStringBundle> mBundle;
+
+ bundleCacheEntry_t()
+ {
+ MOZ_COUNT_CTOR(bundleCacheEntry_t);
+ }
+
+ ~bundleCacheEntry_t()
+ {
+ MOZ_COUNT_DTOR(bundleCacheEntry_t);
+ }
+};
+
+
+nsStringBundleService::nsStringBundleService() :
+ mBundleMap(MAX_CACHED_BUNDLES)
+{
+ mErrorService = do_GetService(kErrorServiceCID);
+ NS_ASSERTION(mErrorService, "Couldn't get error service");
+}
+
+NS_IMPL_ISUPPORTS(nsStringBundleService,
+ nsIStringBundleService,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+nsStringBundleService::~nsStringBundleService()
+{
+ flushBundleCache();
+}
+
+nsresult
+nsStringBundleService::Init()
+{
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, "memory-pressure", true);
+ os->AddObserver(this, "profile-do-change", true);
+ os->AddObserver(this, "chrome-flush-caches", true);
+ os->AddObserver(this, "xpcom-category-entry-added", true);
+ }
+
+ // instantiate the override service, if there is any.
+ // at some point we probably want to make this a category, and
+ // support multiple overrides
+ mOverrideStrings = do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringBundleService::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData)
+{
+ if (strcmp("memory-pressure", aTopic) == 0 ||
+ strcmp("profile-do-change", aTopic) == 0 ||
+ strcmp("chrome-flush-caches", aTopic) == 0)
+ {
+ flushBundleCache();
+ }
+ else if (strcmp("xpcom-category-entry-added", aTopic) == 0 &&
+ NS_LITERAL_STRING("xpcom-autoregistration").Equals(aSomeData))
+ {
+ mOverrideStrings = do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID);
+ }
+
+ return NS_OK;
+}
+
+void
+nsStringBundleService::flushBundleCache()
+{
+ // release all bundles in the cache
+ mBundleMap.Clear();
+
+ while (!mBundleCache.isEmpty()) {
+ delete mBundleCache.popFirst();
+ }
+}
+
+NS_IMETHODIMP
+nsStringBundleService::FlushBundles()
+{
+ flushBundleCache();
+ return NS_OK;
+}
+
+nsresult
+nsStringBundleService::getStringBundle(const char *aURLSpec,
+ nsIStringBundle **aResult)
+{
+ nsDependentCString key(aURLSpec);
+ bundleCacheEntry_t* cacheEntry = mBundleMap.Get(key);
+
+ if (cacheEntry) {
+ // cache hit!
+ // remove it from the list, it will later be reinserted
+ // at the head of the list
+ cacheEntry->remove();
+
+ } else {
+
+ // hasn't been cached, so insert it into the hash table
+ RefPtr<nsStringBundle> bundle = new nsStringBundle(aURLSpec, mOverrideStrings);
+ cacheEntry = insertIntoCache(bundle.forget(), key);
+ }
+
+ // at this point the cacheEntry should exist in the hashtable,
+ // but is not in the LRU cache.
+ // put the cache entry at the front of the list
+ mBundleCache.insertFront(cacheEntry);
+
+ // finally, return the value
+ *aResult = cacheEntry->mBundle;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+bundleCacheEntry_t *
+nsStringBundleService::insertIntoCache(already_AddRefed<nsIStringBundle> aBundle,
+ nsCString &aHashKey)
+{
+ bundleCacheEntry_t *cacheEntry;
+
+ if (mBundleMap.Count() < MAX_CACHED_BUNDLES) {
+ // cache not full - create a new entry
+ cacheEntry = new bundleCacheEntry_t();
+ } else {
+ // cache is full
+ // take the last entry in the list, and recycle it.
+ cacheEntry = mBundleCache.getLast();
+
+ // remove it from the hash table and linked list
+ NS_ASSERTION(mBundleMap.Contains(cacheEntry->mHashKey),
+ "Element will not be removed!");
+ mBundleMap.Remove(cacheEntry->mHashKey);
+ cacheEntry->remove();
+ }
+
+ // at this point we have a new cacheEntry that doesn't exist
+ // in the hashtable, so set up the cacheEntry
+ cacheEntry->mHashKey = aHashKey;
+ cacheEntry->mBundle = aBundle;
+
+ // insert the entry into the cache and map, make it the MRU
+ mBundleMap.Put(cacheEntry->mHashKey, cacheEntry);
+
+ return cacheEntry;
+}
+
+NS_IMETHODIMP
+nsStringBundleService::CreateBundle(const char* aURLSpec,
+ nsIStringBundle** aResult)
+{
+ return getStringBundle(aURLSpec,aResult);
+}
+
+NS_IMETHODIMP
+nsStringBundleService::CreateExtensibleBundle(const char* aCategory,
+ nsIStringBundle** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ RefPtr<nsExtensibleStringBundle> bundle = new nsExtensibleStringBundle();
+
+ nsresult res = bundle->Init(aCategory, this);
+ if (NS_FAILED(res)) {
+ return res;
+ }
+
+ bundle.forget(aResult);
+ return NS_OK;
+}
+
+#define GLOBAL_PROPERTIES "chrome://global/locale/global-strres.properties"
+
+nsresult
+nsStringBundleService::FormatWithBundle(nsIStringBundle* bundle, nsresult aStatus,
+ uint32_t argCount, char16_t** argArray,
+ char16_t* *result)
+{
+ nsresult rv;
+ nsXPIDLCString key;
+
+ // try looking up the error message with the int key:
+ uint16_t code = NS_ERROR_GET_CODE(aStatus);
+ rv = bundle->FormatStringFromID(code, (const char16_t**)argArray, argCount, result);
+
+ // If the int key fails, try looking up the default error message. E.g. print:
+ // An unknown error has occurred (0x804B0003).
+ if (NS_FAILED(rv)) {
+ nsAutoString statusStr;
+ statusStr.AppendInt(static_cast<uint32_t>(aStatus), 16);
+ const char16_t* otherArgArray[1];
+ otherArgArray[0] = statusStr.get();
+ uint16_t code = NS_ERROR_GET_CODE(NS_ERROR_FAILURE);
+ rv = bundle->FormatStringFromID(code, otherArgArray, 1, result);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStringBundleService::FormatStatusMessage(nsresult aStatus,
+ const char16_t* aStatusArg,
+ char16_t* *result)
+{
+ nsresult rv;
+ uint32_t i, argCount = 0;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsXPIDLCString stringBundleURL;
+
+ // XXX hack for mailnews who has already formatted their messages:
+ if (aStatus == NS_OK && aStatusArg) {
+ *result = NS_strdup(aStatusArg);
+ NS_ENSURE_TRUE(*result, NS_ERROR_OUT_OF_MEMORY);
+ return NS_OK;
+ }
+
+ if (aStatus == NS_OK) {
+ return NS_ERROR_FAILURE; // no message to format
+ }
+
+ // format the arguments:
+ const nsDependentString args(aStatusArg);
+ argCount = args.CountChar(char16_t('\n')) + 1;
+ NS_ENSURE_ARG(argCount <= 10); // enforce 10-parameter limit
+ char16_t* argArray[10];
+
+ // convert the aStatusArg into a char16_t array
+ if (argCount == 1) {
+ // avoid construction for the simple case:
+ argArray[0] = (char16_t*)aStatusArg;
+ }
+ else if (argCount > 1) {
+ int32_t offset = 0;
+ for (i = 0; i < argCount; i++) {
+ int32_t pos = args.FindChar('\n', offset);
+ if (pos == -1)
+ pos = args.Length();
+ argArray[i] = ToNewUnicode(Substring(args, offset, pos - offset));
+ if (argArray[i] == nullptr) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ argCount = i - 1; // don't try to free uninitialized memory
+ goto done;
+ }
+ offset = pos + 1;
+ }
+ }
+
+ // find the string bundle for the error's module:
+ rv = mErrorService->GetErrorStringBundle(NS_ERROR_GET_MODULE(aStatus),
+ getter_Copies(stringBundleURL));
+ if (NS_SUCCEEDED(rv)) {
+ rv = getStringBundle(stringBundleURL, getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ rv = FormatWithBundle(bundle, aStatus, argCount, argArray, result);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ rv = getStringBundle(GLOBAL_PROPERTIES, getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ rv = FormatWithBundle(bundle, aStatus, argCount, argArray, result);
+ }
+ }
+
+done:
+ if (argCount > 1) {
+ for (i = 0; i < argCount; i++) {
+ if (argArray[i])
+ free(argArray[i]);
+ }
+ }
+ return rv;
+}
diff --git a/intl/strres/nsStringBundle.h b/intl/strres/nsStringBundle.h
new file mode 100644
index 000000000..d52f04724
--- /dev/null
+++ b/intl/strres/nsStringBundle.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStringBundle_h__
+#define nsStringBundle_h__
+
+#include "mozilla/ReentrantMonitor.h"
+#include "nsIStringBundle.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsCOMArray.h"
+
+class nsIPersistentProperties;
+class nsIStringBundleOverride;
+
+class nsStringBundle : public nsIStringBundle
+{
+public:
+ // init version
+ nsStringBundle(const char* aURLSpec, nsIStringBundleOverride*);
+ nsresult LoadProperties();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTRINGBUNDLE
+
+ nsCOMPtr<nsIPersistentProperties> mProps;
+
+protected:
+ virtual ~nsStringBundle();
+
+ //
+ // functional decomposition of the funitions repeatively called
+ //
+ nsresult GetStringFromID(int32_t aID, nsAString& aResult);
+ nsresult GetStringFromName(const nsAString& aName, nsAString& aResult);
+
+ nsresult GetCombinedEnumeration(nsIStringBundleOverride* aOverrideString,
+ nsISimpleEnumerator** aResult);
+private:
+ nsCString mPropertiesURL;
+ nsCOMPtr<nsIStringBundleOverride> mOverrideStrings;
+ mozilla::ReentrantMonitor mReentrantMonitor;
+ bool mAttemptedLoad;
+ bool mLoaded;
+
+public:
+ static nsresult FormatString(const char16_t *formatStr,
+ const char16_t **aParams, uint32_t aLength,
+ char16_t **aResult);
+};
+
+class nsExtensibleStringBundle;
+
+/**
+ * An extensible implementation of the StringBundle interface.
+ *
+ * @created 28/Dec/1999
+ * @author Catalin Rotaru [CATA]
+ */
+class nsExtensibleStringBundle final : public nsIStringBundle
+{
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTRINGBUNDLE
+
+ nsresult Init(const char * aCategory, nsIStringBundleService *);
+
+public:
+ nsExtensibleStringBundle();
+
+private:
+ virtual ~nsExtensibleStringBundle();
+
+ nsCOMArray<nsIStringBundle> mBundles;
+ bool mLoaded;
+};
+
+
+
+#endif
diff --git a/intl/strres/nsStringBundleService.h b/intl/strres/nsStringBundleService.h
new file mode 100644
index 000000000..a192cdff8
--- /dev/null
+++ b/intl/strres/nsStringBundleService.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStringBundleService_h__
+#define nsStringBundleService_h__
+
+#include "nsCOMPtr.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIStringBundle.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsIErrorService.h"
+#include "nsIStringBundleOverride.h"
+
+#include "mozilla/LinkedList.h"
+
+struct bundleCacheEntry_t;
+
+class nsStringBundleService : public nsIStringBundleService,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ nsStringBundleService();
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTRINGBUNDLESERVICE
+ NS_DECL_NSIOBSERVER
+
+private:
+ virtual ~nsStringBundleService();
+
+ nsresult getStringBundle(const char *aUrl, nsIStringBundle** aResult);
+ nsresult FormatWithBundle(nsIStringBundle* bundle, nsresult aStatus,
+ uint32_t argCount, char16_t** argArray,
+ char16_t* *result);
+
+ void flushBundleCache();
+
+ bundleCacheEntry_t *insertIntoCache(already_AddRefed<nsIStringBundle> aBundle,
+ nsCString &aHashKey);
+
+ nsDataHashtable<nsCStringHashKey, bundleCacheEntry_t*> mBundleMap;
+ mozilla::LinkedList<bundleCacheEntry_t> mBundleCache;
+
+ nsCOMPtr<nsIErrorService> mErrorService;
+ nsCOMPtr<nsIStringBundleOverride> mOverrideStrings;
+};
+
+#endif
diff --git a/intl/strres/nsStringBundleTextOverride.cpp b/intl/strres/nsStringBundleTextOverride.cpp
new file mode 100644
index 000000000..ec21726d6
--- /dev/null
+++ b/intl/strres/nsStringBundleTextOverride.cpp
@@ -0,0 +1,286 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsStringBundleTextOverride.h"
+#include "nsString.h"
+
+#include "nsNetUtil.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsContentUtils.h"
+#include "nsDirectoryServiceUtils.h"
+
+// first we need a simple class which wraps a nsIPropertyElement and
+// cuts out the leading URL from the key
+class URLPropertyElement : public nsIPropertyElement
+{
+public:
+ URLPropertyElement(nsIPropertyElement *aRealElement, uint32_t aURLLength) :
+ mRealElement(aRealElement),
+ mURLLength(aURLLength)
+ { }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROPERTYELEMENT
+
+private:
+ nsCOMPtr<nsIPropertyElement> mRealElement;
+ uint32_t mURLLength;
+
+ virtual ~URLPropertyElement() {}
+};
+
+NS_IMPL_ISUPPORTS(URLPropertyElement, nsIPropertyElement)
+
+// we'll tweak the key on the way through, and remove the url prefix
+NS_IMETHODIMP
+URLPropertyElement::GetKey(nsACString& aKey)
+{
+ nsresult rv = mRealElement->GetKey(aKey);
+ if (NS_FAILED(rv)) return rv;
+
+ // chop off the url
+ aKey.Cut(0, mURLLength);
+
+ return NS_OK;
+}
+
+// values are unaffected
+NS_IMETHODIMP
+URLPropertyElement::GetValue(nsAString& aValue)
+{
+ return mRealElement->GetValue(aValue);
+}
+
+// setters are kind of strange, hopefully we'll never be called
+NS_IMETHODIMP
+URLPropertyElement::SetKey(const nsACString& aKey)
+{
+ // this is just wrong - ideally you'd take the key, append it to
+ // the url, and set that as the key. However, that would require
+ // us to hold onto a copy of the string, and that's a waste,
+ // considering nobody should ever be calling this.
+ NS_ERROR("This makes no sense!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+URLPropertyElement::SetValue(const nsAString& aValue)
+{
+ return mRealElement->SetValue(aValue);
+}
+
+
+// this is a special enumerator which returns only the elements which
+// are prefixed with a particular url
+class nsPropertyEnumeratorByURL : public nsISimpleEnumerator
+{
+public:
+ nsPropertyEnumeratorByURL(const nsACString& aURL,
+ nsISimpleEnumerator* aOuter) :
+ mOuter(aOuter),
+ mURL(aURL)
+ {
+ // prepare the url once so we can use its value later
+ // persistent properties uses ":" as a delimiter, so escape
+ // that character
+ mURL.ReplaceSubstring(":", "%3A");
+ // there is always a # between the url and the real key
+ mURL.Append('#');
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+private:
+
+ // actual enumerator of all strings from nsIProperties
+ nsCOMPtr<nsISimpleEnumerator> mOuter;
+
+ // the current element that is valid for this url
+ nsCOMPtr<nsIPropertyElement> mCurrent;
+
+ // the url in question, pre-escaped and with the # already in it
+ nsCString mURL;
+
+ virtual ~nsPropertyEnumeratorByURL() {}
+};
+
+//
+// nsStringBundleTextOverride implementation
+//
+NS_IMPL_ISUPPORTS(nsStringBundleTextOverride,
+ nsIStringBundleOverride)
+
+nsresult
+nsStringBundleTextOverride::Init()
+{
+ nsresult rv;
+
+ // check for existence of custom-strings.txt
+
+ nsCOMPtr<nsIFile> customStringsFile;
+ rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR,
+ getter_AddRefs(customStringsFile));
+
+ if (NS_FAILED(rv)) return rv;
+
+ // bail if not found - this will cause the service creation to
+ // bail as well, and cause this object to go away
+
+ customStringsFile->AppendNative(NS_LITERAL_CSTRING("custom-strings.txt"));
+
+ bool exists;
+ rv = customStringsFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return NS_ERROR_FAILURE;
+
+ NS_WARNING("Using custom-strings.txt to override string bundles.");
+ // read in the custom bundle. Keys are in the form
+ // chrome://package/locale/foo.properties:keyname
+
+ nsAutoCString customStringsURLSpec;
+ rv = NS_GetURLSpecFromFile(customStringsFile, customStringsURLSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), customStringsURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStream> in;
+ rv = channel->Open2(getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ static NS_DEFINE_CID(kPersistentPropertiesCID, NS_IPERSISTENTPROPERTIES_CID);
+ mValues = do_CreateInstance(kPersistentPropertiesCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mValues->Load(in);
+
+ // turn this on to see the contents of custom-strings.txt
+#ifdef DEBUG_alecf
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ mValues->Enumerate(getter_AddRefs(enumerator));
+ NS_ASSERTION(enumerator, "no enumerator!\n");
+
+ printf("custom-strings.txt contains:\n");
+ printf("----------------------------\n");
+
+ bool hasMore;
+ enumerator->HasMoreElements(&hasMore);
+ do {
+ nsCOMPtr<nsISupports> sup;
+ enumerator->GetNext(getter_AddRefs(sup));
+
+ nsCOMPtr<nsIPropertyElement> prop = do_QueryInterface(sup);
+
+ nsAutoCString key;
+ nsAutoString value;
+ prop->GetKey(key);
+ prop->GetValue(value);
+
+ printf("%s = '%s'\n", key.get(), NS_ConvertUTF16toUTF8(value).get());
+
+ enumerator->HasMoreElements(&hasMore);
+ } while (hasMore);
+#endif
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStringBundleTextOverride::GetStringFromName(const nsACString& aURL,
+ const nsACString& key,
+ nsAString& aResult)
+{
+ // concatenate url#key to get the key to read
+ nsAutoCString combinedURL(aURL + NS_LITERAL_CSTRING("#") + key);
+
+ // persistent properties uses ":" as a delimiter, so escape that character
+ combinedURL.ReplaceSubstring(":", "%3A");
+
+ return mValues->GetStringProperty(combinedURL, aResult);
+}
+
+NS_IMETHODIMP
+nsStringBundleTextOverride::EnumerateKeysInBundle(const nsACString& aURL,
+ nsISimpleEnumerator** aResult)
+{
+ // enumerate all strings, and let the enumerator know
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ mValues->Enumerate(getter_AddRefs(enumerator));
+
+ // make the enumerator wrapper and pass it off
+ nsPropertyEnumeratorByURL* propEnum =
+ new nsPropertyEnumeratorByURL(aURL, enumerator);
+
+ if (!propEnum) return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult = propEnum);
+
+ return NS_OK;
+}
+
+
+//
+// nsPropertyEnumeratorByURL implementation
+//
+
+
+NS_IMPL_ISUPPORTS(nsPropertyEnumeratorByURL, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+nsPropertyEnumeratorByURL::GetNext(nsISupports **aResult)
+{
+ if (!mCurrent) return NS_ERROR_UNEXPECTED;
+
+ // wrap mCurrent instead of returning it
+ *aResult = new URLPropertyElement(mCurrent, mURL.Length());
+ NS_ADDREF(*aResult);
+
+ // release it so we don't return it twice
+ mCurrent = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPropertyEnumeratorByURL::HasMoreElements(bool * aResult)
+{
+ bool hasMore;
+ mOuter->HasMoreElements(&hasMore);
+ while (hasMore) {
+
+ nsCOMPtr<nsISupports> supports;
+ mOuter->GetNext(getter_AddRefs(supports));
+
+ mCurrent = do_QueryInterface(supports);
+
+ if (mCurrent) {
+ nsAutoCString curKey;
+ mCurrent->GetKey(curKey);
+
+ if (StringBeginsWith(curKey, mURL))
+ break;
+ }
+
+ mOuter->HasMoreElements(&hasMore);
+ }
+
+ if (!hasMore)
+ mCurrent = nullptr;
+
+ *aResult = mCurrent ? true : false;
+
+ return NS_OK;
+}
diff --git a/intl/strres/nsStringBundleTextOverride.h b/intl/strres/nsStringBundleTextOverride.h
new file mode 100644
index 000000000..56b6692eb
--- /dev/null
+++ b/intl/strres/nsStringBundleTextOverride.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsStringBundleTextOverride_h__
+#define nsStringBundleTextOverride_h__
+
+#include "nsIStringBundleOverride.h"
+#include "nsCOMPtr.h"
+#include "nsIPersistentProperties2.h"
+
+// {6316C6CE-12D3-479e-8F53-E289351412B8}
+#define NS_STRINGBUNDLETEXTOVERRIDE_CID \
+ { 0x6316c6ce, 0x12d3, 0x479e, \
+ { 0x8f, 0x53, 0xe2, 0x89, 0x35, 0x14, 0x12, 0xb8 } }
+
+
+#define NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID \
+ "@mozilla.org/intl/stringbundle/text-override;1"
+
+// an implementation which does overrides from a text file
+
+class nsStringBundleTextOverride : public nsIStringBundleOverride
+{
+ public:
+ nsStringBundleTextOverride() { }
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTRINGBUNDLEOVERRIDE
+
+ private:
+ nsCOMPtr<nsIPersistentProperties> mValues;
+
+ virtual ~nsStringBundleTextOverride() {}
+};
+
+#endif
diff --git a/intl/strres/tests/unit/397093.properties b/intl/strres/tests/unit/397093.properties
new file mode 100644
index 000000000..88303d447
--- /dev/null
+++ b/intl/strres/tests/unit/397093.properties
@@ -0,0 +1,4 @@
+# Property file for test_bug397093.js
+asciiProperty=Foo
+utf8Property=Fòò
+latin1Property=Fòò
diff --git a/intl/strres/tests/unit/strres.properties b/intl/strres/tests/unit/strres.properties
new file mode 100644
index 000000000..388f5acff
--- /dev/null
+++ b/intl/strres/tests/unit/strres.properties
@@ -0,0 +1,14 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+file=File
+loyal=\u5fe0\u5fc3
+trout=鳟魚
+edit= Edit
+view=View
+go=\u0020Go
+message=Message\u0020
+communicator=Communicator
+help=Help
+123=onetwothree
+hello=Hello %S
diff --git a/intl/strres/tests/unit/test_bug378839.js b/intl/strres/tests/unit/test_bug378839.js
new file mode 100644
index 000000000..e06a30244
--- /dev/null
+++ b/intl/strres/tests/unit/test_bug378839.js
@@ -0,0 +1,64 @@
+/* Tests getting properties from string bundles
+ */
+
+const name_file = "file";
+const value_file = "File";
+
+const name_loyal = "loyal";
+const value_loyal = "\u5fe0\u5fc3"; // tests escaped Unicode
+
+const name_trout = "trout";
+const value_trout = "\u9cdf\u9b5a"; // tests UTF-8
+
+const name_edit = "edit";
+const value_edit = "Edit"; // tests literal leading spaces are stripped
+
+const name_view = "view";
+const value_view = "View"; // tests literal trailing spaces are stripped
+
+const name_go = "go";
+const value_go = " Go"; // tests escaped leading spaces are not stripped
+
+const name_message = "message";
+const value_message = "Message "; // tests escaped trailing spaces are not stripped
+
+const name_hello = "hello";
+const var_hello = "World";
+const value_hello = "Hello World"; // tests formatStringFromName with parameter
+
+
+function run_test() {
+ var StringBundle =
+ Components.classes["@mozilla.org/intl/stringbundle;1"]
+ .getService(Components.interfaces.nsIStringBundleService);
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var bundleURI = ios.newFileURI(do_get_file("strres.properties"));
+
+ var bundle = StringBundle.createBundle(bundleURI.spec);
+
+ var bundle_file = bundle.GetStringFromName(name_file);
+ do_check_eq(bundle_file, value_file);
+
+ var bundle_loyal = bundle.GetStringFromName(name_loyal);
+ do_check_eq(bundle_loyal, value_loyal);
+
+ var bundle_trout = bundle.GetStringFromName(name_trout);
+ do_check_eq(bundle_trout, value_trout);
+
+ var bundle_edit = bundle.GetStringFromName(name_edit);
+ do_check_eq(bundle_edit, value_edit);
+
+ var bundle_view = bundle.GetStringFromName(name_view);
+ do_check_eq(bundle_view, value_view);
+
+ var bundle_go = bundle.GetStringFromName(name_go);
+ do_check_eq(bundle_go, value_go);
+
+ var bundle_message = bundle.GetStringFromName(name_message);
+ do_check_eq(bundle_message, value_message);
+
+ var bundle_hello = bundle.formatStringFromName(name_hello, [var_hello], 1);
+ do_check_eq(bundle_hello, value_hello);
+}
+
diff --git a/intl/strres/tests/unit/test_bug397093.js b/intl/strres/tests/unit/test_bug397093.js
new file mode 100644
index 000000000..36ba3bb14
--- /dev/null
+++ b/intl/strres/tests/unit/test_bug397093.js
@@ -0,0 +1,43 @@
+/* Tests getting properties from string bundles with incorrect encoding.
+ * The string bundle contains one ascii property, one UTF-8 and one Latin-1.
+ * Expected behaviour is that the whole string bundle should be rejected and
+ * all GetStringFromName calls should fail.
+ */
+
+const name_ascii = "asciiProperty";
+const value_ascii = "";
+
+const name_utf8 = "utf8Property";
+const value_utf8 = "";
+
+const name_latin1 = "latin1";
+const value_latin1 = "";
+
+
+function run_test() {
+ var StringBundle =
+ Components.classes["@mozilla.org/intl/stringbundle;1"]
+ .getService(Components.interfaces.nsIStringBundleService);
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var bundleURI = ios.newFileURI(do_get_file("397093.properties"));
+
+ var bundle = StringBundle.createBundle(bundleURI.spec);
+
+ var bundle_ascii="", bundle_utf8="", bundle_latin1="";
+ try {
+ bundle_ascii = bundle.GetStringFromName(name_ascii);
+ } catch(e) {}
+ do_check_eq(bundle_ascii, value_ascii);
+
+ try {
+ bundle_utf8 = bundle.GetStringFromName(name_utf8);
+ } catch(e) {}
+ do_check_eq(bundle_utf8, value_utf8);
+
+ try {
+ bundle_latin1 = bundle.GetStringFromName(name_latin1);
+ } catch(e) {}
+ do_check_eq(bundle_latin1, value_latin1);
+}
+
diff --git a/intl/strres/tests/unit/xpcshell.ini b/intl/strres/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..63e47797d
--- /dev/null
+++ b/intl/strres/tests/unit/xpcshell.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+head =
+tail =
+support-files =
+ 397093.properties
+ strres.properties
+
+[test_bug378839.js]
+[test_bug397093.js]