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