Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/intl/locale/LocaleService.cpp
Line
Count
Source (jump to first uncovered line)
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/DebugOnly.h"
11
#include "mozilla/Omnijar.h"
12
#include "mozilla/Preferences.h"
13
#include "mozilla/Services.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 "nsIToolkitChromeRegistry.h"
20
#include "nsStringEnumerator.h"
21
#include "nsXULAppAPI.h"
22
#include "nsZipArchive.h"
23
24
#include "unicode/uloc.h"
25
26
3
#define INTL_SYSTEM_LOCALES_CHANGED "intl:system-locales-changed"
27
28
3
#define REQUESTED_LOCALES_PREF "intl.locale.requested"
29
30
static const char* kObservedPrefs[] = {
31
  REQUESTED_LOCALES_PREF,
32
  nullptr
33
};
34
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 transforms a canonical Mozilla Language Tag, into it's
45
 * BCP47 compilant form.
46
 *
47
 * Example: "ja-JP-mac" -> "ja-JP-macos"
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 bool
53
SanitizeForBCP47(nsACString& aLocale, bool strict)
54
6
{
55
6
  // Currently, the only locale code we use that's not BCP47-conformant is
56
6
  // "ja-JP-mac" on OS X, and ICU canonicalizes it into a mouthfull
57
6
  // "ja-JP-x-lvariant-mac", so instead we're hardcoding a conversion
58
6
  // of it to "ja-JP-macos".
59
6
  if (aLocale.LowerCaseEqualsASCII("ja-jp-mac")) {
60
0
    aLocale.AssignLiteral("ja-JP-macos");
61
0
    return true;
62
0
  }
63
6
64
6
  // The rest of this function will use ICU canonicalization for any other
65
6
  // tag that may come this way.
66
6
  const int32_t LANG_TAG_CAPACITY = 128;
67
6
  char langTag[LANG_TAG_CAPACITY];
68
6
  nsAutoCString locale(aLocale);
69
6
  locale.Trim(" ");
70
6
  UErrorCode err = U_ZERO_ERROR;
71
6
  // This is a fail-safe method that will set langTag to "und" if it cannot
72
6
  // match any part of the input locale code.
73
6
  int32_t len = uloc_toLanguageTag(locale.get(), langTag, LANG_TAG_CAPACITY,
74
6
                                   strict, &err);
75
6
  if (U_SUCCESS(err) && len > 0) {
76
6
    aLocale.Assign(langTag, len);
77
6
  }
78
6
  return U_SUCCESS(err);
79
6
}
80
81
/**
82
 * This function splits an input string by `,` delimiter, sanitizes the result
83
 * language tags and returns them to the caller.
84
 */
85
static void
86
SplitLocaleListStringIntoArray(nsACString& str, nsTArray<nsCString>& aRetVal)
87
3
{
88
3
  if (str.Length() > 0) {
89
3
    for (const nsACString& part : str.Split(',')) {
90
3
      nsAutoCString locale(part);
91
3
      if (SanitizeForBCP47(locale, true)) {
92
3
        if (!aRetVal.Contains(locale)) {
93
3
          aRetVal.AppendElement(locale);
94
3
        }
95
3
      }
96
3
    }
97
3
  }
98
3
}
99
100
static void
101
ReadRequestedLocales(nsTArray<nsCString>& aRetVal)
102
3
{
103
3
  nsAutoCString str;
104
3
  nsresult rv = Preferences::GetCString(REQUESTED_LOCALES_PREF, str);
105
3
106
3
  // We handle three scenarios here:
107
3
  //
108
3
  // 1) The pref is not set - use default locale
109
3
  // 2) The pref is set to "" - use OS locales
110
3
  // 3) The pref is set to a value - parse the locale list and use it
111
3
  if (NS_SUCCEEDED(rv)) {
112
0
    if (str.Length() == 0) {
113
0
      // If the pref string is empty, we'll take requested locales
114
0
      // from the OS.
115
0
      OSPreferences::GetInstance()->GetSystemLocales(aRetVal);
116
0
    } else {
117
0
      SplitLocaleListStringIntoArray(str, aRetVal);
118
0
    }
119
0
  }
120
3
121
3
  // This will happen when either the pref is not set,
122
3
  // or parsing of the pref didn't produce any usable
123
3
  // result.
124
3
  if (aRetVal.IsEmpty()) {
125
3
    nsAutoCString defaultLocale;
126
3
    LocaleService::GetInstance()->GetDefaultLocale(defaultLocale);
127
3
    aRetVal.AppendElement(defaultLocale);
128
3
  }
129
3
}
130
131
LocaleService::LocaleService(bool aIsServer)
132
  :mIsServer(aIsServer)
133
3
{
134
3
}
135
136
/**
137
 * This function performs the actual language negotiation for the API.
138
 *
139
 * Currently it collects the locale ID used by nsChromeRegistry and
140
 * adds hardcoded default locale as a fallback.
141
 */
142
void
143
LocaleService::NegotiateAppLocales(nsTArray<nsCString>& aRetVal)
144
3
{
145
3
  if (mIsServer) {
146
3
    nsAutoCString defaultLocale;
147
3
    AutoTArray<nsCString, 100> availableLocales;
148
3
    AutoTArray<nsCString, 10> requestedLocales;
149
3
    GetDefaultLocale(defaultLocale);
150
3
    GetAvailableLocales(availableLocales);
151
3
    GetRequestedLocales(requestedLocales);
152
3
153
3
    NegotiateLanguages(requestedLocales, availableLocales, defaultLocale,
154
3
                       kLangNegStrategyFiltering, aRetVal);
155
3
  }
156
3
157
3
  nsAutoCString lastFallbackLocale;
158
3
  GetLastFallbackLocale(lastFallbackLocale);
159
3
160
3
  if (!aRetVal.Contains(lastFallbackLocale)) {
161
0
    // This part is used in one of the two scenarios:
162
0
    //
163
0
    // a) We're in a client mode, and no locale has been set yet,
164
0
    //    so we need to return last fallback locale temporarily.
165
0
    // b) We're in a server mode, and the last fallback locale was excluded
166
0
    //    when negotiating against the requested locales.
167
0
    //    Since we currently package it as a last fallback at build
168
0
    //    time, we should also add it at the end of the list at
169
0
    //    runtime.
170
0
    aRetVal.AppendElement(lastFallbackLocale);
171
0
  }
172
3
}
173
174
LocaleService*
175
LocaleService::GetInstance()
176
81
{
177
81
  if (!sInstance) {
178
3
    sInstance = new LocaleService(XRE_IsParentProcess());
179
3
180
3
    if (sInstance->IsServer()) {
181
3
      // We're going to observe for requested languages changes which come
182
3
      // from prefs.
183
3
      DebugOnly<nsresult> rv = Preferences::AddWeakObservers(sInstance, kObservedPrefs);
184
3
      MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
185
3
186
3
      nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
187
3
      if (obs) {
188
3
        obs->AddObserver(sInstance, INTL_SYSTEM_LOCALES_CHANGED, true);
189
3
      }
190
3
    }
191
3
    ClearOnShutdown(&sInstance, ShutdownPhase::Shutdown);
192
3
  }
193
81
  return sInstance;
194
81
}
195
196
LocaleService::~LocaleService()
197
0
{
198
0
  if (mIsServer) {
199
0
    Preferences::RemoveObservers(this, kObservedPrefs);
200
0
201
0
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
202
0
    if (obs) {
203
0
      obs->RemoveObserver(this, INTL_SYSTEM_LOCALES_CHANGED);
204
0
    }
205
0
  }
206
0
}
207
208
void
209
LocaleService::AssignAppLocales(const nsTArray<nsCString>& aAppLocales)
210
0
{
211
0
  MOZ_ASSERT(!mIsServer, "This should only be called for LocaleService in client mode.");
212
0
213
0
  mAppLocales = aAppLocales;
214
0
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
215
0
  if (obs) {
216
0
    obs->NotifyObservers(nullptr, "intl:app-locales-changed", nullptr);
217
0
  }
218
0
}
219
220
void
221
LocaleService::AssignRequestedLocales(const nsTArray<nsCString>& aRequestedLocales)
222
0
{
223
0
  MOZ_ASSERT(!mIsServer, "This should only be called for LocaleService in client mode.");
224
0
225
0
  mRequestedLocales = aRequestedLocales;
226
0
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
227
0
  if (obs) {
228
0
    obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
229
0
  }
230
0
}
231
232
void
233
LocaleService::RequestedLocalesChanged()
234
0
{
235
0
  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
236
0
237
0
  nsTArray<nsCString> newLocales;
238
0
  ReadRequestedLocales(newLocales);
239
0
240
0
  if (mRequestedLocales != newLocales) {
241
0
    mRequestedLocales = std::move(newLocales);
242
0
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
243
0
    if (obs) {
244
0
      obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
245
0
    }
246
0
    LocalesChanged();
247
0
  }
248
0
}
249
250
void
251
LocaleService::LocalesChanged()
252
0
{
253
0
  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
254
0
255
0
  // if mAppLocales has not been initialized yet, just return
256
0
  if (mAppLocales.IsEmpty()) {
257
0
    return;
258
0
  }
259
0
260
0
  nsTArray<nsCString> newLocales;
261
0
  NegotiateAppLocales(newLocales);
262
0
263
0
  if (mAppLocales != newLocales) {
264
0
    mAppLocales = std::move(newLocales);
265
0
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
266
0
    if (obs) {
267
0
      obs->NotifyObservers(nullptr, "intl:app-locales-changed", nullptr);
268
0
    }
269
0
  }
270
0
}
271
272
// After trying each step of the negotiation algorithm for each requested locale,
273
// if a match was found we use this macro to decide whether to return immediately,
274
// skip to the next requested locale, or continue searching for additional matches,
275
// according to the desired negotiation strategy.
276
#define HANDLE_STRATEGY \
277
3
          switch (aStrategy) { \
278
3
            case kLangNegStrategyLookup: \
279
0
              return; \
280
3
            case kLangNegStrategyMatching: \
281
0
              continue; \
282
3
            case kLangNegStrategyFiltering: \
283
3
              break; \
284
3
          }
285
286
/**
287
 * This is the raw algorithm for language negotiation based roughly
288
 * on RFC4647 language filtering, with changes from LDML language matching.
289
 *
290
 * The exact algorithm is custom, and consists of a 6 level strategy:
291
 *
292
 * 1) Attempt to find an exact match for each requested locale in available
293
 *    locales.
294
 *    Example: ['en-US'] * ['en-US'] = ['en-US']
295
 *
296
 * 2) Attempt to match a requested locale to an available locale treated
297
 *    as a locale range.
298
 *    Example: ['en-US'] * ['en'] = ['en']
299
 *                           ^^
300
 *                           |-- becomes 'en-*-*-*'
301
 *
302
 * 3) Attempt to use the maximized version of the requested locale, to
303
 *    find the best match in available locales.
304
 *    Example: ['en'] * ['en-GB', 'en-US'] = ['en-US']
305
 *               ^^
306
 *               |-- ICU likelySubtags expands it to 'en-Latn-US'
307
 *
308
 * 4) Attempt to look up for a different variant of the same locale.
309
 *    Example: ['ja-JP-win'] * ['ja-JP-mac'] = ['ja-JP-mac']
310
 *               ^^^^^^^^^
311
 *               |----------- replace variant with range: 'ja-JP-*'
312
 *
313
 * 5) Attempt to look up for a maximized version of the requested locale,
314
 *    stripped of the region code.
315
 *    Example: ['en-CA'] * ['en-ZA', 'en-US'] = ['en-US', 'en-ZA']
316
 *               ^^^^^
317
 *               |----------- look for likelySubtag of 'en': 'en-Latn-US'
318
 *
319
 *
320
 * 6) Attempt to look up for a different region of the same locale.
321
 *    Example: ['en-GB'] * ['en-AU'] = ['en-AU']
322
 *               ^^^^^
323
 *               |----- replace region with range: 'en-*'
324
 *
325
 * It uses one of the strategies described in LocaleService.h.
326
 */
327
void
328
LocaleService::FilterMatches(const nsTArray<nsCString>& aRequested,
329
                             const nsTArray<nsCString>& aAvailable,
330
                             int32_t aStrategy,
331
                             nsTArray<nsCString>& aRetVal)
332
3
{
333
3
  // Local copy of the list of available locales, in Locale form for flexible
334
3
  // matching. We will invalidate entries in this list when they are matched
335
3
  // and the corresponding strings from aAvailable added to aRetVal, so that
336
3
  // no available locale will be found more than once.
337
3
  AutoTArray<Locale, 100> availLocales;
338
3
  for (auto& avail : aAvailable) {
339
3
    availLocales.AppendElement(Locale(avail));
340
3
  }
341
3
342
3
  for (auto& requested : aRequested) {
343
3
    if (requested.IsEmpty()) {
344
0
      continue;
345
0
    }
346
3
347
3
    // 1) Try to find a simple (case-insensitive) string match for the request.
348
3
    auto matchesExactly = [&](Locale& aLoc) {
349
3
      return requested.Equals(aLoc.AsString(),
350
3
                              nsCaseInsensitiveCStringComparator());
351
3
    };
352
3
    auto match = std::find_if(availLocales.begin(), availLocales.end(),
353
3
                              matchesExactly);
354
3
    if (match != availLocales.end()) {
355
3
      aRetVal.AppendElement(aAvailable[match - availLocales.begin()]);
356
3
      match->Invalidate();
357
3
    }
358
3
359
3
    if (!aRetVal.IsEmpty()) {
360
3
      HANDLE_STRATEGY;
361
3
    }
362
3
363
3
    // 2) Try to match against the available locales treated as ranges.
364
15
    auto findRangeMatches = [&](Locale& aReq, bool aAvailRange, bool aReqRange) {
365
15
      auto matchesRange = [&](Locale& aLoc) {
366
15
        return aLoc.Matches(aReq, aAvailRange, aReqRange);
367
15
      };
368
15
      bool foundMatch = false;
369
15
      auto match = availLocales.begin();
370
15
      while ((match = std::find_if(match, availLocales.end(),
371
15
                                   matchesRange)) != availLocales.end()) {
372
0
        aRetVal.AppendElement(aAvailable[match - availLocales.begin()]);
373
0
        match->Invalidate();
374
0
        foundMatch = true;
375
0
        if (aStrategy != kLangNegStrategyFiltering) {
376
0
          return true; // we only want the first match
377
0
        }
378
0
      }
379
15
      return foundMatch;
380
15
    };
381
3
382
3
    Locale requestedLocale = Locale(requested);
383
3
    if (findRangeMatches(requestedLocale, true, false)) {
384
0
      HANDLE_STRATEGY;
385
0
    }
386
3
387
3
    // 3) Try to match against a maximized version of the requested locale
388
3
    if (requestedLocale.AddLikelySubtags()) {
389
3
      if (findRangeMatches(requestedLocale, true, false)) {
390
0
        HANDLE_STRATEGY;
391
0
      }
392
3
    }
393
3
394
3
    // 4) Try to match against a variant as a range
395
3
    requestedLocale.ClearVariants();
396
3
    if (findRangeMatches(requestedLocale, true, true)) {
397
0
      HANDLE_STRATEGY;
398
0
    }
399
3
400
3
    // 5) Try to match against the likely subtag without region
401
3
    requestedLocale.ClearRegion();
402
3
    if (requestedLocale.AddLikelySubtags()) {
403
3
      if (findRangeMatches(requestedLocale, true, false)) {
404
0
        HANDLE_STRATEGY;
405
0
      }
406
3
    }
407
3
408
3
    // 6) Try to match against a region as a range
409
3
    requestedLocale.ClearRegion();
410
3
    if (findRangeMatches(requestedLocale, true, true)) {
411
0
      HANDLE_STRATEGY;
412
0
    }
413
3
  }
414
3
}
415
416
bool
417
LocaleService::IsAppLocaleRTL()
418
0
{
419
0
  nsAutoCString locale;
420
0
  GetAppLocaleAsBCP47(locale);
421
0
422
0
  int pref = Preferences::GetInt("intl.uidirection", -1);
423
0
  if (pref >= 0) {
424
0
    return (pref > 0);
425
0
  }
426
0
  return uloc_isRightToLeft(locale.get());
427
0
}
428
429
NS_IMETHODIMP
430
LocaleService::Observe(nsISupports *aSubject, const char *aTopic,
431
                      const char16_t *aData)
432
0
{
433
0
  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
434
0
435
0
  if (!strcmp(aTopic, INTL_SYSTEM_LOCALES_CHANGED)) {
436
0
    RequestedLocalesChanged();
437
0
  } else {
438
0
    NS_ConvertUTF16toUTF8 pref(aData);
439
0
    // At the moment the only thing we're observing are settings indicating
440
0
    // user requested locales.
441
0
    if (pref.EqualsLiteral(REQUESTED_LOCALES_PREF)) {
442
0
      RequestedLocalesChanged();
443
0
    }
444
0
  }
445
0
446
0
  return NS_OK;
447
0
}
448
449
bool
450
LocaleService::LanguagesMatch(const nsACString& aRequested,
451
                              const nsACString& aAvailable)
452
0
{
453
0
  Locale requested = Locale(aRequested);
454
0
  Locale available = Locale(aAvailable);
455
0
  return requested.GetLanguage().Equals(available.GetLanguage());
456
0
}
457
458
459
bool
460
LocaleService::IsServer()
461
3
{
462
3
  return mIsServer;
463
3
}
464
465
static bool
466
GetGREFileContents(const char* aFilePath, nsCString* aOutString)
467
6
{
468
6
  // Look for the requested file in omnijar.
469
6
  RefPtr<nsZipArchive> zip = Omnijar::GetReader(Omnijar::GRE);
470
6
  if (zip) {
471
6
    nsZipItemPtr<char> item(zip, aFilePath);
472
6
    if (!item) {
473
0
      return false;
474
0
    }
475
6
    aOutString->Assign(item.Buffer(), item.Length());
476
6
    return true;
477
6
  }
478
0
479
0
  // If we didn't have an omnijar (i.e. we're running a non-packaged
480
0
  // build), then look in the GRE directory.
481
0
  nsCOMPtr<nsIFile> path;
482
0
  if (NS_FAILED(nsDirectoryService::gService->Get(NS_GRE_DIR,
483
0
                                                  NS_GET_IID(nsIFile),
484
0
                                                  getter_AddRefs(path)))) {
485
0
    return false;
486
0
  }
487
0
488
0
  path->AppendRelativeNativePath(nsDependentCString(aFilePath));
489
0
  bool result;
490
0
  if (NS_FAILED(path->IsFile(&result)) || !result ||
491
0
      NS_FAILED(path->IsReadable(&result)) || !result) {
492
0
    return false;
493
0
  }
494
0
495
0
  // This is a small file, only used once, so it's not worth doing some fancy
496
0
  // off-main-thread file I/O or whatever. Just read it.
497
0
  FILE* fp;
498
0
  if (NS_FAILED(path->OpenANSIFileDesc("r", &fp)) || !fp) {
499
0
    return false;
500
0
  }
501
0
502
0
  fseek(fp, 0, SEEK_END);
503
0
  long len = ftell(fp);
504
0
  rewind(fp);
505
0
  aOutString->SetLength(len);
506
0
  size_t cc = fread(aOutString->BeginWriting(), 1, len, fp);
507
0
508
0
  fclose(fp);
509
0
510
0
  return cc == size_t(len);
511
0
}
512
513
void
514
LocaleService::InitPackagedLocales()
515
3
{
516
3
  MOZ_ASSERT(mPackagedLocales.IsEmpty());
517
3
518
3
  nsAutoCString localesString;
519
3
  if (GetGREFileContents("res/multilocale.txt", &localesString)) {
520
3
    localesString.Trim(" \t\n\r");
521
3
    // This should never be empty in a correctly-built product.
522
3
    MOZ_ASSERT(!localesString.IsEmpty());
523
3
    SplitLocaleListStringIntoArray(localesString, mPackagedLocales);
524
3
  }
525
3
526
3
  // Last resort in case of broken build
527
3
  if (mPackagedLocales.IsEmpty()) {
528
0
    nsAutoCString defaultLocale;
529
0
    GetDefaultLocale(defaultLocale);
530
0
    mPackagedLocales.AppendElement(defaultLocale);
531
0
  }
532
3
}
533
534
/**
535
 * mozILocaleService methods
536
 */
537
538
NS_IMETHODIMP
539
LocaleService::GetDefaultLocale(nsACString& aRetVal)
540
6
{
541
6
  // We don't allow this to change during a session (it's set at build/package
542
6
  // time), so we cache the result the first time we're called.
543
6
  if (mDefaultLocale.IsEmpty()) {
544
3
    nsAutoCString locale;
545
3
    // Try to get the package locale from update.locale in omnijar. If the
546
3
    // update.locale file is not found, item.len will remain 0 and we'll
547
3
    // just use our hard-coded default below.
548
3
    GetGREFileContents("update.locale", &locale);
549
3
    locale.Trim(" \t\n\r");
550
3
    // This should never be empty.
551
3
    MOZ_ASSERT(!locale.IsEmpty());
552
3
    if (SanitizeForBCP47(locale, true)) {
553
3
      mDefaultLocale.Assign(locale);
554
3
    }
555
3
556
3
    // Hard-coded fallback to allow us to survive even if update.locale was
557
3
    // missing/broken in some way.
558
3
    if (mDefaultLocale.IsEmpty()) {
559
0
      GetLastFallbackLocale(mDefaultLocale);
560
0
    }
561
3
  }
562
6
563
6
  aRetVal = mDefaultLocale;
564
6
  return NS_OK;
565
6
}
566
567
NS_IMETHODIMP
568
LocaleService::GetLastFallbackLocale(nsACString& aRetVal)
569
3
{
570
3
  aRetVal.AssignLiteral("en-US");
571
3
  return NS_OK;
572
3
}
573
574
NS_IMETHODIMP
575
LocaleService::GetAppLocalesAsLangTags(nsTArray<nsCString>& aRetVal)
576
75
{
577
75
  if (mAppLocales.IsEmpty()) {
578
3
    NegotiateAppLocales(mAppLocales);
579
3
  }
580
150
  for (uint32_t i = 0; i < mAppLocales.Length(); i++) {
581
75
    nsAutoCString locale(mAppLocales[i]);
582
75
    if (locale.LowerCaseEqualsASCII("ja-jp-macos")) {
583
0
      aRetVal.AppendElement("ja-JP-mac");
584
75
    } else {
585
75
      aRetVal.AppendElement(locale);
586
75
    }
587
75
  }
588
75
  return NS_OK;
589
75
}
590
591
592
NS_IMETHODIMP
593
LocaleService::GetAppLocalesAsBCP47(nsTArray<nsCString>& aRetVal)
594
0
{
595
0
  if (mAppLocales.IsEmpty()) {
596
0
    NegotiateAppLocales(mAppLocales);
597
0
  }
598
0
  aRetVal = mAppLocales;
599
0
600
0
  return NS_OK;
601
0
}
602
603
NS_IMETHODIMP
604
LocaleService::GetAppLocaleAsLangTag(nsACString& aRetVal)
605
75
{
606
75
  AutoTArray<nsCString, 32> locales;
607
75
  GetAppLocalesAsLangTags(locales);
608
75
609
75
  aRetVal = locales[0];
610
75
  return NS_OK;
611
75
}
612
613
NS_IMETHODIMP
614
LocaleService::GetAppLocaleAsBCP47(nsACString& aRetVal)
615
3
{
616
3
  if (mAppLocales.IsEmpty()) {
617
0
    NegotiateAppLocales(mAppLocales);
618
0
  }
619
3
  aRetVal = mAppLocales[0];
620
3
  return NS_OK;
621
3
}
622
623
NS_IMETHODIMP
624
LocaleService::GetRegionalPrefsLocales(nsTArray<nsCString>& aRetVal)
625
0
{
626
0
  bool useOSLocales = Preferences::GetBool("intl.regional_prefs.use_os_locales", false);
627
0
628
0
  // If the user specified that they want to use OS Regional Preferences locales,
629
0
  // try to retrieve them and use.
630
0
  if (useOSLocales) {
631
0
    if (NS_SUCCEEDED(OSPreferences::GetInstance()->GetRegionalPrefsLocales(aRetVal))) {
632
0
      return NS_OK;
633
0
    }
634
0
635
0
    // If we fail to retrieve them, return the app locales.
636
0
    GetAppLocalesAsBCP47(aRetVal);
637
0
    return NS_OK;
638
0
  }
639
0
640
0
  // Otherwise, fetch OS Regional Preferences locales and compare the first one
641
0
  // to the app locale. If the language subtag matches, we can safely use
642
0
  // the OS Regional Preferences locale.
643
0
  //
644
0
  // This facilitates scenarios such as Firefox in "en-US" and User sets
645
0
  // regional prefs to "en-GB".
646
0
  nsAutoCString appLocale;
647
0
  AutoTArray<nsCString, 10> regionalPrefsLocales;
648
0
  LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale);
649
0
650
0
  if (NS_FAILED(OSPreferences::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales))) {
651
0
    GetAppLocalesAsBCP47(aRetVal);
652
0
    return NS_OK;
653
0
  }
654
0
655
0
  if (LocaleService::LanguagesMatch(appLocale, regionalPrefsLocales[0])) {
656
0
    aRetVal = regionalPrefsLocales;
657
0
    return NS_OK;
658
0
  }
659
0
660
0
  // Otherwise use the app locales.
661
0
  GetAppLocalesAsBCP47(aRetVal);
662
0
  return NS_OK;
663
0
}
664
665
NS_IMETHODIMP
666
LocaleService::NegotiateLanguages(const nsTArray<nsCString>& aRequested,
667
                                  const nsTArray<nsCString>& aAvailable,
668
                                  const nsACString& aDefaultLocale,
669
                                  int32_t aStrategy,
670
                                  nsTArray<nsCString>& aRetVal)
671
3
{
672
3
  if (aStrategy < 0 || aStrategy > 2) {
673
0
    return NS_ERROR_INVALID_ARG;
674
0
  }
675
3
676
3
  MOZ_ASSERT(aDefaultLocale.IsEmpty() || Locale(aDefaultLocale).IsWellFormed(),
677
3
    "If specified, default locale must be a well-formed BCP47 language tag.");
678
3
679
3
  if (aStrategy == kLangNegStrategyLookup && aDefaultLocale.IsEmpty()) {
680
0
    NS_WARNING("Default locale should be specified when using lookup strategy.");
681
0
  }
682
3
683
3
  FilterMatches(aRequested, aAvailable, aStrategy, aRetVal);
684
3
685
3
  if (aStrategy == kLangNegStrategyLookup) {
686
0
    // If the strategy is Lookup and Filtering returned no matches, use
687
0
    // the default locale.
688
0
    if (aRetVal.Length() == 0) {
689
0
      // If the default locale is empty, we already issued a warning, so
690
0
      // now we will just pick up the LocaleService's defaultLocale.
691
0
      if (aDefaultLocale.IsEmpty()) {
692
0
        nsAutoCString defaultLocale;
693
0
        GetDefaultLocale(defaultLocale);
694
0
        aRetVal.AppendElement(defaultLocale);
695
0
      } else {
696
0
        aRetVal.AppendElement(aDefaultLocale);
697
0
      }
698
0
    }
699
3
  } else if (!aDefaultLocale.IsEmpty() && !aRetVal.Contains(aDefaultLocale)) {
700
0
    // If it's not a Lookup strategy, add the default locale only if it's
701
0
    // set and it's not in the results already.
702
0
    aRetVal.AppendElement(aDefaultLocale);
703
0
  }
704
3
  return NS_OK;
705
3
}
706
707
NS_IMETHODIMP
708
LocaleService::GetRequestedLocales(nsTArray<nsCString>& aRetVal)
709
3
{
710
3
  if (mRequestedLocales.IsEmpty()) {
711
3
    ReadRequestedLocales(mRequestedLocales);
712
3
  }
713
3
714
3
  aRetVal = mRequestedLocales;
715
3
  return NS_OK;
716
3
}
717
718
NS_IMETHODIMP
719
LocaleService::GetRequestedLocale(nsACString& aRetVal)
720
0
{
721
0
  if (mRequestedLocales.IsEmpty()) {
722
0
    ReadRequestedLocales(mRequestedLocales);
723
0
  }
724
0
725
0
  if (mRequestedLocales.Length() > 0) {
726
0
    aRetVal = mRequestedLocales[0];
727
0
  }
728
0
729
0
  return NS_OK;
730
0
}
731
732
NS_IMETHODIMP
733
LocaleService::SetRequestedLocales(const nsTArray<nsCString>& aRequested)
734
0
{
735
0
  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
736
0
  if (!mIsServer) {
737
0
    return NS_ERROR_UNEXPECTED;
738
0
  }
739
0
740
0
  nsAutoCString str;
741
0
742
0
  for (auto& req : aRequested) {
743
0
    nsAutoCString locale(req);
744
0
    if (!SanitizeForBCP47(locale, true)) {
745
0
      NS_ERROR("Invalid language tag provided to SetRequestedLocales!");
746
0
      return NS_ERROR_INVALID_ARG;
747
0
    }
748
0
749
0
    if (!str.IsEmpty()) {
750
0
      str.AppendLiteral(",");
751
0
    }
752
0
    str.Append(locale);
753
0
  }
754
0
  Preferences::SetCString(REQUESTED_LOCALES_PREF, str);
755
0
756
0
  return NS_OK;
757
0
}
758
759
NS_IMETHODIMP
760
LocaleService::GetAvailableLocales(nsTArray<nsCString>& aRetVal)
761
3
{
762
3
  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
763
3
  if (!mIsServer) {
764
0
    return NS_ERROR_UNEXPECTED;
765
0
  }
766
3
767
3
  if (mAvailableLocales.IsEmpty()) {
768
3
    // If there are no available locales set, it means that L10nRegistry
769
3
    // did not register its locale pool yet. The best course of action
770
3
    // is to use packaged locales until that happens.
771
3
    GetPackagedLocales(mAvailableLocales);
772
3
  }
773
3
774
3
  aRetVal = mAvailableLocales;
775
3
  return NS_OK;
776
3
}
777
778
NS_IMETHODIMP
779
LocaleService::GetIsAppLocaleRTL(bool* aRetVal)
780
0
{
781
0
  (*aRetVal) = IsAppLocaleRTL();
782
0
  return NS_OK;
783
0
}
784
785
NS_IMETHODIMP
786
LocaleService::SetAvailableLocales(const nsTArray<nsCString>& aAvailable)
787
0
{
788
0
  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
789
0
  if (!mIsServer) {
790
0
    return NS_ERROR_UNEXPECTED;
791
0
  }
792
0
793
0
  nsTArray<nsCString> newLocales;
794
0
795
0
  for (auto& avail : aAvailable) {
796
0
    nsAutoCString locale(avail);
797
0
    if (!SanitizeForBCP47(locale, true)) {
798
0
      NS_ERROR("Invalid language tag provided to SetAvailableLocales!");
799
0
      return NS_ERROR_INVALID_ARG;
800
0
    }
801
0
    newLocales.AppendElement(locale);
802
0
  }
803
0
804
0
  if (newLocales != mAvailableLocales) {
805
0
    mAvailableLocales = std::move(newLocales);
806
0
    LocalesChanged();
807
0
  }
808
0
809
0
  return NS_OK;
810
0
}
811
812
NS_IMETHODIMP
813
LocaleService::GetPackagedLocales(nsTArray<nsCString>& aRetVal)
814
3
{
815
3
  if (mPackagedLocales.IsEmpty()) {
816
3
    InitPackagedLocales();
817
3
  }
818
3
  aRetVal = mPackagedLocales;
819
3
  return NS_OK;
820
3
}