diff options
Diffstat (limited to 'intl/strres')
-rw-r--r-- | intl/strres/moz.build | 24 | ||||
-rw-r--r-- | intl/strres/nsIStringBundle.idl | 78 | ||||
-rw-r--r-- | intl/strres/nsIStringBundleOverride.idl | 25 | ||||
-rw-r--r-- | intl/strres/nsStringBundle.cpp | 769 | ||||
-rw-r--r-- | intl/strres/nsStringBundle.h | 81 | ||||
-rw-r--r-- | intl/strres/nsStringBundleService.h | 56 | ||||
-rw-r--r-- | intl/strres/nsStringBundleTextOverride.cpp | 286 | ||||
-rw-r--r-- | intl/strres/nsStringBundleTextOverride.h | 41 | ||||
-rw-r--r-- | intl/strres/tests/unit/397093.properties | 4 | ||||
-rw-r--r-- | intl/strres/tests/unit/strres.properties | 14 | ||||
-rw-r--r-- | intl/strres/tests/unit/test_bug378839.js | 64 | ||||
-rw-r--r-- | intl/strres/tests/unit/test_bug397093.js | 43 | ||||
-rw-r--r-- | intl/strres/tests/unit/xpcshell.ini | 9 |
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] |