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