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