Bug 1883874 [wpt PR 44933] - Update wpt metadata, a=testonly
[gecko.git] / intl / locale / OSPreferences.cpp
blobb87924b61a53d9e3f91c8079eb09b8f1f218dab5
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/. */
6 /**
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;
32 if (!result) {
33 MOZ_ASSERT(NS_IsMainThread(),
34 "OSPreferences should be initialized on main thread!");
35 if (!NS_IsMainThread()) {
36 return nullptr;
38 sInstance = new OSPreferences();
39 result = sInstance;
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());
54 if (!sInstance) {
55 // This will create the static instance; then we just drop the extra
56 // reference.
57 RefPtr<OSPreferences> result = GetInstanceAddRefed();
59 return sInstance;
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();
69 if (obs) {
70 obs->NotifyObservers(nullptr, "intl:system-locales-changed", nullptr);
75 OSPreferences::~OSPreferences() {
76 Preferences::UnregisterPrefixCallback(PreferenceChanged,
77 "intl.date_time.pattern_override");
78 RemoveObservers();
81 /*static*/
82 void OSPreferences::PreferenceChanged(const char* aPrefName,
83 void* /* aClosure */) {
84 if (sInstance) {
85 sInstance->mPatternCache.Clear();
89 /**
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
104 * date/time style.
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);
115 break;
116 case DateTimeFormatStyle::Medium:
117 style.time = Some(DateTimeFormat::Style::Medium);
118 break;
119 case DateTimeFormatStyle::Long:
120 style.time = Some(DateTimeFormat::Style::Long);
121 break;
122 case DateTimeFormatStyle::Full:
123 style.time = Some(DateTimeFormat::Style::Full);
124 break;
125 case DateTimeFormatStyle::None:
126 case DateTimeFormatStyle::Invalid:
127 // Do nothing.
128 break;
131 switch (aDateStyle) {
132 case DateTimeFormatStyle::Short:
133 style.date = Some(DateTimeFormat::Style::Short);
134 break;
135 case DateTimeFormatStyle::Medium:
136 style.date = Some(DateTimeFormat::Style::Medium);
137 break;
138 case DateTimeFormatStyle::Long:
139 style.date = Some(DateTimeFormat::Style::Long);
140 break;
141 case DateTimeFormatStyle::Full:
142 style.date = Some(DateTimeFormat::Style::Full);
143 break;
144 case DateTimeFormatStyle::None:
145 case DateTimeFormatStyle::Invalid:
146 // Do nothing.
147 break;
150 nsAutoCString locale;
151 if (aLocale.IsEmpty()) {
152 AutoTArray<nsCString, 10> regionalPrefsLocales;
153 LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales);
154 locale.Assign(regionalPrefsLocales[0]);
155 } else {
156 locale.Assign(aLocale);
159 auto genResult =
160 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
161 if (genResult.isErr()) {
162 return false;
164 auto generator = genResult.unwrap();
166 auto dfResult = DateTimeFormat::TryCreateFromStyle(
167 MakeStringSpan(locale.get()), style, generator.get(), Nothing());
168 if (dfResult.isErr()) {
169 return false;
171 auto df = dfResult.unwrap();
173 DateTimeFormat::PatternVector pattern;
174 auto patternResult = df->GetPattern(pattern);
175 if (patternResult.isErr()) {
176 return false;
179 aRetVal = NS_ConvertUTF16toUTF8(pattern.begin(), pattern.length());
180 return true;
184 * This method retrieves from mozilla::intl the best skeleton for a given
185 * date/time style.
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)) {
198 return false;
201 auto genResult =
202 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
203 if (genResult.isErr()) {
204 return false;
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()) {
212 return false;
215 aRetVal = NS_ConvertUTF16toUTF8(skeleton.begin(), skeleton.length());
216 return true;
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> {
227 nsAutoCString value;
228 nsresult nr = Preferences::GetCString(pref, value);
229 if (NS_FAILED(nr) || value.IsEmpty()) {
230 return Nothing();
232 return Some(std::move(value));
235 Maybe<nsAutoCString> timeSkeleton;
236 switch (aTimeStyle) {
237 case DateTimeFormatStyle::Short:
238 timeSkeleton =
239 PrefToMaybeString("intl.date_time.pattern_override.time_short");
240 break;
241 case DateTimeFormatStyle::Medium:
242 timeSkeleton =
243 PrefToMaybeString("intl.date_time.pattern_override.time_medium");
244 break;
245 case DateTimeFormatStyle::Long:
246 timeSkeleton =
247 PrefToMaybeString("intl.date_time.pattern_override.time_long");
248 break;
249 case DateTimeFormatStyle::Full:
250 timeSkeleton =
251 PrefToMaybeString("intl.date_time.pattern_override.time_full");
252 break;
253 default:
254 break;
257 Maybe<nsAutoCString> dateSkeleton;
258 switch (aDateStyle) {
259 case DateTimeFormatStyle::Short:
260 dateSkeleton =
261 PrefToMaybeString("intl.date_time.pattern_override.date_short");
262 break;
263 case DateTimeFormatStyle::Medium:
264 dateSkeleton =
265 PrefToMaybeString("intl.date_time.pattern_override.date_medium");
266 break;
267 case DateTimeFormatStyle::Long:
268 dateSkeleton =
269 PrefToMaybeString("intl.date_time.pattern_override.date_long");
270 break;
271 case DateTimeFormatStyle::Full:
272 dateSkeleton =
273 PrefToMaybeString("intl.date_time.pattern_override.date_full");
274 break;
275 default:
276 break;
279 nsAutoCString locale;
280 if (aLocale.IsEmpty()) {
281 AutoTArray<nsCString, 10> regionalPrefsLocales;
282 LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales);
283 locale.Assign(regionalPrefsLocales[0]);
284 } else {
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);
301 return pattern;
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,
310 pattern) &&
311 !GetDateTimePatternForStyle(aDateStyle, DateTimeFormatStyle::None,
312 aLocale, pattern)) {
313 return false;
315 aRetVal.Assign(FillConnectorPattern(pattern, *timeSkeleton));
316 } else {
317 aRetVal.Assign(*timeSkeleton);
319 } else if (dateSkeleton) {
320 if (aTimeStyle != DateTimeFormatStyle::None) {
321 nsAutoCString pattern;
322 if (!ReadDateTimePattern(DateTimeFormatStyle::None, aTimeStyle, aLocale,
323 pattern) &&
324 !GetDateTimePatternForStyle(DateTimeFormatStyle::None, aTimeStyle,
325 aLocale, pattern)) {
326 return false;
328 aRetVal.Assign(FillConnectorPattern(*dateSkeleton, pattern));
329 } else {
330 aRetVal.Assign(*dateSkeleton);
332 } else {
333 return false;
336 return true;
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.
345 * For example:
346 * "Hm" skeleton for "en-US" will return "H:m"
348 bool OSPreferences::GetPatternForSkeleton(const nsACString& aSkeleton,
349 const nsACString& aLocale,
350 nsACString& aRetVal) {
351 aRetVal.Truncate();
353 auto genResult =
354 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
355 if (genResult.isErr()) {
356 return false;
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()) {
364 return false;
367 aRetVal = NS_ConvertUTF16toUTF8(pattern.begin(), pattern.length());
368 return true;
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
376 * pattern.
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.
383 nsAutoCString value;
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);
389 return true;
392 auto genResult =
393 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
394 if (genResult.isErr()) {
395 return false;
398 auto generator = genResult.unwrap();
399 Span<const char16_t> result = generator->GetPlaceholderPattern();
400 aRetVal = NS_ConvertUTF16toUTF8(result.data(), result.size());
401 return true;
405 * mozIOSPreferences methods
407 NS_IMETHODIMP
408 OSPreferences::GetSystemLocales(nsTArray<nsCString>& aRetVal) {
409 if (!mSystemLocales.IsEmpty()) {
410 aRetVal = mSystemLocales.Clone();
411 return NS_OK;
414 if (ReadSystemLocales(aRetVal)) {
415 mSystemLocales = aRetVal.Clone();
416 return NS_OK;
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;
426 NS_IMETHODIMP
427 OSPreferences::GetSystemLocale(nsACString& aRetVal) {
428 if (!mSystemLocales.IsEmpty()) {
429 aRetVal = mSystemLocales[0];
430 } else {
431 AutoTArray<nsCString, 10> locales;
432 GetSystemLocales(locales);
433 if (!locales.IsEmpty()) {
434 aRetVal = locales[0];
437 return NS_OK;
440 NS_IMETHODIMP
441 OSPreferences::GetRegionalPrefsLocales(nsTArray<nsCString>& aRetVal) {
442 if (!mRegionalPrefsLocales.IsEmpty()) {
443 aRetVal = mRegionalPrefsLocales.Clone();
444 return NS_OK;
447 if (ReadRegionalPrefsLocales(aRetVal)) {
448 mRegionalPrefsLocales = aRetVal.Clone();
449 return NS_OK;
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.
461 case 0:
462 return OSPreferences::DateTimeFormatStyle::None;
463 case 1:
464 return OSPreferences::DateTimeFormatStyle::Short;
465 case 2:
466 return OSPreferences::DateTimeFormatStyle::Medium;
467 case 3:
468 return OSPreferences::DateTimeFormatStyle::Long;
469 case 4:
470 return OSPreferences::DateTimeFormatStyle::Full;
472 return OSPreferences::DateTimeFormatStyle::Invalid;
475 NS_IMETHODIMP
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,
490 // let's exit early.
491 if (timeStyle == DateTimeFormatStyle::None &&
492 dateStyle == DateTimeFormatStyle::None) {
493 return NS_OK;
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);
507 key.Append(':');
508 key.AppendInt(aDateFormatStyle);
509 key.Append(':');
510 key.AppendInt(aTimeFormatStyle);
512 nsCString pattern;
513 if (mPatternCache.Get(key, &pattern)) {
514 aRetVal = pattern;
515 return NS_OK;
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
529 // and locales.
530 NS_WARNING("flushing DateTimePattern cache");
531 mPatternCache.Clear();
533 mPatternCache.InsertOrUpdate(key, pattern);
535 aRetVal = pattern;
536 return NS_OK;
539 void OSPreferences::OverrideSkeletonHourCycle(bool aIs24Hour,
540 nsAutoCString& aSkeleton) {
541 if (aIs24Hour) {
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) {
545 return;
547 for (int32_t i = 0; i < int32_t(aSkeleton.Length()); ++i) {
548 switch (aSkeleton[i]) {
549 case 'a':
550 aSkeleton.Cut(i, 1);
551 --i;
552 break;
553 case 'h':
554 aSkeleton.SetCharAt('H', i);
555 break;
556 case 'K':
557 aSkeleton.SetCharAt('k', i);
558 break;
561 } else {
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) {
565 return;
567 bool foundA = false;
568 for (size_t i = 0; i < aSkeleton.Length(); ++i) {
569 switch (aSkeleton[i]) {
570 case 'a':
571 foundA = true;
572 break;
573 case 'H':
574 aSkeleton.SetCharAt('h', i);
575 break;
576 case 'k':
577 aSkeleton.SetCharAt('K', i);
578 break;
581 if (!foundA) {
582 aSkeleton.Append(char16_t('a'));