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