summaryrefslogtreecommitdiffstats
path: root/intl/locale/OSPreferences.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'intl/locale/OSPreferences.cpp')
-rw-r--r--intl/locale/OSPreferences.cpp585
1 files changed, 585 insertions, 0 deletions
diff --git a/intl/locale/OSPreferences.cpp b/intl/locale/OSPreferences.cpp
new file mode 100644
index 0000000000..b87924b61a
--- /dev/null
+++ b/intl/locale/OSPreferences.cpp
@@ -0,0 +1,585 @@
+/* -*- 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/. */
+
+/**
+ * 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/intl/DateTimePatternGenerator.h"
+#include "mozilla/intl/DateTimeFormat.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Result.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+
+using namespace mozilla::intl;
+
+NS_IMPL_ISUPPORTS(OSPreferences, mozIOSPreferences)
+
+mozilla::StaticRefPtr<OSPreferences> OSPreferences::sInstance;
+
+// Return a new strong reference to the instance, creating it if necessary.
+already_AddRefed<OSPreferences> OSPreferences::GetInstanceAddRefed() {
+ RefPtr<OSPreferences> result = sInstance;
+ if (!result) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "OSPreferences should be initialized on main thread!");
+ if (!NS_IsMainThread()) {
+ return nullptr;
+ }
+ sInstance = new OSPreferences();
+ result = sInstance;
+
+ DebugOnly<nsresult> rv = Preferences::RegisterPrefixCallback(
+ PreferenceChanged, "intl.date_time.pattern_override");
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
+
+ ClearOnShutdown(&sInstance);
+ }
+ return result.forget();
+}
+
+// Return a raw pointer to the instance: not for off-main-thread use,
+// because ClearOnShutdown means it could go away unexpectedly.
+OSPreferences* OSPreferences::GetInstance() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!sInstance) {
+ // This will create the static instance; then we just drop the extra
+ // reference.
+ RefPtr<OSPreferences> result = GetInstanceAddRefed();
+ }
+ return sInstance;
+}
+
+void OSPreferences::Refresh() {
+ nsTArray<nsCString> newLocales;
+ ReadSystemLocales(newLocales);
+
+ if (mSystemLocales != newLocales) {
+ mSystemLocales = std::move(newLocales);
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "intl:system-locales-changed", nullptr);
+ }
+ }
+}
+
+OSPreferences::~OSPreferences() {
+ Preferences::UnregisterPrefixCallback(PreferenceChanged,
+ "intl.date_time.pattern_override");
+ RemoveObservers();
+}
+
+/*static*/
+void OSPreferences::PreferenceChanged(const char* aPrefName,
+ void* /* aClosure */) {
+ if (sInstance) {
+ sInstance->mPatternCache.Clear();
+ }
+}
+
+/**
+ * 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.
+ */
+bool OSPreferences::CanonicalizeLanguageTag(nsCString& aLoc) {
+ return LocaleService::CanonicalizeLanguageId(aLoc);
+}
+
+/**
+ * This method retrieves from mozilla::intl the best pattern for a given
+ * date/time style.
+ */
+bool OSPreferences::GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle,
+ DateTimeFormatStyle aTimeStyle,
+ const nsACString& aLocale,
+ nsACString& aRetVal) {
+ DateTimeFormat::StyleBag style;
+
+ switch (aTimeStyle) {
+ case DateTimeFormatStyle::Short:
+ style.time = Some(DateTimeFormat::Style::Short);
+ break;
+ case DateTimeFormatStyle::Medium:
+ style.time = Some(DateTimeFormat::Style::Medium);
+ break;
+ case DateTimeFormatStyle::Long:
+ style.time = Some(DateTimeFormat::Style::Long);
+ break;
+ case DateTimeFormatStyle::Full:
+ style.time = Some(DateTimeFormat::Style::Full);
+ break;
+ case DateTimeFormatStyle::None:
+ case DateTimeFormatStyle::Invalid:
+ // Do nothing.
+ break;
+ }
+
+ switch (aDateStyle) {
+ case DateTimeFormatStyle::Short:
+ style.date = Some(DateTimeFormat::Style::Short);
+ break;
+ case DateTimeFormatStyle::Medium:
+ style.date = Some(DateTimeFormat::Style::Medium);
+ break;
+ case DateTimeFormatStyle::Long:
+ style.date = Some(DateTimeFormat::Style::Long);
+ break;
+ case DateTimeFormatStyle::Full:
+ style.date = Some(DateTimeFormat::Style::Full);
+ break;
+ case DateTimeFormatStyle::None:
+ case DateTimeFormatStyle::Invalid:
+ // Do nothing.
+ break;
+ }
+
+ nsAutoCString locale;
+ if (aLocale.IsEmpty()) {
+ AutoTArray<nsCString, 10> regionalPrefsLocales;
+ LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales);
+ locale.Assign(regionalPrefsLocales[0]);
+ } else {
+ locale.Assign(aLocale);
+ }
+
+ auto genResult =
+ DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
+ if (genResult.isErr()) {
+ return false;
+ }
+ auto generator = genResult.unwrap();
+
+ auto dfResult = DateTimeFormat::TryCreateFromStyle(
+ MakeStringSpan(locale.get()), style, generator.get(), Nothing());
+ if (dfResult.isErr()) {
+ return false;
+ }
+ auto df = dfResult.unwrap();
+
+ DateTimeFormat::PatternVector pattern;
+ auto patternResult = df->GetPattern(pattern);
+ if (patternResult.isErr()) {
+ return false;
+ }
+
+ aRetVal = NS_ConvertUTF16toUTF8(pattern.begin(), pattern.length());
+ return true;
+}
+
+/**
+ * This method retrieves from mozilla::intl 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,
+ nsACString& aRetVal) {
+ nsAutoCString pattern;
+ if (!GetDateTimePatternForStyle(aDateStyle, aTimeStyle, aLocale, pattern)) {
+ return false;
+ }
+
+ auto genResult =
+ DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
+ if (genResult.isErr()) {
+ return false;
+ }
+
+ nsAutoString patternAsUtf16 = NS_ConvertUTF8toUTF16(pattern);
+ DateTimeFormat::SkeletonVector skeleton;
+ auto generator = genResult.unwrap();
+ auto skeletonResult = generator->GetSkeleton(patternAsUtf16, skeleton);
+ if (skeletonResult.isErr()) {
+ return false;
+ }
+
+ aRetVal = NS_ConvertUTF16toUTF8(skeleton.begin(), skeleton.length());
+ return true;
+}
+
+/**
+ * This method checks for preferences that override the defaults
+ */
+bool OSPreferences::OverrideDateTimePattern(DateTimeFormatStyle aDateStyle,
+ DateTimeFormatStyle aTimeStyle,
+ const nsACString& aLocale,
+ nsACString& aRetVal) {
+ const auto PrefToMaybeString = [](const char* pref) -> Maybe<nsAutoCString> {
+ nsAutoCString value;
+ nsresult nr = Preferences::GetCString(pref, value);
+ if (NS_FAILED(nr) || value.IsEmpty()) {
+ return Nothing();
+ }
+ return Some(std::move(value));
+ };
+
+ Maybe<nsAutoCString> timeSkeleton;
+ switch (aTimeStyle) {
+ case DateTimeFormatStyle::Short:
+ timeSkeleton =
+ PrefToMaybeString("intl.date_time.pattern_override.time_short");
+ break;
+ case DateTimeFormatStyle::Medium:
+ timeSkeleton =
+ PrefToMaybeString("intl.date_time.pattern_override.time_medium");
+ break;
+ case DateTimeFormatStyle::Long:
+ timeSkeleton =
+ PrefToMaybeString("intl.date_time.pattern_override.time_long");
+ break;
+ case DateTimeFormatStyle::Full:
+ timeSkeleton =
+ PrefToMaybeString("intl.date_time.pattern_override.time_full");
+ break;
+ default:
+ break;
+ }
+
+ Maybe<nsAutoCString> dateSkeleton;
+ switch (aDateStyle) {
+ case DateTimeFormatStyle::Short:
+ dateSkeleton =
+ PrefToMaybeString("intl.date_time.pattern_override.date_short");
+ break;
+ case DateTimeFormatStyle::Medium:
+ dateSkeleton =
+ PrefToMaybeString("intl.date_time.pattern_override.date_medium");
+ break;
+ case DateTimeFormatStyle::Long:
+ dateSkeleton =
+ PrefToMaybeString("intl.date_time.pattern_override.date_long");
+ break;
+ case DateTimeFormatStyle::Full:
+ dateSkeleton =
+ PrefToMaybeString("intl.date_time.pattern_override.date_full");
+ break;
+ default:
+ break;
+ }
+
+ nsAutoCString locale;
+ if (aLocale.IsEmpty()) {
+ AutoTArray<nsCString, 10> regionalPrefsLocales;
+ LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales);
+ locale.Assign(regionalPrefsLocales[0]);
+ } else {
+ locale.Assign(aLocale);
+ }
+
+ const auto FillConnectorPattern = [&locale](
+ const nsAutoCString& datePattern,
+ const nsAutoCString& timePattern) {
+ nsAutoCString pattern;
+ GetDateTimeConnectorPattern(nsDependentCString(locale.get()), pattern);
+ int32_t index = pattern.Find("{1}");
+ if (index != kNotFound) {
+ pattern.Replace(index, 3, datePattern);
+ }
+ index = pattern.Find("{0}");
+ if (index != kNotFound) {
+ pattern.Replace(index, 3, timePattern);
+ }
+ return pattern;
+ };
+
+ if (timeSkeleton && dateSkeleton) {
+ aRetVal.Assign(FillConnectorPattern(*dateSkeleton, *timeSkeleton));
+ } else if (timeSkeleton) {
+ if (aDateStyle != DateTimeFormatStyle::None) {
+ nsAutoCString pattern;
+ if (!ReadDateTimePattern(aDateStyle, DateTimeFormatStyle::None, aLocale,
+ pattern) &&
+ !GetDateTimePatternForStyle(aDateStyle, DateTimeFormatStyle::None,
+ aLocale, pattern)) {
+ return false;
+ }
+ aRetVal.Assign(FillConnectorPattern(pattern, *timeSkeleton));
+ } else {
+ aRetVal.Assign(*timeSkeleton);
+ }
+ } else if (dateSkeleton) {
+ if (aTimeStyle != DateTimeFormatStyle::None) {
+ nsAutoCString pattern;
+ if (!ReadDateTimePattern(DateTimeFormatStyle::None, aTimeStyle, aLocale,
+ pattern) &&
+ !GetDateTimePatternForStyle(DateTimeFormatStyle::None, aTimeStyle,
+ aLocale, pattern)) {
+ return false;
+ }
+ aRetVal.Assign(FillConnectorPattern(*dateSkeleton, pattern));
+ } else {
+ aRetVal.Assign(*dateSkeleton);
+ }
+ } else {
+ return false;
+ }
+
+ 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 nsACString& aSkeleton,
+ const nsACString& aLocale,
+ nsACString& aRetVal) {
+ aRetVal.Truncate();
+
+ auto genResult =
+ DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
+ if (genResult.isErr()) {
+ return false;
+ }
+
+ nsAutoString skeletonAsUtf16 = NS_ConvertUTF8toUTF16(aSkeleton);
+ DateTimeFormat::PatternVector pattern;
+ auto generator = genResult.unwrap();
+ auto patternResult = generator->GetBestPattern(skeletonAsUtf16, pattern);
+ if (patternResult.isErr()) {
+ return false;
+ }
+
+ aRetVal = NS_ConvertUTF16toUTF8(pattern.begin(), pattern.length());
+ return true;
+}
+
+/**
+ * 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,
+ nsACString& aRetVal) {
+ // Check for a valid override pref and use that if present.
+ nsAutoCString value;
+ nsresult nr = Preferences::GetCString(
+ "intl.date_time.pattern_override.connector_short", value);
+ if (NS_SUCCEEDED(nr) && value.Find("{0}") != kNotFound &&
+ value.Find("{1}") != kNotFound) {
+ aRetVal = std::move(value);
+ return true;
+ }
+
+ auto genResult =
+ DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
+ if (genResult.isErr()) {
+ return false;
+ }
+
+ auto generator = genResult.unwrap();
+ Span<const char16_t> result = generator->GetPlaceholderPattern();
+ aRetVal = NS_ConvertUTF16toUTF8(result.data(), result.size());
+ return true;
+}
+
+/**
+ * mozIOSPreferences methods
+ */
+NS_IMETHODIMP
+OSPreferences::GetSystemLocales(nsTArray<nsCString>& aRetVal) {
+ if (!mSystemLocales.IsEmpty()) {
+ aRetVal = mSystemLocales.Clone();
+ return NS_OK;
+ }
+
+ if (ReadSystemLocales(aRetVal)) {
+ mSystemLocales = aRetVal.Clone();
+ return NS_OK;
+ }
+
+ // If we failed to get the system locale, we still need
+ // to return something because there are tests out there that
+ // depend on system locale to be set.
+ aRetVal.AppendElement("en-US"_ns);
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+OSPreferences::GetSystemLocale(nsACString& aRetVal) {
+ if (!mSystemLocales.IsEmpty()) {
+ aRetVal = mSystemLocales[0];
+ } else {
+ AutoTArray<nsCString, 10> locales;
+ GetSystemLocales(locales);
+ if (!locales.IsEmpty()) {
+ aRetVal = locales[0];
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OSPreferences::GetRegionalPrefsLocales(nsTArray<nsCString>& aRetVal) {
+ if (!mRegionalPrefsLocales.IsEmpty()) {
+ aRetVal = mRegionalPrefsLocales.Clone();
+ return NS_OK;
+ }
+
+ if (ReadRegionalPrefsLocales(aRetVal)) {
+ mRegionalPrefsLocales = aRetVal.Clone();
+ return NS_OK;
+ }
+
+ // If we failed to read regional prefs locales,
+ // use system locales as last fallback.
+ return GetSystemLocales(aRetVal);
+}
+
+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,
+ nsACString& 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 the locale is not specified, default to first regional prefs locale
+ const nsACString* locale = &aLocale;
+ AutoTArray<nsCString, 10> rpLocales;
+ if (aLocale.IsEmpty()) {
+ LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales);
+ MOZ_ASSERT(rpLocales.Length() > 0);
+ locale = &rpLocales[0];
+ }
+
+ // Create a cache key from the locale + style options
+ nsAutoCString key(*locale);
+ key.Append(':');
+ key.AppendInt(aDateFormatStyle);
+ key.Append(':');
+ key.AppendInt(aTimeFormatStyle);
+
+ nsCString pattern;
+ if (mPatternCache.Get(key, &pattern)) {
+ aRetVal = pattern;
+ return NS_OK;
+ }
+
+ if (!OverrideDateTimePattern(dateStyle, timeStyle, *locale, pattern)) {
+ if (!ReadDateTimePattern(dateStyle, timeStyle, *locale, pattern)) {
+ if (!GetDateTimePatternForStyle(dateStyle, timeStyle, *locale, pattern)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ if (mPatternCache.Count() == kMaxCachedPatterns) {
+ // Don't allow unlimited cache growth; just throw it away in the case of
+ // pathological behavior where a page keeps requesting different formats
+ // and locales.
+ NS_WARNING("flushing DateTimePattern cache");
+ mPatternCache.Clear();
+ }
+ mPatternCache.InsertOrUpdate(key, pattern);
+
+ aRetVal = pattern;
+ return NS_OK;
+}
+
+void OSPreferences::OverrideSkeletonHourCycle(bool aIs24Hour,
+ nsAutoCString& aSkeleton) {
+ if (aIs24Hour) {
+ // If aSkeleton contains 'h' or 'K', replace with 'H' or 'k' respectively,
+ // and delete 'a' if present.
+ if (aSkeleton.FindChar('h') == -1 && aSkeleton.FindChar('K') == -1) {
+ return;
+ }
+ for (int32_t i = 0; i < int32_t(aSkeleton.Length()); ++i) {
+ switch (aSkeleton[i]) {
+ case 'a':
+ aSkeleton.Cut(i, 1);
+ --i;
+ break;
+ case 'h':
+ aSkeleton.SetCharAt('H', i);
+ break;
+ case 'K':
+ aSkeleton.SetCharAt('k', i);
+ break;
+ }
+ }
+ } else {
+ // If skeleton contains 'H' or 'k', replace with 'h' or 'K' respectively,
+ // and add 'a' unless already present.
+ if (aSkeleton.FindChar('H') == -1 && aSkeleton.FindChar('k') == -1) {
+ return;
+ }
+ bool foundA = false;
+ for (size_t i = 0; i < aSkeleton.Length(); ++i) {
+ switch (aSkeleton[i]) {
+ case 'a':
+ foundA = true;
+ break;
+ case 'H':
+ aSkeleton.SetCharAt('h', i);
+ break;
+ case 'k':
+ aSkeleton.SetCharAt('K', i);
+ break;
+ }
+ }
+ if (!foundA) {
+ aSkeleton.Append(char16_t('a'));
+ }
+ }
+}