LCOV - code coverage report
Current view: top level - intl/locale - LocaleService.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 206 389 53.0 %
Date: 2017-07-14 16:53:18 Functions: 31 53 58.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
       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 <algorithm>  // find_if()
       9             : #include "mozilla/ClearOnShutdown.h"
      10             : #include "mozilla/Services.h"
      11             : #include "mozilla/Preferences.h"
      12             : #include "mozilla/intl/OSPreferences.h"
      13             : #include "nsIObserverService.h"
      14             : #include "nsStringEnumerator.h"
      15             : #include "nsIToolkitChromeRegistry.h"
      16             : #include "nsXULAppAPI.h"
      17             : 
      18             : #ifdef ENABLE_INTL_API
      19             : #include "unicode/uloc.h"
      20             : #endif
      21             : 
      22             : #define MATCH_OS_LOCALE_PREF "intl.locale.matchOS"
      23             : #define SELECTED_LOCALE_PREF "general.useragent.locale"
      24             : 
      25             : //XXX: This pref is used only by Android and we use it to emulate
      26             : //     retrieving OS locale until we get proper hook into JNI in bug 1337078.
      27             : #define ANDROID_OS_LOCALE_PREF "intl.locale.os"
      28             : 
      29             : static const char* kObservedPrefs[] = {
      30             :   MATCH_OS_LOCALE_PREF,
      31             :   SELECTED_LOCALE_PREF,
      32             :   ANDROID_OS_LOCALE_PREF,
      33             :   nullptr
      34             : };
      35             : 
      36             : using namespace mozilla::intl;
      37             : using namespace mozilla;
      38             : 
      39         124 : NS_IMPL_ISUPPORTS(LocaleService, mozILocaleService, nsIObserver)
      40             : 
      41           3 : mozilla::StaticRefPtr<LocaleService> LocaleService::sInstance;
      42             : 
      43             : /**
      44             :  * This function transforms a canonical Mozilla Language Tag, into it's
      45             :  * BCP47 compilant form.
      46             :  *
      47             :  * Example: "ja-JP-mac" -> "ja-JP-x-lvariant-mac"
      48             :  *
      49             :  * The BCP47 form should be used for all calls to ICU/Intl APIs.
      50             :  * The canonical form is used for all internal operations.
      51             :  */
      52             : static void
      53           8 : SanitizeForBCP47(nsACString& aLocale)
      54             : {
      55             : #ifdef ENABLE_INTL_API
      56             :   // Currently, the only locale code we use that's not BCP47-conformant is
      57             :   // "ja-JP-mac" on OS X, but let's try to be more general than just
      58             :   // hard-coding that here.
      59           8 :   const int32_t LANG_TAG_CAPACITY = 128;
      60             :   char langTag[LANG_TAG_CAPACITY];
      61          16 :   nsAutoCString locale(aLocale);
      62           8 :   UErrorCode err = U_ZERO_ERROR;
      63             :   // This is a fail-safe method that will set langTag to "und" if it cannot
      64             :   // match any part of the input locale code.
      65           8 :   int32_t len = uloc_toLanguageTag(locale.get(), langTag, LANG_TAG_CAPACITY,
      66           8 :                                    false, &err);
      67           8 :   if (U_SUCCESS(err) && len > 0) {
      68           8 :     aLocale.Assign(langTag, len);
      69             :   }
      70             : #else
      71             :   // This is only really needed for Intl API purposes, AFAIK,
      72             :   // so probably won't be used in a non-ENABLE_INTL_API build.
      73             :   // But let's fix up the single anomalous code we actually ship,
      74             :   // just in case:
      75             :   if (aLocale.EqualsLiteral("ja-JP-mac")) {
      76             :     aLocale.AssignLiteral("ja-JP");
      77             :   }
      78             : #endif
      79           8 : }
      80             : 
      81             : static bool
      82           1 : ReadRequestedLocales(nsTArray<nsCString>& aRetVal)
      83             : {
      84           2 :   nsAutoCString locale;
      85             : 
      86             :   // First, we'll try to check if the user has `matchOS` pref selected
      87           1 :   bool matchOSLocale = Preferences::GetBool(MATCH_OS_LOCALE_PREF);
      88             : 
      89           1 :   if (matchOSLocale) {
      90             :     // If he has, we'll pick the locale from the system
      91           0 :     if (OSPreferences::GetInstance()->GetSystemLocales(aRetVal)) {
      92             :       // If we succeeded, return.
      93           0 :       return true;
      94             :     }
      95             :   }
      96             : 
      97             :   // Otherwise, we'll try to get the requested locale from the prefs.
      98           1 :   if (!NS_SUCCEEDED(Preferences::GetCString(SELECTED_LOCALE_PREF, &locale))) {
      99           0 :     return false;
     100             :   }
     101             : 
     102             :   // At the moment we just take a single locale, but in the future
     103             :   // we'll want to allow user to specify a list of requested locales.
     104           1 :   aRetVal.AppendElement(locale);
     105           1 :   return true;
     106             : }
     107             : 
     108             : static bool
     109           2 : ReadAvailableLocales(nsTArray<nsCString>& aRetVal)
     110             : {
     111             :   nsCOMPtr<nsIToolkitChromeRegistry> cr =
     112           4 :     mozilla::services::GetToolkitChromeRegistryService();
     113           2 :   if (!cr) {
     114           0 :     return false;
     115             :   }
     116             : 
     117           4 :   nsCOMPtr<nsIUTF8StringEnumerator> localesEnum;
     118             : 
     119             :   nsresult rv =
     120           2 :     cr->GetLocalesForPackage(NS_LITERAL_CSTRING("global"), getter_AddRefs(localesEnum));
     121           2 :   if (!NS_SUCCEEDED(rv)) {
     122           0 :     return false;
     123             :   }
     124             : 
     125             :   bool more;
     126           4 :   while (NS_SUCCEEDED(rv = localesEnum->HasMore(&more)) && more) {
     127           2 :     nsAutoCString localeStr;
     128           1 :     rv = localesEnum->GetNext(localeStr);
     129           1 :     if (!NS_SUCCEEDED(rv)) {
     130           0 :       return false;
     131             :     }
     132             : 
     133           1 :     aRetVal.AppendElement(localeStr);
     134             :   }
     135           2 :   return !aRetVal.IsEmpty();
     136             : }
     137             : 
     138           3 : LocaleService::LocaleService(bool aIsServer)
     139           3 :   :mIsServer(aIsServer)
     140             : {
     141           3 : }
     142             : 
     143             : /**
     144             :  * This function performs the actual language negotiation for the API.
     145             :  *
     146             :  * Currently it collects the locale ID used by nsChromeRegistry and
     147             :  * adds hardcoded "en-US" locale as a fallback.
     148             :  */
     149             : void
     150           4 : LocaleService::NegotiateAppLocales(nsTArray<nsCString>& aRetVal)
     151             : {
     152           8 :   nsAutoCString defaultLocale;
     153           4 :   GetDefaultLocale(defaultLocale);
     154             : 
     155           4 :   if (mIsServer) {
     156           4 :     AutoTArray<nsCString, 100> availableLocales;
     157           4 :     AutoTArray<nsCString, 10> requestedLocales;
     158           2 :     GetAvailableLocales(availableLocales);
     159           2 :     GetRequestedLocales(requestedLocales);
     160             : 
     161             :     NegotiateLanguages(requestedLocales, availableLocales, defaultLocale,
     162           2 :                        LangNegStrategy::Filtering, aRetVal);
     163             :   } else {
     164             :     // In content process, we will not do any language negotiation.
     165             :     // Instead, the language is set manually by SetAppLocales.
     166             :     //
     167             :     // If this method has been called, it means that we did not fire
     168             :     // SetAppLocales yet (happens during initialization).
     169             :     // In that case, all we can do is return the default locale.
     170             :     // Once SetAppLocales will be called later, it'll fire an event
     171             :     // allowing callers to update the locale.
     172           2 :     aRetVal.AppendElement(defaultLocale);
     173             :   }
     174           4 : }
     175             : 
     176             : LocaleService*
     177         201 : LocaleService::GetInstance()
     178             : {
     179         201 :   if (!sInstance) {
     180           6 :     sInstance = new LocaleService(XRE_IsParentProcess());
     181             : 
     182           3 :     if (sInstance->IsServer()) {
     183             :       // We're going to observe for requested languages changes which come
     184             :       // from prefs.
     185           2 :       DebugOnly<nsresult> rv = Preferences::AddStrongObservers(sInstance, kObservedPrefs);
     186           1 :       MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
     187             :     }
     188           3 :     ClearOnShutdown(&sInstance);
     189             :   }
     190         201 :   return sInstance;
     191             : }
     192             : 
     193           0 : LocaleService::~LocaleService()
     194             : {
     195           0 :   if (mIsServer) {
     196           0 :     Preferences::RemoveObservers(sInstance, kObservedPrefs);
     197             :   }
     198           0 : }
     199             : 
     200             : void
     201           2 : LocaleService::GetAppLocalesAsLangTags(nsTArray<nsCString>& aRetVal)
     202             : {
     203           2 :   if (mAppLocales.IsEmpty()) {
     204           0 :     NegotiateAppLocales(mAppLocales);
     205             :   }
     206           2 :   aRetVal = mAppLocales;
     207           2 : }
     208             : 
     209             : void
     210           0 : LocaleService::GetAppLocalesAsBCP47(nsTArray<nsCString>& aRetVal)
     211             : {
     212           0 :   if (mAppLocales.IsEmpty()) {
     213           0 :     NegotiateAppLocales(mAppLocales);
     214             :   }
     215           0 :   for (uint32_t i = 0; i < mAppLocales.Length(); i++) {
     216           0 :     nsAutoCString locale(mAppLocales[i]);
     217           0 :     SanitizeForBCP47(locale);
     218           0 :     aRetVal.AppendElement(locale);
     219             :   }
     220           0 : }
     221             : 
     222             : void
     223           0 : LocaleService::GetRegionalPrefsLocales(nsTArray<nsCString>& aRetVal)
     224             : {
     225           0 :   bool useOSLocales = Preferences::GetBool("intl.regional_prefs.use_os_locales", false);
     226             : 
     227           0 :   if (useOSLocales && OSPreferences::GetInstance()->GetSystemLocales(aRetVal)) {
     228           0 :     return;
     229             :   }
     230             : 
     231           0 :   GetAppLocalesAsBCP47(aRetVal);
     232             : }
     233             : 
     234             : void
     235           2 : LocaleService::AssignAppLocales(const nsTArray<nsCString>& aAppLocales)
     236             : {
     237           2 :   MOZ_ASSERT(!mIsServer, "This should only be called for LocaleService in client mode.");
     238             : 
     239           2 :   mAppLocales = aAppLocales;
     240           4 :   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     241           2 :   if (obs) {
     242           2 :     obs->NotifyObservers(nullptr, "intl:app-locales-changed", nullptr);
     243             :   }
     244           2 : }
     245             : 
     246             : void
     247           2 : LocaleService::AssignRequestedLocales(const nsTArray<nsCString>& aRequestedLocales)
     248             : {
     249           2 :   MOZ_ASSERT(!mIsServer, "This should only be called for LocaleService in client mode.");
     250             : 
     251           2 :   mRequestedLocales = aRequestedLocales;
     252           4 :   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     253           2 :   if (obs) {
     254           2 :     obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
     255             :   }
     256           2 : }
     257             : 
     258             : bool
     259           5 : LocaleService::GetRequestedLocales(nsTArray<nsCString>& aRetVal)
     260             : {
     261           5 :   if (mRequestedLocales.IsEmpty()) {
     262           1 :     ReadRequestedLocales(mRequestedLocales);
     263             :   }
     264             : 
     265           5 :   aRetVal = mRequestedLocales;
     266           5 :   return true;
     267             : }
     268             : 
     269             : bool
     270           2 : LocaleService::GetAvailableLocales(nsTArray<nsCString>& aRetVal)
     271             : {
     272           2 :   if (mAvailableLocales.IsEmpty()) {
     273           2 :     ReadAvailableLocales(mAvailableLocales);
     274             :   }
     275             : 
     276           2 :   aRetVal = mAvailableLocales;
     277           2 :   return true;
     278             : }
     279             : 
     280             : 
     281             : void
     282           1 : LocaleService::OnAvailableLocalesChanged()
     283             : {
     284           1 :   MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
     285           1 :   mAvailableLocales.Clear();
     286             :   // In the future we may want to trigger here intl:available-locales-changed
     287           1 :   OnLocalesChanged();
     288           1 : }
     289             : 
     290             : void
     291           0 : LocaleService::OnRequestedLocalesChanged()
     292             : {
     293           0 :   MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
     294             : 
     295           0 :   nsTArray<nsCString> newLocales;
     296           0 :   ReadRequestedLocales(newLocales);
     297             : 
     298           0 :   if (mRequestedLocales != newLocales) {
     299           0 :     mRequestedLocales = Move(newLocales);
     300           0 :     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     301           0 :     if (obs) {
     302           0 :       obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
     303             :     }
     304           0 :     OnLocalesChanged();
     305             :   }
     306           0 : }
     307             : 
     308             : void
     309           1 : LocaleService::OnLocalesChanged()
     310             : {
     311           1 :   MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
     312             : 
     313             :   // if mAppLocales has not been initialized yet, just return
     314           1 :   if (mAppLocales.IsEmpty()) {
     315           0 :     return;
     316             :   }
     317             : 
     318           2 :   nsTArray<nsCString> newLocales;
     319           1 :   NegotiateAppLocales(newLocales);
     320             : 
     321           1 :   if (mAppLocales != newLocales) {
     322           0 :     mAppLocales = Move(newLocales);
     323           0 :     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     324           0 :     if (obs) {
     325           0 :       obs->NotifyObservers(nullptr, "intl:app-locales-changed", nullptr);
     326             : 
     327             :       // Deprecated, please use `intl:app-locales-changed`.
     328             :       // Kept for now for compatibility reasons
     329           0 :       obs->NotifyObservers(nullptr, "selected-locale-has-changed", nullptr);
     330             :     }
     331             :   }
     332             : }
     333             : 
     334             : // After trying each step of the negotiation algorithm for each requested locale,
     335             : // if a match was found we use this macro to decide whether to return immediately,
     336             : // skip to the next requested locale, or continue searching for additional matches,
     337             : // according to the desired negotiation strategy.
     338             : #define HANDLE_STRATEGY \
     339             :           switch (aStrategy) { \
     340             :             case LangNegStrategy::Lookup: \
     341             :               return; \
     342             :             case LangNegStrategy::Matching: \
     343             :               continue; \
     344             :             case LangNegStrategy::Filtering: \
     345             :               break; \
     346             :           }
     347             : 
     348             : /**
     349             :  * This is the raw algorithm for language negotiation based roughly
     350             :  * on RFC4647 language filtering, with changes from LDML language matching.
     351             :  *
     352             :  * The exact algorithm is custom, and consist of 5 level strategy:
     353             :  *
     354             :  * 1) Attempt to find an exact match for each requested locale in available
     355             :  *    locales.
     356             :  *    Example: ['en-US'] * ['en-US'] = ['en-US']
     357             :  *
     358             :  * 2) Attempt to match a requested locale to an available locale treated
     359             :  *    as a locale range.
     360             :  *    Example: ['en-US'] * ['en'] = ['en']
     361             :  *                           ^^
     362             :  *                           |-- becomes 'en-*-*-*'
     363             :  *
     364             :  * 3) Attempt to use the maximized version of the requested locale, to
     365             :  *    find the best match in available locales.
     366             :  *    Example: ['en'] * ['en-GB', 'en-US'] = ['en-US']
     367             :  *               ^^
     368             :  *               |-- ICU likelySubtags expands it to 'en-Latn-US'
     369             :  *
     370             :  * 4) Attempt to look up for a different variant of the same locale.
     371             :  *    Example: ['ja-JP-win'] * ['ja-JP-mac'] = ['ja-JP-mac']
     372             :  *               ^^^^^^^^^
     373             :  *               |----------- replace variant with range: 'ja-JP-*'
     374             :  *
     375             :  * 5) Attempt to look up for a different region of the same locale.
     376             :  *    Example: ['en-GB'] * ['en-AU'] = ['en-AU']
     377             :  *               ^^^^^
     378             :  *               |----- replace region with range: 'en-*'
     379             :  *
     380             :  * It uses one of the strategies described in LocaleService.h.
     381             :  */
     382             : void
     383           2 : LocaleService::FilterMatches(const nsTArray<nsCString>& aRequested,
     384             :                              const nsTArray<nsCString>& aAvailable,
     385             :                              LangNegStrategy aStrategy,
     386             :                              nsTArray<nsCString>& aRetVal)
     387             : {
     388             :   // Local copy of the list of available locales, in Locale form for flexible
     389             :   // matching. We will remove entries from this list as they get appended to
     390             :   // aRetVal, so that no available locale will be found more than once.
     391           4 :   AutoTArray<Locale, 100> availLocales;
     392           3 :   for (auto& avail : aAvailable) {
     393           1 :     availLocales.AppendElement(Locale(avail, true));
     394             :   }
     395             : 
     396             :   // Helper to erase an entry from availLocales once we have copied it to
     397             :   // the result list. Returns an iterator pointing to the entry that was
     398             :   // immediately after the one that was erased (or availLocales.end() if
     399             :   // the target was the last in the array).
     400           1 :   auto eraseFromAvail = [&](nsTArray<Locale>::iterator aIter) {
     401           2 :     nsTArray<Locale>::size_type index = aIter - availLocales.begin();
     402           1 :     availLocales.RemoveElementAt(index);
     403           2 :     return availLocales.begin() + index;
     404           2 :   };
     405             : 
     406           4 :   for (auto& requested : aRequested) {
     407             : 
     408             :     // 1) Try to find a simple (case-insensitive) string match for the request.
     409           1 :     auto matchesExactly = [&](const Locale& aLoc) {
     410           1 :       return requested.Equals(aLoc.AsString(),
     411           2 :                               nsCaseInsensitiveCStringComparator());
     412           4 :     };
     413           4 :     auto match = std::find_if(availLocales.begin(), availLocales.end(),
     414           2 :                               matchesExactly);
     415           2 :     if (match != availLocales.end()) {
     416           1 :       aRetVal.AppendElement(match->AsString());
     417           1 :       eraseFromAvail(match);
     418             :     }
     419             : 
     420           2 :     if (!aRetVal.IsEmpty()) {
     421           1 :       HANDLE_STRATEGY;
     422             :     }
     423             : 
     424             :     // 2) Try to match against the available locales treated as ranges.
     425           8 :     auto findRangeMatches = [&](const Locale& aReq) {
     426           0 :       auto matchesRange = [&](const Locale& aLoc) {
     427           0 :         return aReq.Matches(aLoc);
     428           8 :       };
     429           8 :       bool foundMatch = false;
     430          16 :       auto match = availLocales.begin();
     431          24 :       while ((match = std::find_if(match, availLocales.end(),
     432          24 :                                    matchesRange)) != availLocales.end()) {
     433           0 :         aRetVal.AppendElement(match->AsString());
     434           0 :         match = eraseFromAvail(match);
     435           0 :         foundMatch = true;
     436           0 :         if (aStrategy != LangNegStrategy::Filtering) {
     437           0 :           return true; // we only want the first match
     438             :         }
     439             :       }
     440           8 :       return foundMatch;
     441           2 :     };
     442             : 
     443           4 :     Locale requestedLocale = Locale(requested, false);
     444           2 :     if (findRangeMatches(requestedLocale)) {
     445           0 :       HANDLE_STRATEGY;
     446             :     }
     447             : 
     448             :     // 3) Try to match against a maximized version of the requested locale
     449           2 :     if (requestedLocale.AddLikelySubtags()) {
     450           2 :       if (findRangeMatches(requestedLocale)) {
     451           0 :         HANDLE_STRATEGY;
     452             :       }
     453             :     }
     454             : 
     455             :     // 4) Try to match against a variant as a range
     456           2 :     requestedLocale.SetVariantRange();
     457           2 :     if (findRangeMatches(requestedLocale)) {
     458           0 :       HANDLE_STRATEGY;
     459             :     }
     460             : 
     461             :     // 5) Try to match against a region as a range
     462           2 :     requestedLocale.SetRegionRange();
     463           2 :     if (findRangeMatches(requestedLocale)) {
     464           0 :       HANDLE_STRATEGY;
     465             :     }
     466             :   }
     467             : }
     468             : 
     469             : bool
     470           2 : LocaleService::NegotiateLanguages(const nsTArray<nsCString>& aRequested,
     471             :                                   const nsTArray<nsCString>& aAvailable,
     472             :                                   const nsACString& aDefaultLocale,
     473             :                                   LangNegStrategy aStrategy,
     474             :                                   nsTArray<nsCString>& aRetVal)
     475             : {
     476             :   // If the strategy is Lookup, we require the defaultLocale to be set.
     477           2 :   if (aStrategy == LangNegStrategy::Lookup && aDefaultLocale.IsEmpty()) {
     478           0 :     return false;
     479             :   }
     480             : 
     481           2 :   FilterMatches(aRequested, aAvailable, aStrategy, aRetVal);
     482             : 
     483           2 :   if (aStrategy == LangNegStrategy::Lookup) {
     484           0 :     if (aRetVal.Length() == 0) {
     485             :       // If the strategy is Lookup and Filtering returned no matches, use
     486             :       // the default locale.
     487           0 :       aRetVal.AppendElement(aDefaultLocale);
     488             :     }
     489           2 :   } else if (!aDefaultLocale.IsEmpty() && !aRetVal.Contains(aDefaultLocale)) {
     490             :     // If it's not a Lookup strategy, add the default locale only if it's
     491             :     // set and it's not in the results already.
     492           1 :     aRetVal.AppendElement(aDefaultLocale);
     493             :   }
     494           2 :   return true;
     495             : }
     496             : 
     497             : bool
     498           3 : LocaleService::IsAppLocaleRTL()
     499             : {
     500           6 :   nsAutoCString locale;
     501           3 :   GetAppLocaleAsBCP47(locale);
     502             : 
     503             : #ifdef ENABLE_INTL_API
     504           3 :   int pref = Preferences::GetInt("intl.uidirection", -1);
     505           3 :   if (pref >= 0) {
     506           0 :     return (pref > 0);
     507             :   }
     508           3 :   return uloc_isRightToLeft(locale.get());
     509             : #else
     510             :   // first check the intl.uidirection.<locale> preference, and if that is not
     511             :   // set, check the same preference but with just the first two characters of
     512             :   // the locale. If that isn't set, default to left-to-right.
     513             :   nsAutoCString prefString = NS_LITERAL_CSTRING("intl.uidirection.") + locale;
     514             :   nsAutoCString dir;
     515             :   Preferences::GetCString(prefString.get(), &dir);
     516             :   if (dir.IsEmpty()) {
     517             :     int32_t hyphen = prefString.FindChar('-');
     518             :     if (hyphen >= 1) {
     519             :       prefString.Truncate(hyphen);
     520             :       Preferences::GetCString(prefString.get(), &dir);
     521             :     }
     522             :   }
     523             :   return dir.EqualsLiteral("rtl");
     524             : #endif
     525             : }
     526             : 
     527             : NS_IMETHODIMP
     528           0 : LocaleService::Observe(nsISupports *aSubject, const char *aTopic,
     529             :                       const char16_t *aData)
     530             : {
     531           0 :   MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
     532             : 
     533             :   // At the moment the only thing we're observing are settings indicating
     534             :   // user requested locales.
     535           0 :   NS_ConvertUTF16toUTF8 pref(aData);
     536           0 :   if (pref.EqualsLiteral(MATCH_OS_LOCALE_PREF) ||
     537           0 :       pref.EqualsLiteral(SELECTED_LOCALE_PREF) ||
     538           0 :       pref.EqualsLiteral(ANDROID_OS_LOCALE_PREF)) {
     539           0 :     OnRequestedLocalesChanged();
     540             :   }
     541           0 :   return NS_OK;
     542             : }
     543             : 
     544             : bool
     545           0 : LocaleService::LanguagesMatch(const nsCString& aRequested,
     546             :                               const nsCString& aAvailable)
     547             : {
     548           0 :   return Locale(aRequested, true).LanguageMatches(Locale(aAvailable, true));
     549             : }
     550             : 
     551             : 
     552             : bool
     553           3 : LocaleService::IsServer()
     554             : {
     555           3 :   return mIsServer;
     556             : }
     557             : 
     558             : /**
     559             :  * mozILocaleService methods
     560             :  */
     561             : 
     562             : static char**
     563           0 : CreateOutArray(const nsTArray<nsCString>& aArray)
     564             : {
     565           0 :   uint32_t n = aArray.Length();
     566           0 :   char** result = static_cast<char**>(moz_xmalloc(n * sizeof(char*)));
     567           0 :   for (uint32_t i = 0; i < n; i++) {
     568           0 :     result[i] = moz_xstrdup(aArray[i].get());
     569             :   }
     570           0 :   return result;
     571             : }
     572             : 
     573             : NS_IMETHODIMP
     574           4 : LocaleService::GetDefaultLocale(nsACString& aRetVal)
     575             : {
     576           4 :   aRetVal.AssignLiteral("en-US");
     577           4 :   return NS_OK;
     578             : }
     579             : 
     580             : NS_IMETHODIMP
     581           0 : LocaleService::GetAppLocalesAsLangTags(uint32_t* aCount, char*** aOutArray)
     582             : {
     583           0 :   if (mAppLocales.IsEmpty()) {
     584           0 :     NegotiateAppLocales(mAppLocales);
     585             :   }
     586             : 
     587           0 :   *aCount = mAppLocales.Length();
     588           0 :   *aOutArray = CreateOutArray(mAppLocales);
     589             : 
     590           0 :   return NS_OK;
     591             : }
     592             : 
     593             : NS_IMETHODIMP
     594           0 : LocaleService::GetAppLocalesAsBCP47(uint32_t* aCount, char*** aOutArray)
     595             : {
     596           0 :   AutoTArray<nsCString, 32> locales;
     597           0 :   GetAppLocalesAsBCP47(locales);
     598             : 
     599           0 :   *aCount = locales.Length();
     600           0 :   *aOutArray = CreateOutArray(locales);
     601             : 
     602           0 :   return NS_OK;
     603             : }
     604             : 
     605             : NS_IMETHODIMP
     606         185 : LocaleService::GetAppLocaleAsLangTag(nsACString& aRetVal)
     607             : {
     608         185 :   if (mAppLocales.IsEmpty()) {
     609           1 :     NegotiateAppLocales(mAppLocales);
     610             :   }
     611         185 :   aRetVal = mAppLocales[0];
     612         185 :   return NS_OK;
     613             : }
     614             : 
     615             : NS_IMETHODIMP
     616           8 : LocaleService::GetAppLocaleAsBCP47(nsACString& aRetVal)
     617             : {
     618           8 :   if (mAppLocales.IsEmpty()) {
     619           2 :     NegotiateAppLocales(mAppLocales);
     620             :   }
     621           8 :   aRetVal = mAppLocales[0];
     622             : 
     623           8 :   SanitizeForBCP47(aRetVal);
     624           8 :   return NS_OK;
     625             : }
     626             : 
     627             : NS_IMETHODIMP
     628           0 : LocaleService::GetRegionalPrefsLocales(uint32_t* aCount, char*** aOutArray)
     629             : {
     630           0 :   AutoTArray<nsCString,10> rgLocales;
     631             : 
     632           0 :   GetRegionalPrefsLocales(rgLocales);
     633             : 
     634           0 :   *aCount = rgLocales.Length();
     635           0 :   *aOutArray = static_cast<char**>(moz_xmalloc(*aCount * sizeof(char*)));
     636             : 
     637           0 :   for (uint32_t i = 0; i < *aCount; i++) {
     638           0 :     (*aOutArray)[i] = moz_xstrdup(rgLocales[i].get());
     639             :   }
     640             : 
     641           0 :   return NS_OK;
     642             : }
     643             : 
     644             : static LocaleService::LangNegStrategy
     645           0 : ToLangNegStrategy(int32_t aStrategy)
     646             : {
     647           0 :   switch (aStrategy) {
     648             :     case 1:
     649           0 :       return LocaleService::LangNegStrategy::Matching;
     650             :     case 2:
     651           0 :       return LocaleService::LangNegStrategy::Lookup;
     652             :     default:
     653           0 :       return LocaleService::LangNegStrategy::Filtering;
     654             :   }
     655             : }
     656             : 
     657             : NS_IMETHODIMP
     658           0 : LocaleService::NegotiateLanguages(const char** aRequested,
     659             :                                   const char** aAvailable,
     660             :                                   const char*  aDefaultLocale,
     661             :                                   int32_t aStrategy,
     662             :                                   uint32_t aRequestedCount,
     663             :                                   uint32_t aAvailableCount,
     664             :                                   uint32_t* aCount, char*** aRetVal)
     665             : {
     666           0 :   if (aStrategy < 0 || aStrategy > 2) {
     667           0 :     return NS_ERROR_INVALID_ARG;
     668             :   }
     669             : 
     670             :   // Check that the given string contains only ASCII characters valid in tags
     671             :   // (i.e. alphanumerics, plus '-' and '_'), and is non-empty.
     672           0 :   auto validTagChars = [](const char* s) {
     673           0 :     if (!s || !*s) {
     674           0 :       return false;
     675             :     }
     676           0 :     while (*s) {
     677           0 :       if (isalnum((unsigned char)*s) || *s == '-' || *s == '_' || *s == '*') {
     678           0 :         s++;
     679             :       } else {
     680           0 :         return false;
     681             :       }
     682             :     }
     683           0 :     return true;
     684             :   };
     685             : 
     686           0 :   AutoTArray<nsCString, 100> requestedLocales;
     687           0 :   for (uint32_t i = 0; i < aRequestedCount; i++) {
     688           0 :     if (!validTagChars(aRequested[i])) {
     689           0 :       continue;
     690             :     }
     691           0 :     requestedLocales.AppendElement(aRequested[i]);
     692             :   }
     693             : 
     694           0 :   AutoTArray<nsCString, 100> availableLocales;
     695           0 :   for (uint32_t i = 0; i < aAvailableCount; i++) {
     696           0 :     if (!validTagChars(aAvailable[i])) {
     697           0 :       continue;
     698             :     }
     699           0 :     availableLocales.AppendElement(aAvailable[i]);
     700             :   }
     701             : 
     702           0 :   nsAutoCString defaultLocale(aDefaultLocale);
     703             : 
     704           0 :   LangNegStrategy strategy = ToLangNegStrategy(aStrategy);
     705             : 
     706           0 :   AutoTArray<nsCString, 100> supportedLocales;
     707             :   bool result = NegotiateLanguages(requestedLocales, availableLocales,
     708           0 :                                    defaultLocale, strategy, supportedLocales);
     709             : 
     710           0 :   if (!result) {
     711           0 :     return NS_ERROR_INVALID_ARG;
     712             :   }
     713             : 
     714           0 :   *aRetVal =
     715           0 :     static_cast<char**>(moz_xmalloc(sizeof(char*) * supportedLocales.Length()));
     716             : 
     717           0 :   *aCount = 0;
     718           0 :   for (const auto& supported : supportedLocales) {
     719           0 :     (*aRetVal)[(*aCount)++] = moz_xstrdup(supported.get());
     720             :   }
     721             : 
     722           0 :   return NS_OK;
     723             : }
     724             : 
     725           5 : LocaleService::Locale::Locale(const nsCString& aLocale, bool aRange)
     726           5 :   : mLocaleStr(aLocale)
     727             : {
     728           5 :   int32_t partNum = 0;
     729             : 
     730          10 :   nsAutoCString normLocale(aLocale);
     731           5 :   normLocale.ReplaceChar('_', '-');
     732             : 
     733          17 :   for (const nsACString& part : normLocale.Split('-')) {
     734          12 :     switch (partNum) {
     735             :       case 0:
     736          15 :         if (part.EqualsLiteral("*") ||
     737           5 :             part.Length() == 2 || part.Length() == 3) {
     738           5 :           mLanguage.Assign(part);
     739             :         }
     740           5 :         break;
     741             :       case 1:
     742           5 :         if (part.EqualsLiteral("*") || part.Length() == 4) {
     743           2 :           mScript.Assign(part);
     744           2 :           break;
     745             :         }
     746             : 
     747             :         // fallover to region case
     748           3 :         partNum++;
     749             :         MOZ_FALLTHROUGH;
     750             :       case 2:
     751           5 :         if (part.EqualsLiteral("*") || part.Length() == 2) {
     752           5 :           mRegion.Assign(part);
     753             :         }
     754           5 :         break;
     755             :       case 3:
     756           0 :         if (part.EqualsLiteral("*") || part.Length() == 3) {
     757           0 :           mVariant.Assign(part);
     758             :         }
     759           0 :         break;
     760             :     }
     761          12 :     partNum++;
     762             :   }
     763             : 
     764           5 :   if (aRange) {
     765           1 :     if (mLanguage.IsEmpty()) {
     766           0 :       mLanguage.Assign(NS_LITERAL_CSTRING("*"));
     767             :     }
     768           1 :     if (mScript.IsEmpty()) {
     769           1 :       mScript.Assign(NS_LITERAL_CSTRING("*"));
     770             :     }
     771           1 :     if (mRegion.IsEmpty()) {
     772           0 :       mRegion.Assign(NS_LITERAL_CSTRING("*"));
     773             :     }
     774           1 :     if (mVariant.IsEmpty()) {
     775           1 :       mVariant.Assign(NS_LITERAL_CSTRING("*"));
     776             :     }
     777             :   }
     778           5 : }
     779             : 
     780             : static bool
     781           0 : SubtagMatches(const nsCString& aSubtag1, const nsCString& aSubtag2)
     782             : {
     783           0 :   return aSubtag1.EqualsLiteral("*") ||
     784           0 :          aSubtag2.EqualsLiteral("*") ||
     785           0 :          aSubtag1.Equals(aSubtag2, nsCaseInsensitiveCStringComparator());
     786             : }
     787             : 
     788             : bool
     789           0 : LocaleService::Locale::Matches(const LocaleService::Locale& aLocale) const
     790             : {
     791           0 :   return SubtagMatches(mLanguage, aLocale.mLanguage) &&
     792           0 :          SubtagMatches(mScript, aLocale.mScript) &&
     793           0 :          SubtagMatches(mRegion, aLocale.mRegion) &&
     794           0 :          SubtagMatches(mVariant, aLocale.mVariant);
     795             : }
     796             : 
     797             : bool
     798           0 : LocaleService::Locale::LanguageMatches(const LocaleService::Locale& aLocale) const
     799             : {
     800           0 :   return SubtagMatches(mLanguage, aLocale.mLanguage) &&
     801           0 :          SubtagMatches(mScript, aLocale.mScript);
     802             : }
     803             : 
     804             : void
     805           2 : LocaleService::Locale::SetVariantRange()
     806             : {
     807           2 :   mVariant.AssignLiteral("*");
     808           2 : }
     809             : 
     810             : void
     811           2 : LocaleService::Locale::SetRegionRange()
     812             : {
     813           2 :   mRegion.AssignLiteral("*");
     814           2 : }
     815             : 
     816             : bool
     817           2 : LocaleService::Locale::AddLikelySubtags()
     818             : {
     819             : #ifdef ENABLE_INTL_API
     820           2 :   const int32_t kLocaleMax = 160;
     821             :   char maxLocale[kLocaleMax];
     822             : 
     823           2 :   UErrorCode status = U_ZERO_ERROR;
     824           2 :   uloc_addLikelySubtags(mLocaleStr.get(), maxLocale, kLocaleMax, &status);
     825             : 
     826           2 :   if (U_FAILURE(status)) {
     827           0 :     return false;
     828             :   }
     829             : 
     830           4 :   nsDependentCString maxLocStr(maxLocale);
     831           4 :   Locale loc = Locale(maxLocStr, false);
     832             : 
     833           2 :   if (loc == *this) {
     834           0 :     return false;
     835             :   }
     836             : 
     837           2 :   mLanguage = loc.mLanguage;
     838           2 :   mScript = loc.mScript;
     839           2 :   mRegion = loc.mRegion;
     840           2 :   mVariant = loc.mVariant;
     841           2 :   return true;
     842             : #else
     843             :   return false;
     844             : #endif
     845             : }
     846             : 
     847             : NS_IMETHODIMP
     848           0 : LocaleService::GetRequestedLocales(uint32_t* aCount, char*** aOutArray)
     849             : {
     850           0 :   AutoTArray<nsCString, 16> requestedLocales;
     851           0 :   bool res = GetRequestedLocales(requestedLocales);
     852             : 
     853           0 :   if (!res) {
     854           0 :     NS_ERROR("Couldn't retrieve selected locales from prefs!");
     855           0 :     return NS_ERROR_FAILURE;
     856             :   }
     857             : 
     858           0 :   *aCount = requestedLocales.Length();
     859           0 :   *aOutArray = CreateOutArray(requestedLocales);
     860             : 
     861           0 :   return NS_OK;
     862             : }
     863             : 
     864             : NS_IMETHODIMP
     865           0 : LocaleService::GetRequestedLocale(nsACString& aRetVal)
     866             : {
     867           0 :   AutoTArray<nsCString, 16> requestedLocales;
     868           0 :   bool res = GetRequestedLocales(requestedLocales);
     869             : 
     870           0 :   if (!res) {
     871           0 :     NS_ERROR("Couldn't retrieve selected locales from prefs!");
     872           0 :     return NS_ERROR_FAILURE;
     873             :   }
     874             : 
     875           0 :   if (requestedLocales.Length() > 0) {
     876           0 :     aRetVal = requestedLocales[0];
     877             :   }
     878             : 
     879           0 :   return NS_OK;
     880             : }
     881             : 
     882             : NS_IMETHODIMP
     883           0 : LocaleService::SetRequestedLocales(const char** aRequested,
     884             :                                    uint32_t aRequestedCount)
     885             : {
     886           0 :   MOZ_ASSERT(aRequestedCount < 2, "We can only handle one requested locale");
     887             : 
     888           0 :   if (aRequestedCount == 0) {
     889           0 :     Preferences::ClearUser(SELECTED_LOCALE_PREF);
     890             :   } else {
     891           0 :     Preferences::SetCString(SELECTED_LOCALE_PREF, aRequested[0]);
     892             :   }
     893             : 
     894           0 :   Preferences::SetBool(MATCH_OS_LOCALE_PREF, aRequestedCount == 0);
     895           0 :   return NS_OK;
     896             : }
     897             : 
     898             : NS_IMETHODIMP
     899           0 : LocaleService::GetAvailableLocales(uint32_t* aCount, char*** aOutArray)
     900             : {
     901           0 :   AutoTArray<nsCString, 100> availableLocales;
     902           0 :   bool res = GetAvailableLocales(availableLocales);
     903             : 
     904           0 :   if (!res) {
     905           0 :     NS_ERROR("Couldn't retrieve available locales!");
     906           0 :     return NS_ERROR_FAILURE;
     907             :   }
     908             : 
     909           0 :   *aCount = availableLocales.Length();
     910           0 :   *aOutArray = CreateOutArray(availableLocales);
     911           0 :   return NS_OK;
     912             : }
     913             : 
     914             : NS_IMETHODIMP
     915           1 : LocaleService::GetIsAppLocaleRTL(bool* aRetVal)
     916             : {
     917           1 :   (*aRetVal) = IsAppLocaleRTL();
     918           1 :   return NS_OK;
     919             : }

Generated by: LCOV version 1.13