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