1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /* Implementation of the Intl object and its non-constructor properties. */
8
9 #include "builtin/intl/IntlObject.h"
10
11 #include "mozilla/Assertions.h"
12 #include "mozilla/Likely.h"
13 #include "mozilla/Range.h"
14
15 #include <algorithm>
16 #include <iterator>
17
18 #include "jsapi.h"
19
20 #include "builtin/Array.h"
21 #include "builtin/intl/Collator.h"
22 #include "builtin/intl/CommonFunctions.h"
23 #include "builtin/intl/DateTimeFormat.h"
24 #include "builtin/intl/LanguageTag.h"
25 #include "builtin/intl/NumberFormat.h"
26 #include "builtin/intl/PluralRules.h"
27 #include "builtin/intl/RelativeTimeFormat.h"
28 #include "builtin/intl/ScopedICUObject.h"
29 #include "builtin/intl/SharedIntlData.h"
30 #include "js/CharacterEncoding.h"
31 #include "js/Class.h"
32 #include "js/PropertySpec.h"
33 #include "js/Result.h"
34 #include "js/StableStringChars.h"
35 #include "unicode/ucal.h"
36 #include "unicode/udat.h"
37 #include "unicode/udatpg.h"
38 #include "unicode/uloc.h"
39 #include "unicode/utypes.h"
40 #include "vm/GlobalObject.h"
41 #include "vm/JSAtom.h"
42 #include "vm/JSContext.h"
43 #include "vm/JSObject.h"
44 #include "vm/PlainObject.h" // js::PlainObject
45 #include "vm/StringType.h"
46
47 #include "vm/JSObject-inl.h"
48 #include "vm/NativeObject-inl.h"
49
50 using namespace js;
51
52 using mozilla::Range;
53 using mozilla::RangedPtr;
54
55 using JS::AutoStableStringChars;
56
57 using js::intl::CallICU;
58 using js::intl::DateTimeFormatOptions;
59 using js::intl::IcuLocale;
60
61 /******************** Intl ********************/
62
intl_GetCalendarInfo(JSContext * cx,unsigned argc,Value * vp)63 bool js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp) {
64 CallArgs args = CallArgsFromVp(argc, vp);
65 MOZ_ASSERT(args.length() == 1);
66
67 UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
68 if (!locale) {
69 return false;
70 }
71
72 UErrorCode status = U_ZERO_ERROR;
73 const UChar* uTimeZone = nullptr;
74 int32_t uTimeZoneLength = 0;
75 UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, locale.get(),
76 UCAL_DEFAULT, &status);
77 if (U_FAILURE(status)) {
78 intl::ReportInternalError(cx);
79 return false;
80 }
81 ScopedICUObject<UCalendar, ucal_close> toClose(cal);
82
83 RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
84 if (!info) {
85 return false;
86 }
87
88 RootedValue v(cx);
89 int32_t firstDayOfWeek = ucal_getAttribute(cal, UCAL_FIRST_DAY_OF_WEEK);
90 v.setInt32(firstDayOfWeek);
91
92 if (!DefineDataProperty(cx, info, cx->names().firstDayOfWeek, v)) {
93 return false;
94 }
95
96 int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
97 v.setInt32(minDays);
98 if (!DefineDataProperty(cx, info, cx->names().minDays, v)) {
99 return false;
100 }
101
102 UCalendarWeekdayType prevDayType =
103 ucal_getDayOfWeekType(cal, UCAL_SATURDAY, &status);
104 if (U_FAILURE(status)) {
105 intl::ReportInternalError(cx);
106 return false;
107 }
108
109 RootedValue weekendStart(cx), weekendEnd(cx);
110
111 for (int i = UCAL_SUNDAY; i <= UCAL_SATURDAY; i++) {
112 UCalendarDaysOfWeek dayOfWeek = static_cast<UCalendarDaysOfWeek>(i);
113 UCalendarWeekdayType type = ucal_getDayOfWeekType(cal, dayOfWeek, &status);
114 if (U_FAILURE(status)) {
115 intl::ReportInternalError(cx);
116 return false;
117 }
118
119 if (prevDayType != type) {
120 switch (type) {
121 case UCAL_WEEKDAY:
122 // If the first Weekday after Weekend is Sunday (1),
123 // then the last Weekend day is Saturday (7).
124 // Otherwise we'll just take the previous days number.
125 weekendEnd.setInt32(i == 1 ? 7 : i - 1);
126 break;
127 case UCAL_WEEKEND:
128 weekendStart.setInt32(i);
129 break;
130 case UCAL_WEEKEND_ONSET:
131 case UCAL_WEEKEND_CEASE:
132 // At the time this code was added, ICU apparently never behaves this
133 // way, so just throw, so that users will report a bug and we can
134 // decide what to do.
135 intl::ReportInternalError(cx);
136 return false;
137 default:
138 break;
139 }
140 }
141
142 prevDayType = type;
143 }
144
145 MOZ_ASSERT(weekendStart.isInt32());
146 MOZ_ASSERT(weekendEnd.isInt32());
147
148 if (!DefineDataProperty(cx, info, cx->names().weekendStart, weekendStart)) {
149 return false;
150 }
151
152 if (!DefineDataProperty(cx, info, cx->names().weekendEnd, weekendEnd)) {
153 return false;
154 }
155
156 args.rval().setObject(*info);
157 return true;
158 }
159
ReportBadKey(JSContext * cx,HandleString key)160 static void ReportBadKey(JSContext* cx, HandleString key) {
161 if (UniqueChars chars = QuoteString(cx, key, '"')) {
162 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY,
163 chars.get());
164 }
165 }
166
167 template <typename ConstChar>
MatchPart(RangedPtr<ConstChar> iter,const RangedPtr<ConstChar> end,const char * part,size_t partlen)168 static bool MatchPart(RangedPtr<ConstChar> iter, const RangedPtr<ConstChar> end,
169 const char* part, size_t partlen) {
170 for (size_t i = 0; i < partlen; iter++, i++) {
171 if (iter == end || *iter != part[i]) {
172 return false;
173 }
174 }
175
176 return true;
177 }
178
179 template <typename ConstChar, size_t N>
MatchPart(RangedPtr<ConstChar> * iter,const RangedPtr<ConstChar> end,const char (& part)[N])180 inline bool MatchPart(RangedPtr<ConstChar>* iter,
181 const RangedPtr<ConstChar> end, const char (&part)[N]) {
182 if (!MatchPart(*iter, end, part, N - 1)) {
183 return false;
184 }
185
186 *iter += N - 1;
187 return true;
188 }
189
190 enum class DisplayNameStyle {
191 Narrow,
192 Short,
193 Long,
194 };
195
196 template <typename ConstChar>
ComputeSingleDisplayName(JSContext * cx,UDateFormat * fmt,UDateTimePatternGenerator * dtpg,DisplayNameStyle style,const Range<ConstChar> & pattern,HandleString patternString)197 static JSString* ComputeSingleDisplayName(JSContext* cx, UDateFormat* fmt,
198 UDateTimePatternGenerator* dtpg,
199 DisplayNameStyle style,
200 const Range<ConstChar>& pattern,
201 HandleString patternString) {
202 RangedPtr<ConstChar> iter = pattern.begin();
203 const RangedPtr<ConstChar> end = pattern.end();
204
205 auto MatchSlash = [cx, patternString, &iter, end]() {
206 if (MOZ_LIKELY(iter != end && *iter == '/')) {
207 iter++;
208 return true;
209 }
210
211 ReportBadKey(cx, patternString);
212 return false;
213 };
214
215 if (!MatchPart(&iter, end, "dates")) {
216 ReportBadKey(cx, patternString);
217 return nullptr;
218 }
219
220 if (!MatchSlash()) {
221 return nullptr;
222 }
223
224 if (MatchPart(&iter, end, "fields")) {
225 if (!MatchSlash()) {
226 return nullptr;
227 }
228
229 UDateTimePatternField fieldType;
230
231 if (MatchPart(&iter, end, "year")) {
232 fieldType = UDATPG_YEAR_FIELD;
233 } else if (MatchPart(&iter, end, "month")) {
234 fieldType = UDATPG_MONTH_FIELD;
235 } else if (MatchPart(&iter, end, "week")) {
236 fieldType = UDATPG_WEEK_OF_YEAR_FIELD;
237 } else if (MatchPart(&iter, end, "day")) {
238 fieldType = UDATPG_DAY_FIELD;
239 } else {
240 ReportBadKey(cx, patternString);
241 return nullptr;
242 }
243
244 // This part must be the final part with no trailing data.
245 if (iter != end) {
246 ReportBadKey(cx, patternString);
247 return nullptr;
248 }
249
250 int32_t resultSize;
251 const UChar* value = udatpg_getAppendItemName(dtpg, fieldType, &resultSize);
252 MOZ_ASSERT(resultSize >= 0);
253
254 return NewStringCopyN<CanGC>(cx, value, size_t(resultSize));
255 }
256
257 if (MatchPart(&iter, end, "gregorian")) {
258 if (!MatchSlash()) {
259 return nullptr;
260 }
261
262 UDateFormatSymbolType symbolType;
263 int32_t index;
264
265 if (MatchPart(&iter, end, "months")) {
266 if (!MatchSlash()) {
267 return nullptr;
268 }
269
270 switch (style) {
271 case DisplayNameStyle::Narrow:
272 symbolType = UDAT_STANDALONE_NARROW_MONTHS;
273 break;
274
275 case DisplayNameStyle::Short:
276 symbolType = UDAT_STANDALONE_SHORT_MONTHS;
277 break;
278
279 case DisplayNameStyle::Long:
280 symbolType = UDAT_STANDALONE_MONTHS;
281 break;
282 }
283
284 if (MatchPart(&iter, end, "january")) {
285 index = UCAL_JANUARY;
286 } else if (MatchPart(&iter, end, "february")) {
287 index = UCAL_FEBRUARY;
288 } else if (MatchPart(&iter, end, "march")) {
289 index = UCAL_MARCH;
290 } else if (MatchPart(&iter, end, "april")) {
291 index = UCAL_APRIL;
292 } else if (MatchPart(&iter, end, "may")) {
293 index = UCAL_MAY;
294 } else if (MatchPart(&iter, end, "june")) {
295 index = UCAL_JUNE;
296 } else if (MatchPart(&iter, end, "july")) {
297 index = UCAL_JULY;
298 } else if (MatchPart(&iter, end, "august")) {
299 index = UCAL_AUGUST;
300 } else if (MatchPart(&iter, end, "september")) {
301 index = UCAL_SEPTEMBER;
302 } else if (MatchPart(&iter, end, "october")) {
303 index = UCAL_OCTOBER;
304 } else if (MatchPart(&iter, end, "november")) {
305 index = UCAL_NOVEMBER;
306 } else if (MatchPart(&iter, end, "december")) {
307 index = UCAL_DECEMBER;
308 } else {
309 ReportBadKey(cx, patternString);
310 return nullptr;
311 }
312 } else if (MatchPart(&iter, end, "weekdays")) {
313 if (!MatchSlash()) {
314 return nullptr;
315 }
316
317 switch (style) {
318 case DisplayNameStyle::Narrow:
319 symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
320 break;
321
322 case DisplayNameStyle::Short:
323 symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
324 break;
325
326 case DisplayNameStyle::Long:
327 symbolType = UDAT_STANDALONE_WEEKDAYS;
328 break;
329 }
330
331 if (MatchPart(&iter, end, "monday")) {
332 index = UCAL_MONDAY;
333 } else if (MatchPart(&iter, end, "tuesday")) {
334 index = UCAL_TUESDAY;
335 } else if (MatchPart(&iter, end, "wednesday")) {
336 index = UCAL_WEDNESDAY;
337 } else if (MatchPart(&iter, end, "thursday")) {
338 index = UCAL_THURSDAY;
339 } else if (MatchPart(&iter, end, "friday")) {
340 index = UCAL_FRIDAY;
341 } else if (MatchPart(&iter, end, "saturday")) {
342 index = UCAL_SATURDAY;
343 } else if (MatchPart(&iter, end, "sunday")) {
344 index = UCAL_SUNDAY;
345 } else {
346 ReportBadKey(cx, patternString);
347 return nullptr;
348 }
349 } else if (MatchPart(&iter, end, "dayperiods")) {
350 if (!MatchSlash()) {
351 return nullptr;
352 }
353
354 symbolType = UDAT_AM_PMS;
355
356 if (MatchPart(&iter, end, "am")) {
357 index = UCAL_AM;
358 } else if (MatchPart(&iter, end, "pm")) {
359 index = UCAL_PM;
360 } else {
361 ReportBadKey(cx, patternString);
362 return nullptr;
363 }
364 } else {
365 ReportBadKey(cx, patternString);
366 return nullptr;
367 }
368
369 // This part must be the final part with no trailing data.
370 if (iter != end) {
371 ReportBadKey(cx, patternString);
372 return nullptr;
373 }
374
375 return CallICU(cx, [fmt, symbolType, index](UChar* chars, int32_t size,
376 UErrorCode* status) {
377 return udat_getSymbols(fmt, symbolType, index, chars, size, status);
378 });
379 }
380
381 ReportBadKey(cx, patternString);
382 return nullptr;
383 }
384
intl_ComputeDisplayNames(JSContext * cx,unsigned argc,Value * vp)385 bool js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp) {
386 CallArgs args = CallArgsFromVp(argc, vp);
387 MOZ_ASSERT(args.length() == 3);
388
389 // 1. Assert: locale is a string.
390 UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
391 if (!locale) {
392 return false;
393 }
394
395 // 2. Assert: style is a string.
396 DisplayNameStyle dnStyle;
397 {
398 JSLinearString* style = args[1].toString()->ensureLinear(cx);
399 if (!style) {
400 return false;
401 }
402
403 if (StringEqualsLiteral(style, "narrow")) {
404 dnStyle = DisplayNameStyle::Narrow;
405 } else if (StringEqualsLiteral(style, "short")) {
406 dnStyle = DisplayNameStyle::Short;
407 } else {
408 MOZ_ASSERT(StringEqualsLiteral(style, "long"));
409 dnStyle = DisplayNameStyle::Long;
410 }
411 }
412
413 // 3. Assert: keys is an Array.
414 RootedArrayObject keys(cx, &args[2].toObject().as<ArrayObject>());
415 if (!keys) {
416 return false;
417 }
418
419 // 4. Let result be ArrayCreate(0).
420 RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, keys->length()));
421 if (!result) {
422 return false;
423 }
424 result->ensureDenseInitializedLength(cx, 0, keys->length());
425
426 UErrorCode status = U_ZERO_ERROR;
427
428 UDateFormat* fmt =
429 udat_open(UDAT_DEFAULT, UDAT_DEFAULT, IcuLocale(locale.get()), nullptr, 0,
430 nullptr, 0, &status);
431 if (U_FAILURE(status)) {
432 intl::ReportInternalError(cx);
433 return false;
434 }
435 ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);
436
437 // UDateTimePatternGenerator will be needed for translations of date and
438 // time fields like "month", "week", "day" etc.
439 UDateTimePatternGenerator* dtpg =
440 udatpg_open(IcuLocale(locale.get()), &status);
441 if (U_FAILURE(status)) {
442 intl::ReportInternalError(cx);
443 return false;
444 }
445 ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
446
447 // 5. For each element of keys,
448 RootedString keyValStr(cx);
449 RootedValue v(cx);
450 for (uint32_t i = 0; i < keys->length(); i++) {
451 if (!GetElement(cx, keys, keys, i, &v)) {
452 return false;
453 }
454
455 keyValStr = v.toString();
456
457 AutoStableStringChars stablePatternChars(cx);
458 if (!stablePatternChars.init(cx, keyValStr)) {
459 return false;
460 }
461
462 // 5.a. Perform an implementation dependent algorithm to map a key to a
463 // corresponding display name.
464 JSString* displayName =
465 stablePatternChars.isLatin1()
466 ? ComputeSingleDisplayName(cx, fmt, dtpg, dnStyle,
467 stablePatternChars.latin1Range(),
468 keyValStr)
469 : ComputeSingleDisplayName(cx, fmt, dtpg, dnStyle,
470 stablePatternChars.twoByteRange(),
471 keyValStr);
472 if (!displayName) {
473 return false;
474 }
475
476 // 5.b. Append the result string to result.
477 result->setDenseElement(i, StringValue(displayName));
478 }
479
480 // 6. Return result.
481 args.rval().setObject(*result);
482 return true;
483 }
484
intl_GetLocaleInfo(JSContext * cx,unsigned argc,Value * vp)485 bool js::intl_GetLocaleInfo(JSContext* cx, unsigned argc, Value* vp) {
486 CallArgs args = CallArgsFromVp(argc, vp);
487 MOZ_ASSERT(args.length() == 1);
488
489 UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
490 if (!locale) {
491 return false;
492 }
493
494 RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
495 if (!info) {
496 return false;
497 }
498
499 if (!DefineDataProperty(cx, info, cx->names().locale, args[0])) {
500 return false;
501 }
502
503 bool rtl = uloc_isRightToLeft(IcuLocale(locale.get()));
504
505 RootedValue dir(cx, StringValue(rtl ? cx->names().rtl : cx->names().ltr));
506
507 if (!DefineDataProperty(cx, info, cx->names().direction, dir)) {
508 return false;
509 }
510
511 args.rval().setObject(*info);
512 return true;
513 }
514
515 using SupportedLocaleKind = js::intl::SharedIntlData::SupportedLocaleKind;
516
517 // 9.2.2 BestAvailableLocale ( availableLocales, locale )
BestAvailableLocale(JSContext * cx,SupportedLocaleKind kind,HandleLinearString locale,HandleLinearString defaultLocale)518 static JS::Result<JSString*> BestAvailableLocale(
519 JSContext* cx, SupportedLocaleKind kind, HandleLinearString locale,
520 HandleLinearString defaultLocale) {
521 // In the spec, [[availableLocales]] is formally a list of all available
522 // locales. But in our implementation, it's an *incomplete* list, not
523 // necessarily including the default locale (and all locales implied by it,
524 // e.g. "de" implied by "de-CH"), if that locale isn't in every
525 // [[availableLocales]] list (because that locale is supported through
526 // fallback, e.g. "de-CH" supported through "de").
527 //
528 // If we're considering the default locale, augment the spec loop with
529 // additional checks to also test whether the current prefix is a prefix of
530 // the default locale.
531
532 intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
533
534 auto findLast = [](const auto* chars, size_t length) {
535 auto rbegin = std::make_reverse_iterator(chars + length);
536 auto rend = std::make_reverse_iterator(chars);
537 auto p = std::find(rbegin, rend, '-');
538
539 // |dist(chars, p.base())| is equal to |dist(p, rend)|, pick whichever you
540 // find easier to reason about when using reserve iterators.
541 ptrdiff_t r = std::distance(chars, p.base());
542 MOZ_ASSERT(r == std::distance(p, rend));
543
544 // But always subtract one to convert from the reverse iterator result to
545 // the correspoding forward iterator value, because reserve iterators point
546 // to one element past the forward iterator value.
547 return r - 1;
548 };
549
550 // Step 1.
551 RootedLinearString candidate(cx, locale);
552
553 // Step 2.
554 while (true) {
555 // Step 2.a.
556 bool supported = false;
557 if (!sharedIntlData.isSupportedLocale(cx, kind, candidate, &supported)) {
558 return cx->alreadyReportedError();
559 }
560 if (supported) {
561 return candidate.get();
562 }
563
564 if (defaultLocale && candidate->length() <= defaultLocale->length()) {
565 if (EqualStrings(candidate, defaultLocale)) {
566 return candidate.get();
567 }
568
569 if (candidate->length() < defaultLocale->length() &&
570 HasSubstringAt(defaultLocale, candidate, 0) &&
571 defaultLocale->latin1OrTwoByteChar(candidate->length()) == '-') {
572 return candidate.get();
573 }
574 }
575
576 // Step 2.b.
577 ptrdiff_t pos;
578 if (candidate->hasLatin1Chars()) {
579 JS::AutoCheckCannotGC nogc;
580 pos = findLast(candidate->latin1Chars(nogc), candidate->length());
581 } else {
582 JS::AutoCheckCannotGC nogc;
583 pos = findLast(candidate->twoByteChars(nogc), candidate->length());
584 }
585
586 if (pos < 0) {
587 return nullptr;
588 }
589
590 // Step 2.c.
591 size_t length = size_t(pos);
592 if (length >= 2 && candidate->latin1OrTwoByteChar(length - 2) == '-') {
593 length -= 2;
594 }
595
596 // Step 2.d.
597 candidate = NewDependentString(cx, candidate, 0, length);
598 if (!candidate) {
599 return cx->alreadyReportedError();
600 }
601 }
602 }
603
604 // 9.2.2 BestAvailableLocale ( availableLocales, locale )
605 //
606 // Carries an additional third argument in our implementation to provide the
607 // default locale. See the doc-comment in the header file.
intl_BestAvailableLocale(JSContext * cx,unsigned argc,Value * vp)608 bool js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp) {
609 CallArgs args = CallArgsFromVp(argc, vp);
610 MOZ_ASSERT(args.length() == 3);
611
612 SupportedLocaleKind kind;
613 {
614 JSLinearString* typeStr = args[0].toString()->ensureLinear(cx);
615 if (!typeStr) {
616 return false;
617 }
618
619 if (StringEqualsLiteral(typeStr, "Collator")) {
620 kind = SupportedLocaleKind::Collator;
621 } else if (StringEqualsLiteral(typeStr, "DateTimeFormat")) {
622 kind = SupportedLocaleKind::DateTimeFormat;
623 } else if (StringEqualsLiteral(typeStr, "DisplayNames")) {
624 kind = SupportedLocaleKind::DisplayNames;
625 } else if (StringEqualsLiteral(typeStr, "ListFormat")) {
626 kind = SupportedLocaleKind::ListFormat;
627 } else if (StringEqualsLiteral(typeStr, "NumberFormat")) {
628 kind = SupportedLocaleKind::NumberFormat;
629 } else if (StringEqualsLiteral(typeStr, "PluralRules")) {
630 kind = SupportedLocaleKind::PluralRules;
631 } else {
632 MOZ_ASSERT(StringEqualsLiteral(typeStr, "RelativeTimeFormat"));
633 kind = SupportedLocaleKind::RelativeTimeFormat;
634 }
635 }
636
637 RootedLinearString locale(cx, args[1].toString()->ensureLinear(cx));
638 if (!locale) {
639 return false;
640 }
641
642 #ifdef DEBUG
643 {
644 intl::LanguageTag tag(cx);
645 bool ok;
646 JS_TRY_VAR_OR_RETURN_FALSE(
647 cx, ok, intl::LanguageTagParser::tryParse(cx, locale, tag));
648 MOZ_ASSERT(ok, "locale is a structurally valid language tag");
649
650 MOZ_ASSERT(!tag.unicodeExtension(),
651 "locale must contain no Unicode extensions");
652
653 if (!tag.canonicalize(cx)) {
654 return false;
655 }
656
657 JSString* tagStr = tag.toString(cx);
658 if (!tagStr) {
659 return false;
660 }
661
662 bool canonical;
663 if (!EqualStrings(cx, locale, tagStr, &canonical)) {
664 return false;
665 }
666 MOZ_ASSERT(canonical, "locale is a canonicalized language tag");
667 }
668 #endif
669
670 MOZ_ASSERT(args[2].isNull() || args[2].isString());
671
672 RootedLinearString defaultLocale(cx);
673 if (args[2].isString()) {
674 defaultLocale = args[2].toString()->ensureLinear(cx);
675 if (!defaultLocale) {
676 return false;
677 }
678 }
679
680 JSString* result;
681 JS_TRY_VAR_OR_RETURN_FALSE(
682 cx, result, BestAvailableLocale(cx, kind, locale, defaultLocale));
683
684 if (result) {
685 args.rval().setString(result);
686 } else {
687 args.rval().setUndefined();
688 }
689 return true;
690 }
691
intl_supportedLocaleOrFallback(JSContext * cx,unsigned argc,Value * vp)692 bool js::intl_supportedLocaleOrFallback(JSContext* cx, unsigned argc,
693 Value* vp) {
694 CallArgs args = CallArgsFromVp(argc, vp);
695 MOZ_ASSERT(args.length() == 1);
696
697 RootedLinearString locale(cx, args[0].toString()->ensureLinear(cx));
698 if (!locale) {
699 return false;
700 }
701
702 intl::LanguageTag tag(cx);
703 bool ok;
704 JS_TRY_VAR_OR_RETURN_FALSE(
705 cx, ok, intl::LanguageTagParser::tryParse(cx, locale, tag));
706
707 RootedLinearString candidate(cx);
708 if (!ok) {
709 candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
710 if (!candidate) {
711 return false;
712 }
713 } else {
714 if (!tag.canonicalize(cx)) {
715 return false;
716 }
717
718 // The default locale must be in [[AvailableLocales]], and that list must
719 // not contain any locales with Unicode extension sequences, so remove any
720 // present in the candidate.
721 tag.clearUnicodeExtension();
722
723 JSString* canonical = tag.toString(cx);
724 if (!canonical) {
725 return false;
726 }
727
728 candidate = canonical->ensureLinear(cx);
729 if (!candidate) {
730 return false;
731 }
732
733 for (const auto& mapping : js::intl::oldStyleLanguageTagMappings) {
734 const char* oldStyle = mapping.oldStyle;
735 const char* modernStyle = mapping.modernStyle;
736
737 if (StringEqualsAscii(candidate, oldStyle)) {
738 candidate = NewStringCopyZ<CanGC>(cx, modernStyle);
739 if (!candidate) {
740 return false;
741 }
742 break;
743 }
744 }
745 }
746
747 // 9.1 Internal slots of Service Constructors
748 //
749 // - [[AvailableLocales]] is a List [...]. The list must include the value
750 // returned by the DefaultLocale abstract operation (6.2.4), [...].
751 //
752 // That implies we must ignore any candidate which isn't supported by all Intl
753 // service constructors.
754 //
755 // Note: We don't test the supported locales of either Intl.ListFormat,
756 // Intl.PluralRules, Intl.RelativeTimeFormat, because ICU doesn't provide the
757 // necessary API to return actual set of supported locales for these
758 // constructors. Instead it returns the complete set of available locales for
759 // ULocale, which is a superset of the locales supported by Collator,
760 // NumberFormat, and DateTimeFormat.
761 bool isSupported = true;
762 for (auto kind :
763 {SupportedLocaleKind::Collator, SupportedLocaleKind::DateTimeFormat,
764 SupportedLocaleKind::NumberFormat}) {
765 JSString* supported;
766 JS_TRY_VAR_OR_RETURN_FALSE(
767 cx, supported, BestAvailableLocale(cx, kind, candidate, nullptr));
768
769 if (!supported) {
770 isSupported = false;
771 break;
772 }
773 }
774
775 if (!isSupported) {
776 candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
777 if (!candidate) {
778 return false;
779 }
780 }
781
782 args.rval().setString(candidate);
783 return true;
784 }
785
intl_toSource(JSContext * cx,unsigned argc,Value * vp)786 static bool intl_toSource(JSContext* cx, unsigned argc, Value* vp) {
787 CallArgs args = CallArgsFromVp(argc, vp);
788 args.rval().setString(cx->names().Intl);
789 return true;
790 }
791
792 static const JSFunctionSpec intl_static_methods[] = {
793 JS_FN(js_toSource_str, intl_toSource, 0, 0),
794 JS_SELF_HOSTED_FN("getCanonicalLocales", "Intl_getCanonicalLocales", 1, 0),
795 JS_FS_END};
796
CreateIntlObject(JSContext * cx,JSProtoKey key)797 static JSObject* CreateIntlObject(JSContext* cx, JSProtoKey key) {
798 Handle<GlobalObject*> global = cx->global();
799 RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
800 if (!proto) {
801 return nullptr;
802 }
803
804 // The |Intl| object is just a plain object with some "static" function
805 // properties and some constructor properties.
806 return NewSingletonObjectWithGivenProto(cx, &IntlClass, proto);
807 }
808
809 /**
810 * Initializes the Intl Object and its standard built-in properties.
811 * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
812 */
IntlClassFinish(JSContext * cx,HandleObject intl,HandleObject proto)813 static bool IntlClassFinish(JSContext* cx, HandleObject intl,
814 HandleObject proto) {
815 // Add the constructor properties.
816 RootedId ctorId(cx);
817 RootedValue ctorValue(cx);
818 for (const auto& protoKey :
819 {JSProto_Collator, JSProto_DateTimeFormat, JSProto_ListFormat,
820 JSProto_Locale, JSProto_NumberFormat, JSProto_PluralRules,
821 JSProto_RelativeTimeFormat}) {
822 JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, protoKey);
823 if (!ctor) {
824 return false;
825 }
826
827 ctorId = NameToId(ClassName(protoKey, cx));
828 ctorValue.setObject(*ctor);
829 if (!DefineDataProperty(cx, intl, ctorId, ctorValue, 0)) {
830 return false;
831 }
832 }
833
834 return true;
835 }
836
837 static const ClassSpec IntlClassSpec = {
838 CreateIntlObject, nullptr, intl_static_methods, nullptr,
839 nullptr, nullptr, IntlClassFinish};
840
841 const JSClass js::IntlClass = {js_Object_str,
842 JSCLASS_HAS_CACHED_PROTO(JSProto_Intl),
843 JS_NULL_CLASS_OPS, &IntlClassSpec};
844