1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
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 /*
8  * The Intl module specified by standard ECMA-402,
9  * ECMAScript Internationalization API Specification.
10  */
11 
12 #include "builtin/Intl.h"
13 
14 #include "mozilla/PodOperations.h"
15 #include "mozilla/Range.h"
16 #include "mozilla/ScopeExit.h"
17 
18 #include <string.h>
19 
20 #include "jsapi.h"
21 #include "jsatom.h"
22 #include "jscntxt.h"
23 #include "jsobj.h"
24 
25 #include "builtin/IntlTimeZoneData.h"
26 #if ENABLE_INTL_API
27 #include "unicode/ucal.h"
28 #include "unicode/ucol.h"
29 #include "unicode/udat.h"
30 #include "unicode/udatpg.h"
31 #include "unicode/uenum.h"
32 #include "unicode/unum.h"
33 #include "unicode/unumsys.h"
34 #include "unicode/ustring.h"
35 #endif
36 #include "vm/DateTime.h"
37 #include "vm/GlobalObject.h"
38 #include "vm/Interpreter.h"
39 #include "vm/Stack.h"
40 #include "vm/StringBuffer.h"
41 #include "vm/Unicode.h"
42 
43 #include "jsobjinlines.h"
44 
45 #include "vm/NativeObject-inl.h"
46 
47 using namespace js;
48 
49 using mozilla::IsFinite;
50 using mozilla::IsNegativeZero;
51 using mozilla::MakeScopeExit;
52 using mozilla::PodCopy;
53 
54 
55 /*
56  * Pervasive note: ICU functions taking a UErrorCode in/out parameter always
57  * test that parameter before doing anything, and will return immediately if
58  * the value indicates that a failure occurred in a prior ICU call,
59  * without doing anything else. See
60  * http://userguide.icu-project.org/design#TOC-Error-Handling
61  */
62 
63 
64 /******************** ICU stubs ********************/
65 
66 #if !ENABLE_INTL_API
67 
68 /*
69  * When the Internationalization API isn't enabled, we also shouldn't link
70  * against ICU. However, we still want to compile this code in order to prevent
71  * bit rot. The following stub implementations for ICU functions make this
72  * possible. The functions using them should never be called, so they assert
73  * and return error codes. Signatures adapted from ICU header files locid.h,
74  * numsys.h, ucal.h, ucol.h, udat.h, udatpg.h, uenum.h, unum.h; see the ICU
75  * directory for license.
76  */
77 
78 typedef bool UBool;
79 typedef char16_t UChar;
80 typedef double UDate;
81 
82 enum UErrorCode {
83     U_ZERO_ERROR,
84     U_BUFFER_OVERFLOW_ERROR,
85 };
86 
87 static inline UBool
U_FAILURE(UErrorCode code)88 U_FAILURE(UErrorCode code)
89 {
90     MOZ_CRASH("U_FAILURE: Intl API disabled");
91 }
92 
93 inline const UChar*
Char16ToUChar(const char16_t * chars)94 Char16ToUChar(const char16_t* chars)
95 {
96     MOZ_CRASH("Char16ToUChar: Intl API disabled");
97 }
98 
99 inline UChar*
Char16ToUChar(char16_t * chars)100 Char16ToUChar(char16_t* chars)
101 {
102     MOZ_CRASH("Char16ToUChar: Intl API disabled");
103 }
104 
105 static int32_t
u_strlen(const UChar * s)106 u_strlen(const UChar* s)
107 {
108     MOZ_CRASH("u_strlen: Intl API disabled");
109 }
110 
111 struct UEnumeration;
112 
113 static int32_t
uenum_count(UEnumeration * en,UErrorCode * status)114 uenum_count(UEnumeration* en, UErrorCode* status)
115 {
116     MOZ_CRASH("uenum_count: Intl API disabled");
117 }
118 
119 static const char*
uenum_next(UEnumeration * en,int32_t * resultLength,UErrorCode * status)120 uenum_next(UEnumeration* en, int32_t* resultLength, UErrorCode* status)
121 {
122     MOZ_CRASH("uenum_next: Intl API disabled");
123 }
124 
125 static void
uenum_close(UEnumeration * en)126 uenum_close(UEnumeration* en)
127 {
128     MOZ_CRASH("uenum_close: Intl API disabled");
129 }
130 
131 struct UCollator;
132 
133 enum UColAttribute {
134     UCOL_ALTERNATE_HANDLING,
135     UCOL_CASE_FIRST,
136     UCOL_CASE_LEVEL,
137     UCOL_NORMALIZATION_MODE,
138     UCOL_STRENGTH,
139     UCOL_NUMERIC_COLLATION,
140 };
141 
142 enum UColAttributeValue {
143     UCOL_DEFAULT = -1,
144     UCOL_PRIMARY = 0,
145     UCOL_SECONDARY = 1,
146     UCOL_TERTIARY = 2,
147     UCOL_OFF = 16,
148     UCOL_ON = 17,
149     UCOL_SHIFTED = 20,
150     UCOL_LOWER_FIRST = 24,
151     UCOL_UPPER_FIRST = 25,
152 };
153 
154 enum UCollationResult {
155     UCOL_EQUAL = 0,
156     UCOL_GREATER = 1,
157     UCOL_LESS = -1
158 };
159 
160 static int32_t
ucol_countAvailable()161 ucol_countAvailable()
162 {
163     MOZ_CRASH("ucol_countAvailable: Intl API disabled");
164 }
165 
166 static const char*
ucol_getAvailable(int32_t localeIndex)167 ucol_getAvailable(int32_t localeIndex)
168 {
169     MOZ_CRASH("ucol_getAvailable: Intl API disabled");
170 }
171 
172 static UCollator*
ucol_open(const char * loc,UErrorCode * status)173 ucol_open(const char* loc, UErrorCode* status)
174 {
175     MOZ_CRASH("ucol_open: Intl API disabled");
176 }
177 
178 static void
ucol_setAttribute(UCollator * coll,UColAttribute attr,UColAttributeValue value,UErrorCode * status)179 ucol_setAttribute(UCollator* coll, UColAttribute attr, UColAttributeValue value, UErrorCode* status)
180 {
181     MOZ_CRASH("ucol_setAttribute: Intl API disabled");
182 }
183 
184 static UCollationResult
ucol_strcoll(const UCollator * coll,const UChar * source,int32_t sourceLength,const UChar * target,int32_t targetLength)185 ucol_strcoll(const UCollator* coll, const UChar* source, int32_t sourceLength,
186              const UChar* target, int32_t targetLength)
187 {
188     MOZ_CRASH("ucol_strcoll: Intl API disabled");
189 }
190 
191 static void
ucol_close(UCollator * coll)192 ucol_close(UCollator* coll)
193 {
194     MOZ_CRASH("ucol_close: Intl API disabled");
195 }
196 
197 static UEnumeration*
ucol_getKeywordValuesForLocale(const char * key,const char * locale,UBool commonlyUsed,UErrorCode * status)198 ucol_getKeywordValuesForLocale(const char* key, const char* locale, UBool commonlyUsed,
199                                UErrorCode* status)
200 {
201     MOZ_CRASH("ucol_getKeywordValuesForLocale: Intl API disabled");
202 }
203 
204 struct UParseError;
205 struct UFieldPosition;
206 struct UFieldPositionIterator;
207 typedef void* UNumberFormat;
208 
209 enum UNumberFormatStyle {
210     UNUM_DECIMAL = 1,
211     UNUM_CURRENCY,
212     UNUM_PERCENT,
213     UNUM_CURRENCY_ISO,
214     UNUM_CURRENCY_PLURAL,
215 };
216 
217 enum UNumberFormatRoundingMode {
218     UNUM_ROUND_HALFUP,
219 };
220 
221 enum UNumberFormatAttribute {
222     UNUM_GROUPING_USED,
223     UNUM_MIN_INTEGER_DIGITS,
224     UNUM_MAX_FRACTION_DIGITS,
225     UNUM_MIN_FRACTION_DIGITS,
226     UNUM_ROUNDING_MODE,
227     UNUM_SIGNIFICANT_DIGITS_USED,
228     UNUM_MIN_SIGNIFICANT_DIGITS,
229     UNUM_MAX_SIGNIFICANT_DIGITS,
230 };
231 
232 enum UNumberFormatTextAttribute {
233     UNUM_CURRENCY_CODE,
234 };
235 
236 static int32_t
unum_countAvailable()237 unum_countAvailable()
238 {
239     MOZ_CRASH("unum_countAvailable: Intl API disabled");
240 }
241 
242 static const char*
unum_getAvailable(int32_t localeIndex)243 unum_getAvailable(int32_t localeIndex)
244 {
245     MOZ_CRASH("unum_getAvailable: Intl API disabled");
246 }
247 
248 static UNumberFormat*
unum_open(UNumberFormatStyle style,const UChar * pattern,int32_t patternLength,const char * locale,UParseError * parseErr,UErrorCode * status)249 unum_open(UNumberFormatStyle style, const UChar* pattern, int32_t patternLength,
250           const char* locale, UParseError* parseErr, UErrorCode* status)
251 {
252     MOZ_CRASH("unum_open: Intl API disabled");
253 }
254 
255 static void
unum_setAttribute(UNumberFormat * fmt,UNumberFormatAttribute attr,int32_t newValue)256 unum_setAttribute(UNumberFormat* fmt, UNumberFormatAttribute  attr, int32_t newValue)
257 {
258     MOZ_CRASH("unum_setAttribute: Intl API disabled");
259 }
260 
261 static int32_t
unum_formatDouble(const UNumberFormat * fmt,double number,UChar * result,int32_t resultLength,UFieldPosition * pos,UErrorCode * status)262 unum_formatDouble(const UNumberFormat* fmt, double number, UChar* result,
263                   int32_t resultLength, UFieldPosition* pos, UErrorCode* status)
264 {
265     MOZ_CRASH("unum_formatDouble: Intl API disabled");
266 }
267 
268 static void
unum_close(UNumberFormat * fmt)269 unum_close(UNumberFormat* fmt)
270 {
271     MOZ_CRASH("unum_close: Intl API disabled");
272 }
273 
274 static void
unum_setTextAttribute(UNumberFormat * fmt,UNumberFormatTextAttribute tag,const UChar * newValue,int32_t newValueLength,UErrorCode * status)275 unum_setTextAttribute(UNumberFormat* fmt, UNumberFormatTextAttribute tag, const UChar* newValue,
276                       int32_t newValueLength, UErrorCode* status)
277 {
278     MOZ_CRASH("unum_setTextAttribute: Intl API disabled");
279 }
280 
281 typedef void* UNumberingSystem;
282 
283 static UNumberingSystem*
unumsys_open(const char * locale,UErrorCode * status)284 unumsys_open(const char* locale, UErrorCode* status)
285 {
286     MOZ_CRASH("unumsys_open: Intl API disabled");
287 }
288 
289 static const char*
unumsys_getName(const UNumberingSystem * unumsys)290 unumsys_getName(const UNumberingSystem* unumsys)
291 {
292     MOZ_CRASH("unumsys_getName: Intl API disabled");
293 }
294 
295 static void
unumsys_close(UNumberingSystem * unumsys)296 unumsys_close(UNumberingSystem* unumsys)
297 {
298     MOZ_CRASH("unumsys_close: Intl API disabled");
299 }
300 
301 typedef void* UCalendar;
302 
303 enum UCalendarType {
304     UCAL_TRADITIONAL,
305     UCAL_DEFAULT = UCAL_TRADITIONAL,
306     UCAL_GREGORIAN
307 };
308 
309 enum UCalendarAttribute {
310     UCAL_FIRST_DAY_OF_WEEK,
311     UCAL_MINIMAL_DAYS_IN_FIRST_WEEK
312 };
313 
314 enum UCalendarDaysOfWeek {
315     UCAL_SUNDAY,
316     UCAL_MONDAY,
317     UCAL_TUESDAY,
318     UCAL_WEDNESDAY,
319     UCAL_THURSDAY,
320     UCAL_FRIDAY,
321     UCAL_SATURDAY
322 };
323 
324 enum UCalendarWeekdayType {
325     UCAL_WEEKDAY,
326     UCAL_WEEKEND,
327     UCAL_WEEKEND_ONSET,
328     UCAL_WEEKEND_CEASE
329 };
330 
331 enum UCalendarDateFields {
332     UCAL_ERA,
333     UCAL_YEAR,
334     UCAL_MONTH,
335     UCAL_WEEK_OF_YEAR,
336     UCAL_WEEK_OF_MONTH,
337     UCAL_DATE,
338     UCAL_DAY_OF_YEAR,
339     UCAL_DAY_OF_WEEK,
340     UCAL_DAY_OF_WEEK_IN_MONTH,
341     UCAL_AM_PM,
342     UCAL_HOUR,
343     UCAL_HOUR_OF_DAY,
344     UCAL_MINUTE,
345     UCAL_SECOND,
346     UCAL_MILLISECOND,
347     UCAL_ZONE_OFFSET,
348     UCAL_DST_OFFSET,
349     UCAL_YEAR_WOY,
350     UCAL_DOW_LOCAL,
351     UCAL_EXTENDED_YEAR,
352     UCAL_JULIAN_DAY,
353     UCAL_MILLISECONDS_IN_DAY,
354     UCAL_IS_LEAP_MONTH,
355     UCAL_FIELD_COUNT,
356     UCAL_DAY_OF_MONTH = UCAL_DATE
357 };
358 
359 static UCalendar*
ucal_open(const UChar * zoneID,int32_t len,const char * locale,UCalendarType type,UErrorCode * status)360 ucal_open(const UChar* zoneID, int32_t len, const char* locale,
361           UCalendarType type, UErrorCode* status)
362 {
363     MOZ_CRASH("ucal_open: Intl API disabled");
364 }
365 
366 static const char*
ucal_getType(const UCalendar * cal,UErrorCode * status)367 ucal_getType(const UCalendar* cal, UErrorCode* status)
368 {
369     MOZ_CRASH("ucal_getType: Intl API disabled");
370 }
371 
372 static UEnumeration*
ucal_getKeywordValuesForLocale(const char * key,const char * locale,UBool commonlyUsed,UErrorCode * status)373 ucal_getKeywordValuesForLocale(const char* key, const char* locale,
374                                UBool commonlyUsed, UErrorCode* status)
375 {
376     MOZ_CRASH("ucal_getKeywordValuesForLocale: Intl API disabled");
377 }
378 
379 static void
ucal_close(UCalendar * cal)380 ucal_close(UCalendar* cal)
381 {
382     MOZ_CRASH("ucal_close: Intl API disabled");
383 }
384 
385 static UCalendarWeekdayType
ucal_getDayOfWeekType(const UCalendar * cal,UCalendarDaysOfWeek dayOfWeek,UErrorCode * status)386 ucal_getDayOfWeekType(const UCalendar *cal, UCalendarDaysOfWeek dayOfWeek, UErrorCode* status)
387 {
388     MOZ_CRASH("ucal_getDayOfWeekType: Intl API disabled");
389 }
390 
391 static int32_t
ucal_getAttribute(const UCalendar * cal,UCalendarAttribute attr)392 ucal_getAttribute(const UCalendar*    cal,
393                   UCalendarAttribute  attr)
394 {
395     MOZ_CRASH("ucal_getAttribute: Intl API disabled");
396 }
397 
398 static int32_t
ucal_get(const UCalendar * cal,UCalendarDateFields field,UErrorCode * status)399 ucal_get(const UCalendar *cal, UCalendarDateFields field, UErrorCode *status)
400 {
401     MOZ_CRASH("ucal_get: Intl API disabled");
402 }
403 
404 static UEnumeration*
ucal_openTimeZones(UErrorCode * status)405 ucal_openTimeZones(UErrorCode* status)
406 {
407     MOZ_CRASH("ucal_openTimeZones: Intl API disabled");
408 }
409 
410 static int32_t
ucal_getCanonicalTimeZoneID(const UChar * id,int32_t len,UChar * result,int32_t resultCapacity,UBool * isSystemID,UErrorCode * status)411 ucal_getCanonicalTimeZoneID(const UChar* id, int32_t len, UChar* result, int32_t resultCapacity,
412                             UBool* isSystemID, UErrorCode* status)
413 {
414     MOZ_CRASH("ucal_getCanonicalTimeZoneID: Intl API disabled");
415 }
416 
417 static int32_t
ucal_getDefaultTimeZone(UChar * result,int32_t resultCapacity,UErrorCode * status)418 ucal_getDefaultTimeZone(UChar* result, int32_t resultCapacity, UErrorCode* status)
419 {
420     MOZ_CRASH("ucal_getDefaultTimeZone: Intl API disabled");
421 }
422 
423 typedef void* UDateTimePatternGenerator;
424 
425 static UDateTimePatternGenerator*
udatpg_open(const char * locale,UErrorCode * pErrorCode)426 udatpg_open(const char* locale, UErrorCode* pErrorCode)
427 {
428     MOZ_CRASH("udatpg_open: Intl API disabled");
429 }
430 
431 static int32_t
udatpg_getBestPattern(UDateTimePatternGenerator * dtpg,const UChar * skeleton,int32_t length,UChar * bestPattern,int32_t capacity,UErrorCode * pErrorCode)432 udatpg_getBestPattern(UDateTimePatternGenerator* dtpg, const UChar* skeleton,
433                       int32_t length, UChar* bestPattern, int32_t capacity,
434                       UErrorCode* pErrorCode)
435 {
436     MOZ_CRASH("udatpg_getBestPattern: Intl API disabled");
437 }
438 
439 static void
udatpg_close(UDateTimePatternGenerator * dtpg)440 udatpg_close(UDateTimePatternGenerator* dtpg)
441 {
442     MOZ_CRASH("udatpg_close: Intl API disabled");
443 }
444 
445 typedef void* UCalendar;
446 typedef void* UDateFormat;
447 
448 enum UDateFormatField {
449     UDAT_ERA_FIELD = 0,
450     UDAT_YEAR_FIELD = 1,
451     UDAT_MONTH_FIELD = 2,
452     UDAT_DATE_FIELD = 3,
453     UDAT_HOUR_OF_DAY1_FIELD = 4,
454     UDAT_HOUR_OF_DAY0_FIELD = 5,
455     UDAT_MINUTE_FIELD = 6,
456     UDAT_SECOND_FIELD = 7,
457     UDAT_FRACTIONAL_SECOND_FIELD = 8,
458     UDAT_DAY_OF_WEEK_FIELD = 9,
459     UDAT_DAY_OF_YEAR_FIELD = 10,
460     UDAT_DAY_OF_WEEK_IN_MONTH_FIELD = 11,
461     UDAT_WEEK_OF_YEAR_FIELD = 12,
462     UDAT_WEEK_OF_MONTH_FIELD = 13,
463     UDAT_AM_PM_FIELD = 14,
464     UDAT_HOUR1_FIELD = 15,
465     UDAT_HOUR0_FIELD = 16,
466     UDAT_TIMEZONE_FIELD = 17,
467     UDAT_YEAR_WOY_FIELD = 18,
468     UDAT_DOW_LOCAL_FIELD = 19,
469     UDAT_EXTENDED_YEAR_FIELD = 20,
470     UDAT_JULIAN_DAY_FIELD = 21,
471     UDAT_MILLISECONDS_IN_DAY_FIELD = 22,
472     UDAT_TIMEZONE_RFC_FIELD = 23,
473     UDAT_TIMEZONE_GENERIC_FIELD = 24,
474     UDAT_STANDALONE_DAY_FIELD = 25,
475     UDAT_STANDALONE_MONTH_FIELD = 26,
476     UDAT_QUARTER_FIELD = 27,
477     UDAT_STANDALONE_QUARTER_FIELD = 28,
478     UDAT_TIMEZONE_SPECIAL_FIELD = 29,
479     UDAT_YEAR_NAME_FIELD = 30,
480     UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD = 31,
481     UDAT_TIMEZONE_ISO_FIELD = 32,
482     UDAT_TIMEZONE_ISO_LOCAL_FIELD = 33,
483     UDAT_RELATED_YEAR_FIELD = 34,
484     UDAT_AM_PM_MIDNIGHT_NOON_FIELD = 35,
485     UDAT_FLEXIBLE_DAY_PERIOD_FIELD = 36,
486     UDAT_TIME_SEPARATOR_FIELD = 37,
487     UDAT_FIELD_COUNT = 38
488 };
489 
490 enum UDateFormatStyle {
491     UDAT_PATTERN = -2,
492     UDAT_IGNORE = UDAT_PATTERN
493 };
494 
495 static int32_t
udat_countAvailable()496 udat_countAvailable()
497 {
498     MOZ_CRASH("udat_countAvailable: Intl API disabled");
499 }
500 
501 static const char*
udat_getAvailable(int32_t localeIndex)502 udat_getAvailable(int32_t localeIndex)
503 {
504     MOZ_CRASH("udat_getAvailable: Intl API disabled");
505 }
506 
507 static UDateFormat*
udat_open(UDateFormatStyle timeStyle,UDateFormatStyle dateStyle,const char * locale,const UChar * tzID,int32_t tzIDLength,const UChar * pattern,int32_t patternLength,UErrorCode * status)508 udat_open(UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const char* locale,
509           const UChar* tzID, int32_t tzIDLength, const UChar* pattern,
510           int32_t patternLength, UErrorCode* status)
511 {
512     MOZ_CRASH("udat_open: Intl API disabled");
513 }
514 
515 static const UCalendar*
udat_getCalendar(const UDateFormat * fmt)516 udat_getCalendar(const UDateFormat* fmt)
517 {
518     MOZ_CRASH("udat_getCalendar: Intl API disabled");
519 }
520 
521 static void
ucal_setGregorianChange(UCalendar * cal,UDate date,UErrorCode * pErrorCode)522 ucal_setGregorianChange(UCalendar* cal, UDate date, UErrorCode* pErrorCode)
523 {
524     MOZ_CRASH("ucal_setGregorianChange: Intl API disabled");
525 }
526 
527 static int32_t
udat_format(const UDateFormat * format,UDate dateToFormat,UChar * result,int32_t resultLength,UFieldPosition * position,UErrorCode * status)528 udat_format(const UDateFormat* format, UDate dateToFormat, UChar* result,
529             int32_t resultLength, UFieldPosition* position, UErrorCode* status)
530 {
531     MOZ_CRASH("udat_format: Intl API disabled");
532 }
533 
534 static int32_t
udat_formatForFields(const UDateFormat * format,UDate dateToFormat,UChar * result,int32_t resultLength,UFieldPositionIterator * fpositer,UErrorCode * status)535 udat_formatForFields(const UDateFormat* format, UDate dateToFormat,
536                      UChar* result, int32_t resultLength, UFieldPositionIterator* fpositer,
537                      UErrorCode* status)
538 {
539     MOZ_CRASH("udat_formatForFields: Intl API disabled");
540 }
541 
542 static UFieldPositionIterator*
ufieldpositer_open(UErrorCode * status)543 ufieldpositer_open(UErrorCode* status)
544 {
545     MOZ_CRASH("ufieldpositer_open: Intl API disabled");
546 }
547 
548 static void
ufieldpositer_close(UFieldPositionIterator * fpositer)549 ufieldpositer_close(UFieldPositionIterator* fpositer)
550 {
551     MOZ_CRASH("ufieldpositer_close: Intl API disabled");
552 }
553 
554 static int32_t
ufieldpositer_next(UFieldPositionIterator * fpositer,int32_t * beginIndex,int32_t * endIndex)555 ufieldpositer_next(UFieldPositionIterator* fpositer, int32_t* beginIndex, int32_t* endIndex)
556 {
557     MOZ_CRASH("ufieldpositer_next: Intl API disabled");
558 }
559 
560 static void
udat_close(UDateFormat * format)561 udat_close(UDateFormat* format)
562 {
563     MOZ_CRASH("udat_close: Intl API disabled");
564 }
565 
566 #endif
567 
568 
569 /******************** Common to Intl constructors ********************/
570 
571 static bool
IntlInitialize(JSContext * cx,HandleObject obj,Handle<PropertyName * > initializer,HandleValue locales,HandleValue options)572 IntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
573                HandleValue locales, HandleValue options)
574 {
575     RootedValue initializerValue(cx);
576     if (!GlobalObject::getIntrinsicValue(cx, cx->global(), initializer, &initializerValue))
577         return false;
578     MOZ_ASSERT(initializerValue.isObject());
579     MOZ_ASSERT(initializerValue.toObject().is<JSFunction>());
580 
581     FixedInvokeArgs<3> args(cx);
582 
583     args[0].setObject(*obj);
584     args[1].set(locales);
585     args[2].set(options);
586 
587     RootedValue thisv(cx, NullValue());
588     RootedValue ignored(cx);
589     return js::Call(cx, initializerValue, thisv, args, &ignored);
590 }
591 
592 static bool
CreateDefaultOptions(JSContext * cx,MutableHandleValue defaultOptions)593 CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions)
594 {
595     RootedObject options(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
596     if (!options)
597         return false;
598     defaultOptions.setObject(*options);
599     return true;
600 }
601 
602 // CountAvailable and GetAvailable describe the signatures used for ICU API
603 // to determine available locales for various functionality.
604 typedef int32_t
605 (* CountAvailable)();
606 
607 typedef const char*
608 (* GetAvailable)(int32_t localeIndex);
609 
610 static bool
intl_availableLocales(JSContext * cx,CountAvailable countAvailable,GetAvailable getAvailable,MutableHandleValue result)611 intl_availableLocales(JSContext* cx, CountAvailable countAvailable,
612                       GetAvailable getAvailable, MutableHandleValue result)
613 {
614     RootedObject locales(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
615     if (!locales)
616         return false;
617 
618 #if ENABLE_INTL_API
619     uint32_t count = countAvailable();
620     RootedValue t(cx, BooleanValue(true));
621     for (uint32_t i = 0; i < count; i++) {
622         const char* locale = getAvailable(i);
623         auto lang = DuplicateString(cx, locale);
624         if (!lang)
625             return false;
626         char* p;
627         while ((p = strchr(lang.get(), '_')))
628             *p = '-';
629         RootedAtom a(cx, Atomize(cx, lang.get(), strlen(lang.get())));
630         if (!a)
631             return false;
632         if (!DefineProperty(cx, locales, a->asPropertyName(), t, nullptr, nullptr,
633                             JSPROP_ENUMERATE))
634         {
635             return false;
636         }
637     }
638 #endif
639     result.setObject(*locales);
640     return true;
641 }
642 
643 /**
644  * Returns the object holding the internal properties for obj.
645  */
646 static JSObject*
GetInternals(JSContext * cx,HandleObject obj)647 GetInternals(JSContext* cx, HandleObject obj)
648 {
649     RootedValue getInternalsValue(cx);
650     if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().getInternals,
651                                          &getInternalsValue))
652     {
653         return nullptr;
654     }
655     MOZ_ASSERT(getInternalsValue.isObject());
656     MOZ_ASSERT(getInternalsValue.toObject().is<JSFunction>());
657 
658     FixedInvokeArgs<1> args(cx);
659 
660     args[0].setObject(*obj);
661 
662     RootedValue v(cx, NullValue());
663     if (!js::Call(cx, getInternalsValue, v, args, &v))
664         return nullptr;
665 
666     return &v.toObject();
667 }
668 
669 static bool
equal(const char * s1,const char * s2)670 equal(const char* s1, const char* s2)
671 {
672     return !strcmp(s1, s2);
673 }
674 
675 static bool
equal(JSAutoByteString & s1,const char * s2)676 equal(JSAutoByteString& s1, const char* s2)
677 {
678     return !strcmp(s1.ptr(), s2);
679 }
680 
681 static const char*
icuLocale(const char * locale)682 icuLocale(const char* locale)
683 {
684     if (equal(locale, "und"))
685         return ""; // ICU root locale
686     return locale;
687 }
688 
689 // Simple RAII for ICU objects.  Unfortunately, ICU's C++ API is uniformly
690 // unstable, so we can't use its smart pointers for this.
691 template <typename T, void (Delete)(T*)>
692 class ScopedICUObject
693 {
694     T* ptr_;
695 
696   public:
ScopedICUObject(T * ptr)697     explicit ScopedICUObject(T* ptr)
698       : ptr_(ptr)
699     {}
700 
~ScopedICUObject()701     ~ScopedICUObject() {
702         if (ptr_)
703             Delete(ptr_);
704     }
705 
706     // In cases where an object should be deleted on abnormal exits,
707     // but returned to the caller if everything goes well, call forget()
708     // to transfer the object just before returning.
forget()709     T* forget() {
710         T* tmp = ptr_;
711         ptr_ = nullptr;
712         return tmp;
713     }
714 };
715 
716 // The inline capacity we use for the char16_t Vectors.
717 static const size_t INITIAL_CHAR_BUFFER_SIZE = 32;
718 
719 /******************** Collator ********************/
720 
721 static void collator_finalize(FreeOp* fop, JSObject* obj);
722 
723 static const uint32_t UCOLLATOR_SLOT = 0;
724 static const uint32_t COLLATOR_SLOTS_COUNT = 1;
725 
726 static const ClassOps CollatorClassOps = {
727     nullptr, /* addProperty */
728     nullptr, /* delProperty */
729     nullptr, /* getProperty */
730     nullptr, /* setProperty */
731     nullptr, /* enumerate */
732     nullptr, /* resolve */
733     nullptr, /* mayResolve */
734     collator_finalize
735 };
736 
737 static const Class CollatorClass = {
738     js_Object_str,
739     JSCLASS_HAS_RESERVED_SLOTS(COLLATOR_SLOTS_COUNT) |
740     JSCLASS_FOREGROUND_FINALIZE,
741     &CollatorClassOps
742 };
743 
744 #if JS_HAS_TOSOURCE
745 static bool
collator_toSource(JSContext * cx,unsigned argc,Value * vp)746 collator_toSource(JSContext* cx, unsigned argc, Value* vp)
747 {
748     CallArgs args = CallArgsFromVp(argc, vp);
749     args.rval().setString(cx->names().Collator);
750     return true;
751 }
752 #endif
753 
754 static const JSFunctionSpec collator_static_methods[] = {
755     JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_Collator_supportedLocalesOf", 1, 0),
756     JS_FS_END
757 };
758 
759 static const JSFunctionSpec collator_methods[] = {
760     JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0),
761 #if JS_HAS_TOSOURCE
762     JS_FN(js_toSource_str, collator_toSource, 0, 0),
763 #endif
764     JS_FS_END
765 };
766 
767 /**
768  * Collator constructor.
769  * Spec: ECMAScript Internationalization API Specification, 10.1
770  */
771 static bool
Collator(JSContext * cx,const CallArgs & args,bool construct)772 Collator(JSContext* cx, const CallArgs& args, bool construct)
773 {
774     RootedObject obj(cx);
775 
776     if (!construct) {
777         // 10.1.2.1 step 3
778         JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
779         if (!intl)
780             return false;
781         RootedValue self(cx, args.thisv());
782         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
783             // 10.1.2.1 step 4
784             obj = ToObject(cx, self);
785             if (!obj)
786                 return false;
787 
788             // 10.1.2.1 step 5
789             bool extensible;
790             if (!IsExtensible(cx, obj, &extensible))
791                 return false;
792             if (!extensible)
793                 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
794         } else {
795             // 10.1.2.1 step 3.a
796             construct = true;
797         }
798     }
799     if (construct) {
800         // 10.1.3.1 paragraph 2
801         RootedObject proto(cx, cx->global()->getOrCreateCollatorPrototype(cx));
802         if (!proto)
803             return false;
804         obj = NewObjectWithGivenProto(cx, &CollatorClass, proto);
805         if (!obj)
806             return false;
807 
808         obj->as<NativeObject>().setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr));
809     }
810 
811     // 10.1.2.1 steps 1 and 2; 10.1.3.1 steps 1 and 2
812     RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
813     RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
814 
815     // 10.1.2.1 step 6; 10.1.3.1 step 3
816     if (!IntlInitialize(cx, obj, cx->names().InitializeCollator, locales, options))
817         return false;
818 
819     // 10.1.2.1 steps 3.a and 7
820     args.rval().setObject(*obj);
821     return true;
822 }
823 
824 static bool
Collator(JSContext * cx,unsigned argc,Value * vp)825 Collator(JSContext* cx, unsigned argc, Value* vp)
826 {
827     CallArgs args = CallArgsFromVp(argc, vp);
828     return Collator(cx, args, args.isConstructing());
829 }
830 
831 bool
intl_Collator(JSContext * cx,unsigned argc,Value * vp)832 js::intl_Collator(JSContext* cx, unsigned argc, Value* vp)
833 {
834     CallArgs args = CallArgsFromVp(argc, vp);
835     MOZ_ASSERT(args.length() == 2);
836     // intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot
837     // be used with "new", but it still has to be treated as a constructor.
838     return Collator(cx, args, true);
839 }
840 
841 static void
collator_finalize(FreeOp * fop,JSObject * obj)842 collator_finalize(FreeOp* fop, JSObject* obj)
843 {
844     MOZ_ASSERT(fop->onMainThread());
845 
846     // This is-undefined check shouldn't be necessary, but for internal
847     // brokenness in object allocation code.  For the moment, hack around it by
848     // explicitly guarding against the possibility of the reserved slot not
849     // containing a private.  See bug 949220.
850     const Value& slot = obj->as<NativeObject>().getReservedSlot(UCOLLATOR_SLOT);
851     if (!slot.isUndefined()) {
852         if (UCollator* coll = static_cast<UCollator*>(slot.toPrivate()))
853             ucol_close(coll);
854     }
855 }
856 
857 static JSObject*
CreateCollatorPrototype(JSContext * cx,HandleObject Intl,Handle<GlobalObject * > global)858 CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
859 {
860     RootedFunction ctor(cx, global->createConstructor(cx, &Collator, cx->names().Collator, 0));
861     if (!ctor)
862         return nullptr;
863 
864     RootedNativeObject proto(cx, global->createBlankPrototype(cx, &CollatorClass));
865     if (!proto)
866         return nullptr;
867     proto->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr));
868 
869     if (!LinkConstructorAndPrototype(cx, ctor, proto))
870         return nullptr;
871 
872     // 10.2.2
873     if (!JS_DefineFunctions(cx, ctor, collator_static_methods))
874         return nullptr;
875 
876     // 10.3.2 and 10.3.3
877     if (!JS_DefineFunctions(cx, proto, collator_methods))
878         return nullptr;
879 
880     /*
881      * Install the getter for Collator.prototype.compare, which returns a bound
882      * comparison function for the specified Collator object (suitable for
883      * passing to methods like Array.prototype.sort).
884      */
885     RootedValue getter(cx);
886     if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().CollatorCompareGet, &getter))
887         return nullptr;
888     if (!DefineProperty(cx, proto, cx->names().compare, UndefinedHandleValue,
889                         JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()),
890                         nullptr, JSPROP_GETTER | JSPROP_SHARED))
891     {
892         return nullptr;
893     }
894 
895     RootedValue options(cx);
896     if (!CreateDefaultOptions(cx, &options))
897         return nullptr;
898 
899     // 10.2.1 and 10.3
900     if (!IntlInitialize(cx, proto, cx->names().InitializeCollator, UndefinedHandleValue, options))
901         return nullptr;
902 
903     // 8.1
904     RootedValue ctorValue(cx, ObjectValue(*ctor));
905     if (!DefineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0))
906         return nullptr;
907 
908     return proto;
909 }
910 
911 bool
intl_Collator_availableLocales(JSContext * cx,unsigned argc,Value * vp)912 js::intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp)
913 {
914     CallArgs args = CallArgsFromVp(argc, vp);
915     MOZ_ASSERT(args.length() == 0);
916 
917     RootedValue result(cx);
918     if (!intl_availableLocales(cx, ucol_countAvailable, ucol_getAvailable, &result))
919         return false;
920     args.rval().set(result);
921     return true;
922 }
923 
924 bool
intl_availableCollations(JSContext * cx,unsigned argc,Value * vp)925 js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp)
926 {
927     CallArgs args = CallArgsFromVp(argc, vp);
928     MOZ_ASSERT(args.length() == 1);
929     MOZ_ASSERT(args[0].isString());
930 
931     JSAutoByteString locale(cx, args[0].toString());
932     if (!locale)
933         return false;
934     UErrorCode status = U_ZERO_ERROR;
935     UEnumeration* values = ucol_getKeywordValuesForLocale("co", locale.ptr(), false, &status);
936     if (U_FAILURE(status)) {
937         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
938         return false;
939     }
940     ScopedICUObject<UEnumeration, uenum_close> toClose(values);
941 
942     uint32_t count = uenum_count(values, &status);
943     if (U_FAILURE(status)) {
944         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
945         return false;
946     }
947 
948     RootedObject collations(cx, NewDenseEmptyArray(cx));
949     if (!collations)
950         return false;
951 
952     uint32_t index = 0;
953     for (uint32_t i = 0; i < count; i++) {
954         const char* collation = uenum_next(values, nullptr, &status);
955         if (U_FAILURE(status)) {
956             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
957             return false;
958         }
959 
960         // Per ECMA-402, 10.2.3, we don't include standard and search:
961         // "The values 'standard' and 'search' must not be used as elements in
962         // any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co
963         // array."
964         if (equal(collation, "standard") || equal(collation, "search"))
965             continue;
966 
967         // ICU returns old-style keyword values; map them to BCP 47 equivalents
968         // (see http://bugs.icu-project.org/trac/ticket/9620).
969         if (equal(collation, "dictionary"))
970             collation = "dict";
971         else if (equal(collation, "gb2312han"))
972             collation = "gb2312";
973         else if (equal(collation, "phonebook"))
974             collation = "phonebk";
975         else if (equal(collation, "traditional"))
976             collation = "trad";
977 
978         RootedString jscollation(cx, JS_NewStringCopyZ(cx, collation));
979         if (!jscollation)
980             return false;
981         RootedValue element(cx, StringValue(jscollation));
982         if (!DefineElement(cx, collations, index++, element))
983             return false;
984     }
985 
986     args.rval().setObject(*collations);
987     return true;
988 }
989 
990 /**
991  * Returns a new UCollator with the locale and collation options
992  * of the given Collator.
993  */
994 static UCollator*
NewUCollator(JSContext * cx,HandleObject collator)995 NewUCollator(JSContext* cx, HandleObject collator)
996 {
997     RootedValue value(cx);
998 
999     RootedObject internals(cx, GetInternals(cx, collator));
1000     if (!internals)
1001         return nullptr;
1002 
1003     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
1004         return nullptr;
1005     JSAutoByteString locale(cx, value.toString());
1006     if (!locale)
1007         return nullptr;
1008 
1009     // UCollator options with default values.
1010     UColAttributeValue uStrength = UCOL_DEFAULT;
1011     UColAttributeValue uCaseLevel = UCOL_OFF;
1012     UColAttributeValue uAlternate = UCOL_DEFAULT;
1013     UColAttributeValue uNumeric = UCOL_OFF;
1014     // Normalization is always on to meet the canonical equivalence requirement.
1015     UColAttributeValue uNormalization = UCOL_ON;
1016     UColAttributeValue uCaseFirst = UCOL_DEFAULT;
1017 
1018     if (!GetProperty(cx, internals, internals, cx->names().usage, &value))
1019         return nullptr;
1020     JSAutoByteString usage(cx, value.toString());
1021     if (!usage)
1022         return nullptr;
1023     if (equal(usage, "search")) {
1024         // ICU expects search as a Unicode locale extension on locale.
1025         // Unicode locale extensions must occur before private use extensions.
1026         const char* oldLocale = locale.ptr();
1027         const char* p;
1028         size_t index;
1029         size_t localeLen = strlen(oldLocale);
1030         if ((p = strstr(oldLocale, "-x-")))
1031             index = p - oldLocale;
1032         else
1033             index = localeLen;
1034 
1035         const char* insert;
1036         if ((p = strstr(oldLocale, "-u-")) && static_cast<size_t>(p - oldLocale) < index) {
1037             index = p - oldLocale + 2;
1038             insert = "-co-search";
1039         } else {
1040             insert = "-u-co-search";
1041         }
1042         size_t insertLen = strlen(insert);
1043         char* newLocale = cx->pod_malloc<char>(localeLen + insertLen + 1);
1044         if (!newLocale)
1045             return nullptr;
1046         memcpy(newLocale, oldLocale, index);
1047         memcpy(newLocale + index, insert, insertLen);
1048         memcpy(newLocale + index + insertLen, oldLocale + index, localeLen - index + 1); // '\0'
1049         locale.clear();
1050         locale.initBytes(newLocale);
1051     }
1052 
1053     // We don't need to look at the collation property - it can only be set
1054     // via the Unicode locale extension and is therefore already set on
1055     // locale.
1056 
1057     if (!GetProperty(cx, internals, internals, cx->names().sensitivity, &value))
1058         return nullptr;
1059     JSAutoByteString sensitivity(cx, value.toString());
1060     if (!sensitivity)
1061         return nullptr;
1062     if (equal(sensitivity, "base")) {
1063         uStrength = UCOL_PRIMARY;
1064     } else if (equal(sensitivity, "accent")) {
1065         uStrength = UCOL_SECONDARY;
1066     } else if (equal(sensitivity, "case")) {
1067         uStrength = UCOL_PRIMARY;
1068         uCaseLevel = UCOL_ON;
1069     } else {
1070         MOZ_ASSERT(equal(sensitivity, "variant"));
1071         uStrength = UCOL_TERTIARY;
1072     }
1073 
1074     if (!GetProperty(cx, internals, internals, cx->names().ignorePunctuation, &value))
1075         return nullptr;
1076     // According to the ICU team, UCOL_SHIFTED causes punctuation to be
1077     // ignored. Looking at Unicode Technical Report 35, Unicode Locale Data
1078     // Markup Language, "shifted" causes whitespace and punctuation to be
1079     // ignored - that's a bit more than asked for, but there's no way to get
1080     // less.
1081     if (value.toBoolean())
1082         uAlternate = UCOL_SHIFTED;
1083 
1084     if (!GetProperty(cx, internals, internals, cx->names().numeric, &value))
1085         return nullptr;
1086     if (!value.isUndefined() && value.toBoolean())
1087         uNumeric = UCOL_ON;
1088 
1089     if (!GetProperty(cx, internals, internals, cx->names().caseFirst, &value))
1090         return nullptr;
1091     if (!value.isUndefined()) {
1092         JSAutoByteString caseFirst(cx, value.toString());
1093         if (!caseFirst)
1094             return nullptr;
1095         if (equal(caseFirst, "upper"))
1096             uCaseFirst = UCOL_UPPER_FIRST;
1097         else if (equal(caseFirst, "lower"))
1098             uCaseFirst = UCOL_LOWER_FIRST;
1099         else
1100             MOZ_ASSERT(equal(caseFirst, "false"));
1101     }
1102 
1103     UErrorCode status = U_ZERO_ERROR;
1104     UCollator* coll = ucol_open(icuLocale(locale.ptr()), &status);
1105     if (U_FAILURE(status)) {
1106         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1107         return nullptr;
1108     }
1109 
1110     ucol_setAttribute(coll, UCOL_STRENGTH, uStrength, &status);
1111     ucol_setAttribute(coll, UCOL_CASE_LEVEL, uCaseLevel, &status);
1112     ucol_setAttribute(coll, UCOL_ALTERNATE_HANDLING, uAlternate, &status);
1113     ucol_setAttribute(coll, UCOL_NUMERIC_COLLATION, uNumeric, &status);
1114     ucol_setAttribute(coll, UCOL_NORMALIZATION_MODE, uNormalization, &status);
1115     ucol_setAttribute(coll, UCOL_CASE_FIRST, uCaseFirst, &status);
1116     if (U_FAILURE(status)) {
1117         ucol_close(coll);
1118         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1119         return nullptr;
1120     }
1121 
1122     return coll;
1123 }
1124 
1125 static bool
intl_CompareStrings(JSContext * cx,UCollator * coll,HandleString str1,HandleString str2,MutableHandleValue result)1126 intl_CompareStrings(JSContext* cx, UCollator* coll, HandleString str1, HandleString str2,
1127                     MutableHandleValue result)
1128 {
1129     MOZ_ASSERT(str1);
1130     MOZ_ASSERT(str2);
1131 
1132     if (str1 == str2) {
1133         result.setInt32(0);
1134         return true;
1135     }
1136 
1137     AutoStableStringChars stableChars1(cx);
1138     if (!stableChars1.initTwoByte(cx, str1))
1139         return false;
1140 
1141     AutoStableStringChars stableChars2(cx);
1142     if (!stableChars2.initTwoByte(cx, str2))
1143         return false;
1144 
1145     mozilla::Range<const char16_t> chars1 = stableChars1.twoByteRange();
1146     mozilla::Range<const char16_t> chars2 = stableChars2.twoByteRange();
1147 
1148     UCollationResult uresult = ucol_strcoll(coll,
1149                                             Char16ToUChar(chars1.begin().get()), chars1.length(),
1150                                             Char16ToUChar(chars2.begin().get()), chars2.length());
1151     int32_t res;
1152     switch (uresult) {
1153         case UCOL_LESS: res = -1; break;
1154         case UCOL_EQUAL: res = 0; break;
1155         case UCOL_GREATER: res = 1; break;
1156         default: MOZ_CRASH("ucol_strcoll returned bad UCollationResult");
1157     }
1158     result.setInt32(res);
1159     return true;
1160 }
1161 
1162 bool
intl_CompareStrings(JSContext * cx,unsigned argc,Value * vp)1163 js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp)
1164 {
1165     CallArgs args = CallArgsFromVp(argc, vp);
1166     MOZ_ASSERT(args.length() == 3);
1167     MOZ_ASSERT(args[0].isObject());
1168     MOZ_ASSERT(args[1].isString());
1169     MOZ_ASSERT(args[2].isString());
1170 
1171     RootedObject collator(cx, &args[0].toObject());
1172 
1173     // Obtain a UCollator object, cached if possible.
1174     // XXX Does this handle Collator instances from other globals correctly?
1175     bool isCollatorInstance = collator->getClass() == &CollatorClass;
1176     UCollator* coll;
1177     if (isCollatorInstance) {
1178         void* priv = collator->as<NativeObject>().getReservedSlot(UCOLLATOR_SLOT).toPrivate();
1179         coll = static_cast<UCollator*>(priv);
1180         if (!coll) {
1181             coll = NewUCollator(cx, collator);
1182             if (!coll)
1183                 return false;
1184             collator->as<NativeObject>().setReservedSlot(UCOLLATOR_SLOT, PrivateValue(coll));
1185         }
1186     } else {
1187         // There's no good place to cache the ICU collator for an object
1188         // that has been initialized as a Collator but is not a Collator
1189         // instance. One possibility might be to add a Collator instance as an
1190         // internal property to each such object.
1191         coll = NewUCollator(cx, collator);
1192         if (!coll)
1193             return false;
1194     }
1195 
1196     // Use the UCollator to actually compare the strings.
1197     RootedString str1(cx, args[1].toString());
1198     RootedString str2(cx, args[2].toString());
1199     RootedValue result(cx);
1200     bool success = intl_CompareStrings(cx, coll, str1, str2, &result);
1201 
1202     if (!isCollatorInstance)
1203         ucol_close(coll);
1204     if (!success)
1205         return false;
1206     args.rval().set(result);
1207     return true;
1208 }
1209 
1210 
1211 /******************** NumberFormat ********************/
1212 
1213 static void numberFormat_finalize(FreeOp* fop, JSObject* obj);
1214 
1215 static const uint32_t UNUMBER_FORMAT_SLOT = 0;
1216 static const uint32_t NUMBER_FORMAT_SLOTS_COUNT = 1;
1217 
1218 static const ClassOps NumberFormatClassOps = {
1219     nullptr, /* addProperty */
1220     nullptr, /* delProperty */
1221     nullptr, /* getProperty */
1222     nullptr, /* setProperty */
1223     nullptr, /* enumerate */
1224     nullptr, /* resolve */
1225     nullptr, /* mayResolve */
1226     numberFormat_finalize
1227 };
1228 
1229 static const Class NumberFormatClass = {
1230     js_Object_str,
1231     JSCLASS_HAS_RESERVED_SLOTS(NUMBER_FORMAT_SLOTS_COUNT) |
1232     JSCLASS_FOREGROUND_FINALIZE,
1233     &NumberFormatClassOps
1234 };
1235 
1236 #if JS_HAS_TOSOURCE
1237 static bool
numberFormat_toSource(JSContext * cx,unsigned argc,Value * vp)1238 numberFormat_toSource(JSContext* cx, unsigned argc, Value* vp)
1239 {
1240     CallArgs args = CallArgsFromVp(argc, vp);
1241     args.rval().setString(cx->names().NumberFormat);
1242     return true;
1243 }
1244 #endif
1245 
1246 static const JSFunctionSpec numberFormat_static_methods[] = {
1247     JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_NumberFormat_supportedLocalesOf", 1, 0),
1248     JS_FS_END
1249 };
1250 
1251 static const JSFunctionSpec numberFormat_methods[] = {
1252     JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0, 0),
1253 #if JS_HAS_TOSOURCE
1254     JS_FN(js_toSource_str, numberFormat_toSource, 0, 0),
1255 #endif
1256     JS_FS_END
1257 };
1258 
1259 /**
1260  * NumberFormat constructor.
1261  * Spec: ECMAScript Internationalization API Specification, 11.1
1262  */
1263 static bool
NumberFormat(JSContext * cx,const CallArgs & args,bool construct)1264 NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
1265 {
1266     RootedObject obj(cx);
1267 
1268     if (!construct) {
1269         // 11.1.2.1 step 3
1270         JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
1271         if (!intl)
1272             return false;
1273         RootedValue self(cx, args.thisv());
1274         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
1275             // 11.1.2.1 step 4
1276             obj = ToObject(cx, self);
1277             if (!obj)
1278                 return false;
1279 
1280             // 11.1.2.1 step 5
1281             bool extensible;
1282             if (!IsExtensible(cx, obj, &extensible))
1283                 return false;
1284             if (!extensible)
1285                 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
1286         } else {
1287             // 11.1.2.1 step 3.a
1288             construct = true;
1289         }
1290     }
1291     if (construct) {
1292         // 11.1.3.1 paragraph 2
1293         RootedObject proto(cx, cx->global()->getOrCreateNumberFormatPrototype(cx));
1294         if (!proto)
1295             return false;
1296         obj = NewObjectWithGivenProto(cx, &NumberFormatClass, proto);
1297         if (!obj)
1298             return false;
1299 
1300         obj->as<NativeObject>().setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
1301     }
1302 
1303     // 11.1.2.1 steps 1 and 2; 11.1.3.1 steps 1 and 2
1304     RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
1305     RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
1306 
1307     // 11.1.2.1 step 6; 11.1.3.1 step 3
1308     if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options))
1309         return false;
1310 
1311     // 11.1.2.1 steps 3.a and 7
1312     args.rval().setObject(*obj);
1313     return true;
1314 }
1315 
1316 static bool
NumberFormat(JSContext * cx,unsigned argc,Value * vp)1317 NumberFormat(JSContext* cx, unsigned argc, Value* vp)
1318 {
1319     CallArgs args = CallArgsFromVp(argc, vp);
1320     return NumberFormat(cx, args, args.isConstructing());
1321 }
1322 
1323 bool
intl_NumberFormat(JSContext * cx,unsigned argc,Value * vp)1324 js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp)
1325 {
1326     CallArgs args = CallArgsFromVp(argc, vp);
1327     MOZ_ASSERT(args.length() == 2);
1328     // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it
1329     // cannot be used with "new", but it still has to be treated as a
1330     // constructor.
1331     return NumberFormat(cx, args, true);
1332 }
1333 
1334 static void
numberFormat_finalize(FreeOp * fop,JSObject * obj)1335 numberFormat_finalize(FreeOp* fop, JSObject* obj)
1336 {
1337     MOZ_ASSERT(fop->onMainThread());
1338 
1339     // This is-undefined check shouldn't be necessary, but for internal
1340     // brokenness in object allocation code.  For the moment, hack around it by
1341     // explicitly guarding against the possibility of the reserved slot not
1342     // containing a private.  See bug 949220.
1343     const Value& slot = obj->as<NativeObject>().getReservedSlot(UNUMBER_FORMAT_SLOT);
1344     if (!slot.isUndefined()) {
1345         if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate()))
1346             unum_close(nf);
1347     }
1348 }
1349 
1350 static JSObject*
CreateNumberFormatPrototype(JSContext * cx,HandleObject Intl,Handle<GlobalObject * > global)1351 CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
1352 {
1353     RootedFunction ctor(cx);
1354     ctor = global->createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0);
1355     if (!ctor)
1356         return nullptr;
1357 
1358     RootedNativeObject proto(cx, global->createBlankPrototype(cx, &NumberFormatClass));
1359     if (!proto)
1360         return nullptr;
1361     proto->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
1362 
1363     if (!LinkConstructorAndPrototype(cx, ctor, proto))
1364         return nullptr;
1365 
1366     // 11.2.2
1367     if (!JS_DefineFunctions(cx, ctor, numberFormat_static_methods))
1368         return nullptr;
1369 
1370     // 11.3.2 and 11.3.3
1371     if (!JS_DefineFunctions(cx, proto, numberFormat_methods))
1372         return nullptr;
1373 
1374     /*
1375      * Install the getter for NumberFormat.prototype.format, which returns a
1376      * bound formatting function for the specified NumberFormat object (suitable
1377      * for passing to methods like Array.prototype.map).
1378      */
1379     RootedValue getter(cx);
1380     if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().NumberFormatFormatGet,
1381                                          &getter))
1382     {
1383         return nullptr;
1384     }
1385     if (!DefineProperty(cx, proto, cx->names().format, UndefinedHandleValue,
1386                         JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()),
1387                         nullptr, JSPROP_GETTER | JSPROP_SHARED))
1388     {
1389         return nullptr;
1390     }
1391 
1392     RootedValue options(cx);
1393     if (!CreateDefaultOptions(cx, &options))
1394         return nullptr;
1395 
1396     // 11.2.1 and 11.3
1397     if (!IntlInitialize(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue,
1398                         options))
1399     {
1400         return nullptr;
1401     }
1402 
1403     // 8.1
1404     RootedValue ctorValue(cx, ObjectValue(*ctor));
1405     if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0))
1406         return nullptr;
1407 
1408     return proto;
1409 }
1410 
1411 bool
intl_NumberFormat_availableLocales(JSContext * cx,unsigned argc,Value * vp)1412 js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
1413 {
1414     CallArgs args = CallArgsFromVp(argc, vp);
1415     MOZ_ASSERT(args.length() == 0);
1416 
1417     RootedValue result(cx);
1418     if (!intl_availableLocales(cx, unum_countAvailable, unum_getAvailable, &result))
1419         return false;
1420     args.rval().set(result);
1421     return true;
1422 }
1423 
1424 bool
intl_numberingSystem(JSContext * cx,unsigned argc,Value * vp)1425 js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
1426 {
1427     CallArgs args = CallArgsFromVp(argc, vp);
1428     MOZ_ASSERT(args.length() == 1);
1429     MOZ_ASSERT(args[0].isString());
1430 
1431     JSAutoByteString locale(cx, args[0].toString());
1432     if (!locale)
1433         return false;
1434 
1435     UErrorCode status = U_ZERO_ERROR;
1436     UNumberingSystem* numbers = unumsys_open(icuLocale(locale.ptr()), &status);
1437     if (U_FAILURE(status)) {
1438         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1439         return false;
1440     }
1441 
1442     ScopedICUObject<UNumberingSystem, unumsys_close> toClose(numbers);
1443 
1444     const char* name = unumsys_getName(numbers);
1445     RootedString jsname(cx, JS_NewStringCopyZ(cx, name));
1446     if (!jsname)
1447         return false;
1448 
1449     args.rval().setString(jsname);
1450     return true;
1451 }
1452 
1453 /**
1454  * Returns a new UNumberFormat with the locale and number formatting options
1455  * of the given NumberFormat.
1456  */
1457 static UNumberFormat*
NewUNumberFormat(JSContext * cx,HandleObject numberFormat)1458 NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
1459 {
1460     RootedValue value(cx);
1461 
1462     RootedObject internals(cx, GetInternals(cx, numberFormat));
1463     if (!internals)
1464        return nullptr;
1465 
1466     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
1467         return nullptr;
1468     JSAutoByteString locale(cx, value.toString());
1469     if (!locale)
1470         return nullptr;
1471 
1472     // UNumberFormat options with default values
1473     UNumberFormatStyle uStyle = UNUM_DECIMAL;
1474     const UChar* uCurrency = nullptr;
1475     uint32_t uMinimumIntegerDigits = 1;
1476     uint32_t uMinimumFractionDigits = 0;
1477     uint32_t uMaximumFractionDigits = 3;
1478     int32_t uMinimumSignificantDigits = -1;
1479     int32_t uMaximumSignificantDigits = -1;
1480     bool uUseGrouping = true;
1481 
1482     // Sprinkle appropriate rooting flavor over things the GC might care about.
1483     RootedString currency(cx);
1484     AutoStableStringChars stableChars(cx);
1485 
1486     // We don't need to look at numberingSystem - it can only be set via
1487     // the Unicode locale extension and is therefore already set on locale.
1488 
1489     if (!GetProperty(cx, internals, internals, cx->names().style, &value))
1490         return nullptr;
1491     JSAutoByteString style(cx, value.toString());
1492     if (!style)
1493         return nullptr;
1494 
1495     if (equal(style, "currency")) {
1496         if (!GetProperty(cx, internals, internals, cx->names().currency, &value))
1497             return nullptr;
1498         currency = value.toString();
1499         MOZ_ASSERT(currency->length() == 3,
1500                    "IsWellFormedCurrencyCode permits only length-3 strings");
1501         if (!currency->ensureFlat(cx) || !stableChars.initTwoByte(cx, currency))
1502             return nullptr;
1503         // uCurrency remains owned by stableChars.
1504         uCurrency = Char16ToUChar(stableChars.twoByteRange().begin().get());
1505         if (!uCurrency)
1506             return nullptr;
1507 
1508         if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay, &value))
1509             return nullptr;
1510         JSAutoByteString currencyDisplay(cx, value.toString());
1511         if (!currencyDisplay)
1512             return nullptr;
1513         if (equal(currencyDisplay, "code")) {
1514             uStyle = UNUM_CURRENCY_ISO;
1515         } else if (equal(currencyDisplay, "symbol")) {
1516             uStyle = UNUM_CURRENCY;
1517         } else {
1518             MOZ_ASSERT(equal(currencyDisplay, "name"));
1519             uStyle = UNUM_CURRENCY_PLURAL;
1520         }
1521     } else if (equal(style, "percent")) {
1522         uStyle = UNUM_PERCENT;
1523     } else {
1524         MOZ_ASSERT(equal(style, "decimal"));
1525         uStyle = UNUM_DECIMAL;
1526     }
1527 
1528     RootedId id(cx, NameToId(cx->names().minimumSignificantDigits));
1529     bool hasP;
1530     if (!HasProperty(cx, internals, id, &hasP))
1531         return nullptr;
1532     if (hasP) {
1533         if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
1534                          &value))
1535         {
1536             return nullptr;
1537         }
1538         uMinimumSignificantDigits = int32_t(value.toNumber());
1539         if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
1540                          &value))
1541         {
1542             return nullptr;
1543         }
1544         uMaximumSignificantDigits = int32_t(value.toNumber());
1545     } else {
1546         if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
1547                          &value))
1548         {
1549             return nullptr;
1550         }
1551         uMinimumIntegerDigits = int32_t(value.toNumber());
1552         if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
1553                          &value))
1554         {
1555             return nullptr;
1556         }
1557         uMinimumFractionDigits = int32_t(value.toNumber());
1558         if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
1559                          &value))
1560         {
1561             return nullptr;
1562         }
1563         uMaximumFractionDigits = int32_t(value.toNumber());
1564     }
1565 
1566     if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value))
1567         return nullptr;
1568     uUseGrouping = value.toBoolean();
1569 
1570     UErrorCode status = U_ZERO_ERROR;
1571     UNumberFormat* nf = unum_open(uStyle, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status);
1572     if (U_FAILURE(status)) {
1573         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1574         return nullptr;
1575     }
1576     ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
1577 
1578     if (uCurrency) {
1579         unum_setTextAttribute(nf, UNUM_CURRENCY_CODE, uCurrency, 3, &status);
1580         if (U_FAILURE(status)) {
1581             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1582             return nullptr;
1583         }
1584     }
1585     if (uMinimumSignificantDigits != -1) {
1586         unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true);
1587         unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits);
1588         unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits);
1589     } else {
1590         unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits);
1591         unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits);
1592         unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits);
1593     }
1594     unum_setAttribute(nf, UNUM_GROUPING_USED, uUseGrouping);
1595     unum_setAttribute(nf, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);
1596 
1597     return toClose.forget();
1598 }
1599 
1600 static bool
intl_FormatNumber(JSContext * cx,UNumberFormat * nf,double x,MutableHandleValue result)1601 intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
1602 {
1603     // FormatNumber doesn't consider -0.0 to be negative.
1604     if (IsNegativeZero(x))
1605         x = 0.0;
1606 
1607     Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
1608     if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
1609         return false;
1610     UErrorCode status = U_ZERO_ERROR;
1611     int size = unum_formatDouble(nf, x, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE,
1612                                  nullptr, &status);
1613     if (status == U_BUFFER_OVERFLOW_ERROR) {
1614         if (!chars.resize(size))
1615             return false;
1616         status = U_ZERO_ERROR;
1617         unum_formatDouble(nf, x, Char16ToUChar(chars.begin()), size, nullptr, &status);
1618     }
1619     if (U_FAILURE(status)) {
1620         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1621         return false;
1622     }
1623 
1624     JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size);
1625     if (!str)
1626         return false;
1627 
1628     result.setString(str);
1629     return true;
1630 }
1631 
1632 bool
intl_FormatNumber(JSContext * cx,unsigned argc,Value * vp)1633 js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp)
1634 {
1635     CallArgs args = CallArgsFromVp(argc, vp);
1636     MOZ_ASSERT(args.length() == 2);
1637     MOZ_ASSERT(args[0].isObject());
1638     MOZ_ASSERT(args[1].isNumber());
1639 
1640     RootedObject numberFormat(cx, &args[0].toObject());
1641 
1642     // Obtain a UNumberFormat object, cached if possible.
1643     bool isNumberFormatInstance = numberFormat->getClass() == &NumberFormatClass;
1644     UNumberFormat* nf;
1645     if (isNumberFormatInstance) {
1646         void* priv =
1647             numberFormat->as<NativeObject>().getReservedSlot(UNUMBER_FORMAT_SLOT).toPrivate();
1648         nf = static_cast<UNumberFormat*>(priv);
1649         if (!nf) {
1650             nf = NewUNumberFormat(cx, numberFormat);
1651             if (!nf)
1652                 return false;
1653             numberFormat->as<NativeObject>().setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nf));
1654         }
1655     } else {
1656         // There's no good place to cache the ICU number format for an object
1657         // that has been initialized as a NumberFormat but is not a
1658         // NumberFormat instance. One possibility might be to add a
1659         // NumberFormat instance as an internal property to each such object.
1660         nf = NewUNumberFormat(cx, numberFormat);
1661         if (!nf)
1662             return false;
1663     }
1664 
1665     // Use the UNumberFormat to actually format the number.
1666     RootedValue result(cx);
1667     bool success = intl_FormatNumber(cx, nf, args[1].toNumber(), &result);
1668 
1669     if (!isNumberFormatInstance)
1670         unum_close(nf);
1671     if (!success)
1672         return false;
1673     args.rval().set(result);
1674     return true;
1675 }
1676 
1677 
1678 /******************** DateTimeFormat ********************/
1679 
1680 static void dateTimeFormat_finalize(FreeOp* fop, JSObject* obj);
1681 
1682 static const uint32_t UDATE_FORMAT_SLOT = 0;
1683 static const uint32_t DATE_TIME_FORMAT_SLOTS_COUNT = 1;
1684 
1685 static const ClassOps DateTimeFormatClassOps = {
1686     nullptr, /* addProperty */
1687     nullptr, /* delProperty */
1688     nullptr, /* getProperty */
1689     nullptr, /* setProperty */
1690     nullptr, /* enumerate */
1691     nullptr, /* resolve */
1692     nullptr, /* mayResolve */
1693     dateTimeFormat_finalize
1694 };
1695 
1696 static const Class DateTimeFormatClass = {
1697     js_Object_str,
1698     JSCLASS_HAS_RESERVED_SLOTS(DATE_TIME_FORMAT_SLOTS_COUNT) |
1699     JSCLASS_FOREGROUND_FINALIZE,
1700     &DateTimeFormatClassOps
1701 };
1702 
1703 #if JS_HAS_TOSOURCE
1704 static bool
dateTimeFormat_toSource(JSContext * cx,unsigned argc,Value * vp)1705 dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp)
1706 {
1707     CallArgs args = CallArgsFromVp(argc, vp);
1708     args.rval().setString(cx->names().DateTimeFormat);
1709     return true;
1710 }
1711 #endif
1712 
1713 static const JSFunctionSpec dateTimeFormat_static_methods[] = {
1714     JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_DateTimeFormat_supportedLocalesOf", 1, 0),
1715     JS_FS_END
1716 };
1717 
1718 static const JSFunctionSpec dateTimeFormat_methods[] = {
1719     JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions", 0, 0),
1720     JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 0, 0),
1721 #if JS_HAS_TOSOURCE
1722     JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0),
1723 #endif
1724     JS_FS_END
1725 };
1726 
1727 /**
1728  * DateTimeFormat constructor.
1729  * Spec: ECMAScript Internationalization API Specification, 12.1
1730  */
1731 static bool
DateTimeFormat(JSContext * cx,const CallArgs & args,bool construct)1732 DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct)
1733 {
1734     RootedObject obj(cx);
1735 
1736     if (!construct) {
1737         // 12.1.2.1 step 3
1738         JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
1739         if (!intl)
1740             return false;
1741         RootedValue self(cx, args.thisv());
1742         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
1743             // 12.1.2.1 step 4
1744             obj = ToObject(cx, self);
1745             if (!obj)
1746                 return false;
1747 
1748             // 12.1.2.1 step 5
1749             bool extensible;
1750             if (!IsExtensible(cx, obj, &extensible))
1751                 return false;
1752             if (!extensible)
1753                 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
1754         } else {
1755             // 12.1.2.1 step 3.a
1756             construct = true;
1757         }
1758     }
1759     if (construct) {
1760         // 12.1.3.1 paragraph 2
1761         RootedObject proto(cx, cx->global()->getOrCreateDateTimeFormatPrototype(cx));
1762         if (!proto)
1763             return false;
1764         obj = NewObjectWithGivenProto(cx, &DateTimeFormatClass, proto);
1765         if (!obj)
1766             return false;
1767 
1768         obj->as<NativeObject>().setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr));
1769     }
1770 
1771     // 12.1.2.1 steps 1 and 2; 12.1.3.1 steps 1 and 2
1772     RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
1773     RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
1774 
1775     // 12.1.2.1 step 6; 12.1.3.1 step 3
1776     if (!IntlInitialize(cx, obj, cx->names().InitializeDateTimeFormat, locales, options))
1777         return false;
1778 
1779     // 12.1.2.1 steps 3.a and 7
1780     args.rval().setObject(*obj);
1781     return true;
1782 }
1783 
1784 static bool
DateTimeFormat(JSContext * cx,unsigned argc,Value * vp)1785 DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
1786 {
1787     CallArgs args = CallArgsFromVp(argc, vp);
1788     return DateTimeFormat(cx, args, args.isConstructing());
1789 }
1790 
1791 bool
intl_DateTimeFormat(JSContext * cx,unsigned argc,Value * vp)1792 js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
1793 {
1794     CallArgs args = CallArgsFromVp(argc, vp);
1795     MOZ_ASSERT(args.length() == 2);
1796     // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it
1797     // cannot be used with "new", but it still has to be treated as a
1798     // constructor.
1799     return DateTimeFormat(cx, args, true);
1800 }
1801 
1802 static void
dateTimeFormat_finalize(FreeOp * fop,JSObject * obj)1803 dateTimeFormat_finalize(FreeOp* fop, JSObject* obj)
1804 {
1805     MOZ_ASSERT(fop->onMainThread());
1806 
1807     // This is-undefined check shouldn't be necessary, but for internal
1808     // brokenness in object allocation code.  For the moment, hack around it by
1809     // explicitly guarding against the possibility of the reserved slot not
1810     // containing a private.  See bug 949220.
1811     const Value& slot = obj->as<NativeObject>().getReservedSlot(UDATE_FORMAT_SLOT);
1812     if (!slot.isUndefined()) {
1813         if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate()))
1814             udat_close(df);
1815     }
1816 }
1817 
1818 static JSObject*
CreateDateTimeFormatPrototype(JSContext * cx,HandleObject Intl,Handle<GlobalObject * > global)1819 CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
1820 {
1821     RootedFunction ctor(cx);
1822     ctor = global->createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
1823     if (!ctor)
1824         return nullptr;
1825 
1826     RootedNativeObject proto(cx, global->createBlankPrototype(cx, &DateTimeFormatClass));
1827     if (!proto)
1828         return nullptr;
1829     proto->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr));
1830 
1831     if (!LinkConstructorAndPrototype(cx, ctor, proto))
1832         return nullptr;
1833 
1834     // 12.2.2
1835     if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods))
1836         return nullptr;
1837 
1838     // 12.3.2 and 12.3.3
1839     if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods))
1840         return nullptr;
1841 
1842     // Install a getter for DateTimeFormat.prototype.format that returns a
1843     // formatting function bound to a specified DateTimeFormat object (suitable
1844     // for passing to methods like Array.prototype.map).
1845     RootedValue getter(cx);
1846     if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().DateTimeFormatFormatGet,
1847                                          &getter))
1848     {
1849         return nullptr;
1850     }
1851     if (!DefineProperty(cx, proto, cx->names().format, UndefinedHandleValue,
1852                         JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()),
1853                         nullptr, JSPROP_GETTER | JSPROP_SHARED))
1854     {
1855         return nullptr;
1856     }
1857 
1858     RootedValue options(cx);
1859     if (!CreateDefaultOptions(cx, &options))
1860         return nullptr;
1861 
1862     // 12.2.1 and 12.3
1863     if (!IntlInitialize(cx, proto, cx->names().InitializeDateTimeFormat, UndefinedHandleValue,
1864                         options))
1865     {
1866         return nullptr;
1867     }
1868 
1869     // 8.1
1870     RootedValue ctorValue(cx, ObjectValue(*ctor));
1871     if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0))
1872         return nullptr;
1873 
1874     return proto;
1875 }
1876 
1877 bool
intl_DateTimeFormat_availableLocales(JSContext * cx,unsigned argc,Value * vp)1878 js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
1879 {
1880     CallArgs args = CallArgsFromVp(argc, vp);
1881     MOZ_ASSERT(args.length() == 0);
1882 
1883     RootedValue result(cx);
1884     if (!intl_availableLocales(cx, udat_countAvailable, udat_getAvailable, &result))
1885         return false;
1886     args.rval().set(result);
1887     return true;
1888 }
1889 
1890 // ICU returns old-style keyword values; map them to BCP 47 equivalents
1891 // (see http://bugs.icu-project.org/trac/ticket/9620).
1892 static const char*
bcp47CalendarName(const char * icuName)1893 bcp47CalendarName(const char* icuName)
1894 {
1895     if (equal(icuName, "ethiopic-amete-alem"))
1896         return "ethioaa";
1897     if (equal(icuName, "gregorian"))
1898         return "gregory";
1899     if (equal(icuName, "islamic-civil"))
1900         return "islamicc";
1901     return icuName;
1902 }
1903 
1904 bool
intl_availableCalendars(JSContext * cx,unsigned argc,Value * vp)1905 js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp)
1906 {
1907     CallArgs args = CallArgsFromVp(argc, vp);
1908     MOZ_ASSERT(args.length() == 1);
1909     MOZ_ASSERT(args[0].isString());
1910 
1911     JSAutoByteString locale(cx, args[0].toString());
1912     if (!locale)
1913         return false;
1914 
1915     RootedObject calendars(cx, NewDenseEmptyArray(cx));
1916     if (!calendars)
1917         return false;
1918     uint32_t index = 0;
1919 
1920     // We need the default calendar for the locale as the first result.
1921     UErrorCode status = U_ZERO_ERROR;
1922     RootedString jscalendar(cx);
1923     {
1924         UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status);
1925 
1926         // This correctly handles nullptr |cal| when opening failed.
1927         ScopedICUObject<UCalendar, ucal_close> closeCalendar(cal);
1928 
1929         const char* calendar = ucal_getType(cal, &status);
1930         if (U_FAILURE(status)) {
1931             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1932             return false;
1933         }
1934 
1935         jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar));
1936         if (!jscalendar)
1937             return false;
1938     }
1939 
1940     RootedValue element(cx, StringValue(jscalendar));
1941     if (!DefineElement(cx, calendars, index++, element))
1942         return false;
1943 
1944     // Now get the calendars that "would make a difference", i.e., not the default.
1945     UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status);
1946     if (U_FAILURE(status)) {
1947         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1948         return false;
1949     }
1950     ScopedICUObject<UEnumeration, uenum_close> toClose(values);
1951 
1952     uint32_t count = uenum_count(values, &status);
1953     if (U_FAILURE(status)) {
1954         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1955         return false;
1956     }
1957 
1958     for (; count > 0; count--) {
1959         const char* calendar = uenum_next(values, nullptr, &status);
1960         if (U_FAILURE(status)) {
1961             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1962             return false;
1963         }
1964 
1965         jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar));
1966         if (!jscalendar)
1967             return false;
1968         element = StringValue(jscalendar);
1969         if (!DefineElement(cx, calendars, index++, element))
1970             return false;
1971     }
1972 
1973     args.rval().setObject(*calendars);
1974     return true;
1975 }
1976 
1977 template<typename Char>
1978 static constexpr Char
ToUpperASCII(Char c)1979 ToUpperASCII(Char c)
1980 {
1981     return ('a' <= c && c <= 'z')
1982            ? (c & ~0x20)
1983            : c;
1984 }
1985 
1986 static_assert(ToUpperASCII('a') == 'A', "verifying 'a' uppercases correctly");
1987 static_assert(ToUpperASCII('m') == 'M', "verifying 'm' uppercases correctly");
1988 static_assert(ToUpperASCII('z') == 'Z', "verifying 'z' uppercases correctly");
1989 static_assert(ToUpperASCII(u'a') == u'A', "verifying u'a' uppercases correctly");
1990 static_assert(ToUpperASCII(u'k') == u'K', "verifying u'k' uppercases correctly");
1991 static_assert(ToUpperASCII(u'z') == u'Z', "verifying u'z' uppercases correctly");
1992 
1993 template<typename Char1, typename Char2>
1994 static bool
EqualCharsIgnoreCaseASCII(const Char1 * s1,const Char2 * s2,size_t len)1995 EqualCharsIgnoreCaseASCII(const Char1* s1, const Char2* s2, size_t len)
1996 {
1997     for (const Char1* s1end = s1 + len; s1 < s1end; s1++, s2++) {
1998         if (ToUpperASCII(*s1) != ToUpperASCII(*s2))
1999             return false;
2000     }
2001     return true;
2002 }
2003 
2004 template<typename Char>
2005 static js::HashNumber
HashStringIgnoreCaseASCII(const Char * s,size_t length)2006 HashStringIgnoreCaseASCII(const Char* s, size_t length)
2007 {
2008     uint32_t hash = 0;
2009     for (size_t i = 0; i < length; i++)
2010         hash = mozilla::AddToHash(hash, ToUpperASCII(s[i]));
2011     return hash;
2012 }
2013 
Lookup(JSFlatString * timeZone)2014 js::SharedIntlData::TimeZoneHasher::Lookup::Lookup(JSFlatString* timeZone)
2015   : isLatin1(timeZone->hasLatin1Chars()), length(timeZone->length())
2016 {
2017     if (isLatin1) {
2018         latin1Chars = timeZone->latin1Chars(nogc);
2019         hash = HashStringIgnoreCaseASCII(latin1Chars, length);
2020     } else {
2021         twoByteChars = timeZone->twoByteChars(nogc);
2022         hash = HashStringIgnoreCaseASCII(twoByteChars, length);
2023     }
2024 }
2025 
2026 bool
match(TimeZoneName key,const Lookup & lookup)2027 js::SharedIntlData::TimeZoneHasher::match(TimeZoneName key, const Lookup& lookup)
2028 {
2029     if (key->length() != lookup.length)
2030         return false;
2031 
2032     // Compare time zone names ignoring ASCII case differences.
2033     if (key->hasLatin1Chars()) {
2034         const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
2035         if (lookup.isLatin1)
2036             return EqualCharsIgnoreCaseASCII(keyChars, lookup.latin1Chars, lookup.length);
2037         return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
2038     }
2039 
2040     const char16_t* keyChars = key->twoByteChars(lookup.nogc);
2041     if (lookup.isLatin1)
2042         return EqualCharsIgnoreCaseASCII(lookup.latin1Chars, keyChars, lookup.length);
2043     return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
2044 }
2045 
2046 static bool
IsLegacyICUTimeZone(const char * timeZone)2047 IsLegacyICUTimeZone(const char* timeZone)
2048 {
2049     for (const auto& legacyTimeZone : js::timezone::legacyICUTimeZones) {
2050         if (equal(timeZone, legacyTimeZone))
2051             return true;
2052     }
2053     return false;
2054 }
2055 
2056 bool
ensureTimeZones(JSContext * cx)2057 js::SharedIntlData::ensureTimeZones(JSContext* cx)
2058 {
2059     if (timeZoneDataInitialized)
2060         return true;
2061 
2062     // If initTimeZones() was called previously, but didn't complete due to
2063     // OOM, clear all sets/maps and start from scratch.
2064     if (availableTimeZones.initialized())
2065         availableTimeZones.finish();
2066     if (!availableTimeZones.init()) {
2067         ReportOutOfMemory(cx);
2068         return false;
2069     }
2070 
2071     UErrorCode status = U_ZERO_ERROR;
2072     UEnumeration* values = ucal_openTimeZones(&status);
2073     if (U_FAILURE(status)) {
2074         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2075         return false;
2076     }
2077     ScopedICUObject<UEnumeration, uenum_close> toClose(values);
2078 
2079     RootedAtom timeZone(cx);
2080     while (true) {
2081         int32_t size;
2082         const char* rawTimeZone = uenum_next(values, &size, &status);
2083         if (U_FAILURE(status)) {
2084             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2085             return false;
2086         }
2087 
2088         if (rawTimeZone == nullptr)
2089             break;
2090 
2091         // Skip legacy ICU time zone names.
2092         if (IsLegacyICUTimeZone(rawTimeZone))
2093             continue;
2094 
2095         MOZ_ASSERT(size >= 0);
2096         timeZone = Atomize(cx, rawTimeZone, size_t(size));
2097         if (!timeZone)
2098             return false;
2099 
2100         TimeZoneHasher::Lookup lookup(timeZone);
2101         TimeZoneSet::AddPtr p = availableTimeZones.lookupForAdd(lookup);
2102 
2103         // ICU shouldn't report any duplicate time zone names, but if it does,
2104         // just ignore the duplicate name.
2105         if (!p && !availableTimeZones.add(p, timeZone)) {
2106             ReportOutOfMemory(cx);
2107             return false;
2108         }
2109     }
2110 
2111     if (ianaZonesTreatedAsLinksByICU.initialized())
2112         ianaZonesTreatedAsLinksByICU.finish();
2113     if (!ianaZonesTreatedAsLinksByICU.init()) {
2114         ReportOutOfMemory(cx);
2115         return false;
2116     }
2117 
2118     for (const char* rawTimeZone : timezone::ianaZonesTreatedAsLinksByICU) {
2119         MOZ_ASSERT(rawTimeZone != nullptr);
2120         timeZone = Atomize(cx, rawTimeZone, strlen(rawTimeZone));
2121         if (!timeZone)
2122             return false;
2123 
2124         TimeZoneHasher::Lookup lookup(timeZone);
2125         TimeZoneSet::AddPtr p = ianaZonesTreatedAsLinksByICU.lookupForAdd(lookup);
2126         MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaZonesTreatedAsLinksByICU");
2127 
2128         if (!ianaZonesTreatedAsLinksByICU.add(p, timeZone)) {
2129             ReportOutOfMemory(cx);
2130             return false;
2131         }
2132     }
2133 
2134     if (ianaLinksCanonicalizedDifferentlyByICU.initialized())
2135         ianaLinksCanonicalizedDifferentlyByICU.finish();
2136     if (!ianaLinksCanonicalizedDifferentlyByICU.init()) {
2137         ReportOutOfMemory(cx);
2138         return false;
2139     }
2140 
2141     RootedAtom linkName(cx);
2142     RootedAtom& target = timeZone;
2143     for (const auto& linkAndTarget : timezone::ianaLinksCanonicalizedDifferentlyByICU) {
2144         const char* rawLinkName = linkAndTarget.link;
2145         const char* rawTarget = linkAndTarget.target;
2146 
2147         MOZ_ASSERT(rawLinkName != nullptr);
2148         linkName = Atomize(cx, rawLinkName, strlen(rawLinkName));
2149         if (!linkName)
2150             return false;
2151 
2152         MOZ_ASSERT(rawTarget != nullptr);
2153         target = Atomize(cx, rawTarget, strlen(rawTarget));
2154         if (!target)
2155             return false;
2156 
2157         TimeZoneHasher::Lookup lookup(linkName);
2158         TimeZoneMap::AddPtr p = ianaLinksCanonicalizedDifferentlyByICU.lookupForAdd(lookup);
2159         MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaLinksCanonicalizedDifferentlyByICU");
2160 
2161         if (!ianaLinksCanonicalizedDifferentlyByICU.add(p, linkName, target)) {
2162             ReportOutOfMemory(cx);
2163             return false;
2164         }
2165     }
2166 
2167     MOZ_ASSERT(!timeZoneDataInitialized, "ensureTimeZones is neither reentrant nor thread-safe");
2168     timeZoneDataInitialized = true;
2169 
2170     return true;
2171 }
2172 
2173 bool
validateTimeZoneName(JSContext * cx,HandleString timeZone,MutableHandleString result)2174 js::SharedIntlData::validateTimeZoneName(JSContext* cx, HandleString timeZone,
2175                                          MutableHandleString result)
2176 {
2177     if (!ensureTimeZones(cx))
2178         return false;
2179 
2180     Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
2181     if (!timeZoneFlat)
2182         return false;
2183 
2184     TimeZoneHasher::Lookup lookup(timeZoneFlat);
2185     if (TimeZoneSet::Ptr p = availableTimeZones.lookup(lookup))
2186         result.set(*p);
2187 
2188     return true;
2189 }
2190 
2191 bool
tryCanonicalizeTimeZoneConsistentWithIANA(JSContext * cx,HandleString timeZone,MutableHandleString result)2192 js::SharedIntlData::tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, HandleString timeZone,
2193                                                               MutableHandleString result)
2194 {
2195     if (!ensureTimeZones(cx))
2196         return false;
2197 
2198     Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
2199     if (!timeZoneFlat)
2200         return false;
2201 
2202     TimeZoneHasher::Lookup lookup(timeZoneFlat);
2203     MOZ_ASSERT(availableTimeZones.has(lookup), "Invalid time zone name");
2204 
2205     if (TimeZoneMap::Ptr p = ianaLinksCanonicalizedDifferentlyByICU.lookup(lookup)) {
2206         // The effectively supported time zones aren't known at compile time,
2207         // when
2208         // 1. SpiderMonkey was compiled with "--with-system-icu".
2209         // 2. ICU's dynamic time zone data loading feature was used.
2210         //    (ICU supports loading time zone files at runtime through the
2211         //    ICU_TIMEZONE_FILES_DIR environment variable.)
2212         // Ensure ICU supports the new target zone before applying the update.
2213         TimeZoneName targetTimeZone = p->value();
2214         TimeZoneHasher::Lookup targetLookup(targetTimeZone);
2215         if (availableTimeZones.has(targetLookup))
2216             result.set(targetTimeZone);
2217     } else if (TimeZoneSet::Ptr p = ianaZonesTreatedAsLinksByICU.lookup(lookup)) {
2218         result.set(*p);
2219     }
2220 
2221     return true;
2222 }
2223 
2224 void
destroyInstance()2225 js::SharedIntlData::destroyInstance()
2226 {
2227     availableTimeZones.finish();
2228     ianaZonesTreatedAsLinksByICU.finish();
2229     ianaLinksCanonicalizedDifferentlyByICU.finish();
2230 }
2231 
2232 void
trace(JSTracer * trc)2233 js::SharedIntlData::trace(JSTracer* trc)
2234 {
2235     // Atoms are always tenured.
2236     if (!trc->runtime()->isHeapMinorCollecting()) {
2237         availableTimeZones.trace(trc);
2238         ianaZonesTreatedAsLinksByICU.trace(trc);
2239         ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
2240     }
2241 }
2242 
2243 size_t
sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const2244 js::SharedIntlData::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
2245 {
2246     return availableTimeZones.sizeOfExcludingThis(mallocSizeOf) +
2247            ianaZonesTreatedAsLinksByICU.sizeOfExcludingThis(mallocSizeOf) +
2248            ianaLinksCanonicalizedDifferentlyByICU.sizeOfExcludingThis(mallocSizeOf);
2249 }
2250 
2251 bool
intl_IsValidTimeZoneName(JSContext * cx,unsigned argc,Value * vp)2252 js::intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp)
2253 {
2254     CallArgs args = CallArgsFromVp(argc, vp);
2255     MOZ_ASSERT(args.length() == 1);
2256     MOZ_ASSERT(args[0].isString());
2257 
2258     SharedIntlData& sharedIntlData = cx->sharedIntlData;
2259 
2260     RootedString timeZone(cx, args[0].toString());
2261     RootedString validatedTimeZone(cx);
2262     if (!sharedIntlData.validateTimeZoneName(cx, timeZone, &validatedTimeZone))
2263         return false;
2264 
2265     if (validatedTimeZone)
2266         args.rval().setString(validatedTimeZone);
2267     else
2268         args.rval().setNull();
2269 
2270     return true;
2271 }
2272 
2273 bool
intl_canonicalizeTimeZone(JSContext * cx,unsigned argc,Value * vp)2274 js::intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp)
2275 {
2276     CallArgs args = CallArgsFromVp(argc, vp);
2277     MOZ_ASSERT(args.length() == 1);
2278     MOZ_ASSERT(args[0].isString());
2279 
2280     SharedIntlData& sharedIntlData = cx->sharedIntlData;
2281 
2282     // Some time zone names are canonicalized differently by ICU -- handle
2283     // those first:
2284     RootedString timeZone(cx, args[0].toString());
2285     RootedString ianaTimeZone(cx);
2286     if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA(cx, timeZone, &ianaTimeZone))
2287         return false;
2288 
2289     if (ianaTimeZone) {
2290         args.rval().setString(ianaTimeZone);
2291         return true;
2292     }
2293 
2294     AutoStableStringChars stableChars(cx);
2295     if (!stableChars.initTwoByte(cx, timeZone))
2296         return false;
2297 
2298     mozilla::Range<const char16_t> tzchars = stableChars.twoByteRange();
2299 
2300     Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
2301     if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
2302         return false;
2303 
2304     UBool* isSystemID = nullptr;
2305     UErrorCode status = U_ZERO_ERROR;
2306     int32_t size = ucal_getCanonicalTimeZoneID(Char16ToUChar(tzchars.begin().get()),
2307                                                tzchars.length(), Char16ToUChar(chars.begin()),
2308                                                INITIAL_CHAR_BUFFER_SIZE, isSystemID, &status);
2309     if (status == U_BUFFER_OVERFLOW_ERROR) {
2310         MOZ_ASSERT(size >= 0);
2311         if (!chars.resize(size_t(size)))
2312             return false;
2313         status = U_ZERO_ERROR;
2314         ucal_getCanonicalTimeZoneID(Char16ToUChar(tzchars.begin().get()), tzchars.length(),
2315                                     Char16ToUChar(chars.begin()), size, isSystemID, &status);
2316     }
2317     if (U_FAILURE(status)) {
2318         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2319         return false;
2320     }
2321 
2322     MOZ_ASSERT(size >= 0);
2323     JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size));
2324     if (!str)
2325         return false;
2326     args.rval().setString(str);
2327     return true;
2328 }
2329 
2330 bool
intl_defaultTimeZone(JSContext * cx,unsigned argc,Value * vp)2331 js::intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp)
2332 {
2333     CallArgs args = CallArgsFromVp(argc, vp);
2334     MOZ_ASSERT(args.length() == 0);
2335 
2336     // The current default might be stale, because JS::ResetTimeZone() doesn't
2337     // immediately update ICU's default time zone. So perform an update if
2338     // needed.
2339     js::ResyncICUDefaultTimeZone();
2340 
2341     Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
2342     if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
2343         return false;
2344 
2345     UErrorCode status = U_ZERO_ERROR;
2346     int32_t size = ucal_getDefaultTimeZone(Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE,
2347                                            &status);
2348     if (status == U_BUFFER_OVERFLOW_ERROR) {
2349         MOZ_ASSERT(size >= 0);
2350         if (!chars.resize(size_t(size)))
2351             return false;
2352         status = U_ZERO_ERROR;
2353         ucal_getDefaultTimeZone(Char16ToUChar(chars.begin()), size, &status);
2354     }
2355     if (U_FAILURE(status)) {
2356         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2357         return false;
2358     }
2359 
2360     MOZ_ASSERT(size >= 0);
2361     JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size));
2362     if (!str)
2363         return false;
2364     args.rval().setString(str);
2365     return true;
2366 }
2367 
2368 bool
intl_defaultTimeZoneOffset(JSContext * cx,unsigned argc,Value * vp)2369 js::intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp) {
2370     CallArgs args = CallArgsFromVp(argc, vp);
2371     MOZ_ASSERT(args.length() == 0);
2372 
2373     UErrorCode status = U_ZERO_ERROR;
2374     const UChar* uTimeZone = nullptr;
2375     int32_t uTimeZoneLength = 0;
2376     const char* rootLocale = "";
2377     UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, rootLocale, UCAL_DEFAULT, &status);
2378     if (U_FAILURE(status)) {
2379         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2380         return false;
2381     }
2382     ScopedICUObject<UCalendar, ucal_close> toClose(cal);
2383 
2384     int32_t offset = ucal_get(cal, UCAL_ZONE_OFFSET, &status);
2385     if (U_FAILURE(status)) {
2386         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2387         return false;
2388     }
2389 
2390     args.rval().setInt32(offset);
2391     return true;
2392 }
2393 
2394 bool
intl_patternForSkeleton(JSContext * cx,unsigned argc,Value * vp)2395 js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp)
2396 {
2397     CallArgs args = CallArgsFromVp(argc, vp);
2398     MOZ_ASSERT(args.length() == 2);
2399     MOZ_ASSERT(args[0].isString());
2400     MOZ_ASSERT(args[1].isString());
2401 
2402     JSAutoByteString locale(cx, args[0].toString());
2403     if (!locale)
2404         return false;
2405 
2406     JSFlatString* skeletonFlat = args[1].toString()->ensureFlat(cx);
2407     if (!skeletonFlat)
2408         return false;
2409 
2410     AutoStableStringChars stableChars(cx);
2411     if (!stableChars.initTwoByte(cx, skeletonFlat))
2412         return false;
2413 
2414     mozilla::Range<const char16_t> skeletonChars = stableChars.twoByteRange();
2415     uint32_t skeletonLen = u_strlen(Char16ToUChar(skeletonChars.begin().get()));
2416 
2417     UErrorCode status = U_ZERO_ERROR;
2418     UDateTimePatternGenerator* gen = udatpg_open(icuLocale(locale.ptr()), &status);
2419     if (U_FAILURE(status)) {
2420         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2421         return false;
2422     }
2423     ScopedICUObject<UDateTimePatternGenerator, udatpg_close> toClose(gen);
2424 
2425     int32_t size = udatpg_getBestPattern(gen, Char16ToUChar(skeletonChars.begin().get()),
2426                                          skeletonLen, nullptr, 0, &status);
2427     if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
2428         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2429         return false;
2430     }
2431     ScopedJSFreePtr<UChar> pattern(cx->pod_malloc<UChar>(size + 1));
2432     if (!pattern)
2433         return false;
2434     pattern[size] = '\0';
2435     status = U_ZERO_ERROR;
2436     udatpg_getBestPattern(gen, Char16ToUChar(skeletonChars.begin().get()),
2437                           skeletonLen, pattern, size, &status);
2438     if (U_FAILURE(status)) {
2439         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2440         return false;
2441     }
2442 
2443     RootedString str(cx, JS_NewUCStringCopyZ(cx, reinterpret_cast<char16_t*>(pattern.get())));
2444     if (!str)
2445         return false;
2446     args.rval().setString(str);
2447     return true;
2448 }
2449 
2450 /**
2451  * Returns a new UDateFormat with the locale and date-time formatting options
2452  * of the given DateTimeFormat.
2453  */
2454 static UDateFormat*
NewUDateFormat(JSContext * cx,HandleObject dateTimeFormat)2455 NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat)
2456 {
2457     RootedValue value(cx);
2458 
2459     RootedObject internals(cx, GetInternals(cx, dateTimeFormat));
2460     if (!internals)
2461        return nullptr;
2462 
2463     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
2464         return nullptr;
2465     JSAutoByteString locale(cx, value.toString());
2466     if (!locale)
2467         return nullptr;
2468 
2469     // We don't need to look at calendar and numberingSystem - they can only be
2470     // set via the Unicode locale extension and are therefore already set on
2471     // locale.
2472 
2473     if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value))
2474         return nullptr;
2475 
2476     AutoStableStringChars timeZoneChars(cx);
2477     Rooted<JSFlatString*> timeZoneFlat(cx, value.toString()->ensureFlat(cx));
2478     if (!timeZoneFlat || !timeZoneChars.initTwoByte(cx, timeZoneFlat))
2479         return nullptr;
2480 
2481     const UChar* uTimeZone = Char16ToUChar(timeZoneChars.twoByteRange().begin().get());
2482     uint32_t uTimeZoneLength = u_strlen(uTimeZone);
2483 
2484     if (!GetProperty(cx, internals, internals, cx->names().pattern, &value))
2485         return nullptr;
2486 
2487     AutoStableStringChars patternChars(cx);
2488     Rooted<JSFlatString*> patternFlat(cx, value.toString()->ensureFlat(cx));
2489     if (!patternFlat || !patternChars.initTwoByte(cx, patternFlat))
2490         return nullptr;
2491 
2492     const UChar* uPattern = Char16ToUChar(patternChars.twoByteRange().begin().get());
2493     uint32_t uPatternLength = u_strlen(uPattern);
2494 
2495     UErrorCode status = U_ZERO_ERROR;
2496     UDateFormat* df =
2497         udat_open(UDAT_PATTERN, UDAT_PATTERN, icuLocale(locale.ptr()), uTimeZone, uTimeZoneLength,
2498                   uPattern, uPatternLength, &status);
2499     if (U_FAILURE(status)) {
2500         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2501         return nullptr;
2502     }
2503 
2504     // ECMAScript requires the Gregorian calendar to be used from the beginning
2505     // of ECMAScript time.
2506     UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(df));
2507     ucal_setGregorianChange(cal, StartOfTime, &status);
2508 
2509     // An error here means the calendar is not Gregorian, so we don't care.
2510 
2511     return df;
2512 }
2513 
2514 static bool
intl_FormatDateTime(JSContext * cx,UDateFormat * df,double x,MutableHandleValue result)2515 intl_FormatDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result)
2516 {
2517     if (!IsFinite(x)) {
2518         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE);
2519         return false;
2520     }
2521 
2522     Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
2523     if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
2524         return false;
2525     UErrorCode status = U_ZERO_ERROR;
2526     int size = udat_format(df, x, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE,
2527                            nullptr, &status);
2528     if (status == U_BUFFER_OVERFLOW_ERROR) {
2529         if (!chars.resize(size))
2530             return false;
2531         status = U_ZERO_ERROR;
2532         udat_format(df, x, Char16ToUChar(chars.begin()), size, nullptr, &status);
2533     }
2534     if (U_FAILURE(status)) {
2535         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2536         return false;
2537     }
2538 
2539     JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size);
2540     if (!str)
2541         return false;
2542 
2543     result.setString(str);
2544 
2545     return true;
2546 }
2547 
2548 using FieldType = ImmutablePropertyNamePtr JSAtomState::*;
2549 
2550 static FieldType
GetFieldTypeForFormatField(UDateFormatField fieldName)2551 GetFieldTypeForFormatField(UDateFormatField fieldName)
2552 {
2553     // See intl/icu/source/i18n/unicode/udat.h for a detailed field list.  This
2554     // switch is deliberately exhaustive: cases might have to be added/removed
2555     // if this code is compiled with a different ICU with more
2556     // UDateFormatField enum initializers.  Please guard such cases with
2557     // appropriate ICU version-testing #ifdefs, should cross-version divergence
2558     // occur.
2559     switch (fieldName) {
2560       case UDAT_ERA_FIELD:
2561         return &JSAtomState::era;
2562       case UDAT_YEAR_FIELD:
2563       case UDAT_YEAR_WOY_FIELD:
2564       case UDAT_EXTENDED_YEAR_FIELD:
2565       case UDAT_YEAR_NAME_FIELD:
2566         return &JSAtomState::year;
2567 
2568       case UDAT_MONTH_FIELD:
2569       case UDAT_STANDALONE_MONTH_FIELD:
2570         return &JSAtomState::month;
2571 
2572       case UDAT_DATE_FIELD:
2573       case UDAT_JULIAN_DAY_FIELD:
2574         return &JSAtomState::day;
2575 
2576       case UDAT_HOUR_OF_DAY1_FIELD:
2577       case UDAT_HOUR_OF_DAY0_FIELD:
2578       case UDAT_HOUR1_FIELD:
2579       case UDAT_HOUR0_FIELD:
2580         return &JSAtomState::hour;
2581 
2582       case UDAT_MINUTE_FIELD:
2583         return &JSAtomState::minute;
2584 
2585       case UDAT_SECOND_FIELD:
2586         return &JSAtomState::second;
2587 
2588       case UDAT_DAY_OF_WEEK_FIELD:
2589       case UDAT_STANDALONE_DAY_FIELD:
2590       case UDAT_DOW_LOCAL_FIELD:
2591       case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD:
2592         return &JSAtomState::weekday;
2593 
2594       case UDAT_AM_PM_FIELD:
2595         return &JSAtomState::dayPeriod;
2596 
2597       case UDAT_TIMEZONE_FIELD:
2598         return &JSAtomState::timeZoneName;
2599 
2600       case UDAT_FRACTIONAL_SECOND_FIELD:
2601       case UDAT_DAY_OF_YEAR_FIELD:
2602       case UDAT_WEEK_OF_YEAR_FIELD:
2603       case UDAT_WEEK_OF_MONTH_FIELD:
2604       case UDAT_MILLISECONDS_IN_DAY_FIELD:
2605       case UDAT_TIMEZONE_RFC_FIELD:
2606       case UDAT_TIMEZONE_GENERIC_FIELD:
2607       case UDAT_QUARTER_FIELD:
2608       case UDAT_STANDALONE_QUARTER_FIELD:
2609       case UDAT_TIMEZONE_SPECIAL_FIELD:
2610       case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
2611       case UDAT_TIMEZONE_ISO_FIELD:
2612       case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
2613 #ifndef U_HIDE_INTERNAL_API
2614       case UDAT_RELATED_YEAR_FIELD:
2615 #endif
2616 #ifndef U_HIDE_DRAFT_API
2617       case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
2618       case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
2619 #endif
2620 #ifndef U_HIDE_INTERNAL_API
2621       case UDAT_TIME_SEPARATOR_FIELD:
2622 #endif
2623         // These fields are all unsupported.
2624         return nullptr;
2625 
2626       case UDAT_FIELD_COUNT:
2627         MOZ_ASSERT_UNREACHABLE("format field sentinel value returned by "
2628                                "iterator!");
2629     }
2630 
2631     MOZ_ASSERT_UNREACHABLE("unenumerated, undocumented format field returned "
2632                            "by iterator");
2633     return nullptr;
2634 }
2635 
2636 static bool
intl_FormatToPartsDateTime(JSContext * cx,UDateFormat * df,double x,MutableHandleValue result)2637 intl_FormatToPartsDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result)
2638 {
2639     if (!IsFinite(x)) {
2640         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE);
2641         return false;
2642     }
2643 
2644     Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
2645     if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
2646         return false;
2647 
2648     UErrorCode status = U_ZERO_ERROR;
2649     UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
2650     if (U_FAILURE(status)) {
2651         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2652         return false;
2653     }
2654     auto closeFieldPosIter = MakeScopeExit([&]() { ufieldpositer_close(fpositer); });
2655 
2656     int resultSize =
2657         udat_formatForFields(df, x, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE,
2658                              fpositer, &status);
2659     if (status == U_BUFFER_OVERFLOW_ERROR) {
2660         if (!chars.resize(resultSize))
2661             return false;
2662         status = U_ZERO_ERROR;
2663         udat_formatForFields(df, x, Char16ToUChar(chars.begin()), resultSize, fpositer, &status);
2664     }
2665     if (U_FAILURE(status)) {
2666         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2667         return false;
2668     }
2669 
2670     RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
2671     if (!partsArray)
2672         return false;
2673     if (resultSize == 0) {
2674         // An empty string contains no parts, so avoid extra work below.
2675         result.setObject(*partsArray);
2676         return true;
2677     }
2678 
2679     RootedString overallResult(cx, NewStringCopyN<CanGC>(cx, chars.begin(), resultSize));
2680     if (!overallResult)
2681         return false;
2682 
2683     size_t lastEndIndex = 0;
2684 
2685     uint32_t partIndex = 0;
2686     RootedObject singlePart(cx);
2687     RootedValue partType(cx);
2688     RootedString partSubstr(cx);
2689     RootedValue val(cx);
2690 
2691     auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) {
2692         singlePart = NewBuiltinClassInstance<PlainObject>(cx);
2693         if (!singlePart)
2694             return false;
2695 
2696         partType = StringValue(cx->names().*type);
2697         if (!DefineProperty(cx, singlePart, cx->names().type, partType))
2698             return false;
2699 
2700         partSubstr = SubstringKernel(cx, overallResult, beginIndex, endIndex - beginIndex);
2701         if (!partSubstr)
2702             return false;
2703 
2704         val = StringValue(partSubstr);
2705         if (!DefineProperty(cx, singlePart, cx->names().value, val))
2706             return false;
2707 
2708         val = ObjectValue(*singlePart);
2709         if (!DefineElement(cx, partsArray, partIndex, val))
2710             return false;
2711 
2712         lastEndIndex = endIndex;
2713         partIndex++;
2714         return true;
2715     };
2716 
2717     int32_t fieldInt, beginIndexInt, endIndexInt;
2718     while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt, &endIndexInt)) >= 0) {
2719         MOZ_ASSERT(beginIndexInt >= 0);
2720         MOZ_ASSERT(endIndexInt >= 0);
2721         MOZ_ASSERT(beginIndexInt <= endIndexInt,
2722                    "field iterator returning invalid range");
2723 
2724         size_t beginIndex(beginIndexInt);
2725         size_t endIndex(endIndexInt);
2726 
2727         // Technically this isn't guaranteed.  But it appears true in pratice,
2728         // and http://bugs.icu-project.org/trac/ticket/12024 is expected to
2729         // correct the documentation lapse.
2730         MOZ_ASSERT(lastEndIndex <= beginIndex,
2731                    "field iteration didn't return fields in order start to "
2732                    "finish as expected");
2733 
2734         if (FieldType type = GetFieldTypeForFormatField(static_cast<UDateFormatField>(fieldInt))) {
2735             if (lastEndIndex < beginIndex) {
2736                 if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex))
2737                     return false;
2738             }
2739 
2740             if (!AppendPart(type, beginIndex, endIndex))
2741                 return false;
2742         }
2743     }
2744 
2745     // Append any final literal.
2746     if (lastEndIndex < overallResult->length()) {
2747         if (!AppendPart(&JSAtomState::literal, lastEndIndex, overallResult->length()))
2748             return false;
2749     }
2750 
2751     result.setObject(*partsArray);
2752     return true;
2753 }
2754 
2755 bool
intl_FormatDateTime(JSContext * cx,unsigned argc,Value * vp)2756 js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp)
2757 {
2758     CallArgs args = CallArgsFromVp(argc, vp);
2759     MOZ_ASSERT(args.length() == 3);
2760     MOZ_ASSERT(args[0].isObject());
2761     MOZ_ASSERT(args[1].isNumber());
2762     MOZ_ASSERT(args[2].isBoolean());
2763 
2764     RootedObject dateTimeFormat(cx, &args[0].toObject());
2765 
2766     // Obtain a UDateFormat object, cached if possible.
2767     bool isDateTimeFormatInstance = dateTimeFormat->getClass() == &DateTimeFormatClass;
2768     UDateFormat* df;
2769     if (isDateTimeFormatInstance) {
2770         void* priv =
2771             dateTimeFormat->as<NativeObject>().getReservedSlot(UDATE_FORMAT_SLOT).toPrivate();
2772         df = static_cast<UDateFormat*>(priv);
2773         if (!df) {
2774             df = NewUDateFormat(cx, dateTimeFormat);
2775             if (!df)
2776                 return false;
2777             dateTimeFormat->as<NativeObject>().setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(df));
2778         }
2779     } else {
2780         // There's no good place to cache the ICU date-time format for an object
2781         // that has been initialized as a DateTimeFormat but is not a
2782         // DateTimeFormat instance. One possibility might be to add a
2783         // DateTimeFormat instance as an internal property to each such object.
2784         df = NewUDateFormat(cx, dateTimeFormat);
2785         if (!df)
2786             return false;
2787     }
2788 
2789     // Use the UDateFormat to actually format the time stamp.
2790     RootedValue result(cx);
2791     bool success = args[2].toBoolean()
2792                    ? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), &result)
2793                    : intl_FormatDateTime(cx, df, args[1].toNumber(), &result);
2794 
2795     if (!isDateTimeFormatInstance)
2796         udat_close(df);
2797     if (!success)
2798         return false;
2799     args.rval().set(result);
2800     return true;
2801 }
2802 
2803 bool
intl_GetCalendarInfo(JSContext * cx,unsigned argc,Value * vp)2804 js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp)
2805 {
2806     CallArgs args = CallArgsFromVp(argc, vp);
2807     MOZ_ASSERT(args.length() == 1);
2808 
2809     JSAutoByteString locale(cx, args[0].toString());
2810     if (!locale)
2811         return false;
2812 
2813     UErrorCode status = U_ZERO_ERROR;
2814     const UChar* uTimeZone = nullptr;
2815     int32_t uTimeZoneLength = 0;
2816     UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, locale.ptr(), UCAL_DEFAULT, &status);
2817     if (U_FAILURE(status)) {
2818         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2819         return false;
2820     }
2821     ScopedICUObject<UCalendar, ucal_close> toClose(cal);
2822 
2823     RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
2824     if (!info)
2825         return false;
2826 
2827     RootedValue v(cx);
2828     int32_t firstDayOfWeek = ucal_getAttribute(cal, UCAL_FIRST_DAY_OF_WEEK);
2829     v.setInt32(firstDayOfWeek);
2830 
2831     if (!DefineProperty(cx, info, cx->names().firstDayOfWeek, v))
2832         return false;
2833 
2834     int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
2835     v.setInt32(minDays);
2836     if (!DefineProperty(cx, info, cx->names().minDays, v))
2837         return false;
2838 
2839     UCalendarWeekdayType prevDayType = ucal_getDayOfWeekType(cal, UCAL_SATURDAY, &status);
2840     if (U_FAILURE(status)) {
2841         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2842         return false;
2843     }
2844 
2845     RootedValue weekendStart(cx), weekendEnd(cx);
2846 
2847     for (int i = UCAL_SUNDAY; i <= UCAL_SATURDAY; i++) {
2848         UCalendarDaysOfWeek dayOfWeek = static_cast<UCalendarDaysOfWeek>(i);
2849         UCalendarWeekdayType type = ucal_getDayOfWeekType(cal, dayOfWeek, &status);
2850         if (U_FAILURE(status)) {
2851             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2852             return false;
2853         }
2854 
2855         if (prevDayType != type) {
2856             switch (type) {
2857               case UCAL_WEEKDAY:
2858                 // If the first Weekday after Weekend is Sunday (1),
2859                 // then the last Weekend day is Saturday (7).
2860                 // Otherwise we'll just take the previous days number.
2861                 weekendEnd.setInt32(i == 1 ? 7 : i - 1);
2862                 break;
2863               case UCAL_WEEKEND:
2864                 weekendStart.setInt32(i);
2865                 break;
2866               case UCAL_WEEKEND_ONSET:
2867               case UCAL_WEEKEND_CEASE:
2868                 // At the time this code was added, ICU apparently never behaves this way,
2869                 // so just throw, so that users will report a bug and we can decide what to
2870                 // do.
2871                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
2872                 return false;
2873               default:
2874                 break;
2875             }
2876         }
2877 
2878         prevDayType = type;
2879     }
2880 
2881     MOZ_ASSERT(weekendStart.isInt32());
2882     MOZ_ASSERT(weekendEnd.isInt32());
2883 
2884     if (!DefineProperty(cx, info, cx->names().weekendStart, weekendStart))
2885         return false;
2886 
2887     if (!DefineProperty(cx, info, cx->names().weekendEnd, weekendEnd))
2888         return false;
2889 
2890     args.rval().setObject(*info);
2891     return true;
2892 }
2893 
2894 /******************** Intl ********************/
2895 
2896 const Class js::IntlClass = {
2897     js_Object_str,
2898     JSCLASS_HAS_CACHED_PROTO(JSProto_Intl)
2899 };
2900 
2901 #if JS_HAS_TOSOURCE
2902 static bool
intl_toSource(JSContext * cx,unsigned argc,Value * vp)2903 intl_toSource(JSContext* cx, unsigned argc, Value* vp)
2904 {
2905     CallArgs args = CallArgsFromVp(argc, vp);
2906     args.rval().setString(cx->names().Intl);
2907     return true;
2908 }
2909 #endif
2910 
2911 static const JSFunctionSpec intl_static_methods[] = {
2912 #if JS_HAS_TOSOURCE
2913     JS_FN(js_toSource_str,  intl_toSource,        0, 0),
2914 #endif
2915     JS_SELF_HOSTED_FN("getCanonicalLocales", "Intl_getCanonicalLocales", 1, 0),
2916     JS_FS_END
2917 };
2918 
2919 /**
2920  * Initializes the Intl Object and its standard built-in properties.
2921  * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
2922  */
2923 bool
initIntlObject(JSContext * cx,Handle<GlobalObject * > global)2924 GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
2925 {
2926     RootedObject proto(cx, global->getOrCreateObjectPrototype(cx));
2927     if (!proto)
2928         return false;
2929 
2930     // The |Intl| object is just a plain object with some "static" function
2931     // properties and some constructor properties.
2932     RootedObject intl(cx, NewObjectWithGivenProto(cx, &IntlClass, proto, SingletonObject));
2933     if (!intl)
2934         return false;
2935 
2936     // Add the static functions.
2937     if (!JS_DefineFunctions(cx, intl, intl_static_methods))
2938         return false;
2939 
2940     // Add the constructor properties, computing and returning the relevant
2941     // prototype objects needed below.
2942     RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global));
2943     if (!collatorProto)
2944         return false;
2945     RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
2946     if (!dateTimeFormatProto)
2947         return false;
2948     RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
2949     if (!numberFormatProto)
2950         return false;
2951 
2952     // The |Intl| object is fully set up now, so define the global property.
2953     RootedValue intlValue(cx, ObjectValue(*intl));
2954     if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr,
2955                         JSPROP_RESOLVING))
2956     {
2957         return false;
2958     }
2959 
2960     // Now that the |Intl| object is successfully added, we can OOM-safely fill
2961     // in all relevant reserved global slots.
2962 
2963     // Cache the various prototypes, for use in creating instances of these
2964     // objects with the proper [[Prototype]] as "the original value of
2965     // |Intl.Collator.prototype|" and similar.  For builtin classes like
2966     // |String.prototype| we have |JSProto_*| that enables
2967     // |getPrototype(JSProto_*)|, but that has global-object-property-related
2968     // baggage we don't need or want, so we use one-off reserved slots.
2969     global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
2970     global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
2971     global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
2972 
2973     // Also cache |Intl| to implement spec language that conditions behavior
2974     // based on values being equal to "the standard built-in |Intl| object".
2975     // Use |setConstructor| to correspond with |JSProto_Intl|.
2976     //
2977     // XXX We should possibly do a one-off reserved slot like above.
2978     global->setConstructor(JSProto_Intl, ObjectValue(*intl));
2979     return true;
2980 }
2981 
2982 JSObject*
InitIntlClass(JSContext * cx,HandleObject obj)2983 js::InitIntlClass(JSContext* cx, HandleObject obj)
2984 {
2985     Handle<GlobalObject*> global = obj.as<GlobalObject>();
2986     if (!GlobalObject::initIntlObject(cx, global))
2987         return nullptr;
2988 
2989     return &global->getConstructor(JSProto_Intl).toObject();
2990 }
2991