tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }