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