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 : }
|