author | Zibi Braniecki <gandalf@mozilla.com> |
Wed, 08 Feb 2017 17:17:51 -0800 | |
changeset 392141 | 0fb088fb7d66147ff48d99596dfd3f67960d6426 |
parent 392140 | 6085498051cc33bee189d1c2c71816194d456401 |
child 392142 | 0f9b03faff358ed78b951b0f67401f8bd108bf34 |
push id | 7198 |
push user | jlorenzo@mozilla.com |
push date | Tue, 18 Apr 2017 12:07:49 +0000 |
treeherder | mozilla-beta@d57aa49c3948 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jfkthame |
bugs | 1308329 |
milestone | 54.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/intl/build/nsI18nModule.cpp +++ b/intl/build/nsI18nModule.cpp @@ -40,16 +40,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSt NS_GENERIC_FACTORY_CONSTRUCTOR(nsCaseConversionImp2) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsCategoryImp, nsCategoryImp::GetInstance) NS_GENERIC_FACTORY_CONSTRUCTOR(nsEntityConverter) NS_GENERIC_FACTORY_CONSTRUCTOR(nsSaveAsCharset) NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeNormalizer) NS_DEFINE_NAMED_CID(MOZ_LOCALESERVICE_CID); +NS_DEFINE_NAMED_CID(MOZ_OSPREFERENCES_CID); NS_DEFINE_NAMED_CID(NS_LBRK_CID); NS_DEFINE_NAMED_CID(NS_WBRK_CID); NS_DEFINE_NAMED_CID(NS_SEMANTICUNITSCANNER_CID); NS_DEFINE_NAMED_CID(NS_UNICHARUTIL_CID); NS_DEFINE_NAMED_CID(NS_UNICHARCATEGORY_CID); NS_DEFINE_NAMED_CID(NS_ENTITYCONVERTER_CID); NS_DEFINE_NAMED_CID(NS_SAVEASCHARSET_CID); NS_DEFINE_NAMED_CID(NS_UNICODE_NORMALIZER_CID); @@ -67,16 +68,17 @@ NS_DEFINE_NAMED_CID(NS_COLLATION_CID); NS_DEFINE_NAMED_CID(NS_COLLATION_CID); #endif #ifdef USE_MAC_LOCALE NS_DEFINE_NAMED_CID(NS_COLLATION_CID); #endif static const mozilla::Module::CIDEntry kIntlCIDs[] = { { &kMOZ_LOCALESERVICE_CID, false, nullptr, mozilla::intl::LocaleServiceConstructor }, + { &kMOZ_OSPREFERENCES_CID, false, nullptr, mozilla::intl::OSPreferencesConstructor }, { &kNS_LBRK_CID, false, nullptr, nsJISx4051LineBreakerConstructor }, { &kNS_WBRK_CID, false, nullptr, nsSampleWordBreakerConstructor }, { &kNS_SEMANTICUNITSCANNER_CID, false, nullptr, nsSemanticUnitScannerConstructor }, { &kNS_UNICHARUTIL_CID, false, nullptr, nsCaseConversionImp2Constructor }, { &kNS_UNICHARCATEGORY_CID, false, nullptr, nsCategoryImpConstructor }, { &kNS_ENTITYCONVERTER_CID, false, nullptr, nsEntityConverterConstructor }, { &kNS_SAVEASCHARSET_CID, false, nullptr, nsSaveAsCharsetConstructor }, { &kNS_UNICODE_NORMALIZER_CID, false, nullptr, nsUnicodeNormalizerConstructor }, @@ -96,16 +98,17 @@ static const mozilla::Module::CIDEntry k #ifdef USE_MAC_LOCALE { &kNS_COLLATION_CID, false, nullptr, nsCollationMacUCConstructor }, #endif { nullptr } }; static const mozilla::Module::ContractIDEntry kIntlContracts[] = { { MOZ_LOCALESERVICE_CONTRACTID, &kMOZ_LOCALESERVICE_CID }, + { MOZ_OSPREFERENCES_CONTRACTID, &kMOZ_OSPREFERENCES_CID }, { NS_LBRK_CONTRACTID, &kNS_LBRK_CID }, { NS_WBRK_CONTRACTID, &kNS_WBRK_CID }, { NS_SEMANTICUNITSCANNER_CONTRACTID, &kNS_SEMANTICUNITSCANNER_CID }, { NS_UNICHARUTIL_CONTRACTID, &kNS_UNICHARUTIL_CID }, { NS_UNICHARCATEGORY_CONTRACTID, &kNS_UNICHARCATEGORY_CID }, { NS_ENTITYCONVERTER_CONTRACTID, &kNS_ENTITYCONVERTER_CID }, { NS_SAVEASCHARSET_CONTRACTID, &kNS_SAVEASCHARSET_CID }, { NS_UNICODE_NORMALIZER_CONTRACTID, &kNS_UNICODE_NORMALIZER_CID },
--- a/intl/locale/OSPreferences.cpp +++ b/intl/locale/OSPreferences.cpp @@ -5,21 +5,28 @@ /** * This is a shared part of the OSPreferences API implementation. * It defines helper methods and public methods that are calling * platform-specific private methods. */ #include "OSPreferences.h" + #include "mozilla/ClearOnShutdown.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "unicode/udat.h" +#include "unicode/udatpg.h" using namespace mozilla::intl; -mozilla::StaticAutoPtr<OSPreferences> OSPreferences::sInstance; +NS_IMPL_ISUPPORTS(OSPreferences, mozIOSPreferences) + +mozilla::StaticRefPtr<OSPreferences> OSPreferences::sInstance; OSPreferences* OSPreferences::GetInstance() { if (!sInstance) { sInstance = new OSPreferences(); ClearOnShutdown(&sInstance); } @@ -32,16 +39,31 @@ OSPreferences::GetSystemLocales(nsTArray bool status = true; if (mSystemLocales.IsEmpty()) { status = ReadSystemLocales(mSystemLocales); } aRetVal = mSystemLocales; return status; } +void +OSPreferences::Refresh() +{ + nsTArray<nsCString> newLocales; + ReadSystemLocales(newLocales); + + if (mSystemLocales != newLocales) { + mSystemLocales = Move(newLocales); + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "intl:system-locales-changed", nullptr); + } + } +} + /** * This method should be called by every method of OSPreferences that * retrieves a locale id from external source. * * It attempts to retrieve as much of the locale ID as possible, cutting * out bits that are not understood (non-strict behavior of ICU). * * It returns true if the canonicalization was successful. @@ -58,8 +80,264 @@ OSPreferences::CanonicalizeLanguageTag(n if (U_FAILURE(status)) { return false; } aLoc.Assign(langTag, langTagLen); return true; } + +/** + * This method retrieves from ICU the best pattern for a given date/time style. + */ +bool +OSPreferences::GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle, + DateTimeFormatStyle aTimeStyle, + const nsACString& aLocale, + nsAString& aRetVal) +{ + UDateFormatStyle timeStyle = UDAT_NONE; + UDateFormatStyle dateStyle = UDAT_NONE; + + switch (aTimeStyle) { + case DateTimeFormatStyle::None: + timeStyle = UDAT_NONE; + break; + case DateTimeFormatStyle::Short: + timeStyle = UDAT_SHORT; + break; + case DateTimeFormatStyle::Medium: + timeStyle = UDAT_MEDIUM; + break; + case DateTimeFormatStyle::Long: + timeStyle = UDAT_LONG; + break; + case DateTimeFormatStyle::Full: + timeStyle = UDAT_FULL; + break; + case DateTimeFormatStyle::Invalid: + timeStyle = UDAT_NONE; + break; + } + + switch (aDateStyle) { + case DateTimeFormatStyle::None: + dateStyle = UDAT_NONE; + break; + case DateTimeFormatStyle::Short: + dateStyle = UDAT_SHORT; + break; + case DateTimeFormatStyle::Medium: + dateStyle = UDAT_MEDIUM; + break; + case DateTimeFormatStyle::Long: + dateStyle = UDAT_LONG; + break; + case DateTimeFormatStyle::Full: + dateStyle = UDAT_FULL; + break; + case DateTimeFormatStyle::Invalid: + dateStyle = UDAT_NONE; + break; + } + + const int32_t kPatternMax = 160; + UChar pattern[kPatternMax]; + + UErrorCode status = U_ZERO_ERROR; + UDateFormat* df = udat_open(timeStyle, dateStyle, + PromiseFlatCString(aLocale).get(), + nullptr, -1, nullptr, -1, &status); + if (U_FAILURE(status)) { + return false; + } + + int32_t patsize = udat_toPattern(df, false, pattern, kPatternMax, &status); + udat_close(df); + if (U_FAILURE(status)) { + return false; + } + aRetVal.Assign((const char16_t*)pattern, patsize); + return true; +} + + +/** + * This method retrieves from ICU the best skeleton for a given date/time style. + * + * This is useful for cases where an OS does not provide its own patterns, + * but provide ability to customize the skeleton, like alter hourCycle setting. + * + * The returned value is a skeleton that matches the styles. + */ +bool +OSPreferences::GetDateTimeSkeletonForStyle(DateTimeFormatStyle aDateStyle, + DateTimeFormatStyle aTimeStyle, + const nsACString& aLocale, + nsAString& aRetVal) +{ + nsAutoString pattern; + if (!GetDateTimePatternForStyle(aDateStyle, aTimeStyle, aLocale, pattern)) { + return false; + } + + const int32_t kSkeletonMax = 160; + UChar skeleton[kSkeletonMax]; + + UErrorCode status = U_ZERO_ERROR; + int32_t skelsize = udatpg_getSkeleton( + nullptr, (const UChar*)pattern.BeginReading(), pattern.Length(), + skeleton, kSkeletonMax, &status + ); + if (U_FAILURE(status)) { + return false; + } + + aRetVal.Assign((const char16_t*)skeleton, skelsize); + return true; +} + +/** + * This function is a counterpart to GetDateTimeSkeletonForStyle. + * + * It takes a skeleton and returns the best available pattern for a given locale + * that represents the provided skeleton. + * + * For example: + * "Hm" skeleton for "en-US" will return "H:m" + */ +bool +OSPreferences::GetPatternForSkeleton(const nsAString& aSkeleton, + const nsACString& aLocale, + nsAString& aRetVal) +{ + UErrorCode status = U_ZERO_ERROR; + UDateTimePatternGenerator* pg = udatpg_open(PromiseFlatCString(aLocale).get(), &status); + if (U_FAILURE(status)) { + return false; + } + + int32_t len = + udatpg_getBestPattern(pg, (const UChar*)aSkeleton.BeginReading(), + aSkeleton.Length(), nullptr, 0, &status); + if (status == U_BUFFER_OVERFLOW_ERROR) { // expected + aRetVal.SetLength(len); + status = U_ZERO_ERROR; + udatpg_getBestPattern(pg, (const UChar*)aSkeleton.BeginReading(), + aSkeleton.Length(), (UChar*)aRetVal.BeginWriting(), + len, &status); + } + + udatpg_close(pg); + + return U_SUCCESS(status); +} + +/** + * This function returns a pattern that should be used to join date and time + * patterns into a single date/time pattern string. + * + * It's useful for OSes that do not provide an API to retrieve such combined + * pattern. + * + * An example output is "{1}, {0}". + */ +bool +OSPreferences::GetDateTimeConnectorPattern(const nsACString& aLocale, + nsAString& aRetVal) +{ + UErrorCode status = U_ZERO_ERROR; + UDateTimePatternGenerator* pg = udatpg_open(PromiseFlatCString(aLocale).get(), &status); + if (U_FAILURE(status)) { + return false; + } + + int32_t resultSize; + const UChar* value = udatpg_getDateTimeFormat(pg, &resultSize); + MOZ_ASSERT(resultSize >= 0); + + aRetVal.Assign((char16_t*)value, resultSize); + return true; +} + +/** + * mozIOSPreferences methods + */ +NS_IMETHODIMP +OSPreferences::GetSystemLocales(uint32_t* aCount, char*** aOutArray) +{ + if (mSystemLocales.IsEmpty()) { + ReadSystemLocales(mSystemLocales); + } + + *aCount = mSystemLocales.Length(); + *aOutArray = static_cast<char**>(moz_xmalloc(*aCount * sizeof(char*))); + + for (uint32_t i = 0; i < *aCount; i++) { + (*aOutArray)[i] = moz_xstrdup(mSystemLocales[i].get()); + } + + return NS_OK; +} + +NS_IMETHODIMP +OSPreferences::GetSystemLocale(nsACString& aRetVal) +{ + if (mSystemLocales.IsEmpty()) { + ReadSystemLocales(mSystemLocales); + } + + if (!mSystemLocales.IsEmpty()) { + aRetVal = mSystemLocales[0]; + } + return NS_OK; +} + +static OSPreferences::DateTimeFormatStyle +ToDateTimeFormatStyle(int32_t aTimeFormat) +{ + switch (aTimeFormat) { + // See mozIOSPreferences.idl for the integer values here. + case 0: + return OSPreferences::DateTimeFormatStyle::None; + case 1: + return OSPreferences::DateTimeFormatStyle::Short; + case 2: + return OSPreferences::DateTimeFormatStyle::Medium; + case 3: + return OSPreferences::DateTimeFormatStyle::Long; + case 4: + return OSPreferences::DateTimeFormatStyle::Full; + } + return OSPreferences::DateTimeFormatStyle::Invalid; +} + +NS_IMETHODIMP +OSPreferences::GetDateTimePattern(int32_t aDateFormatStyle, + int32_t aTimeFormatStyle, + const nsACString& aLocale, + nsAString& aRetVal) +{ + DateTimeFormatStyle dateStyle = ToDateTimeFormatStyle(aDateFormatStyle); + if (dateStyle == DateTimeFormatStyle::Invalid) { + return NS_ERROR_INVALID_ARG; + } + DateTimeFormatStyle timeStyle = ToDateTimeFormatStyle(aTimeFormatStyle); + if (timeStyle == DateTimeFormatStyle::Invalid) { + return NS_ERROR_INVALID_ARG; + } + + // If the user is asking for None on both, date and time style, + // let's exit early. + if (timeStyle == DateTimeFormatStyle::None && + dateStyle == DateTimeFormatStyle::None) { + return NS_OK; + } + + if (!ReadDateTimePattern(dateStyle, timeStyle, aLocale, aRetVal)) { + if (!GetDateTimePatternForStyle(dateStyle, timeStyle, aLocale, aRetVal)) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +}
--- a/intl/locale/OSPreferences.h +++ b/intl/locale/OSPreferences.h @@ -6,16 +6,18 @@ #ifndef mozilla_intl_IntlOSPreferences_h__ #define mozilla_intl_IntlOSPreferences_h__ #include "mozilla/StaticPtr.h" #include "nsString.h" #include "nsTArray.h" #include "unicode/uloc.h" +#include "mozIOSPreferences.h" + namespace mozilla { namespace intl { /** * OSPreferences API provides a set of methods for retrieving information from * the host environment on topics such as: * - Internationalization * - Localization @@ -33,23 +35,52 @@ namespace intl { * * Second is caching. This API does cache values and where possible will * hook into the environment for some event-driven cache invalidation. * * This means that on platforms that do not support a mechanism to * notify apps about changes, new OS-level settings may not be reflected * in the app until it is relaunched. */ -class OSPreferences +class OSPreferences: public mozIOSPreferences { public: + NS_DECL_ISUPPORTS + NS_DECL_MOZIOSPREFERENCES + + enum class DateTimeFormatStyle { + Invalid = -1, + None, + Short, // e.g. time: HH:mm, date: Y/m/d + Medium, // likely same as Short + Long, // e.g. time: including seconds, date: including weekday + Full // e.g. time: with timezone, date: with long weekday, month + }; + + /** + * Create (if necessary) and return a raw pointer to the singleton instance. + * Use this accessor in C++ code that just wants to call a method on the + * instance, but does not need to hold a reference, as in + * nsAutoCString str; + * OSPreferences::GetInstance()->GetSystemLocale(str); + */ static OSPreferences* GetInstance(); /** + * Return an addRef'd pointer to the singleton instance. This is used by the + * XPCOM constructor that exists to support usage from JS. + */ + static already_AddRefed<OSPreferences> GetInstanceAddRefed() + { + return RefPtr<OSPreferences>(GetInstance()).forget(); + } + + + /** * Returns a list of locales used by the host environment. * * The result is a sorted list and we expect that the OS attempts to * use the top locale from the list for which it has data. * * Each element of the list is a valid locale ID that can be passed to ICU * and ECMA402 Intl APIs, * At the same time each element is a valid BCP47 language tag that can be @@ -58,35 +89,91 @@ public: * Example: ["en-US", "de", "pl", "sr-Cyrl", "zh-Hans-HK"] * * The return bool value indicates whether the function successfully * resolved at least one locale. * * Usage: * nsTArray<nsCString> systemLocales; * OSPreferences::GetInstance()->GetSystemLocales(systemLocales); + * + * (See mozIOSPreferences.idl for a JS-callable version of this.) */ bool GetSystemLocales(nsTArray<nsCString>& aRetVal); protected: nsTArray<nsCString> mSystemLocales; private: - static StaticAutoPtr<OSPreferences> sInstance; + virtual ~OSPreferences() {}; + + static StaticRefPtr<OSPreferences> sInstance; static bool CanonicalizeLanguageTag(nsCString& aLoc); /** + * Helper methods to get formats from ICU; these will return false + * in case of error, in which case the caller cannot rely on aRetVal. + */ + bool GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle, + DateTimeFormatStyle aTimeStyle, + const nsACString& aLocale, + nsAString& aRetVal); + + bool GetDateTimeSkeletonForStyle(DateTimeFormatStyle aDateStyle, + DateTimeFormatStyle aTimeStyle, + const nsACString& aLocale, + nsAString& aRetVal); + + bool GetPatternForSkeleton(const nsAString& aSkeleton, + const nsACString& aLocale, + nsAString& aRetVal); + + bool GetDateTimeConnectorPattern(const nsACString& aLocale, + nsAString& aRetVal); + + /** * This is a host environment specific method that will be implemented * separately for each platform. * * It is only called when the cache is empty or invalidated. * * The return value indicates whether the function successfully * resolved at least one locale. */ bool ReadSystemLocales(nsTArray<nsCString>& aRetVal); + + /** + * This is a host environment specific method that will be implemented + * separately for each platform. + * + * It is `best-effort` kind of API that attempts to construct the best + * possible date/time pattern for the given styles and locales. + * + * In case we fail to, or don't know how to retrieve the pattern in a + * given environment this function will return false. + * Callers should always be prepared to handle that scenario. + * + * The heuristic may depend on the OS API and HIG guidelines. + */ + bool ReadDateTimePattern(DateTimeFormatStyle aDateFormatStyle, + DateTimeFormatStyle aTimeFormatStyle, + const nsACString& aLocale, + nsAString& aRetVal); + + /** + * Triggers a refresh of retrieving data from host environment. + * + * If the result differs from the previous list, it will additionally + * trigger global events for changed values: + * + * * SystemLocales: "intl:system-locales-changed" + * + * This method should not be called from anywhere except of per-platform + * hooks into OS events. + */ + void Refresh(); }; } // intl } // namespace mozilla #endif /* mozilla_intl_IntlOSPreferences_h__ */
new file mode 100644 --- /dev/null +++ b/intl/locale/gtk/OSPreferences_gtk.cpp @@ -0,0 +1,174 @@ +/* -*- 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 "OSPreferences.h" +#include "dlfcn.h" +#include "glib.h" +#include "gio/gio.h" + +using namespace mozilla::intl; + +bool +OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList) +{ + MOZ_ASSERT(aLocaleList.IsEmpty()); + + nsAutoCString defaultLang(uloc_getDefault()); + + if (CanonicalizeLanguageTag(defaultLang)) { + aLocaleList.AppendElement(defaultLang); + return true; + } + return false; +} + +/* + * This looks up into gtk settings for hourCycle format. + * + * This works for all GUIs that use gtk settings like Gnome, Elementary etc. + * Ubuntu does not use those settings so we'll want to support them separately. + * + * We're taking the current 12/24h settings irrelevant of the locale, because + * in the UI user selects this setting for all locales. + */ +typedef GVariant* (*get_value_fn_t)(GSettings*, const gchar*); + +static get_value_fn_t +FindGetValueFunction() +{ + get_value_fn_t fn = reinterpret_cast<get_value_fn_t>( + dlsym(RTLD_DEFAULT, "g_settings_get_user_value") + ); + return fn ? fn : &g_settings_get_value; +} + +static int +HourCycle() +{ + int rval = 0; + + const char* schema; + const char* key; + const char* env = getenv("XDG_CURRENT_DESKTOP"); + if (env && strcmp(env, "Unity") == 0) { + schema = "com.canonical.indicator.datetime"; + key = "time-format"; + } else { + schema = "org.gnome.desktop.interface"; + key = "clock-format"; + } + + GSettings* settings = g_settings_new(schema); + if (settings) { + // We really want to use g_settings_get_user_value which will + // only want to take it if user manually changed the value. + // But this requires glib 2.40, and we still support older glib versions, + // so we have to check whether it's available and fall back to the older + // g_settings_get_value if not. + static get_value_fn_t sGetValueFunction = FindGetValueFunction(); + GVariant* value = sGetValueFunction(settings, key); + if (value) { + if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) { + const char* strVal = g_variant_get_string(value, nullptr); + if (strncmp("12", strVal, 2) == 0) { + rval = 12; + } else if (strncmp("24", strVal, 2) == 0) { + rval = 24; + } + } + g_variant_unref(value); + } + g_object_unref(settings); + } + return rval; +} + +/** + * Since Gtk does not provide a way to customize or format date/time patterns, + * we're reusing ICU data here, but we do modify it according to the only + * setting Gtk gives us - hourCycle. + * + * This means that for gtk we will return a pattern from ICU altered to + * represent h12/h24 hour cycle if the user modified the default value. + * + * In short, this should work like this: + * + * * gtk defaults, pl: 24h + * * gtk defaults, en: 12h + * + * * gtk 12h, pl: 12h + * * gtk 12h, en: 12h + * + * * gtk 24h, pl: 24h + * * gtk 12h, en: 12h + */ +bool +OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle, + DateTimeFormatStyle aTimeStyle, + const nsACString& aLocale, nsAString& aRetVal) +{ + nsAutoString skeleton; + if (!GetDateTimeSkeletonForStyle(aDateStyle, aTimeStyle, aLocale, skeleton)) { + return false; + } + + // Customize the skeleton if necessary to reflect user's 12/24hr pref + switch (HourCycle()) { + case 12: { + // If skeleton contains 'H' or 'k', replace with 'h' or 'K' respectively, + // and add 'a' unless already present. + if (skeleton.FindChar('H') == -1 && skeleton.FindChar('k') == -1) { + break; // nothing to do + } + bool foundA = false; + for (size_t i = 0; i < skeleton.Length(); ++i) { + switch (skeleton[i]) { + case 'a': + foundA = true; + break; + case 'H': + skeleton.SetCharAt('h', i); + break; + case 'k': + skeleton.SetCharAt('K', i); + break; + } + } + if (!foundA) { + skeleton.Append(char16_t('a')); + } + break; + } + case 24: + // If skeleton contains 'h' or 'K', replace with 'H' or 'k' respectively, + // and delete 'a' if present. + if (skeleton.FindChar('h') == -1 && skeleton.FindChar('K') == -1) { + break; // nothing to do + } + for (int32_t i = 0; i < int32_t(skeleton.Length()); ++i) { + switch (skeleton[i]) { + case 'a': + skeleton.Cut(i, 1); + --i; + break; + case 'h': + skeleton.SetCharAt('H', i); + break; + case 'K': + skeleton.SetCharAt('k', i); + break; + } + } + break; + } + + if (!GetPatternForSkeleton(skeleton, aLocale, aRetVal)) { + return false; + } + + return true; +} +
new file mode 100644 --- /dev/null +++ b/intl/locale/gtk/moz.build @@ -0,0 +1,15 @@ +# -*- 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/. + +SOURCES += ['OSPreferences_gtk.cpp'] + +CXXFLAGS += CONFIG['GLIB_CFLAGS'] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '..', +]
--- a/intl/locale/mac/OSPreferences_mac.cpp +++ b/intl/locale/mac/OSPreferences_mac.cpp @@ -33,8 +33,86 @@ OSPreferences::ReadSystemLocales(nsTArra if (CanonicalizeLanguageTag(locale)) { aLocaleList.AppendElement(locale); return true; } return false; } + +static CFDateFormatterStyle +ToCFDateFormatterStyle(OSPreferences::DateTimeFormatStyle aFormatStyle) +{ + switch (aFormatStyle) { + case OSPreferences::DateTimeFormatStyle::None: + return kCFDateFormatterNoStyle; + case OSPreferences::DateTimeFormatStyle::Short: + return kCFDateFormatterShortStyle; + case OSPreferences::DateTimeFormatStyle::Medium: + return kCFDateFormatterMediumStyle; + case OSPreferences::DateTimeFormatStyle::Long: + return kCFDateFormatterLongStyle; + case OSPreferences::DateTimeFormatStyle::Full: + return kCFDateFormatterFullStyle; + case OSPreferences::DateTimeFormatStyle::Invalid: + MOZ_ASSERT_UNREACHABLE("invalid time format"); + return kCFDateFormatterNoStyle; + } +} + +// Given an 8-bit Gecko string, create a corresponding CFLocale; +// if aLocale is empty, returns a copy of the system's current locale. +// May return null on failure. +// Follows Core Foundation's Create rule, so the caller is responsible to +// release the returned reference. +static CFLocaleRef +CreateCFLocaleFor(const nsACString& aLocale) +{ + if (aLocale.IsEmpty()) { + return CFLocaleCopyCurrent(); + } + CFStringRef identifier = + CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, + (const uint8_t*)aLocale.BeginReading(), + aLocale.Length(), kCFStringEncodingASCII, + false, kCFAllocatorNull); + if (!identifier) { + return nullptr; + } + CFLocaleRef locale = CFLocaleCreate(kCFAllocatorDefault, identifier); + CFRelease(identifier); + return locale; +} + +/** + * Cocoa API maps nicely to our four styles of date/time. + * + * The only caveat is that Cocoa takes regional preferences modifications + * into account only when we pass an empty string as a locale. + * + * In all other cases it will return the default pattern for a given locale. + */ +bool +OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle, + DateTimeFormatStyle aTimeStyle, + const nsACString& aLocale, nsAString& aRetVal) +{ + CFLocaleRef locale = CreateCFLocaleFor(aLocale); + if (!locale) { + return false; + } + + CFDateFormatterRef formatter = + CFDateFormatterCreate(kCFAllocatorDefault, locale, + ToCFDateFormatterStyle(aDateStyle), + ToCFDateFormatterStyle(aTimeStyle)); + CFStringRef format = CFDateFormatterGetFormat(formatter); + CFRelease(locale); + + CFRange range = CFRangeMake(0, CFStringGetLength(format)); + aRetVal.SetLength(range.length); + CFStringGetCharacters(format, range, + reinterpret_cast<UniChar*>(aRetVal.BeginWriting())); + CFRelease(formatter); + + return true; +}
--- a/intl/locale/moz.build +++ b/intl/locale/moz.build @@ -15,19 +15,22 @@ if CONFIG['ENABLE_INTL_API']: toolkit = CONFIG['MOZ_WIDGET_TOOLKIT'] if toolkit == 'windows': DIRS += ['windows'] elif toolkit == 'cocoa': DIRS += ['mac'] else: DIRS += ['unix'] + if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + DIRS += ['gtk'] XPIDL_SOURCES += [ 'mozILocaleService.idl', + 'mozIOSPreferences.idl', 'nsICollation.idl', 'nsILocale.idl', 'nsILocaleService.idl', 'nsIScriptableDateFormat.idl', ] XPIDL_MODULE = 'locale'
new file mode 100644 --- /dev/null +++ b/intl/locale/mozIOSPreferences.idl @@ -0,0 +1,86 @@ +/* -*- 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" + +%{C++ +// Define Contractid and CID +#define MOZ_OSPREFERENCES_CID \ + { 0x65944815, 0xe9ae, 0x48bd, { 0xa2, 0xbf, 0xf1, 0x10, 0x87, 0x20, 0x95, 0x0c } } + +#define MOZ_OSPREFERENCES_CONTRACTID "@mozilla.org/intl/ospreferences;1" +%} + +[scriptable, uuid(65944815-e9ae-48bd-a2bf-f1108720950c)] +interface mozIOSPreferences : nsISupports +{ + const long dateTimeFormatStyleNone = 0; + const long dateTimeFormatStyleShort = 1; + const long dateTimeFormatStyleMedium = 2; + const long dateTimeFormatStyleLong = 3; + const long dateTimeFormatStyleFull = 4; + + /** + * Returns a list of locales used by the host environment. + * + * The result is a sorted list and we expect that the OS attempts to + * use the top locale from the list for which it has data. + * + * Each element of the list is a valid locale ID that can be passed to ICU + * and ECMA402 Intl APIs, + * At the same time each element is a valid BCP47 language tag that can be + * used for language negotiation. + * + * Example: ["en-US", "de", "pl", "sr-Cyrl", "zh-Hans-HK"] + * + * The API may return an empty list in case no locale could + * be retrieved from the system. + * + * (See OSPreferences.h for a more C++-friendly version of this.) + */ + void getSystemLocales([optional] out unsigned long aCount, + [retval, array, size_is(aCount)] out string aOutArray); + + /** + * Returns the best locale that the host environment is localized to. + * + * The result is a valid locale ID and it should be + * used for all APIs that do not handle language negotiation. + * + * In any scenario involving language negotiation, GetSystemLocales should + * be preferred over the single value. + * + * Example: "zh-Hans-HK" + */ + readonly attribute ACString systemLocale; + + /** + * Returns the best possible date/time pattern for the host environment + * taking into account date/time regional settings user defined in the OS + * preferences. + * + * Notice, that depending on the OS it may take into account those settings + * for all locales, or only if the locale matches the OS locale. + * + * It takes two integer arguments that must be valid `dateTimeFormatStyle*` + * values (see constants defined above), and a string representing a + * BCP47 locale. + * + * It returns a string with a LDML date/time pattern. + * + * If no pattern can be retrieved from the host environment, it will + * lookup the best available pattern from ICU. + * + * Notice, this is a pretty unique method in this API in that it does + * more than look up into host environment. + * The reason for that is that constructing the right date/time pattern + * requires a lot of OS-specific logic and it ends up being easier to just + * handle all scenarios, including with cases where we fail to retrieve + * anything from the OS, here. + */ + AString getDateTimePattern(in long timeFormatStyle, + in long dateFormatStyle, + [optional] in ACString locale); +};
--- a/intl/locale/nsLocaleConstructors.h +++ b/intl/locale/nsLocaleConstructors.h @@ -9,16 +9,17 @@ #include "nsCollationCID.h" #include "mozilla/ModuleUtils.h" #include "nsILocaleService.h" #include "nsIScriptableDateFormat.h" #include "nsIServiceManager.h" #include "nsLanguageAtomService.h" #include "nsPlatformCharset.h" #include "LocaleService.h" +#include "OSPreferences.h" #if defined(XP_MACOSX) #define USE_MAC_LOCALE #endif #if defined(XP_UNIX) && !defined(XP_MACOSX) #define USE_UNIX_LOCALE #endif @@ -57,16 +58,18 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollati //NS_GENERIC_FACTORY_CONSTRUCTOR(nsScriptableDateTimeFormat) NS_GENERIC_FACTORY_CONSTRUCTOR(nsLanguageAtomService) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPlatformCharset, Init) namespace mozilla { namespace intl { NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(LocaleService, LocaleService::GetInstanceAddRefed) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(OSPreferences, + OSPreferences::GetInstanceAddRefed) } } #ifdef XP_WIN NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollationWin) #endif #ifdef USE_UNIX_LOCALE
--- a/intl/locale/tests/gtest/TestOSPreferences.cpp +++ b/intl/locale/tests/gtest/TestOSPreferences.cpp @@ -1,14 +1,15 @@ /* -*- 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 "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" #include "mozilla/intl/OSPreferences.h" using namespace mozilla::intl; /** * We test that on all platforms we test against (irrelevant of the tier), * we will be able to retrieve at least a single locale out of the system. * @@ -17,8 +18,56 @@ using namespace mozilla::intl; * it not happen without us noticing. */ TEST(Intl_Locale_OSPreferences, GetSystemLocales) { nsTArray<nsCString> systemLocales; ASSERT_TRUE(OSPreferences::GetInstance()->GetSystemLocales(systemLocales)); ASSERT_FALSE(systemLocales.IsEmpty()); } + +/** + * We test that on all platforms we test against, + * we will be able to retrieve a date and time pattern. + * + * This may come back empty on platforms where we don't have platforms + * bindings for, so effectively, we're testing for crashes. We should + * never crash. + */ +TEST(Intl_Locale_OSPreferences, GetDateTimePattern) { + nsAutoString pattern; + OSPreferences* osprefs = OSPreferences::GetInstance(); + + struct Test { + int dateStyle; + int timeStyle; + const char* locale; + }; + Test tests[] = { + { 0, 0, "" }, + { 1, 0, "pl" }, + { 2, 0, "de-DE" }, + { 3, 0, "fr" }, + { 4, 0, "ar" }, + + { 0, 1, "" }, + { 0, 2, "it" }, + { 0, 3, "" }, + { 0, 4, "ru" }, + + { 4, 1, "" }, + { 3, 2, "cs" }, + { 2, 3, "" }, + { 1, 4, "ja" } + }; + + for (unsigned i = 0; i < mozilla::ArrayLength(tests); i++) { + const Test& t = tests[i]; + nsAutoString pattern; + if (NS_SUCCEEDED(osprefs->GetDateTimePattern(t.dateStyle, t.timeStyle, + nsDependentCString(t.locale), + pattern))) { + ASSERT_TRUE((t.dateStyle == 0 && t.timeStyle == 0) || !pattern.IsEmpty()); + } + } + + ASSERT_TRUE(1); +}
new file mode 100644 --- /dev/null +++ b/intl/locale/tests/unit/test_osPreferences.js @@ -0,0 +1,38 @@ +/* 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/. */ + +function run_test() +{ + const osprefs = + Components.classes["@mozilla.org/intl/ospreferences;1"] + .getService(Components.interfaces.mozIOSPreferences); + + const systemLocale = osprefs.systemLocale; + do_check_true(systemLocale != "", "systemLocale is non-empty"); + + const systemLocales = osprefs.getSystemLocales(); + do_check_true(Array.isArray(systemLocales), "systemLocales returns an array"); + + do_check_true(systemLocale == systemLocales[0], + "systemLocale matches first entry in systemLocales"); + + const getDateTimePatternTests = [ + [osprefs.dateTimeFormatStyleNone, osprefs.dateTimeFormatStyleNone, ""], + [osprefs.dateTimeFormatStyleShort, osprefs.dateTimeFormatStyleNone, ""], + [osprefs.dateTimeFormatStyleNone, osprefs.dateTimeFormatStyleLong, "ar"], + [osprefs.dateTimeFormatStyleFull, osprefs.dateTimeFormatStyleMedium, "ru"], + ]; + + for (let i = 0; i < getDateTimePatternTests.length; i++) { + const test = getDateTimePatternTests[i]; + + const pattern = osprefs.getDateTimePattern(...test); + if (test[0] !== osprefs.dateTimeFormatStyleNone && + test[1] !== osprefs.dateTImeFormatStyleNone) { + do_check_true(pattern.length > 0, "pattern is not empty."); + } + } + + do_check_true(1, "osprefs didn't crash"); +}
--- a/intl/locale/tests/unit/xpcshell.ini +++ b/intl/locale/tests/unit/xpcshell.ini @@ -18,8 +18,9 @@ skip-if = toolkit != "cocoa" [test_intl_on_workers.js] skip-if = toolkit == "android" # bug 1309447 [test_pluralForm.js] [test_pluralForm_english.js] [test_pluralForm_makeGetter.js] [test_localeService.js] +[test_osPreferences.js]
--- a/intl/locale/unix/OSPreferences_unix.cpp +++ b/intl/locale/unix/OSPreferences_unix.cpp @@ -16,8 +16,16 @@ OSPreferences::ReadSystemLocales(nsTArra nsAutoCString defaultLang(uloc_getDefault()); if (CanonicalizeLanguageTag(defaultLang)) { aLocaleList.AppendElement(defaultLang); return true; } return false; } + +bool +OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle, + DateTimeFormatStyle aTimeStyle, + const nsACString& aLocale, nsAString& aRetVal) +{ + return false; +}
--- a/intl/locale/unix/moz.build +++ b/intl/locale/unix/moz.build @@ -4,17 +4,17 @@ # 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/. SOURCES += [ 'nsCollationUnix.cpp', 'nsPosixLocale.cpp', ] -if CONFIG['ENABLE_INTL_API']: +if 'gtk' not in CONFIG['MOZ_WIDGET_TOOLKIT']: SOURCES += ['OSPreferences_unix.cpp'] if CONFIG['OS_TARGET'] == 'Android': SOURCES += [ 'nsAndroidCharset.cpp', ] else: SOURCES += [
--- a/intl/locale/windows/OSPreferences_win.cpp +++ b/intl/locale/windows/OSPreferences_win.cpp @@ -22,8 +22,187 @@ OSPreferences::ReadSystemLocales(nsTArra NS_LossyConvertUTF16toASCII loc(locale); if (CanonicalizeLanguageTag(loc)) { aLocaleList.AppendElement(loc); return true; } return false; } + +static LCTYPE +ToDateLCType(OSPreferences::DateTimeFormatStyle aFormatStyle) +{ + switch (aFormatStyle) { + case OSPreferences::DateTimeFormatStyle::None: + return LOCALE_SLONGDATE; + case OSPreferences::DateTimeFormatStyle::Short: + return LOCALE_SSHORTDATE; + case OSPreferences::DateTimeFormatStyle::Medium: + return LOCALE_SSHORTDATE; + case OSPreferences::DateTimeFormatStyle::Long: + return LOCALE_SLONGDATE; + case OSPreferences::DateTimeFormatStyle::Full: + return LOCALE_SLONGDATE; + case OSPreferences::DateTimeFormatStyle::Invalid: + default: + MOZ_ASSERT_UNREACHABLE("invalid date format"); + return LOCALE_SLONGDATE; + } +} + +static LCTYPE +ToTimeLCType(OSPreferences::DateTimeFormatStyle aFormatStyle) +{ + switch (aFormatStyle) { + case OSPreferences::DateTimeFormatStyle::None: + return LOCALE_STIMEFORMAT; + case OSPreferences::DateTimeFormatStyle::Short: + return LOCALE_SSHORTTIME; + case OSPreferences::DateTimeFormatStyle::Medium: + return LOCALE_SSHORTTIME; + case OSPreferences::DateTimeFormatStyle::Long: + return LOCALE_STIMEFORMAT; + case OSPreferences::DateTimeFormatStyle::Full: + return LOCALE_STIMEFORMAT; + case OSPreferences::DateTimeFormatStyle::Invalid: + default: + MOZ_ASSERT_UNREACHABLE("invalid time format"); + return LOCALE_STIMEFORMAT; + } +} + +/** + * Windows API includes regional preferences from the user only + * if we pass empty locale string or if the locale string matches + * the current locale. + * + * Since Windows API only allows us to retrieve two options - short/long + * we map it to our four options as: + * + * short -> short + * medium -> short + * long -> long + * full -> long + * + * In order to produce a single date/time format, we use CLDR pattern + * for combined date/time string, since Windows API does not provide an + * option for this. + */ +bool +OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle, + DateTimeFormatStyle aTimeStyle, + const nsACString& aLocale, nsAString& aRetVal) +{ + LPWSTR localeName = LOCALE_NAME_USER_DEFAULT; + nsAutoString localeNameBuffer; + if (!aLocale.IsEmpty()) { + localeNameBuffer.AppendASCII(aLocale.BeginReading(), aLocale.Length()); + localeName = (LPWSTR)localeNameBuffer.BeginReading(); + } + + bool isDate = aDateStyle != DateTimeFormatStyle::None && + aDateStyle != DateTimeFormatStyle::Invalid; + bool isTime = aTimeStyle != DateTimeFormatStyle::None && + aTimeStyle != DateTimeFormatStyle::Invalid; + + // If both date and time are wanted, we'll initially read them into a + // local string, and then insert them into the overall date+time pattern; + // but if only one is needed we'll work directly with the return value. + // Set 'str' to point to the string we will use to retrieve patterns + // from Windows. + nsAutoString tmpStr; + nsAString* str; + if (isDate && isTime) { + if (!GetDateTimeConnectorPattern(aLocale, aRetVal)) { + NS_WARNING("failed to get date/time connector"); + aRetVal.AssignLiteral(u"{1} {0}"); + } + str = &tmpStr; + } else if (isDate || isTime) { + str = &aRetVal; + } else { + aRetVal.Truncate(0); + return true; + } + + if (isDate) { + LCTYPE lcType = ToDateLCType(aDateStyle); + size_t len = GetLocaleInfoEx(localeName, lcType, nullptr, 0); + if (len == 0) { + return false; + } + str->SetLength(len - 1); // -1 because len counts the null terminator + GetLocaleInfoEx(localeName, lcType, (WCHAR*)str->BeginWriting(), len); + + // Windows uses "ddd" and "dddd" for abbreviated and full day names respectively, + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317787(v=vs.85).aspx + // but in a CLDR/ICU-style pattern these should be "EEE" and "EEEE". + // http://userguide.icu-project.org/formatparse/datetime + // So we fix that up here. + nsAString::const_iterator start, pos, end; + start = str->BeginReading(pos); + str->EndReading(end); + if (FindInReadable(NS_LITERAL_STRING("dddd"), pos, end)) { + str->Replace(pos - start, 4, NS_LITERAL_STRING("EEEE")); + } else if (FindInReadable(NS_LITERAL_STRING("ddd"), pos, end)) { + str->Replace(pos - start, 3, NS_LITERAL_STRING("EEE")); + } + + // Also, Windows uses lowercase "g" or "gg" for era, but ICU wants uppercase "G" + // (it would interpret "g" as "modified Julian day"!). So fix that. + int32_t index = str->FindChar('g'); + if (index >= 0) { + str->Replace(index, 1, 'G'); + // If it was a double "gg", just drop the second one. + index++; + if (str->CharAt(index) == 'g') { + str->Cut(index, 1); + } + } + + // If time was also requested, we need to substitute the date pattern from Windows + // into the date+time format that we have in aRetVal. + if (isTime) { + nsAString::const_iterator start, pos, end; + start = aRetVal.BeginReading(pos); + aRetVal.EndReading(end); + if (FindInReadable(NS_LITERAL_STRING("{1}"), pos, end)) { + aRetVal.Replace(pos - start, 3, tmpStr); + } + } + } + + if (isTime) { + LCTYPE lcType = ToTimeLCType(aTimeStyle); + size_t len = GetLocaleInfoEx(localeName, lcType, nullptr, 0); + if (len == 0) { + return false; + } + str->SetLength(len - 1); + GetLocaleInfoEx(localeName, lcType, (WCHAR*)str->BeginWriting(), len); + + // Windows uses "t" or "tt" for a "time marker" (am/pm indicator), + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd318148(v=vs.85).aspx + // but in a CLDR/ICU-style pattern that should be "a". + // http://userguide.icu-project.org/formatparse/datetime + // So we fix that up here. + int32_t index = str->FindChar('t'); + if (index >= 0) { + str->Replace(index, 1, 'a'); + index++; + if (str->CharAt(index) == 't') { + str->Cut(index, 1); + } + } + + if (isDate) { + nsAString::const_iterator start, pos, end; + start = aRetVal.BeginReading(pos); + aRetVal.EndReading(end); + if (FindInReadable(NS_LITERAL_STRING("{0}"), pos, end)) { + aRetVal.Replace(pos - start, 3, tmpStr); + } + } + } + + return true; +}