OSPreferences.cpp (18608B)
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/. */ 5 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. 10 */ 11 12 #include "OSPreferences.h" 13 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/Services.h" 20 #include "nsIObserverService.h" 21 22 using namespace mozilla::intl; 23 24 NS_IMPL_ISUPPORTS(OSPreferences, mozIOSPreferences) 25 26 mozilla::StaticRefPtr<OSPreferences> OSPreferences::sInstance; 27 28 // Return a new strong reference to the instance, creating it if necessary. 29 already_AddRefed<OSPreferences> OSPreferences::GetInstanceAddRefed() { 30 RefPtr<OSPreferences> result = sInstance; 31 if (!result) { 32 MOZ_ASSERT(NS_IsMainThread(), 33 "OSPreferences should be initialized on main thread!"); 34 if (!NS_IsMainThread()) { 35 return nullptr; 36 } 37 sInstance = new OSPreferences(); 38 result = sInstance; 39 40 DebugOnly<nsresult> rv = Preferences::RegisterPrefixCallback( 41 PreferenceChanged, "intl.date_time.pattern_override"); 42 MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed."); 43 44 ClearOnShutdown(&sInstance); 45 } 46 return result.forget(); 47 } 48 49 // Return a raw pointer to the instance: not for off-main-thread use, 50 // because ClearOnShutdown means it could go away unexpectedly. 51 OSPreferences* OSPreferences::GetInstance() { 52 MOZ_ASSERT(NS_IsMainThread()); 53 if (!sInstance) { 54 // This will create the static instance; then we just drop the extra 55 // reference. 56 RefPtr<OSPreferences> result = GetInstanceAddRefed(); 57 } 58 return sInstance; 59 } 60 61 void OSPreferences::Refresh() { 62 // TODO: in content processes, get system locales from the parent process 63 // to ensure consistency and avoid depending on APIs that may be blocked 64 // by sandboxing (see bug 1946691). 65 // (Not strictly necessary for now as we don't currently use Refresh() on 66 // Windows.) 67 68 nsTArray<nsCString> newLocales; 69 ReadSystemLocales(newLocales); 70 71 if (mSystemLocales != newLocales) { 72 mSystemLocales = std::move(newLocales); 73 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 74 if (obs) { 75 obs->NotifyObservers(nullptr, "intl:system-locales-changed", nullptr); 76 } 77 } 78 } 79 80 OSPreferences::~OSPreferences() { 81 Preferences::UnregisterPrefixCallback(PreferenceChanged, 82 "intl.date_time.pattern_override"); 83 RemoveObservers(); 84 } 85 86 /*static*/ 87 void OSPreferences::PreferenceChanged(const char* aPrefName, 88 void* /* aClosure */) { 89 if (sInstance) { 90 sInstance->mPatternCache.Clear(); 91 } 92 } 93 94 /** 95 * This method should be called by every method of OSPreferences that 96 * retrieves a locale id from external source. 97 * 98 * It attempts to retrieve as much of the locale ID as possible, cutting 99 * out bits that are not understood (non-strict behavior of ICU). 100 * 101 * It returns true if the canonicalization was successful. 102 */ 103 bool OSPreferences::CanonicalizeLanguageTag(nsCString& aLoc) { 104 return LocaleService::CanonicalizeLanguageId(aLoc); 105 } 106 107 /** 108 * This method retrieves from mozilla::intl the best pattern for a given 109 * date/time style. 110 */ 111 bool OSPreferences::GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle, 112 DateTimeFormatStyle aTimeStyle, 113 const nsACString& aLocale, 114 nsACString& aRetVal) { 115 DateTimeFormat::StyleBag style; 116 117 switch (aTimeStyle) { 118 case DateTimeFormatStyle::Short: 119 style.time = Some(DateTimeFormat::Style::Short); 120 break; 121 case DateTimeFormatStyle::Medium: 122 style.time = Some(DateTimeFormat::Style::Medium); 123 break; 124 case DateTimeFormatStyle::Long: 125 style.time = Some(DateTimeFormat::Style::Long); 126 break; 127 case DateTimeFormatStyle::Full: 128 style.time = Some(DateTimeFormat::Style::Full); 129 break; 130 case DateTimeFormatStyle::None: 131 case DateTimeFormatStyle::Invalid: 132 // Do nothing. 133 break; 134 } 135 136 switch (aDateStyle) { 137 case DateTimeFormatStyle::Short: 138 style.date = Some(DateTimeFormat::Style::Short); 139 break; 140 case DateTimeFormatStyle::Medium: 141 style.date = Some(DateTimeFormat::Style::Medium); 142 break; 143 case DateTimeFormatStyle::Long: 144 style.date = Some(DateTimeFormat::Style::Long); 145 break; 146 case DateTimeFormatStyle::Full: 147 style.date = Some(DateTimeFormat::Style::Full); 148 break; 149 case DateTimeFormatStyle::None: 150 case DateTimeFormatStyle::Invalid: 151 // Do nothing. 152 break; 153 } 154 155 nsAutoCString locale; 156 if (aLocale.IsEmpty()) { 157 AutoTArray<nsCString, 10> regionalPrefsLocales; 158 LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales); 159 locale.Assign(regionalPrefsLocales[0]); 160 } else { 161 locale.Assign(aLocale); 162 } 163 164 auto genResult = 165 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get()); 166 if (genResult.isErr()) { 167 return false; 168 } 169 auto generator = genResult.unwrap(); 170 171 auto dfResult = DateTimeFormat::TryCreateFromStyle( 172 MakeStringSpan(locale.get()), style, generator.get(), Nothing()); 173 if (dfResult.isErr()) { 174 return false; 175 } 176 auto df = dfResult.unwrap(); 177 178 DateTimeFormat::PatternVector pattern; 179 auto patternResult = df->GetPattern(pattern); 180 if (patternResult.isErr()) { 181 return false; 182 } 183 184 aRetVal = NS_ConvertUTF16toUTF8(pattern.begin(), pattern.length()); 185 return true; 186 } 187 188 /** 189 * This method retrieves from mozilla::intl the best skeleton for a given 190 * date/time style. 191 * 192 * This is useful for cases where an OS does not provide its own patterns, 193 * but provide ability to customize the skeleton, like alter hourCycle setting. 194 * 195 * The returned value is a skeleton that matches the styles. 196 */ 197 bool OSPreferences::GetDateTimeSkeletonForStyle(DateTimeFormatStyle aDateStyle, 198 DateTimeFormatStyle aTimeStyle, 199 const nsACString& aLocale, 200 nsACString& aRetVal) { 201 nsAutoCString pattern; 202 if (!GetDateTimePatternForStyle(aDateStyle, aTimeStyle, aLocale, pattern)) { 203 return false; 204 } 205 206 auto genResult = 207 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get()); 208 if (genResult.isErr()) { 209 return false; 210 } 211 212 nsAutoString patternAsUtf16 = NS_ConvertUTF8toUTF16(pattern); 213 DateTimeFormat::SkeletonVector skeleton; 214 auto generator = genResult.unwrap(); 215 auto skeletonResult = generator->GetSkeleton(patternAsUtf16, skeleton); 216 if (skeletonResult.isErr()) { 217 return false; 218 } 219 220 aRetVal = NS_ConvertUTF16toUTF8(skeleton.begin(), skeleton.length()); 221 return true; 222 } 223 224 /** 225 * This method checks for preferences that override the defaults 226 */ 227 bool OSPreferences::OverrideDateTimePattern(DateTimeFormatStyle aDateStyle, 228 DateTimeFormatStyle aTimeStyle, 229 const nsACString& aLocale, 230 nsACString& aRetVal) { 231 const auto PrefToMaybeString = [](const char* pref) -> Maybe<nsAutoCString> { 232 nsAutoCString value; 233 nsresult nr = Preferences::GetCString(pref, value); 234 if (NS_FAILED(nr) || value.IsEmpty()) { 235 return Nothing(); 236 } 237 return Some(std::move(value)); 238 }; 239 240 Maybe<nsAutoCString> timeSkeleton; 241 switch (aTimeStyle) { 242 case DateTimeFormatStyle::Short: 243 timeSkeleton = 244 PrefToMaybeString("intl.date_time.pattern_override.time_short"); 245 break; 246 case DateTimeFormatStyle::Medium: 247 timeSkeleton = 248 PrefToMaybeString("intl.date_time.pattern_override.time_medium"); 249 break; 250 case DateTimeFormatStyle::Long: 251 timeSkeleton = 252 PrefToMaybeString("intl.date_time.pattern_override.time_long"); 253 break; 254 case DateTimeFormatStyle::Full: 255 timeSkeleton = 256 PrefToMaybeString("intl.date_time.pattern_override.time_full"); 257 break; 258 default: 259 break; 260 } 261 262 Maybe<nsAutoCString> dateSkeleton; 263 switch (aDateStyle) { 264 case DateTimeFormatStyle::Short: 265 dateSkeleton = 266 PrefToMaybeString("intl.date_time.pattern_override.date_short"); 267 break; 268 case DateTimeFormatStyle::Medium: 269 dateSkeleton = 270 PrefToMaybeString("intl.date_time.pattern_override.date_medium"); 271 break; 272 case DateTimeFormatStyle::Long: 273 dateSkeleton = 274 PrefToMaybeString("intl.date_time.pattern_override.date_long"); 275 break; 276 case DateTimeFormatStyle::Full: 277 dateSkeleton = 278 PrefToMaybeString("intl.date_time.pattern_override.date_full"); 279 break; 280 default: 281 break; 282 } 283 284 nsAutoCString locale; 285 if (aLocale.IsEmpty()) { 286 AutoTArray<nsCString, 10> regionalPrefsLocales; 287 LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales); 288 locale.Assign(regionalPrefsLocales[0]); 289 } else { 290 locale.Assign(aLocale); 291 } 292 293 const auto FillConnectorPattern = [&locale]( 294 const nsAutoCString& datePattern, 295 const nsAutoCString& timePattern) { 296 nsAutoCString pattern; 297 GetDateTimeConnectorPattern(nsDependentCString(locale.get()), pattern); 298 int32_t index = pattern.Find("{1}"); 299 if (index != kNotFound) { 300 pattern.Replace(index, 3, datePattern); 301 } 302 index = pattern.Find("{0}"); 303 if (index != kNotFound) { 304 pattern.Replace(index, 3, timePattern); 305 } 306 return pattern; 307 }; 308 309 if (timeSkeleton && dateSkeleton) { 310 aRetVal.Assign(FillConnectorPattern(*dateSkeleton, *timeSkeleton)); 311 } else if (timeSkeleton) { 312 if (aDateStyle != DateTimeFormatStyle::None) { 313 nsAutoCString pattern; 314 if (!ReadDateTimePattern(aDateStyle, DateTimeFormatStyle::None, aLocale, 315 pattern) && 316 !GetDateTimePatternForStyle(aDateStyle, DateTimeFormatStyle::None, 317 aLocale, pattern)) { 318 return false; 319 } 320 aRetVal.Assign(FillConnectorPattern(pattern, *timeSkeleton)); 321 } else { 322 aRetVal.Assign(*timeSkeleton); 323 } 324 } else if (dateSkeleton) { 325 if (aTimeStyle != DateTimeFormatStyle::None) { 326 nsAutoCString pattern; 327 if (!ReadDateTimePattern(DateTimeFormatStyle::None, aTimeStyle, aLocale, 328 pattern) && 329 !GetDateTimePatternForStyle(DateTimeFormatStyle::None, aTimeStyle, 330 aLocale, pattern)) { 331 return false; 332 } 333 aRetVal.Assign(FillConnectorPattern(*dateSkeleton, pattern)); 334 } else { 335 aRetVal.Assign(*dateSkeleton); 336 } 337 } else { 338 return false; 339 } 340 341 return true; 342 } 343 344 /** 345 * This function is a counterpart to GetDateTimeSkeletonForStyle. 346 * 347 * It takes a skeleton and returns the best available pattern for a given locale 348 * that represents the provided skeleton. 349 * 350 * For example: 351 * "Hm" skeleton for "en-US" will return "H:m" 352 */ 353 bool OSPreferences::GetPatternForSkeleton(const nsACString& aSkeleton, 354 const nsACString& aLocale, 355 nsACString& aRetVal) { 356 aRetVal.Truncate(); 357 358 auto genResult = 359 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get()); 360 if (genResult.isErr()) { 361 return false; 362 } 363 364 nsAutoString skeletonAsUtf16 = NS_ConvertUTF8toUTF16(aSkeleton); 365 DateTimeFormat::PatternVector pattern; 366 auto generator = genResult.unwrap(); 367 auto patternResult = generator->GetBestPattern(skeletonAsUtf16, pattern); 368 if (patternResult.isErr()) { 369 return false; 370 } 371 372 aRetVal = NS_ConvertUTF16toUTF8(pattern.begin(), pattern.length()); 373 return true; 374 } 375 376 /** 377 * This function returns a pattern that should be used to join date and time 378 * patterns into a single date/time pattern string. 379 * 380 * It's useful for OSes that do not provide an API to retrieve such combined 381 * pattern. 382 * 383 * An example output is "{1}, {0}". 384 */ 385 bool OSPreferences::GetDateTimeConnectorPattern(const nsACString& aLocale, 386 nsACString& aRetVal) { 387 // Check for a valid override pref and use that if present. 388 nsAutoCString value; 389 nsresult nr = Preferences::GetCString( 390 "intl.date_time.pattern_override.connector_short", value); 391 if (NS_SUCCEEDED(nr) && value.Find("{0}") != kNotFound && 392 value.Find("{1}") != kNotFound) { 393 aRetVal = std::move(value); 394 return true; 395 } 396 397 auto genResult = 398 DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get()); 399 if (genResult.isErr()) { 400 return false; 401 } 402 403 auto generator = genResult.unwrap(); 404 Span<const char16_t> result = generator->GetPlaceholderPattern(); 405 aRetVal = NS_ConvertUTF16toUTF8(result.data(), result.size()); 406 return true; 407 } 408 409 /** 410 * mozIOSPreferences methods 411 */ 412 NS_IMETHODIMP 413 OSPreferences::GetSystemLocales(nsTArray<nsCString>& aRetVal) { 414 if (!mSystemLocales.IsEmpty()) { 415 aRetVal = mSystemLocales.Clone(); 416 return NS_OK; 417 } 418 419 if (ReadSystemLocales(aRetVal)) { 420 mSystemLocales = aRetVal.Clone(); 421 return NS_OK; 422 } 423 424 // If we failed to get the system locale, we still need 425 // to return something because there are tests out there that 426 // depend on system locale to be set. 427 aRetVal.AppendElement("en-US"_ns); 428 return NS_ERROR_FAILURE; 429 } 430 431 NS_IMETHODIMP 432 OSPreferences::GetSystemLocale(nsACString& aRetVal) { 433 if (!mSystemLocales.IsEmpty()) { 434 aRetVal = mSystemLocales[0]; 435 } else { 436 AutoTArray<nsCString, 10> locales; 437 GetSystemLocales(locales); 438 if (!locales.IsEmpty()) { 439 aRetVal = locales[0]; 440 } 441 } 442 return NS_OK; 443 } 444 445 NS_IMETHODIMP 446 OSPreferences::GetRegionalPrefsLocales(nsTArray<nsCString>& aRetVal) { 447 if (!mRegionalPrefsLocales.IsEmpty()) { 448 aRetVal = mRegionalPrefsLocales.Clone(); 449 return NS_OK; 450 } 451 452 if (ReadRegionalPrefsLocales(aRetVal)) { 453 mRegionalPrefsLocales = aRetVal.Clone(); 454 return NS_OK; 455 } 456 457 // If we failed to read regional prefs locales, 458 // use system locales as last fallback. 459 return GetSystemLocales(aRetVal); 460 } 461 462 static OSPreferences::DateTimeFormatStyle ToDateTimeFormatStyle( 463 int32_t aTimeFormat) { 464 switch (aTimeFormat) { 465 // See mozIOSPreferences.idl for the integer values here. 466 case 0: 467 return OSPreferences::DateTimeFormatStyle::None; 468 case 1: 469 return OSPreferences::DateTimeFormatStyle::Short; 470 case 2: 471 return OSPreferences::DateTimeFormatStyle::Medium; 472 case 3: 473 return OSPreferences::DateTimeFormatStyle::Long; 474 case 4: 475 return OSPreferences::DateTimeFormatStyle::Full; 476 } 477 return OSPreferences::DateTimeFormatStyle::Invalid; 478 } 479 480 NS_IMETHODIMP 481 OSPreferences::GetDateTimePattern(int32_t aDateFormatStyle, 482 int32_t aTimeFormatStyle, 483 const nsACString& aLocale, 484 nsACString& aRetVal) { 485 DateTimeFormatStyle dateStyle = ToDateTimeFormatStyle(aDateFormatStyle); 486 if (dateStyle == DateTimeFormatStyle::Invalid) { 487 return NS_ERROR_INVALID_ARG; 488 } 489 DateTimeFormatStyle timeStyle = ToDateTimeFormatStyle(aTimeFormatStyle); 490 if (timeStyle == DateTimeFormatStyle::Invalid) { 491 return NS_ERROR_INVALID_ARG; 492 } 493 494 // If the user is asking for None on both, date and time style, 495 // let's exit early. 496 if (timeStyle == DateTimeFormatStyle::None && 497 dateStyle == DateTimeFormatStyle::None) { 498 return NS_OK; 499 } 500 501 // If the locale is not specified, default to first regional prefs locale 502 const nsACString* locale = &aLocale; 503 AutoTArray<nsCString, 10> rpLocales; 504 if (aLocale.IsEmpty()) { 505 LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales); 506 MOZ_ASSERT(rpLocales.Length() > 0); 507 locale = &rpLocales[0]; 508 } 509 510 // Create a cache key from the locale + style options 511 nsAutoCString key(*locale); 512 key.Append(':'); 513 key.AppendInt(aDateFormatStyle); 514 key.Append(':'); 515 key.AppendInt(aTimeFormatStyle); 516 517 nsCString pattern; 518 if (mPatternCache.Get(key, &pattern)) { 519 aRetVal = pattern; 520 return NS_OK; 521 } 522 523 if (!OverrideDateTimePattern(dateStyle, timeStyle, *locale, pattern)) { 524 if (!ReadDateTimePattern(dateStyle, timeStyle, *locale, pattern)) { 525 if (!GetDateTimePatternForStyle(dateStyle, timeStyle, *locale, pattern)) { 526 return NS_ERROR_FAILURE; 527 } 528 } 529 } 530 531 if (mPatternCache.Count() == kMaxCachedPatterns) { 532 // Don't allow unlimited cache growth; just throw it away in the case of 533 // pathological behavior where a page keeps requesting different formats 534 // and locales. 535 NS_WARNING("flushing DateTimePattern cache"); 536 mPatternCache.Clear(); 537 } 538 mPatternCache.InsertOrUpdate(key, pattern); 539 540 aRetVal = pattern; 541 return NS_OK; 542 } 543 544 void OSPreferences::OverrideSkeletonHourCycle(bool aIs24Hour, 545 nsAutoCString& aSkeleton) { 546 if (aIs24Hour) { 547 // If aSkeleton contains 'h' or 'K', replace with 'H' or 'k' respectively, 548 // and delete 'a' if present. 549 if (aSkeleton.FindChar('h') == -1 && aSkeleton.FindChar('K') == -1) { 550 return; 551 } 552 for (int32_t i = 0; i < int32_t(aSkeleton.Length()); ++i) { 553 switch (aSkeleton[i]) { 554 case 'a': 555 aSkeleton.Cut(i, 1); 556 --i; 557 break; 558 case 'h': 559 aSkeleton.SetCharAt('H', i); 560 break; 561 case 'K': 562 aSkeleton.SetCharAt('k', i); 563 break; 564 } 565 } 566 } else { 567 // If skeleton contains 'H' or 'k', replace with 'h' or 'K' respectively, 568 // and add 'a' unless already present. 569 if (aSkeleton.FindChar('H') == -1 && aSkeleton.FindChar('k') == -1) { 570 return; 571 } 572 bool foundA = false; 573 for (size_t i = 0; i < aSkeleton.Length(); ++i) { 574 switch (aSkeleton[i]) { 575 case 'a': 576 foundA = true; 577 break; 578 case 'H': 579 aSkeleton.SetCharAt('h', i); 580 break; 581 case 'k': 582 aSkeleton.SetCharAt('K', i); 583 break; 584 } 585 } 586 if (!foundA) { 587 aSkeleton.Append(char16_t('a')); 588 } 589 } 590 }