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