1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 * This is a shared part of the OSPreferences API implementation.
8 * It defines helper methods and public methods that are calling
9 * platform-specific private methods.
12 #include "OSPreferences.h"
14 #include "mozilla/ClearOnShutdown.h"
15 #include "mozilla/intl/DateTimePatternGenerator.h"
16 #include "mozilla/intl/DateTimeFormat.h"
17 #include "mozilla/intl/LocaleService.h"
18 #include "mozilla/Preferences.h"
19 #include "mozilla/Result.h"
20 #include "mozilla/Services.h"
21 #include "nsIObserverService.h"
23 using namespace mozilla::intl
;
25 NS_IMPL_ISUPPORTS(OSPreferences
, mozIOSPreferences
)
27 mozilla::StaticRefPtr
<OSPreferences
> OSPreferences::sInstance
;
29 // Return a new strong reference to the instance, creating it if necessary.
30 already_AddRefed
<OSPreferences
> OSPreferences::GetInstanceAddRefed() {
31 RefPtr
<OSPreferences
> result
= sInstance
;
33 MOZ_ASSERT(NS_IsMainThread(),
34 "OSPreferences should be initialized on main thread!");
35 if (!NS_IsMainThread()) {
38 sInstance
= new OSPreferences();
41 DebugOnly
<nsresult
> rv
= Preferences::RegisterPrefixCallback(
42 PreferenceChanged
, "intl.date_time.pattern_override");
43 MOZ_ASSERT(NS_SUCCEEDED(rv
), "Adding observers failed.");
45 ClearOnShutdown(&sInstance
);
47 return result
.forget();
50 // Return a raw pointer to the instance: not for off-main-thread use,
51 // because ClearOnShutdown means it could go away unexpectedly.
52 OSPreferences
* OSPreferences::GetInstance() {
53 MOZ_ASSERT(NS_IsMainThread());
55 // This will create the static instance; then we just drop the extra
57 RefPtr
<OSPreferences
> result
= GetInstanceAddRefed();
62 void OSPreferences::Refresh() {
63 nsTArray
<nsCString
> newLocales
;
64 ReadSystemLocales(newLocales
);
66 if (mSystemLocales
!= newLocales
) {
67 mSystemLocales
= std::move(newLocales
);
68 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
70 obs
->NotifyObservers(nullptr, "intl:system-locales-changed", nullptr);
75 OSPreferences::~OSPreferences() {
76 Preferences::UnregisterPrefixCallback(PreferenceChanged
,
77 "intl.date_time.pattern_override");
82 void OSPreferences::PreferenceChanged(const char* aPrefName
,
83 void* /* aClosure */) {
85 sInstance
->mPatternCache
.Clear();
90 * This method should be called by every method of OSPreferences that
91 * retrieves a locale id from external source.
93 * It attempts to retrieve as much of the locale ID as possible, cutting
94 * out bits that are not understood (non-strict behavior of ICU).
96 * It returns true if the canonicalization was successful.
98 bool OSPreferences::CanonicalizeLanguageTag(nsCString
& aLoc
) {
99 return LocaleService::CanonicalizeLanguageId(aLoc
);
103 * This method retrieves from mozilla::intl the best pattern for a given
106 bool OSPreferences::GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle
,
107 DateTimeFormatStyle aTimeStyle
,
108 const nsACString
& aLocale
,
109 nsACString
& aRetVal
) {
110 DateTimeFormat::StyleBag style
;
112 switch (aTimeStyle
) {
113 case DateTimeFormatStyle::Short
:
114 style
.time
= Some(DateTimeFormat::Style::Short
);
116 case DateTimeFormatStyle::Medium
:
117 style
.time
= Some(DateTimeFormat::Style::Medium
);
119 case DateTimeFormatStyle::Long
:
120 style
.time
= Some(DateTimeFormat::Style::Long
);
122 case DateTimeFormatStyle::Full
:
123 style
.time
= Some(DateTimeFormat::Style::Full
);
125 case DateTimeFormatStyle::None
:
126 case DateTimeFormatStyle::Invalid
:
131 switch (aDateStyle
) {
132 case DateTimeFormatStyle::Short
:
133 style
.date
= Some(DateTimeFormat::Style::Short
);
135 case DateTimeFormatStyle::Medium
:
136 style
.date
= Some(DateTimeFormat::Style::Medium
);
138 case DateTimeFormatStyle::Long
:
139 style
.date
= Some(DateTimeFormat::Style::Long
);
141 case DateTimeFormatStyle::Full
:
142 style
.date
= Some(DateTimeFormat::Style::Full
);
144 case DateTimeFormatStyle::None
:
145 case DateTimeFormatStyle::Invalid
:
150 nsAutoCString locale
;
151 if (aLocale
.IsEmpty()) {
152 AutoTArray
<nsCString
, 10> regionalPrefsLocales
;
153 LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales
);
154 locale
.Assign(regionalPrefsLocales
[0]);
156 locale
.Assign(aLocale
);
160 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale
).get());
161 if (genResult
.isErr()) {
164 auto generator
= genResult
.unwrap();
166 auto dfResult
= DateTimeFormat::TryCreateFromStyle(
167 MakeStringSpan(locale
.get()), style
, generator
.get(), Nothing());
168 if (dfResult
.isErr()) {
171 auto df
= dfResult
.unwrap();
173 DateTimeFormat::PatternVector pattern
;
174 auto patternResult
= df
->GetPattern(pattern
);
175 if (patternResult
.isErr()) {
179 aRetVal
= NS_ConvertUTF16toUTF8(pattern
.begin(), pattern
.length());
184 * This method retrieves from mozilla::intl the best skeleton for a given
187 * This is useful for cases where an OS does not provide its own patterns,
188 * but provide ability to customize the skeleton, like alter hourCycle setting.
190 * The returned value is a skeleton that matches the styles.
192 bool OSPreferences::GetDateTimeSkeletonForStyle(DateTimeFormatStyle aDateStyle
,
193 DateTimeFormatStyle aTimeStyle
,
194 const nsACString
& aLocale
,
195 nsACString
& aRetVal
) {
196 nsAutoCString pattern
;
197 if (!GetDateTimePatternForStyle(aDateStyle
, aTimeStyle
, aLocale
, pattern
)) {
202 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale
).get());
203 if (genResult
.isErr()) {
207 nsAutoString patternAsUtf16
= NS_ConvertUTF8toUTF16(pattern
);
208 DateTimeFormat::SkeletonVector skeleton
;
209 auto generator
= genResult
.unwrap();
210 auto skeletonResult
= generator
->GetSkeleton(patternAsUtf16
, skeleton
);
211 if (skeletonResult
.isErr()) {
215 aRetVal
= NS_ConvertUTF16toUTF8(skeleton
.begin(), skeleton
.length());
220 * This method checks for preferences that override the defaults
222 bool OSPreferences::OverrideDateTimePattern(DateTimeFormatStyle aDateStyle
,
223 DateTimeFormatStyle aTimeStyle
,
224 const nsACString
& aLocale
,
225 nsACString
& aRetVal
) {
226 const auto PrefToMaybeString
= [](const char* pref
) -> Maybe
<nsAutoCString
> {
228 nsresult nr
= Preferences::GetCString(pref
, value
);
229 if (NS_FAILED(nr
) || value
.IsEmpty()) {
232 return Some(std::move(value
));
235 Maybe
<nsAutoCString
> timeSkeleton
;
236 switch (aTimeStyle
) {
237 case DateTimeFormatStyle::Short
:
239 PrefToMaybeString("intl.date_time.pattern_override.time_short");
241 case DateTimeFormatStyle::Medium
:
243 PrefToMaybeString("intl.date_time.pattern_override.time_medium");
245 case DateTimeFormatStyle::Long
:
247 PrefToMaybeString("intl.date_time.pattern_override.time_long");
249 case DateTimeFormatStyle::Full
:
251 PrefToMaybeString("intl.date_time.pattern_override.time_full");
257 Maybe
<nsAutoCString
> dateSkeleton
;
258 switch (aDateStyle
) {
259 case DateTimeFormatStyle::Short
:
261 PrefToMaybeString("intl.date_time.pattern_override.date_short");
263 case DateTimeFormatStyle::Medium
:
265 PrefToMaybeString("intl.date_time.pattern_override.date_medium");
267 case DateTimeFormatStyle::Long
:
269 PrefToMaybeString("intl.date_time.pattern_override.date_long");
271 case DateTimeFormatStyle::Full
:
273 PrefToMaybeString("intl.date_time.pattern_override.date_full");
279 nsAutoCString locale
;
280 if (aLocale
.IsEmpty()) {
281 AutoTArray
<nsCString
, 10> regionalPrefsLocales
;
282 LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales
);
283 locale
.Assign(regionalPrefsLocales
[0]);
285 locale
.Assign(aLocale
);
288 const auto FillConnectorPattern
= [&locale
](
289 const nsAutoCString
& datePattern
,
290 const nsAutoCString
& timePattern
) {
291 nsAutoCString pattern
;
292 GetDateTimeConnectorPattern(nsDependentCString(locale
.get()), pattern
);
293 int32_t index
= pattern
.Find("{1}");
294 if (index
!= kNotFound
) {
295 pattern
.Replace(index
, 3, datePattern
);
297 index
= pattern
.Find("{0}");
298 if (index
!= kNotFound
) {
299 pattern
.Replace(index
, 3, timePattern
);
304 if (timeSkeleton
&& dateSkeleton
) {
305 aRetVal
.Assign(FillConnectorPattern(*dateSkeleton
, *timeSkeleton
));
306 } else if (timeSkeleton
) {
307 if (aDateStyle
!= DateTimeFormatStyle::None
) {
308 nsAutoCString pattern
;
309 if (!ReadDateTimePattern(aDateStyle
, DateTimeFormatStyle::None
, aLocale
,
311 !GetDateTimePatternForStyle(aDateStyle
, DateTimeFormatStyle::None
,
315 aRetVal
.Assign(FillConnectorPattern(pattern
, *timeSkeleton
));
317 aRetVal
.Assign(*timeSkeleton
);
319 } else if (dateSkeleton
) {
320 if (aTimeStyle
!= DateTimeFormatStyle::None
) {
321 nsAutoCString pattern
;
322 if (!ReadDateTimePattern(DateTimeFormatStyle::None
, aTimeStyle
, aLocale
,
324 !GetDateTimePatternForStyle(DateTimeFormatStyle::None
, aTimeStyle
,
328 aRetVal
.Assign(FillConnectorPattern(*dateSkeleton
, pattern
));
330 aRetVal
.Assign(*dateSkeleton
);
340 * This function is a counterpart to GetDateTimeSkeletonForStyle.
342 * It takes a skeleton and returns the best available pattern for a given locale
343 * that represents the provided skeleton.
346 * "Hm" skeleton for "en-US" will return "H:m"
348 bool OSPreferences::GetPatternForSkeleton(const nsACString
& aSkeleton
,
349 const nsACString
& aLocale
,
350 nsACString
& aRetVal
) {
354 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale
).get());
355 if (genResult
.isErr()) {
359 nsAutoString skeletonAsUtf16
= NS_ConvertUTF8toUTF16(aSkeleton
);
360 DateTimeFormat::PatternVector pattern
;
361 auto generator
= genResult
.unwrap();
362 auto patternResult
= generator
->GetBestPattern(skeletonAsUtf16
, pattern
);
363 if (patternResult
.isErr()) {
367 aRetVal
= NS_ConvertUTF16toUTF8(pattern
.begin(), pattern
.length());
372 * This function returns a pattern that should be used to join date and time
373 * patterns into a single date/time pattern string.
375 * It's useful for OSes that do not provide an API to retrieve such combined
378 * An example output is "{1}, {0}".
380 bool OSPreferences::GetDateTimeConnectorPattern(const nsACString
& aLocale
,
381 nsACString
& aRetVal
) {
382 // Check for a valid override pref and use that if present.
384 nsresult nr
= Preferences::GetCString(
385 "intl.date_time.pattern_override.connector_short", value
);
386 if (NS_SUCCEEDED(nr
) && value
.Find("{0}") != kNotFound
&&
387 value
.Find("{1}") != kNotFound
) {
388 aRetVal
= std::move(value
);
393 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale
).get());
394 if (genResult
.isErr()) {
398 auto generator
= genResult
.unwrap();
399 Span
<const char16_t
> result
= generator
->GetPlaceholderPattern();
400 aRetVal
= NS_ConvertUTF16toUTF8(result
.data(), result
.size());
405 * mozIOSPreferences methods
408 OSPreferences::GetSystemLocales(nsTArray
<nsCString
>& aRetVal
) {
409 if (!mSystemLocales
.IsEmpty()) {
410 aRetVal
= mSystemLocales
.Clone();
414 if (ReadSystemLocales(aRetVal
)) {
415 mSystemLocales
= aRetVal
.Clone();
419 // If we failed to get the system locale, we still need
420 // to return something because there are tests out there that
421 // depend on system locale to be set.
422 aRetVal
.AppendElement("en-US"_ns
);
423 return NS_ERROR_FAILURE
;
427 OSPreferences::GetSystemLocale(nsACString
& aRetVal
) {
428 if (!mSystemLocales
.IsEmpty()) {
429 aRetVal
= mSystemLocales
[0];
431 AutoTArray
<nsCString
, 10> locales
;
432 GetSystemLocales(locales
);
433 if (!locales
.IsEmpty()) {
434 aRetVal
= locales
[0];
441 OSPreferences::GetRegionalPrefsLocales(nsTArray
<nsCString
>& aRetVal
) {
442 if (!mRegionalPrefsLocales
.IsEmpty()) {
443 aRetVal
= mRegionalPrefsLocales
.Clone();
447 if (ReadRegionalPrefsLocales(aRetVal
)) {
448 mRegionalPrefsLocales
= aRetVal
.Clone();
452 // If we failed to read regional prefs locales,
453 // use system locales as last fallback.
454 return GetSystemLocales(aRetVal
);
457 static OSPreferences::DateTimeFormatStyle
ToDateTimeFormatStyle(
458 int32_t aTimeFormat
) {
459 switch (aTimeFormat
) {
460 // See mozIOSPreferences.idl for the integer values here.
462 return OSPreferences::DateTimeFormatStyle::None
;
464 return OSPreferences::DateTimeFormatStyle::Short
;
466 return OSPreferences::DateTimeFormatStyle::Medium
;
468 return OSPreferences::DateTimeFormatStyle::Long
;
470 return OSPreferences::DateTimeFormatStyle::Full
;
472 return OSPreferences::DateTimeFormatStyle::Invalid
;
476 OSPreferences::GetDateTimePattern(int32_t aDateFormatStyle
,
477 int32_t aTimeFormatStyle
,
478 const nsACString
& aLocale
,
479 nsACString
& aRetVal
) {
480 DateTimeFormatStyle dateStyle
= ToDateTimeFormatStyle(aDateFormatStyle
);
481 if (dateStyle
== DateTimeFormatStyle::Invalid
) {
482 return NS_ERROR_INVALID_ARG
;
484 DateTimeFormatStyle timeStyle
= ToDateTimeFormatStyle(aTimeFormatStyle
);
485 if (timeStyle
== DateTimeFormatStyle::Invalid
) {
486 return NS_ERROR_INVALID_ARG
;
489 // If the user is asking for None on both, date and time style,
491 if (timeStyle
== DateTimeFormatStyle::None
&&
492 dateStyle
== DateTimeFormatStyle::None
) {
496 // If the locale is not specified, default to first regional prefs locale
497 const nsACString
* locale
= &aLocale
;
498 AutoTArray
<nsCString
, 10> rpLocales
;
499 if (aLocale
.IsEmpty()) {
500 LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales
);
501 MOZ_ASSERT(rpLocales
.Length() > 0);
502 locale
= &rpLocales
[0];
505 // Create a cache key from the locale + style options
506 nsAutoCString
key(*locale
);
508 key
.AppendInt(aDateFormatStyle
);
510 key
.AppendInt(aTimeFormatStyle
);
513 if (mPatternCache
.Get(key
, &pattern
)) {
518 if (!OverrideDateTimePattern(dateStyle
, timeStyle
, *locale
, pattern
)) {
519 if (!ReadDateTimePattern(dateStyle
, timeStyle
, *locale
, pattern
)) {
520 if (!GetDateTimePatternForStyle(dateStyle
, timeStyle
, *locale
, pattern
)) {
521 return NS_ERROR_FAILURE
;
526 if (mPatternCache
.Count() == kMaxCachedPatterns
) {
527 // Don't allow unlimited cache growth; just throw it away in the case of
528 // pathological behavior where a page keeps requesting different formats
530 NS_WARNING("flushing DateTimePattern cache");
531 mPatternCache
.Clear();
533 mPatternCache
.InsertOrUpdate(key
, pattern
);
539 void OSPreferences::OverrideSkeletonHourCycle(bool aIs24Hour
,
540 nsAutoCString
& aSkeleton
) {
542 // If aSkeleton contains 'h' or 'K', replace with 'H' or 'k' respectively,
543 // and delete 'a' if present.
544 if (aSkeleton
.FindChar('h') == -1 && aSkeleton
.FindChar('K') == -1) {
547 for (int32_t i
= 0; i
< int32_t(aSkeleton
.Length()); ++i
) {
548 switch (aSkeleton
[i
]) {
554 aSkeleton
.SetCharAt('H', i
);
557 aSkeleton
.SetCharAt('k', i
);
562 // If skeleton contains 'H' or 'k', replace with 'h' or 'K' respectively,
563 // and add 'a' unless already present.
564 if (aSkeleton
.FindChar('H') == -1 && aSkeleton
.FindChar('k') == -1) {
568 for (size_t i
= 0; i
< aSkeleton
.Length(); ++i
) {
569 switch (aSkeleton
[i
]) {
574 aSkeleton
.SetCharAt('h', i
);
577 aSkeleton
.SetCharAt('K', i
);
582 aSkeleton
.Append(char16_t('a'));