LocaleService.cpp (23434B)
1 /* -*- Mode: C++; tab-width: 4; 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 #include "LocaleService.h" 7 8 #include "mozilla/ClearOnShutdown.h" 9 #include "mozilla/DebugOnly.h" 10 #include "mozilla/Omnijar.h" 11 #include "mozilla/Preferences.h" 12 #include "mozilla/Services.h" 13 #include "mozilla/StaticPrefs_privacy.h" 14 #include "mozilla/intl/AppDateTimeFormat.h" 15 #include "mozilla/intl/Locale.h" 16 #include "mozilla/intl/OSPreferences.h" 17 #include "mozilla/intl/locale_service_glue_generated.h" 18 #include "nsContentUtils.h" 19 #include "nsDirectoryService.h" 20 #include "nsDirectoryServiceDefs.h" 21 #include "nsIObserverService.h" 22 #include "nsStringEnumerator.h" 23 #include "nsRFPService.h" 24 #include "nsXULAppAPI.h" 25 #include "nsZipArchive.h" 26 #ifdef XP_WIN 27 # include "WinUtils.h" 28 #endif 29 #ifdef MOZ_WIDGET_GTK 30 # include "mozilla/WidgetUtilsGtk.h" 31 #endif 32 33 #define INTL_SYSTEM_LOCALES_CHANGED "intl:system-locales-changed" 34 35 #define ACCEPT_LANGUAGES_PREF "intl.accept_languages" 36 #define FONT_LANGUAGE_GROUP_PREF "font.language.group" 37 38 #define PSEUDO_LOCALE_PREF "intl.l10n.pseudo" 39 #define REQUESTED_LOCALES_PREF "intl.locale.requested" 40 #define WEB_EXPOSED_LOCALES_PREF "intl.locale.privacy.web_exposed" 41 42 static const char* kObservedPrefs[] = {REQUESTED_LOCALES_PREF, 43 WEB_EXPOSED_LOCALES_PREF, 44 PSEUDO_LOCALE_PREF, nullptr}; 45 46 using namespace mozilla::intl::ffi; 47 using namespace mozilla::intl; 48 using namespace mozilla; 49 50 NS_IMPL_ISUPPORTS(LocaleService, mozILocaleService, nsIObserver, 51 nsISupportsWeakReference) 52 53 mozilla::StaticRefPtr<LocaleService> LocaleService::sInstance; 54 55 /** 56 * This function splits an input string by `,` delimiter, sanitizes the result 57 * language tags and returns them to the caller. 58 */ 59 static void SplitLocaleListStringIntoArray(nsACString& str, 60 nsTArray<nsCString>& aRetVal) { 61 if (str.Length() > 0) { 62 for (const nsACString& part : str.Split(',')) { 63 nsAutoCString locale(part); 64 if (LocaleService::CanonicalizeLanguageId(locale)) { 65 if (!aRetVal.Contains(locale)) { 66 aRetVal.AppendElement(locale); 67 } 68 } 69 } 70 } 71 } 72 73 static void ReadRequestedLocales(nsTArray<nsCString>& aRetVal) { 74 nsAutoCString str; 75 nsresult rv = Preferences::GetCString(REQUESTED_LOCALES_PREF, str); 76 // isRepack means this is a version of Firefox specifically 77 // built for one language. 78 const bool isRepack = 79 #ifdef XP_WIN 80 !mozilla::widget::WinUtils::HasPackageIdentity(); 81 #elif defined(MOZ_WIDGET_GTK) 82 !widget::IsRunningUnderSnap(); 83 #else 84 true; 85 #endif 86 87 // We handle four scenarios here: 88 // 89 // 1) The pref is not set - use default locale 90 // 2) The pref is not set and we're a packaged app - use OS locales 91 // 3) The pref is set to "" - use OS locales 92 // 4) The pref is set to a value - parse the locale list and use it 93 if (NS_SUCCEEDED(rv)) { 94 if (str.Length() == 0) { 95 // Case 3 96 OSPreferences::GetInstance()->GetSystemLocales(aRetVal); 97 } else { 98 // Case 4 99 SplitLocaleListStringIntoArray(str, aRetVal); 100 } 101 } 102 103 // This will happen when either the pref is not set, 104 // or parsing of the pref didn't produce any usable 105 // result. 106 if (aRetVal.IsEmpty()) { 107 if (isRepack) { 108 // Case 1 109 nsAutoCString defaultLocale; 110 LocaleService::GetInstance()->GetDefaultLocale(defaultLocale); 111 aRetVal.AppendElement(defaultLocale); 112 } else { 113 // Case 2 114 OSPreferences::GetInstance()->GetSystemLocales(aRetVal); 115 } 116 } 117 } 118 119 static void ReadWebExposedLocales(nsTArray<nsCString>& aRetVal) { 120 nsAutoCString str; 121 nsresult rv = Preferences::GetCString(WEB_EXPOSED_LOCALES_PREF, str); 122 if (NS_WARN_IF(NS_FAILED(rv)) || str.Length() == 0) { 123 return; 124 } 125 126 SplitLocaleListStringIntoArray(str, aRetVal); 127 } 128 129 LocaleService::LocaleService(bool aIsServer) : mIsServer(aIsServer) {} 130 131 /** 132 * This function performs the actual language negotiation for the API. 133 * 134 * Currently it collects the locale ID used by nsChromeRegistry and 135 * adds hardcoded default locale as a fallback. 136 */ 137 void LocaleService::NegotiateAppLocales(nsTArray<nsCString>& aRetVal) { 138 if (mIsServer) { 139 nsAutoCString defaultLocale; 140 AutoTArray<nsCString, 100> availableLocales; 141 AutoTArray<nsCString, 10> requestedLocales; 142 GetDefaultLocale(defaultLocale); 143 GetAvailableLocales(availableLocales); 144 GetRequestedLocales(requestedLocales); 145 146 NegotiateLanguages(requestedLocales, availableLocales, defaultLocale, 147 kLangNegStrategyFiltering, aRetVal); 148 } 149 150 nsAutoCString lastFallbackLocale; 151 GetLastFallbackLocale(lastFallbackLocale); 152 153 if (!aRetVal.Contains(lastFallbackLocale)) { 154 // This part is used in one of the two scenarios: 155 // 156 // a) We're in a client mode, and no locale has been set yet, 157 // so we need to return last fallback locale temporarily. 158 // b) We're in a server mode, and the last fallback locale was excluded 159 // when negotiating against the requested locales. 160 // Since we currently package it as a last fallback at build 161 // time, we should also add it at the end of the list at 162 // runtime. 163 aRetVal.AppendElement(lastFallbackLocale); 164 } 165 } 166 167 LocaleService* LocaleService::GetInstance() { 168 if (!sInstance) { 169 sInstance = new LocaleService(XRE_IsParentProcess()); 170 171 if (sInstance->IsServer()) { 172 // We're going to observe for requested languages changes which come 173 // from prefs. 174 DebugOnly<nsresult> rv = 175 Preferences::AddWeakObservers(sInstance, kObservedPrefs); 176 MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed."); 177 178 nsCOMPtr<nsIObserverService> obs = 179 mozilla::services::GetObserverService(); 180 if (obs) { 181 obs->AddObserver(sInstance, INTL_SYSTEM_LOCALES_CHANGED, true); 182 obs->AddObserver(sInstance, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); 183 } 184 } 185 // DOM might use ICUUtils and LocaleService during UnbindFromTree by 186 // final cycle collection. 187 ClearOnShutdown(&sInstance, ShutdownPhase::CCPostLastCycleCollection); 188 } 189 return sInstance; 190 } 191 192 static void NotifyAppLocaleChanged() { 193 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 194 if (obs) { 195 obs->NotifyObservers(nullptr, "intl:app-locales-changed", nullptr); 196 } 197 // The locale in AppDateTimeFormat is cached statically. 198 AppDateTimeFormat::ClearLocaleCache(); 199 } 200 201 void LocaleService::RemoveObservers() { 202 if (mIsServer) { 203 Preferences::RemoveObservers(this, kObservedPrefs); 204 205 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 206 if (obs) { 207 obs->RemoveObserver(this, INTL_SYSTEM_LOCALES_CHANGED); 208 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); 209 } 210 } 211 } 212 213 void LocaleService::AssignAppLocales(const nsTArray<nsCString>& aAppLocales) { 214 MOZ_ASSERT(!mIsServer, 215 "This should only be called for LocaleService in client mode."); 216 217 mAppLocales = aAppLocales.Clone(); 218 NotifyAppLocaleChanged(); 219 } 220 221 void LocaleService::AssignRequestedLocales( 222 const nsTArray<nsCString>& aRequestedLocales) { 223 MOZ_ASSERT(!mIsServer, 224 "This should only be called for LocaleService in client mode."); 225 226 mRequestedLocales = aRequestedLocales.Clone(); 227 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 228 if (obs) { 229 obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr); 230 } 231 } 232 233 void LocaleService::RequestedLocalesChanged() { 234 MOZ_ASSERT(mIsServer, "This should only be called in the server mode."); 235 236 nsTArray<nsCString> newLocales; 237 ReadRequestedLocales(newLocales); 238 239 if (mRequestedLocales != newLocales) { 240 mRequestedLocales = std::move(newLocales); 241 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 242 if (obs) { 243 obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr); 244 } 245 LocalesChanged(); 246 } 247 } 248 249 void LocaleService::WebExposedLocalesChanged() { 250 MOZ_ASSERT(mIsServer, "This should only be called in the server mode."); 251 252 nsTArray<nsCString> newLocales; 253 ReadWebExposedLocales(newLocales); 254 if (mWebExposedLocales != newLocales) { 255 mWebExposedLocales = std::move(newLocales); 256 } 257 } 258 259 void LocaleService::LocalesChanged() { 260 MOZ_ASSERT(mIsServer, "This should only be called in the server mode."); 261 262 // if mAppLocales has not been initialized yet, just return 263 if (mAppLocales.IsEmpty()) { 264 return; 265 } 266 267 nsTArray<nsCString> newLocales; 268 NegotiateAppLocales(newLocales); 269 270 if (mAppLocales != newLocales) { 271 mAppLocales = std::move(newLocales); 272 NotifyAppLocaleChanged(); 273 } 274 } 275 276 bool LocaleService::IsLocaleRTL(const nsACString& aLocale) { 277 return unic_langid_is_rtl(&aLocale); 278 } 279 280 bool LocaleService::IsAppLocaleRTL() { 281 // Next, check if there is a pseudo locale `bidi` set. 282 nsAutoCString pseudoLocale; 283 if (NS_SUCCEEDED(Preferences::GetCString("intl.l10n.pseudo", pseudoLocale))) { 284 if (pseudoLocale.EqualsLiteral("bidi")) { 285 return true; 286 } 287 if (pseudoLocale.EqualsLiteral("accented")) { 288 return false; 289 } 290 } 291 292 nsAutoCString locale; 293 GetAppLocaleAsBCP47(locale); 294 return IsLocaleRTL(locale); 295 } 296 297 NS_IMETHODIMP 298 LocaleService::Observe(nsISupports* aSubject, const char* aTopic, 299 const char16_t* aData) { 300 MOZ_ASSERT(mIsServer, "This should only be called in the server mode."); 301 302 if (!strcmp(aTopic, INTL_SYSTEM_LOCALES_CHANGED)) { 303 RequestedLocalesChanged(); 304 WebExposedLocalesChanged(); 305 } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 306 RemoveObservers(); 307 } else { 308 NS_ConvertUTF16toUTF8 pref(aData); 309 // At the moment the only thing we're observing are settings indicating 310 // user requested locales. 311 if (pref.EqualsLiteral(REQUESTED_LOCALES_PREF)) { 312 RequestedLocalesChanged(); 313 } else if (pref.EqualsLiteral(WEB_EXPOSED_LOCALES_PREF)) { 314 WebExposedLocalesChanged(); 315 } else if (pref.EqualsLiteral(PSEUDO_LOCALE_PREF)) { 316 NotifyAppLocaleChanged(); 317 } 318 } 319 320 return NS_OK; 321 } 322 323 bool LocaleService::LanguagesMatch(const nsACString& aRequested, 324 const nsACString& aAvailable) { 325 Locale requested; 326 auto requestedResult = LocaleParser::TryParse(aRequested, requested); 327 Locale available; 328 auto availableResult = LocaleParser::TryParse(aAvailable, available); 329 330 if (requestedResult.isErr() || availableResult.isErr()) { 331 return false; 332 } 333 334 if (requested.Canonicalize().isErr() || available.Canonicalize().isErr()) { 335 return false; 336 } 337 338 return requested.Language().Span() == available.Language().Span(); 339 } 340 341 bool LocaleService::IsServer() { return mIsServer; } 342 343 static bool GetGREFileContents(const char* aFilePath, nsCString* aOutString) { 344 // Look for the requested file in omnijar. 345 RefPtr<nsZipArchive> zip = Omnijar::GetReader(Omnijar::GRE); 346 if (zip) { 347 nsZipItemPtr<char> item(zip, nsDependentCString(aFilePath)); 348 if (!item) { 349 return false; 350 } 351 aOutString->Assign(item.Buffer(), item.Length()); 352 return true; 353 } 354 355 // If we didn't have an omnijar (i.e. we're running a non-packaged 356 // build), then look in the GRE directory. 357 nsCOMPtr<nsIFile> path; 358 if (NS_FAILED(nsDirectoryService::gService->Get( 359 NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(path)))) { 360 return false; 361 } 362 363 path->AppendRelativeNativePath(nsDependentCString(aFilePath)); 364 bool result; 365 if (NS_FAILED(path->IsFile(&result)) || !result || 366 NS_FAILED(path->IsReadable(&result)) || !result) { 367 return false; 368 } 369 370 // This is a small file, only used once, so it's not worth doing some fancy 371 // off-main-thread file I/O or whatever. Just read it. 372 FILE* fp; 373 if (NS_FAILED(path->OpenANSIFileDesc("r", &fp)) || !fp) { 374 return false; 375 } 376 377 fseek(fp, 0, SEEK_END); 378 long len = ftell(fp); 379 rewind(fp); 380 aOutString->SetLength(len); 381 size_t cc = fread(aOutString->BeginWriting(), 1, len, fp); 382 383 fclose(fp); 384 385 return cc == size_t(len); 386 } 387 388 void LocaleService::InitPackagedLocales() { 389 MOZ_ASSERT(mPackagedLocales.IsEmpty()); 390 391 nsAutoCString localesString; 392 if (GetGREFileContents("res/multilocale.txt", &localesString)) { 393 localesString.Trim(" \t\n\r"); 394 // This should never be empty in a correctly-built product. 395 MOZ_ASSERT(!localesString.IsEmpty()); 396 SplitLocaleListStringIntoArray(localesString, mPackagedLocales); 397 } 398 399 // Last resort in case of broken build 400 if (mPackagedLocales.IsEmpty()) { 401 nsAutoCString defaultLocale; 402 GetDefaultLocale(defaultLocale); 403 mPackagedLocales.AppendElement(defaultLocale); 404 } 405 } 406 407 /** 408 * mozILocaleService methods 409 */ 410 411 NS_IMETHODIMP 412 LocaleService::GetDefaultLocale(nsACString& aRetVal) { 413 // We don't allow this to change during a session (it's set at build/package 414 // time), so we cache the result the first time we're called. 415 if (mDefaultLocale.IsEmpty()) { 416 nsAutoCString locale; 417 // Try to get the package locale from default.locale in omnijar. If the 418 // default.locale file is not found, item.len will remain 0 and we'll 419 // just use our hard-coded default below. 420 GetGREFileContents("default.locale", &locale); 421 locale.Trim(" \t\n\r"); 422 #ifdef MOZ_UPDATER 423 // This should never be empty. 424 MOZ_ASSERT(!locale.IsEmpty()); 425 #endif 426 if (CanonicalizeLanguageId(locale)) { 427 mDefaultLocale.Assign(locale); 428 } 429 430 // Hard-coded fallback to allow us to survive even if default.locale was 431 // missing/broken in some way. 432 if (mDefaultLocale.IsEmpty()) { 433 GetLastFallbackLocale(mDefaultLocale); 434 } 435 } 436 437 aRetVal = mDefaultLocale; 438 return NS_OK; 439 } 440 441 NS_IMETHODIMP 442 LocaleService::GetLastFallbackLocale(nsACString& aRetVal) { 443 aRetVal.AssignLiteral("en-US"); 444 return NS_OK; 445 } 446 447 NS_IMETHODIMP 448 LocaleService::GetAppLocalesAsLangTags(nsTArray<nsCString>& aRetVal) { 449 if (mAppLocales.IsEmpty()) { 450 NegotiateAppLocales(mAppLocales); 451 } 452 for (uint32_t i = 0; i < mAppLocales.Length(); i++) { 453 nsAutoCString locale(mAppLocales[i]); 454 if (locale.LowerCaseEqualsASCII("ja-jp-macos")) { 455 aRetVal.AppendElement("ja-JP-mac"); 456 } else { 457 aRetVal.AppendElement(locale); 458 } 459 } 460 return NS_OK; 461 } 462 463 NS_IMETHODIMP 464 LocaleService::GetAppLocalesAsBCP47(nsTArray<nsCString>& aRetVal) { 465 if (mAppLocales.IsEmpty()) { 466 NegotiateAppLocales(mAppLocales); 467 } 468 aRetVal = mAppLocales.Clone(); 469 470 return NS_OK; 471 } 472 473 NS_IMETHODIMP 474 LocaleService::GetAppLocaleAsLangTag(nsACString& aRetVal) { 475 AutoTArray<nsCString, 32> locales; 476 GetAppLocalesAsLangTags(locales); 477 478 aRetVal = locales[0]; 479 return NS_OK; 480 } 481 482 NS_IMETHODIMP 483 LocaleService::GetAppLocaleAsBCP47(nsACString& aRetVal) { 484 if (mAppLocales.IsEmpty()) { 485 NegotiateAppLocales(mAppLocales); 486 } 487 aRetVal = mAppLocales[0]; 488 return NS_OK; 489 } 490 491 NS_IMETHODIMP 492 LocaleService::GetRegionalPrefsLocales(nsTArray<nsCString>& aRetVal) { 493 // tor-browser#42349, #42771: We cannot use JSLocale because it is spoof 494 // English. So, we use another target for now. 495 if (nsContentUtils::ShouldResistFingerprinting( 496 "This is probably a patch that should be refined. But to get the " 497 "build going, we just keep applying this generic check.", 498 RFPTarget::JSDateTimeUTC)) { 499 GetAppLocalesAsBCP47(aRetVal); 500 return NS_OK; 501 } 502 503 bool useOSLocales = 504 Preferences::GetBool("intl.regional_prefs.use_os_locales", false); 505 506 // If the user specified that they want to use OS Regional Preferences 507 // locales, try to retrieve them and use. 508 if (useOSLocales) { 509 if (NS_SUCCEEDED( 510 OSPreferences::GetInstance()->GetRegionalPrefsLocales(aRetVal))) { 511 return NS_OK; 512 } 513 514 // If we fail to retrieve them, return the app locales. 515 GetAppLocalesAsBCP47(aRetVal); 516 return NS_OK; 517 } 518 519 // Otherwise, fetch OS Regional Preferences locales and compare the first one 520 // to the app locale. If the language subtag matches, we can safely use 521 // the OS Regional Preferences locale. 522 // 523 // This facilitates scenarios such as Firefox in "en-US" and User sets 524 // regional prefs to "en-GB". 525 nsAutoCString appLocale; 526 AutoTArray<nsCString, 10> regionalPrefsLocales; 527 LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale); 528 529 if (NS_FAILED(OSPreferences::GetInstance()->GetRegionalPrefsLocales( 530 regionalPrefsLocales))) { 531 GetAppLocalesAsBCP47(aRetVal); 532 return NS_OK; 533 } 534 535 if (LocaleService::LanguagesMatch(appLocale, regionalPrefsLocales[0])) { 536 aRetVal = regionalPrefsLocales.Clone(); 537 return NS_OK; 538 } 539 540 // Otherwise use the app locales. 541 GetAppLocalesAsBCP47(aRetVal); 542 return NS_OK; 543 } 544 545 NS_IMETHODIMP 546 LocaleService::GetWebExposedLocales(nsTArray<nsCString>& aRetVal) { 547 if (nsContentUtils::ShouldResistFingerprinting("No context", 548 RFPTarget::JSLocale)) { 549 aRetVal = nsTArray<nsCString>({nsRFPService::GetSpoofedJSLocale()}); 550 return NS_OK; 551 } 552 553 if (!mWebExposedLocales.IsEmpty()) { 554 aRetVal = mWebExposedLocales.Clone(); 555 return NS_OK; 556 } 557 558 return GetRegionalPrefsLocales(aRetVal); 559 } 560 561 NS_IMETHODIMP 562 LocaleService::NegotiateLanguages(const nsTArray<nsCString>& aRequested, 563 const nsTArray<nsCString>& aAvailable, 564 const nsACString& aDefaultLocale, 565 int32_t aStrategy, 566 nsTArray<nsCString>& aRetVal) { 567 if (aStrategy < 0 || aStrategy > 2) { 568 return NS_ERROR_INVALID_ARG; 569 } 570 571 #ifdef DEBUG 572 Locale parsedLocale; 573 auto result = LocaleParser::TryParse(aDefaultLocale, parsedLocale); 574 575 MOZ_ASSERT( 576 aDefaultLocale.IsEmpty() || result.isOk(), 577 "If specified, default locale must be a well-formed BCP47 language tag."); 578 #endif 579 580 if (aStrategy == kLangNegStrategyLookup && aDefaultLocale.IsEmpty()) { 581 NS_WARNING( 582 "Default locale should be specified when using lookup strategy."); 583 } 584 585 NegotiationStrategy strategy; 586 switch (aStrategy) { 587 case kLangNegStrategyFiltering: 588 strategy = NegotiationStrategy::Filtering; 589 break; 590 case kLangNegStrategyMatching: 591 strategy = NegotiationStrategy::Matching; 592 break; 593 case kLangNegStrategyLookup: 594 strategy = NegotiationStrategy::Lookup; 595 break; 596 } 597 598 fluent_langneg_negotiate_languages(&aRequested, &aAvailable, &aDefaultLocale, 599 strategy, &aRetVal); 600 601 return NS_OK; 602 } 603 604 NS_IMETHODIMP 605 LocaleService::GetRequestedLocales(nsTArray<nsCString>& aRetVal) { 606 if (mRequestedLocales.IsEmpty()) { 607 ReadRequestedLocales(mRequestedLocales); 608 } 609 610 aRetVal = mRequestedLocales.Clone(); 611 return NS_OK; 612 } 613 614 NS_IMETHODIMP 615 LocaleService::GetRequestedLocale(nsACString& aRetVal) { 616 if (mRequestedLocales.IsEmpty()) { 617 ReadRequestedLocales(mRequestedLocales); 618 } 619 620 if (mRequestedLocales.Length() > 0) { 621 aRetVal = mRequestedLocales[0]; 622 } 623 624 return NS_OK; 625 } 626 627 NS_IMETHODIMP 628 LocaleService::SetRequestedLocales(const nsTArray<nsCString>& aRequested) { 629 MOZ_ASSERT(mIsServer, "This should only be called in the server mode."); 630 if (!mIsServer) { 631 return NS_ERROR_UNEXPECTED; 632 } 633 634 nsAutoCString str; 635 636 for (auto& req : aRequested) { 637 nsAutoCString locale(req); 638 if (!CanonicalizeLanguageId(locale)) { 639 NS_ERROR("Invalid language tag provided to SetRequestedLocales!"); 640 return NS_ERROR_INVALID_ARG; 641 } 642 643 if (!str.IsEmpty()) { 644 str.AppendLiteral(","); 645 } 646 str.Append(locale); 647 } 648 Preferences::SetCString(REQUESTED_LOCALES_PREF, str); 649 650 return NS_OK; 651 } 652 653 NS_IMETHODIMP 654 LocaleService::GetAvailableLocales(nsTArray<nsCString>& aRetVal) { 655 MOZ_ASSERT(mIsServer, "This should only be called in the server mode."); 656 if (!mIsServer) { 657 return NS_ERROR_UNEXPECTED; 658 } 659 660 if (mAvailableLocales.IsEmpty()) { 661 // If there are no available locales set, it means that L10nRegistry 662 // did not register its locale pool yet. The best course of action 663 // is to use packaged locales until that happens. 664 GetPackagedLocales(mAvailableLocales); 665 } 666 667 aRetVal = mAvailableLocales.Clone(); 668 return NS_OK; 669 } 670 671 NS_IMETHODIMP 672 LocaleService::GetIsAppLocaleRTL(bool* aRetVal) { 673 (*aRetVal) = IsAppLocaleRTL(); 674 return NS_OK; 675 } 676 677 NS_IMETHODIMP 678 LocaleService::SetAvailableLocales(const nsTArray<nsCString>& aAvailable) { 679 MOZ_ASSERT(mIsServer, "This should only be called in the server mode."); 680 if (!mIsServer) { 681 return NS_ERROR_UNEXPECTED; 682 } 683 684 nsTArray<nsCString> newLocales; 685 686 for (auto& avail : aAvailable) { 687 nsAutoCString locale(avail); 688 if (!CanonicalizeLanguageId(locale)) { 689 NS_ERROR("Invalid language tag provided to SetAvailableLocales!"); 690 return NS_ERROR_INVALID_ARG; 691 } 692 newLocales.AppendElement(locale); 693 } 694 695 if (newLocales != mAvailableLocales) { 696 mAvailableLocales = std::move(newLocales); 697 LocalesChanged(); 698 } 699 700 return NS_OK; 701 } 702 703 NS_IMETHODIMP 704 LocaleService::GetPackagedLocales(nsTArray<nsCString>& aRetVal) { 705 if (mPackagedLocales.IsEmpty()) { 706 InitPackagedLocales(); 707 } 708 aRetVal = mPackagedLocales.Clone(); 709 return NS_OK; 710 } 711 712 NS_IMETHODIMP 713 LocaleService::GetEllipsis(nsAString& aRetVal) { 714 if (mAppLocales.IsEmpty()) { 715 NegotiateAppLocales(mAppLocales); 716 } 717 ffi::locale_service_ellipsis(&mAppLocales[0], &aRetVal); 718 return NS_OK; 719 } 720 721 bool LocaleService::AlwaysAppendAccesskeys() { 722 if (mAppLocales.IsEmpty()) { 723 NegotiateAppLocales(mAppLocales); 724 } 725 return ffi::locale_service_always_append_accesskeys(&mAppLocales[0]); 726 } 727 728 bool LocaleService::InsertSeparatorBeforeAccesskeys() { 729 if (mAppLocales.IsEmpty()) { 730 NegotiateAppLocales(mAppLocales); 731 } 732 return ffi::locale_service_insert_separator_before_accesskeys( 733 &mAppLocales[0]); 734 } 735 736 NS_IMETHODIMP 737 LocaleService::GetAlwaysAppendAccesskeys(bool* aRetVal) { 738 (*aRetVal) = AlwaysAppendAccesskeys(); 739 return NS_OK; 740 } 741 742 NS_IMETHODIMP 743 LocaleService::GetInsertSeparatorBeforeAccesskeys(bool* aRetVal) { 744 (*aRetVal) = InsertSeparatorBeforeAccesskeys(); 745 return NS_OK; 746 } 747 748 NS_IMETHODIMP 749 LocaleService::GetAcceptLanguages(nsACString& aRetVal) { 750 // if there is a user (or locked) value, use it 751 if (Preferences::HasUserValue(ACCEPT_LANGUAGES_PREF) || 752 Preferences::IsLocked(ACCEPT_LANGUAGES_PREF)) { 753 nsresult rv = Preferences::GetCString(ACCEPT_LANGUAGES_PREF, aRetVal); 754 if (NS_SUCCEEDED(rv)) { 755 return NS_OK; 756 } 757 } 758 759 // if we need to fetch the default value, do that instead 760 if (mAppLocales.IsEmpty()) { 761 NegotiateAppLocales(mAppLocales); 762 } 763 ffi::locale_service_default_accept_languages(&mAppLocales[0], &aRetVal); 764 return NS_OK; 765 } 766 767 NS_IMETHODIMP 768 LocaleService::GetFontLanguageGroup(nsACString& aRetVal) { 769 // if there is a user (or locked) value, use it 770 if (Preferences::HasUserValue(FONT_LANGUAGE_GROUP_PREF) || 771 Preferences::IsLocked(FONT_LANGUAGE_GROUP_PREF)) { 772 nsresult rv = Preferences::GetCString(FONT_LANGUAGE_GROUP_PREF, aRetVal); 773 if (NS_SUCCEEDED(rv)) { 774 return NS_OK; 775 } 776 } 777 778 // if we need to fetch the default value, do that instead 779 if (mAppLocales.IsEmpty()) { 780 NegotiateAppLocales(mAppLocales); 781 } 782 ffi::locale_service_default_font_language_group(&mAppLocales[0], &aRetVal); 783 784 return NS_OK; 785 }