Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; 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 : /**
7 : * This is a shared part of the OSPreferences API implementation.
8 : * It defines helper methods and public methods that are calling
9 : * platform-specific private methods.
10 : */
11 :
12 : #include "OSPreferences.h"
13 :
14 : #include "mozilla/ClearOnShutdown.h"
15 : #include "mozilla/Services.h"
16 : #include "nsIObserverService.h"
17 : #ifdef ENABLE_INTL_API
18 : #include "unicode/udat.h"
19 : #include "unicode/udatpg.h"
20 : #endif
21 :
22 : using namespace mozilla::intl;
23 :
24 26 : NS_IMPL_ISUPPORTS(OSPreferences, mozIOSPreferences)
25 :
26 3 : mozilla::StaticRefPtr<OSPreferences> OSPreferences::sInstance;
27 :
28 : OSPreferences*
29 3 : OSPreferences::GetInstance()
30 : {
31 3 : if (!sInstance) {
32 2 : sInstance = new OSPreferences();
33 2 : ClearOnShutdown(&sInstance);
34 : }
35 3 : return sInstance;
36 : }
37 :
38 : bool
39 2 : OSPreferences::GetSystemLocales(nsTArray<nsCString>& aRetVal)
40 : {
41 2 : if (!mSystemLocales.IsEmpty()) {
42 0 : aRetVal = mSystemLocales;
43 0 : return true;
44 : }
45 :
46 2 : if (ReadSystemLocales(aRetVal)) {
47 2 : mSystemLocales = aRetVal;
48 2 : return true;
49 : }
50 :
51 : // If we failed to get the system locale, we still need
52 : // to return something because there are tests out there that
53 : // depend on system locale to be set.
54 0 : aRetVal.AppendElement(NS_LITERAL_CSTRING("en-US"));
55 0 : return false;
56 : }
57 :
58 : void
59 0 : OSPreferences::Refresh()
60 : {
61 0 : nsTArray<nsCString> newLocales;
62 0 : ReadSystemLocales(newLocales);
63 :
64 0 : if (mSystemLocales != newLocales) {
65 0 : mSystemLocales = Move(newLocales);
66 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
67 0 : if (obs) {
68 0 : obs->NotifyObservers(nullptr, "intl:system-locales-changed", nullptr);
69 : }
70 : }
71 0 : }
72 :
73 : /**
74 : * This method should be called by every method of OSPreferences that
75 : * retrieves a locale id from external source.
76 : *
77 : * It attempts to retrieve as much of the locale ID as possible, cutting
78 : * out bits that are not understood (non-strict behavior of ICU).
79 : *
80 : * It returns true if the canonicalization was successful.
81 : */
82 : bool
83 2 : OSPreferences::CanonicalizeLanguageTag(nsCString& aLoc)
84 : {
85 : #ifdef ENABLE_INTL_API
86 : char langTag[512];
87 :
88 2 : UErrorCode status = U_ZERO_ERROR;
89 :
90 : int32_t langTagLen =
91 2 : uloc_toLanguageTag(aLoc.get(), langTag, sizeof(langTag) - 1, false, &status);
92 :
93 2 : if (U_FAILURE(status)) {
94 0 : return false;
95 : }
96 :
97 2 : aLoc.Assign(langTag, langTagLen);
98 2 : return true;
99 : #else
100 : return false;
101 : #endif
102 : }
103 :
104 : /**
105 : * This method retrieves from ICU the best pattern for a given date/time style.
106 : */
107 : bool
108 0 : OSPreferences::GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle,
109 : DateTimeFormatStyle aTimeStyle,
110 : const nsACString& aLocale,
111 : nsAString& aRetVal)
112 : {
113 : #ifdef ENABLE_INTL_API
114 0 : UDateFormatStyle timeStyle = UDAT_NONE;
115 0 : UDateFormatStyle dateStyle = UDAT_NONE;
116 :
117 0 : switch (aTimeStyle) {
118 : case DateTimeFormatStyle::None:
119 0 : timeStyle = UDAT_NONE;
120 0 : break;
121 : case DateTimeFormatStyle::Short:
122 0 : timeStyle = UDAT_SHORT;
123 0 : break;
124 : case DateTimeFormatStyle::Medium:
125 0 : timeStyle = UDAT_MEDIUM;
126 0 : break;
127 : case DateTimeFormatStyle::Long:
128 0 : timeStyle = UDAT_LONG;
129 0 : break;
130 : case DateTimeFormatStyle::Full:
131 0 : timeStyle = UDAT_FULL;
132 0 : break;
133 : case DateTimeFormatStyle::Invalid:
134 0 : timeStyle = UDAT_NONE;
135 0 : break;
136 : }
137 :
138 0 : switch (aDateStyle) {
139 : case DateTimeFormatStyle::None:
140 0 : dateStyle = UDAT_NONE;
141 0 : break;
142 : case DateTimeFormatStyle::Short:
143 0 : dateStyle = UDAT_SHORT;
144 0 : break;
145 : case DateTimeFormatStyle::Medium:
146 0 : dateStyle = UDAT_MEDIUM;
147 0 : break;
148 : case DateTimeFormatStyle::Long:
149 0 : dateStyle = UDAT_LONG;
150 0 : break;
151 : case DateTimeFormatStyle::Full:
152 0 : dateStyle = UDAT_FULL;
153 0 : break;
154 : case DateTimeFormatStyle::Invalid:
155 0 : dateStyle = UDAT_NONE;
156 0 : break;
157 : }
158 :
159 0 : const int32_t kPatternMax = 160;
160 : UChar pattern[kPatternMax];
161 :
162 0 : nsAutoCString locale;
163 0 : if (aLocale.IsEmpty()) {
164 0 : LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale);
165 : } else {
166 0 : locale.Assign(aLocale);
167 : }
168 :
169 0 : UErrorCode status = U_ZERO_ERROR;
170 0 : UDateFormat* df = udat_open(timeStyle, dateStyle,
171 : locale.get(),
172 0 : nullptr, -1, nullptr, -1, &status);
173 0 : if (U_FAILURE(status)) {
174 0 : return false;
175 : }
176 :
177 0 : int32_t patsize = udat_toPattern(df, false, pattern, kPatternMax, &status);
178 0 : udat_close(df);
179 0 : if (U_FAILURE(status)) {
180 0 : return false;
181 : }
182 0 : aRetVal.Assign((const char16_t*)pattern, patsize);
183 0 : return true;
184 : #else
185 : return false;
186 : #endif
187 : }
188 :
189 :
190 : /**
191 : * This method retrieves from ICU the best skeleton for a given date/time style.
192 : *
193 : * This is useful for cases where an OS does not provide its own patterns,
194 : * but provide ability to customize the skeleton, like alter hourCycle setting.
195 : *
196 : * The returned value is a skeleton that matches the styles.
197 : */
198 : bool
199 0 : OSPreferences::GetDateTimeSkeletonForStyle(DateTimeFormatStyle aDateStyle,
200 : DateTimeFormatStyle aTimeStyle,
201 : const nsACString& aLocale,
202 : nsAString& aRetVal)
203 : {
204 : #ifdef ENABLE_INTL_API
205 0 : nsAutoString pattern;
206 0 : if (!GetDateTimePatternForStyle(aDateStyle, aTimeStyle, aLocale, pattern)) {
207 0 : return false;
208 : }
209 :
210 0 : const int32_t kSkeletonMax = 160;
211 : UChar skeleton[kSkeletonMax];
212 :
213 0 : UErrorCode status = U_ZERO_ERROR;
214 0 : int32_t skelsize = udatpg_getSkeleton(
215 0 : nullptr, (const UChar*)pattern.BeginReading(), pattern.Length(),
216 : skeleton, kSkeletonMax, &status
217 0 : );
218 0 : if (U_FAILURE(status)) {
219 0 : return false;
220 : }
221 :
222 0 : aRetVal.Assign((const char16_t*)skeleton, skelsize);
223 0 : return true;
224 : #else
225 : return false;
226 : #endif
227 : }
228 :
229 : /**
230 : * This function is a counterpart to GetDateTimeSkeletonForStyle.
231 : *
232 : * It takes a skeleton and returns the best available pattern for a given locale
233 : * that represents the provided skeleton.
234 : *
235 : * For example:
236 : * "Hm" skeleton for "en-US" will return "H:m"
237 : */
238 : bool
239 0 : OSPreferences::GetPatternForSkeleton(const nsAString& aSkeleton,
240 : const nsACString& aLocale,
241 : nsAString& aRetVal)
242 : {
243 : #ifdef ENABLE_INTL_API
244 0 : UErrorCode status = U_ZERO_ERROR;
245 0 : UDateTimePatternGenerator* pg = udatpg_open(PromiseFlatCString(aLocale).get(), &status);
246 0 : if (U_FAILURE(status)) {
247 0 : return false;
248 : }
249 :
250 : int32_t len =
251 0 : udatpg_getBestPattern(pg, (const UChar*)aSkeleton.BeginReading(),
252 0 : aSkeleton.Length(), nullptr, 0, &status);
253 0 : if (status == U_BUFFER_OVERFLOW_ERROR) { // expected
254 0 : aRetVal.SetLength(len);
255 0 : status = U_ZERO_ERROR;
256 0 : udatpg_getBestPattern(pg, (const UChar*)aSkeleton.BeginReading(),
257 0 : aSkeleton.Length(), (UChar*)aRetVal.BeginWriting(),
258 0 : len, &status);
259 : }
260 :
261 0 : udatpg_close(pg);
262 :
263 0 : return U_SUCCESS(status);
264 : #else
265 : return false;
266 : #endif
267 : }
268 :
269 : /**
270 : * This function returns a pattern that should be used to join date and time
271 : * patterns into a single date/time pattern string.
272 : *
273 : * It's useful for OSes that do not provide an API to retrieve such combined
274 : * pattern.
275 : *
276 : * An example output is "{1}, {0}".
277 : */
278 : bool
279 0 : OSPreferences::GetDateTimeConnectorPattern(const nsACString& aLocale,
280 : nsAString& aRetVal)
281 : {
282 0 : bool result = false;
283 : #ifdef ENABLE_INTL_API
284 0 : UErrorCode status = U_ZERO_ERROR;
285 0 : UDateTimePatternGenerator* pg = udatpg_open(PromiseFlatCString(aLocale).get(), &status);
286 0 : if (U_SUCCESS(status)) {
287 : int32_t resultSize;
288 0 : const UChar* value = udatpg_getDateTimeFormat(pg, &resultSize);
289 0 : MOZ_ASSERT(resultSize >= 0);
290 :
291 0 : aRetVal.Assign((char16_t*)value, resultSize);
292 0 : result = true;
293 : }
294 0 : udatpg_close(pg);
295 : #endif
296 0 : return result;
297 : }
298 :
299 : /**
300 : * mozIOSPreferences methods
301 : */
302 : NS_IMETHODIMP
303 0 : OSPreferences::GetSystemLocales(uint32_t* aCount, char*** aOutArray)
304 : {
305 0 : AutoTArray<nsCString,10> tempLocales;
306 : nsTArray<nsCString>* systemLocalesPtr;
307 :
308 0 : if (!mSystemLocales.IsEmpty()) {
309 : // use cached value
310 0 : systemLocalesPtr = &mSystemLocales;
311 : } else {
312 : // get a (perhaps temporary/fallback/hack) value
313 0 : GetSystemLocales(tempLocales);
314 0 : systemLocalesPtr = &tempLocales;
315 : }
316 0 : *aCount = systemLocalesPtr->Length();
317 0 : *aOutArray = static_cast<char**>(moz_xmalloc(*aCount * sizeof(char*)));
318 :
319 0 : for (uint32_t i = 0; i < *aCount; i++) {
320 0 : (*aOutArray)[i] = moz_xstrdup((*systemLocalesPtr)[i].get());
321 : }
322 :
323 0 : return NS_OK;
324 : }
325 :
326 : NS_IMETHODIMP
327 3 : OSPreferences::GetSystemLocale(nsACString& aRetVal)
328 : {
329 3 : if (!mSystemLocales.IsEmpty()) {
330 1 : aRetVal = mSystemLocales[0];
331 : } else {
332 4 : AutoTArray<nsCString,10> locales;
333 2 : GetSystemLocales(locales);
334 2 : if (!locales.IsEmpty()) {
335 2 : aRetVal = locales[0];
336 : }
337 : }
338 3 : return NS_OK;
339 : }
340 :
341 : static OSPreferences::DateTimeFormatStyle
342 0 : ToDateTimeFormatStyle(int32_t aTimeFormat)
343 : {
344 0 : switch (aTimeFormat) {
345 : // See mozIOSPreferences.idl for the integer values here.
346 : case 0:
347 0 : return OSPreferences::DateTimeFormatStyle::None;
348 : case 1:
349 0 : return OSPreferences::DateTimeFormatStyle::Short;
350 : case 2:
351 0 : return OSPreferences::DateTimeFormatStyle::Medium;
352 : case 3:
353 0 : return OSPreferences::DateTimeFormatStyle::Long;
354 : case 4:
355 0 : return OSPreferences::DateTimeFormatStyle::Full;
356 : }
357 0 : return OSPreferences::DateTimeFormatStyle::Invalid;
358 : }
359 :
360 : NS_IMETHODIMP
361 0 : OSPreferences::GetDateTimePattern(int32_t aDateFormatStyle,
362 : int32_t aTimeFormatStyle,
363 : const nsACString& aLocale,
364 : nsAString& aRetVal)
365 : {
366 0 : DateTimeFormatStyle dateStyle = ToDateTimeFormatStyle(aDateFormatStyle);
367 0 : if (dateStyle == DateTimeFormatStyle::Invalid) {
368 0 : return NS_ERROR_INVALID_ARG;
369 : }
370 0 : DateTimeFormatStyle timeStyle = ToDateTimeFormatStyle(aTimeFormatStyle);
371 0 : if (timeStyle == DateTimeFormatStyle::Invalid) {
372 0 : return NS_ERROR_INVALID_ARG;
373 : }
374 :
375 : // If the user is asking for None on both, date and time style,
376 : // let's exit early.
377 0 : if (timeStyle == DateTimeFormatStyle::None &&
378 : dateStyle == DateTimeFormatStyle::None) {
379 0 : return NS_OK;
380 : }
381 :
382 0 : if (!ReadDateTimePattern(dateStyle, timeStyle, aLocale, aRetVal)) {
383 0 : if (!GetDateTimePatternForStyle(dateStyle, timeStyle, aLocale, aRetVal)) {
384 0 : return NS_ERROR_FAILURE;
385 : }
386 : }
387 :
388 0 : return NS_OK;
389 : }
|