diff options
Diffstat (limited to 'js/src/builtin/Intl.cpp')
-rw-r--r-- | js/src/builtin/Intl.cpp | 2990 |
1 files changed, 2990 insertions, 0 deletions
diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp new file mode 100644 index 000000000..990a4acdb --- /dev/null +++ b/js/src/builtin/Intl.cpp @@ -0,0 +1,2990 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * The Intl module specified by standard ECMA-402, + * ECMAScript Internationalization API Specification. + */ + +#include "builtin/Intl.h" + +#include "mozilla/PodOperations.h" +#include "mozilla/Range.h" +#include "mozilla/ScopeExit.h" + +#include <string.h> + +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsobj.h" + +#include "builtin/IntlTimeZoneData.h" +#if ENABLE_INTL_API +#include "unicode/ucal.h" +#include "unicode/ucol.h" +#include "unicode/udat.h" +#include "unicode/udatpg.h" +#include "unicode/uenum.h" +#include "unicode/unum.h" +#include "unicode/unumsys.h" +#include "unicode/ustring.h" +#endif +#include "vm/DateTime.h" +#include "vm/GlobalObject.h" +#include "vm/Interpreter.h" +#include "vm/Stack.h" +#include "vm/StringBuffer.h" +#include "vm/Unicode.h" + +#include "jsobjinlines.h" + +#include "vm/NativeObject-inl.h" + +using namespace js; + +using mozilla::IsFinite; +using mozilla::IsNegativeZero; +using mozilla::MakeScopeExit; +using mozilla::PodCopy; + + +/* + * Pervasive note: ICU functions taking a UErrorCode in/out parameter always + * test that parameter before doing anything, and will return immediately if + * the value indicates that a failure occurred in a prior ICU call, + * without doing anything else. See + * http://userguide.icu-project.org/design#TOC-Error-Handling + */ + + +/******************** ICU stubs ********************/ + +#if !ENABLE_INTL_API + +/* + * When the Internationalization API isn't enabled, we also shouldn't link + * against ICU. However, we still want to compile this code in order to prevent + * bit rot. The following stub implementations for ICU functions make this + * possible. The functions using them should never be called, so they assert + * and return error codes. Signatures adapted from ICU header files locid.h, + * numsys.h, ucal.h, ucol.h, udat.h, udatpg.h, uenum.h, unum.h; see the ICU + * directory for license. + */ + +typedef bool UBool; +typedef char16_t UChar; +typedef double UDate; + +enum UErrorCode { + U_ZERO_ERROR, + U_BUFFER_OVERFLOW_ERROR, +}; + +static inline UBool +U_FAILURE(UErrorCode code) +{ + MOZ_CRASH("U_FAILURE: Intl API disabled"); +} + +inline const UChar* +Char16ToUChar(const char16_t* chars) +{ + MOZ_CRASH("Char16ToUChar: Intl API disabled"); +} + +inline UChar* +Char16ToUChar(char16_t* chars) +{ + MOZ_CRASH("Char16ToUChar: Intl API disabled"); +} + +static int32_t +u_strlen(const UChar* s) +{ + MOZ_CRASH("u_strlen: Intl API disabled"); +} + +struct UEnumeration; + +static int32_t +uenum_count(UEnumeration* en, UErrorCode* status) +{ + MOZ_CRASH("uenum_count: Intl API disabled"); +} + +static const char* +uenum_next(UEnumeration* en, int32_t* resultLength, UErrorCode* status) +{ + MOZ_CRASH("uenum_next: Intl API disabled"); +} + +static void +uenum_close(UEnumeration* en) +{ + MOZ_CRASH("uenum_close: Intl API disabled"); +} + +struct UCollator; + +enum UColAttribute { + UCOL_ALTERNATE_HANDLING, + UCOL_CASE_FIRST, + UCOL_CASE_LEVEL, + UCOL_NORMALIZATION_MODE, + UCOL_STRENGTH, + UCOL_NUMERIC_COLLATION, +}; + +enum UColAttributeValue { + UCOL_DEFAULT = -1, + UCOL_PRIMARY = 0, + UCOL_SECONDARY = 1, + UCOL_TERTIARY = 2, + UCOL_OFF = 16, + UCOL_ON = 17, + UCOL_SHIFTED = 20, + UCOL_LOWER_FIRST = 24, + UCOL_UPPER_FIRST = 25, +}; + +enum UCollationResult { + UCOL_EQUAL = 0, + UCOL_GREATER = 1, + UCOL_LESS = -1 +}; + +static int32_t +ucol_countAvailable() +{ + MOZ_CRASH("ucol_countAvailable: Intl API disabled"); +} + +static const char* +ucol_getAvailable(int32_t localeIndex) +{ + MOZ_CRASH("ucol_getAvailable: Intl API disabled"); +} + +static UCollator* +ucol_open(const char* loc, UErrorCode* status) +{ + MOZ_CRASH("ucol_open: Intl API disabled"); +} + +static void +ucol_setAttribute(UCollator* coll, UColAttribute attr, UColAttributeValue value, UErrorCode* status) +{ + MOZ_CRASH("ucol_setAttribute: Intl API disabled"); +} + +static UCollationResult +ucol_strcoll(const UCollator* coll, const UChar* source, int32_t sourceLength, + const UChar* target, int32_t targetLength) +{ + MOZ_CRASH("ucol_strcoll: Intl API disabled"); +} + +static void +ucol_close(UCollator* coll) +{ + MOZ_CRASH("ucol_close: Intl API disabled"); +} + +static UEnumeration* +ucol_getKeywordValuesForLocale(const char* key, const char* locale, UBool commonlyUsed, + UErrorCode* status) +{ + MOZ_CRASH("ucol_getKeywordValuesForLocale: Intl API disabled"); +} + +struct UParseError; +struct UFieldPosition; +struct UFieldPositionIterator; +typedef void* UNumberFormat; + +enum UNumberFormatStyle { + UNUM_DECIMAL = 1, + UNUM_CURRENCY, + UNUM_PERCENT, + UNUM_CURRENCY_ISO, + UNUM_CURRENCY_PLURAL, +}; + +enum UNumberFormatRoundingMode { + UNUM_ROUND_HALFUP, +}; + +enum UNumberFormatAttribute { + UNUM_GROUPING_USED, + UNUM_MIN_INTEGER_DIGITS, + UNUM_MAX_FRACTION_DIGITS, + UNUM_MIN_FRACTION_DIGITS, + UNUM_ROUNDING_MODE, + UNUM_SIGNIFICANT_DIGITS_USED, + UNUM_MIN_SIGNIFICANT_DIGITS, + UNUM_MAX_SIGNIFICANT_DIGITS, +}; + +enum UNumberFormatTextAttribute { + UNUM_CURRENCY_CODE, +}; + +static int32_t +unum_countAvailable() +{ + MOZ_CRASH("unum_countAvailable: Intl API disabled"); +} + +static const char* +unum_getAvailable(int32_t localeIndex) +{ + MOZ_CRASH("unum_getAvailable: Intl API disabled"); +} + +static UNumberFormat* +unum_open(UNumberFormatStyle style, const UChar* pattern, int32_t patternLength, + const char* locale, UParseError* parseErr, UErrorCode* status) +{ + MOZ_CRASH("unum_open: Intl API disabled"); +} + +static void +unum_setAttribute(UNumberFormat* fmt, UNumberFormatAttribute attr, int32_t newValue) +{ + MOZ_CRASH("unum_setAttribute: Intl API disabled"); +} + +static int32_t +unum_formatDouble(const UNumberFormat* fmt, double number, UChar* result, + int32_t resultLength, UFieldPosition* pos, UErrorCode* status) +{ + MOZ_CRASH("unum_formatDouble: Intl API disabled"); +} + +static void +unum_close(UNumberFormat* fmt) +{ + MOZ_CRASH("unum_close: Intl API disabled"); +} + +static void +unum_setTextAttribute(UNumberFormat* fmt, UNumberFormatTextAttribute tag, const UChar* newValue, + int32_t newValueLength, UErrorCode* status) +{ + MOZ_CRASH("unum_setTextAttribute: Intl API disabled"); +} + +typedef void* UNumberingSystem; + +static UNumberingSystem* +unumsys_open(const char* locale, UErrorCode* status) +{ + MOZ_CRASH("unumsys_open: Intl API disabled"); +} + +static const char* +unumsys_getName(const UNumberingSystem* unumsys) +{ + MOZ_CRASH("unumsys_getName: Intl API disabled"); +} + +static void +unumsys_close(UNumberingSystem* unumsys) +{ + MOZ_CRASH("unumsys_close: Intl API disabled"); +} + +typedef void* UCalendar; + +enum UCalendarType { + UCAL_TRADITIONAL, + UCAL_DEFAULT = UCAL_TRADITIONAL, + UCAL_GREGORIAN +}; + +enum UCalendarAttribute { + UCAL_FIRST_DAY_OF_WEEK, + UCAL_MINIMAL_DAYS_IN_FIRST_WEEK +}; + +enum UCalendarDaysOfWeek { + UCAL_SUNDAY, + UCAL_MONDAY, + UCAL_TUESDAY, + UCAL_WEDNESDAY, + UCAL_THURSDAY, + UCAL_FRIDAY, + UCAL_SATURDAY +}; + +enum UCalendarWeekdayType { + UCAL_WEEKDAY, + UCAL_WEEKEND, + UCAL_WEEKEND_ONSET, + UCAL_WEEKEND_CEASE +}; + +enum UCalendarDateFields { + UCAL_ERA, + UCAL_YEAR, + UCAL_MONTH, + UCAL_WEEK_OF_YEAR, + UCAL_WEEK_OF_MONTH, + UCAL_DATE, + UCAL_DAY_OF_YEAR, + UCAL_DAY_OF_WEEK, + UCAL_DAY_OF_WEEK_IN_MONTH, + UCAL_AM_PM, + UCAL_HOUR, + UCAL_HOUR_OF_DAY, + UCAL_MINUTE, + UCAL_SECOND, + UCAL_MILLISECOND, + UCAL_ZONE_OFFSET, + UCAL_DST_OFFSET, + UCAL_YEAR_WOY, + UCAL_DOW_LOCAL, + UCAL_EXTENDED_YEAR, + UCAL_JULIAN_DAY, + UCAL_MILLISECONDS_IN_DAY, + UCAL_IS_LEAP_MONTH, + UCAL_FIELD_COUNT, + UCAL_DAY_OF_MONTH = UCAL_DATE +}; + +static UCalendar* +ucal_open(const UChar* zoneID, int32_t len, const char* locale, + UCalendarType type, UErrorCode* status) +{ + MOZ_CRASH("ucal_open: Intl API disabled"); +} + +static const char* +ucal_getType(const UCalendar* cal, UErrorCode* status) +{ + MOZ_CRASH("ucal_getType: Intl API disabled"); +} + +static UEnumeration* +ucal_getKeywordValuesForLocale(const char* key, const char* locale, + UBool commonlyUsed, UErrorCode* status) +{ + MOZ_CRASH("ucal_getKeywordValuesForLocale: Intl API disabled"); +} + +static void +ucal_close(UCalendar* cal) +{ + MOZ_CRASH("ucal_close: Intl API disabled"); +} + +static UCalendarWeekdayType +ucal_getDayOfWeekType(const UCalendar *cal, UCalendarDaysOfWeek dayOfWeek, UErrorCode* status) +{ + MOZ_CRASH("ucal_getDayOfWeekType: Intl API disabled"); +} + +static int32_t +ucal_getAttribute(const UCalendar* cal, + UCalendarAttribute attr) +{ + MOZ_CRASH("ucal_getAttribute: Intl API disabled"); +} + +static int32_t +ucal_get(const UCalendar *cal, UCalendarDateFields field, UErrorCode *status) +{ + MOZ_CRASH("ucal_get: Intl API disabled"); +} + +static UEnumeration* +ucal_openTimeZones(UErrorCode* status) +{ + MOZ_CRASH("ucal_openTimeZones: Intl API disabled"); +} + +static int32_t +ucal_getCanonicalTimeZoneID(const UChar* id, int32_t len, UChar* result, int32_t resultCapacity, + UBool* isSystemID, UErrorCode* status) +{ + MOZ_CRASH("ucal_getCanonicalTimeZoneID: Intl API disabled"); +} + +static int32_t +ucal_getDefaultTimeZone(UChar* result, int32_t resultCapacity, UErrorCode* status) +{ + MOZ_CRASH("ucal_getDefaultTimeZone: Intl API disabled"); +} + +typedef void* UDateTimePatternGenerator; + +static UDateTimePatternGenerator* +udatpg_open(const char* locale, UErrorCode* pErrorCode) +{ + MOZ_CRASH("udatpg_open: Intl API disabled"); +} + +static int32_t +udatpg_getBestPattern(UDateTimePatternGenerator* dtpg, const UChar* skeleton, + int32_t length, UChar* bestPattern, int32_t capacity, + UErrorCode* pErrorCode) +{ + MOZ_CRASH("udatpg_getBestPattern: Intl API disabled"); +} + +static void +udatpg_close(UDateTimePatternGenerator* dtpg) +{ + MOZ_CRASH("udatpg_close: Intl API disabled"); +} + +typedef void* UCalendar; +typedef void* UDateFormat; + +enum UDateFormatField { + UDAT_ERA_FIELD = 0, + UDAT_YEAR_FIELD = 1, + UDAT_MONTH_FIELD = 2, + UDAT_DATE_FIELD = 3, + UDAT_HOUR_OF_DAY1_FIELD = 4, + UDAT_HOUR_OF_DAY0_FIELD = 5, + UDAT_MINUTE_FIELD = 6, + UDAT_SECOND_FIELD = 7, + UDAT_FRACTIONAL_SECOND_FIELD = 8, + UDAT_DAY_OF_WEEK_FIELD = 9, + UDAT_DAY_OF_YEAR_FIELD = 10, + UDAT_DAY_OF_WEEK_IN_MONTH_FIELD = 11, + UDAT_WEEK_OF_YEAR_FIELD = 12, + UDAT_WEEK_OF_MONTH_FIELD = 13, + UDAT_AM_PM_FIELD = 14, + UDAT_HOUR1_FIELD = 15, + UDAT_HOUR0_FIELD = 16, + UDAT_TIMEZONE_FIELD = 17, + UDAT_YEAR_WOY_FIELD = 18, + UDAT_DOW_LOCAL_FIELD = 19, + UDAT_EXTENDED_YEAR_FIELD = 20, + UDAT_JULIAN_DAY_FIELD = 21, + UDAT_MILLISECONDS_IN_DAY_FIELD = 22, + UDAT_TIMEZONE_RFC_FIELD = 23, + UDAT_TIMEZONE_GENERIC_FIELD = 24, + UDAT_STANDALONE_DAY_FIELD = 25, + UDAT_STANDALONE_MONTH_FIELD = 26, + UDAT_QUARTER_FIELD = 27, + UDAT_STANDALONE_QUARTER_FIELD = 28, + UDAT_TIMEZONE_SPECIAL_FIELD = 29, + UDAT_YEAR_NAME_FIELD = 30, + UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD = 31, + UDAT_TIMEZONE_ISO_FIELD = 32, + UDAT_TIMEZONE_ISO_LOCAL_FIELD = 33, + UDAT_RELATED_YEAR_FIELD = 34, + UDAT_AM_PM_MIDNIGHT_NOON_FIELD = 35, + UDAT_FLEXIBLE_DAY_PERIOD_FIELD = 36, + UDAT_TIME_SEPARATOR_FIELD = 37, + UDAT_FIELD_COUNT = 38 +}; + +enum UDateFormatStyle { + UDAT_PATTERN = -2, + UDAT_IGNORE = UDAT_PATTERN +}; + +static int32_t +udat_countAvailable() +{ + MOZ_CRASH("udat_countAvailable: Intl API disabled"); +} + +static const char* +udat_getAvailable(int32_t localeIndex) +{ + MOZ_CRASH("udat_getAvailable: Intl API disabled"); +} + +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) +{ + MOZ_CRASH("udat_open: Intl API disabled"); +} + +static const UCalendar* +udat_getCalendar(const UDateFormat* fmt) +{ + MOZ_CRASH("udat_getCalendar: Intl API disabled"); +} + +static void +ucal_setGregorianChange(UCalendar* cal, UDate date, UErrorCode* pErrorCode) +{ + MOZ_CRASH("ucal_setGregorianChange: Intl API disabled"); +} + +static int32_t +udat_format(const UDateFormat* format, UDate dateToFormat, UChar* result, + int32_t resultLength, UFieldPosition* position, UErrorCode* status) +{ + MOZ_CRASH("udat_format: Intl API disabled"); +} + +static int32_t +udat_formatForFields(const UDateFormat* format, UDate dateToFormat, + UChar* result, int32_t resultLength, UFieldPositionIterator* fpositer, + UErrorCode* status) +{ + MOZ_CRASH("udat_formatForFields: Intl API disabled"); +} + +static UFieldPositionIterator* +ufieldpositer_open(UErrorCode* status) +{ + MOZ_CRASH("ufieldpositer_open: Intl API disabled"); +} + +static void +ufieldpositer_close(UFieldPositionIterator* fpositer) +{ + MOZ_CRASH("ufieldpositer_close: Intl API disabled"); +} + +static int32_t +ufieldpositer_next(UFieldPositionIterator* fpositer, int32_t* beginIndex, int32_t* endIndex) +{ + MOZ_CRASH("ufieldpositer_next: Intl API disabled"); +} + +static void +udat_close(UDateFormat* format) +{ + MOZ_CRASH("udat_close: Intl API disabled"); +} + +#endif + + +/******************** Common to Intl constructors ********************/ + +static bool +IntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer, + HandleValue locales, HandleValue options) +{ + RootedValue initializerValue(cx); + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), initializer, &initializerValue)) + return false; + MOZ_ASSERT(initializerValue.isObject()); + MOZ_ASSERT(initializerValue.toObject().is<JSFunction>()); + + FixedInvokeArgs<3> args(cx); + + args[0].setObject(*obj); + args[1].set(locales); + args[2].set(options); + + RootedValue thisv(cx, NullValue()); + RootedValue ignored(cx); + return js::Call(cx, initializerValue, thisv, args, &ignored); +} + +static bool +CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions) +{ + RootedObject options(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr)); + if (!options) + return false; + defaultOptions.setObject(*options); + return true; +} + +// CountAvailable and GetAvailable describe the signatures used for ICU API +// to determine available locales for various functionality. +typedef int32_t +(* CountAvailable)(); + +typedef const char* +(* GetAvailable)(int32_t localeIndex); + +static bool +intl_availableLocales(JSContext* cx, CountAvailable countAvailable, + GetAvailable getAvailable, MutableHandleValue result) +{ + RootedObject locales(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr)); + if (!locales) + return false; + +#if ENABLE_INTL_API + uint32_t count = countAvailable(); + RootedValue t(cx, BooleanValue(true)); + for (uint32_t i = 0; i < count; i++) { + const char* locale = getAvailable(i); + auto lang = DuplicateString(cx, locale); + if (!lang) + return false; + char* p; + while ((p = strchr(lang.get(), '_'))) + *p = '-'; + RootedAtom a(cx, Atomize(cx, lang.get(), strlen(lang.get()))); + if (!a) + return false; + if (!DefineProperty(cx, locales, a->asPropertyName(), t, nullptr, nullptr, + JSPROP_ENUMERATE)) + { + return false; + } + } +#endif + result.setObject(*locales); + return true; +} + +/** + * Returns the object holding the internal properties for obj. + */ +static JSObject* +GetInternals(JSContext* cx, HandleObject obj) +{ + RootedValue getInternalsValue(cx); + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().getInternals, + &getInternalsValue)) + { + return nullptr; + } + MOZ_ASSERT(getInternalsValue.isObject()); + MOZ_ASSERT(getInternalsValue.toObject().is<JSFunction>()); + + FixedInvokeArgs<1> args(cx); + + args[0].setObject(*obj); + + RootedValue v(cx, NullValue()); + if (!js::Call(cx, getInternalsValue, v, args, &v)) + return nullptr; + + return &v.toObject(); +} + +static bool +equal(const char* s1, const char* s2) +{ + return !strcmp(s1, s2); +} + +static bool +equal(JSAutoByteString& s1, const char* s2) +{ + return !strcmp(s1.ptr(), s2); +} + +static const char* +icuLocale(const char* locale) +{ + if (equal(locale, "und")) + return ""; // ICU root locale + return locale; +} + +// Simple RAII for ICU objects. Unfortunately, ICU's C++ API is uniformly +// unstable, so we can't use its smart pointers for this. +template <typename T, void (Delete)(T*)> +class ScopedICUObject +{ + T* ptr_; + + public: + explicit ScopedICUObject(T* ptr) + : ptr_(ptr) + {} + + ~ScopedICUObject() { + if (ptr_) + Delete(ptr_); + } + + // In cases where an object should be deleted on abnormal exits, + // but returned to the caller if everything goes well, call forget() + // to transfer the object just before returning. + T* forget() { + T* tmp = ptr_; + ptr_ = nullptr; + return tmp; + } +}; + +// The inline capacity we use for the char16_t Vectors. +static const size_t INITIAL_CHAR_BUFFER_SIZE = 32; + +/******************** Collator ********************/ + +static void collator_finalize(FreeOp* fop, JSObject* obj); + +static const uint32_t UCOLLATOR_SLOT = 0; +static const uint32_t COLLATOR_SLOTS_COUNT = 1; + +static const ClassOps CollatorClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + collator_finalize +}; + +static const Class CollatorClass = { + js_Object_str, + JSCLASS_HAS_RESERVED_SLOTS(COLLATOR_SLOTS_COUNT) | + JSCLASS_FOREGROUND_FINALIZE, + &CollatorClassOps +}; + +#if JS_HAS_TOSOURCE +static bool +collator_toSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(cx->names().Collator); + return true; +} +#endif + +static const JSFunctionSpec collator_static_methods[] = { + JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_Collator_supportedLocalesOf", 1, 0), + JS_FS_END +}; + +static const JSFunctionSpec collator_methods[] = { + JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0), +#if JS_HAS_TOSOURCE + JS_FN(js_toSource_str, collator_toSource, 0, 0), +#endif + JS_FS_END +}; + +/** + * Collator constructor. + * Spec: ECMAScript Internationalization API Specification, 10.1 + */ +static bool +Collator(JSContext* cx, const CallArgs& args, bool construct) +{ + RootedObject obj(cx); + + if (!construct) { + // 10.1.2.1 step 3 + JSObject* intl = cx->global()->getOrCreateIntlObject(cx); + if (!intl) + return false; + RootedValue self(cx, args.thisv()); + if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { + // 10.1.2.1 step 4 + obj = ToObject(cx, self); + if (!obj) + return false; + + // 10.1.2.1 step 5 + bool extensible; + if (!IsExtensible(cx, obj, &extensible)) + return false; + if (!extensible) + return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); + } else { + // 10.1.2.1 step 3.a + construct = true; + } + } + if (construct) { + // 10.1.3.1 paragraph 2 + RootedObject proto(cx, cx->global()->getOrCreateCollatorPrototype(cx)); + if (!proto) + return false; + obj = NewObjectWithGivenProto(cx, &CollatorClass, proto); + if (!obj) + return false; + + obj->as<NativeObject>().setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); + } + + // 10.1.2.1 steps 1 and 2; 10.1.3.1 steps 1 and 2 + RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); + RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); + + // 10.1.2.1 step 6; 10.1.3.1 step 3 + if (!IntlInitialize(cx, obj, cx->names().InitializeCollator, locales, options)) + return false; + + // 10.1.2.1 steps 3.a and 7 + args.rval().setObject(*obj); + return true; +} + +static bool +Collator(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return Collator(cx, args, args.isConstructing()); +} + +bool +js::intl_Collator(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + // intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot + // be used with "new", but it still has to be treated as a constructor. + return Collator(cx, args, true); +} + +static void +collator_finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(fop->onMainThread()); + + // This is-undefined check shouldn't be necessary, but for internal + // brokenness in object allocation code. For the moment, hack around it by + // explicitly guarding against the possibility of the reserved slot not + // containing a private. See bug 949220. + const Value& slot = obj->as<NativeObject>().getReservedSlot(UCOLLATOR_SLOT); + if (!slot.isUndefined()) { + if (UCollator* coll = static_cast<UCollator*>(slot.toPrivate())) + ucol_close(coll); + } +} + +static JSObject* +CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global) +{ + RootedFunction ctor(cx, global->createConstructor(cx, &Collator, cx->names().Collator, 0)); + if (!ctor) + return nullptr; + + RootedNativeObject proto(cx, global->createBlankPrototype(cx, &CollatorClass)); + if (!proto) + return nullptr; + proto->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); + + if (!LinkConstructorAndPrototype(cx, ctor, proto)) + return nullptr; + + // 10.2.2 + if (!JS_DefineFunctions(cx, ctor, collator_static_methods)) + return nullptr; + + // 10.3.2 and 10.3.3 + if (!JS_DefineFunctions(cx, proto, collator_methods)) + return nullptr; + + /* + * Install the getter for Collator.prototype.compare, which returns a bound + * comparison function for the specified Collator object (suitable for + * passing to methods like Array.prototype.sort). + */ + RootedValue getter(cx); + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().CollatorCompareGet, &getter)) + return nullptr; + if (!DefineProperty(cx, proto, cx->names().compare, UndefinedHandleValue, + JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()), + nullptr, JSPROP_GETTER | JSPROP_SHARED)) + { + return nullptr; + } + + RootedValue options(cx); + if (!CreateDefaultOptions(cx, &options)) + return nullptr; + + // 10.2.1 and 10.3 + if (!IntlInitialize(cx, proto, cx->names().InitializeCollator, UndefinedHandleValue, options)) + return nullptr; + + // 8.1 + RootedValue ctorValue(cx, ObjectValue(*ctor)); + if (!DefineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0)) + return nullptr; + + return proto; +} + +bool +js::intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + RootedValue result(cx); + if (!intl_availableLocales(cx, ucol_countAvailable, ucol_getAvailable, &result)) + return false; + args.rval().set(result); + return true; +} + +bool +js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString()); + + JSAutoByteString locale(cx, args[0].toString()); + if (!locale) + return false; + UErrorCode status = U_ZERO_ERROR; + UEnumeration* values = ucol_getKeywordValuesForLocale("co", locale.ptr(), false, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + ScopedICUObject<UEnumeration, uenum_close> toClose(values); + + uint32_t count = uenum_count(values, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + RootedObject collations(cx, NewDenseEmptyArray(cx)); + if (!collations) + return false; + + uint32_t index = 0; + for (uint32_t i = 0; i < count; i++) { + const char* collation = uenum_next(values, nullptr, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + // Per ECMA-402, 10.2.3, we don't include standard and search: + // "The values 'standard' and 'search' must not be used as elements in + // any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co + // array." + if (equal(collation, "standard") || equal(collation, "search")) + continue; + + // ICU returns old-style keyword values; map them to BCP 47 equivalents + // (see http://bugs.icu-project.org/trac/ticket/9620). + if (equal(collation, "dictionary")) + collation = "dict"; + else if (equal(collation, "gb2312han")) + collation = "gb2312"; + else if (equal(collation, "phonebook")) + collation = "phonebk"; + else if (equal(collation, "traditional")) + collation = "trad"; + + RootedString jscollation(cx, JS_NewStringCopyZ(cx, collation)); + if (!jscollation) + return false; + RootedValue element(cx, StringValue(jscollation)); + if (!DefineElement(cx, collations, index++, element)) + return false; + } + + args.rval().setObject(*collations); + return true; +} + +/** + * Returns a new UCollator with the locale and collation options + * of the given Collator. + */ +static UCollator* +NewUCollator(JSContext* cx, HandleObject collator) +{ + RootedValue value(cx); + + RootedObject internals(cx, GetInternals(cx, collator)); + if (!internals) + return nullptr; + + if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) + return nullptr; + JSAutoByteString locale(cx, value.toString()); + if (!locale) + return nullptr; + + // UCollator options with default values. + UColAttributeValue uStrength = UCOL_DEFAULT; + UColAttributeValue uCaseLevel = UCOL_OFF; + UColAttributeValue uAlternate = UCOL_DEFAULT; + UColAttributeValue uNumeric = UCOL_OFF; + // Normalization is always on to meet the canonical equivalence requirement. + UColAttributeValue uNormalization = UCOL_ON; + UColAttributeValue uCaseFirst = UCOL_DEFAULT; + + if (!GetProperty(cx, internals, internals, cx->names().usage, &value)) + return nullptr; + JSAutoByteString usage(cx, value.toString()); + if (!usage) + return nullptr; + if (equal(usage, "search")) { + // ICU expects search as a Unicode locale extension on locale. + // Unicode locale extensions must occur before private use extensions. + const char* oldLocale = locale.ptr(); + const char* p; + size_t index; + size_t localeLen = strlen(oldLocale); + if ((p = strstr(oldLocale, "-x-"))) + index = p - oldLocale; + else + index = localeLen; + + const char* insert; + if ((p = strstr(oldLocale, "-u-")) && static_cast<size_t>(p - oldLocale) < index) { + index = p - oldLocale + 2; + insert = "-co-search"; + } else { + insert = "-u-co-search"; + } + size_t insertLen = strlen(insert); + char* newLocale = cx->pod_malloc<char>(localeLen + insertLen + 1); + if (!newLocale) + return nullptr; + memcpy(newLocale, oldLocale, index); + memcpy(newLocale + index, insert, insertLen); + memcpy(newLocale + index + insertLen, oldLocale + index, localeLen - index + 1); // '\0' + locale.clear(); + locale.initBytes(newLocale); + } + + // We don't need to look at the collation property - it can only be set + // via the Unicode locale extension and is therefore already set on + // locale. + + if (!GetProperty(cx, internals, internals, cx->names().sensitivity, &value)) + return nullptr; + JSAutoByteString sensitivity(cx, value.toString()); + if (!sensitivity) + return nullptr; + if (equal(sensitivity, "base")) { + uStrength = UCOL_PRIMARY; + } else if (equal(sensitivity, "accent")) { + uStrength = UCOL_SECONDARY; + } else if (equal(sensitivity, "case")) { + uStrength = UCOL_PRIMARY; + uCaseLevel = UCOL_ON; + } else { + MOZ_ASSERT(equal(sensitivity, "variant")); + uStrength = UCOL_TERTIARY; + } + + if (!GetProperty(cx, internals, internals, cx->names().ignorePunctuation, &value)) + return nullptr; + // According to the ICU team, UCOL_SHIFTED causes punctuation to be + // ignored. Looking at Unicode Technical Report 35, Unicode Locale Data + // Markup Language, "shifted" causes whitespace and punctuation to be + // ignored - that's a bit more than asked for, but there's no way to get + // less. + if (value.toBoolean()) + uAlternate = UCOL_SHIFTED; + + if (!GetProperty(cx, internals, internals, cx->names().numeric, &value)) + return nullptr; + if (!value.isUndefined() && value.toBoolean()) + uNumeric = UCOL_ON; + + if (!GetProperty(cx, internals, internals, cx->names().caseFirst, &value)) + return nullptr; + if (!value.isUndefined()) { + JSAutoByteString caseFirst(cx, value.toString()); + if (!caseFirst) + return nullptr; + if (equal(caseFirst, "upper")) + uCaseFirst = UCOL_UPPER_FIRST; + else if (equal(caseFirst, "lower")) + uCaseFirst = UCOL_LOWER_FIRST; + else + MOZ_ASSERT(equal(caseFirst, "false")); + } + + UErrorCode status = U_ZERO_ERROR; + UCollator* coll = ucol_open(icuLocale(locale.ptr()), &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return nullptr; + } + + ucol_setAttribute(coll, UCOL_STRENGTH, uStrength, &status); + ucol_setAttribute(coll, UCOL_CASE_LEVEL, uCaseLevel, &status); + ucol_setAttribute(coll, UCOL_ALTERNATE_HANDLING, uAlternate, &status); + ucol_setAttribute(coll, UCOL_NUMERIC_COLLATION, uNumeric, &status); + ucol_setAttribute(coll, UCOL_NORMALIZATION_MODE, uNormalization, &status); + ucol_setAttribute(coll, UCOL_CASE_FIRST, uCaseFirst, &status); + if (U_FAILURE(status)) { + ucol_close(coll); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return nullptr; + } + + return coll; +} + +static bool +intl_CompareStrings(JSContext* cx, UCollator* coll, HandleString str1, HandleString str2, + MutableHandleValue result) +{ + MOZ_ASSERT(str1); + MOZ_ASSERT(str2); + + if (str1 == str2) { + result.setInt32(0); + return true; + } + + AutoStableStringChars stableChars1(cx); + if (!stableChars1.initTwoByte(cx, str1)) + return false; + + AutoStableStringChars stableChars2(cx); + if (!stableChars2.initTwoByte(cx, str2)) + return false; + + mozilla::Range<const char16_t> chars1 = stableChars1.twoByteRange(); + mozilla::Range<const char16_t> chars2 = stableChars2.twoByteRange(); + + UCollationResult uresult = ucol_strcoll(coll, + Char16ToUChar(chars1.begin().get()), chars1.length(), + Char16ToUChar(chars2.begin().get()), chars2.length()); + int32_t res; + switch (uresult) { + case UCOL_LESS: res = -1; break; + case UCOL_EQUAL: res = 0; break; + case UCOL_GREATER: res = 1; break; + default: MOZ_CRASH("ucol_strcoll returned bad UCollationResult"); + } + result.setInt32(res); + return true; +} + +bool +js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 3); + MOZ_ASSERT(args[0].isObject()); + MOZ_ASSERT(args[1].isString()); + MOZ_ASSERT(args[2].isString()); + + RootedObject collator(cx, &args[0].toObject()); + + // Obtain a UCollator object, cached if possible. + // XXX Does this handle Collator instances from other globals correctly? + bool isCollatorInstance = collator->getClass() == &CollatorClass; + UCollator* coll; + if (isCollatorInstance) { + void* priv = collator->as<NativeObject>().getReservedSlot(UCOLLATOR_SLOT).toPrivate(); + coll = static_cast<UCollator*>(priv); + if (!coll) { + coll = NewUCollator(cx, collator); + if (!coll) + return false; + collator->as<NativeObject>().setReservedSlot(UCOLLATOR_SLOT, PrivateValue(coll)); + } + } else { + // There's no good place to cache the ICU collator for an object + // that has been initialized as a Collator but is not a Collator + // instance. One possibility might be to add a Collator instance as an + // internal property to each such object. + coll = NewUCollator(cx, collator); + if (!coll) + return false; + } + + // Use the UCollator to actually compare the strings. + RootedString str1(cx, args[1].toString()); + RootedString str2(cx, args[2].toString()); + RootedValue result(cx); + bool success = intl_CompareStrings(cx, coll, str1, str2, &result); + + if (!isCollatorInstance) + ucol_close(coll); + if (!success) + return false; + args.rval().set(result); + return true; +} + + +/******************** NumberFormat ********************/ + +static void numberFormat_finalize(FreeOp* fop, JSObject* obj); + +static const uint32_t UNUMBER_FORMAT_SLOT = 0; +static const uint32_t NUMBER_FORMAT_SLOTS_COUNT = 1; + +static const ClassOps NumberFormatClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + numberFormat_finalize +}; + +static const Class NumberFormatClass = { + js_Object_str, + JSCLASS_HAS_RESERVED_SLOTS(NUMBER_FORMAT_SLOTS_COUNT) | + JSCLASS_FOREGROUND_FINALIZE, + &NumberFormatClassOps +}; + +#if JS_HAS_TOSOURCE +static bool +numberFormat_toSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(cx->names().NumberFormat); + return true; +} +#endif + +static const JSFunctionSpec numberFormat_static_methods[] = { + JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_NumberFormat_supportedLocalesOf", 1, 0), + JS_FS_END +}; + +static const JSFunctionSpec numberFormat_methods[] = { + JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0, 0), +#if JS_HAS_TOSOURCE + JS_FN(js_toSource_str, numberFormat_toSource, 0, 0), +#endif + JS_FS_END +}; + +/** + * NumberFormat constructor. + * Spec: ECMAScript Internationalization API Specification, 11.1 + */ +static bool +NumberFormat(JSContext* cx, const CallArgs& args, bool construct) +{ + RootedObject obj(cx); + + if (!construct) { + // 11.1.2.1 step 3 + JSObject* intl = cx->global()->getOrCreateIntlObject(cx); + if (!intl) + return false; + RootedValue self(cx, args.thisv()); + if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { + // 11.1.2.1 step 4 + obj = ToObject(cx, self); + if (!obj) + return false; + + // 11.1.2.1 step 5 + bool extensible; + if (!IsExtensible(cx, obj, &extensible)) + return false; + if (!extensible) + return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); + } else { + // 11.1.2.1 step 3.a + construct = true; + } + } + if (construct) { + // 11.1.3.1 paragraph 2 + RootedObject proto(cx, cx->global()->getOrCreateNumberFormatPrototype(cx)); + if (!proto) + return false; + obj = NewObjectWithGivenProto(cx, &NumberFormatClass, proto); + if (!obj) + return false; + + obj->as<NativeObject>().setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); + } + + // 11.1.2.1 steps 1 and 2; 11.1.3.1 steps 1 and 2 + RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); + RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); + + // 11.1.2.1 step 6; 11.1.3.1 step 3 + if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options)) + return false; + + // 11.1.2.1 steps 3.a and 7 + args.rval().setObject(*obj); + return true; +} + +static bool +NumberFormat(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return NumberFormat(cx, args, args.isConstructing()); +} + +bool +js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it + // cannot be used with "new", but it still has to be treated as a + // constructor. + return NumberFormat(cx, args, true); +} + +static void +numberFormat_finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(fop->onMainThread()); + + // This is-undefined check shouldn't be necessary, but for internal + // brokenness in object allocation code. For the moment, hack around it by + // explicitly guarding against the possibility of the reserved slot not + // containing a private. See bug 949220. + const Value& slot = obj->as<NativeObject>().getReservedSlot(UNUMBER_FORMAT_SLOT); + if (!slot.isUndefined()) { + if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate())) + unum_close(nf); + } +} + +static JSObject* +CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global) +{ + RootedFunction ctor(cx); + ctor = global->createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0); + if (!ctor) + return nullptr; + + RootedNativeObject proto(cx, global->createBlankPrototype(cx, &NumberFormatClass)); + if (!proto) + return nullptr; + proto->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); + + if (!LinkConstructorAndPrototype(cx, ctor, proto)) + return nullptr; + + // 11.2.2 + if (!JS_DefineFunctions(cx, ctor, numberFormat_static_methods)) + return nullptr; + + // 11.3.2 and 11.3.3 + if (!JS_DefineFunctions(cx, proto, numberFormat_methods)) + return nullptr; + + /* + * Install the getter for NumberFormat.prototype.format, which returns a + * bound formatting function for the specified NumberFormat object (suitable + * for passing to methods like Array.prototype.map). + */ + RootedValue getter(cx); + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().NumberFormatFormatGet, + &getter)) + { + return nullptr; + } + if (!DefineProperty(cx, proto, cx->names().format, UndefinedHandleValue, + JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()), + nullptr, JSPROP_GETTER | JSPROP_SHARED)) + { + return nullptr; + } + + RootedValue options(cx); + if (!CreateDefaultOptions(cx, &options)) + return nullptr; + + // 11.2.1 and 11.3 + if (!IntlInitialize(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue, + options)) + { + return nullptr; + } + + // 8.1 + RootedValue ctorValue(cx, ObjectValue(*ctor)); + if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0)) + return nullptr; + + return proto; +} + +bool +js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + RootedValue result(cx); + if (!intl_availableLocales(cx, unum_countAvailable, unum_getAvailable, &result)) + return false; + args.rval().set(result); + return true; +} + +bool +js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString()); + + JSAutoByteString locale(cx, args[0].toString()); + if (!locale) + return false; + + UErrorCode status = U_ZERO_ERROR; + UNumberingSystem* numbers = unumsys_open(icuLocale(locale.ptr()), &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + ScopedICUObject<UNumberingSystem, unumsys_close> toClose(numbers); + + const char* name = unumsys_getName(numbers); + RootedString jsname(cx, JS_NewStringCopyZ(cx, name)); + if (!jsname) + return false; + + args.rval().setString(jsname); + return true; +} + +/** + * Returns a new UNumberFormat with the locale and number formatting options + * of the given NumberFormat. + */ +static UNumberFormat* +NewUNumberFormat(JSContext* cx, HandleObject numberFormat) +{ + RootedValue value(cx); + + RootedObject internals(cx, GetInternals(cx, numberFormat)); + if (!internals) + return nullptr; + + if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) + return nullptr; + JSAutoByteString locale(cx, value.toString()); + if (!locale) + return nullptr; + + // UNumberFormat options with default values + UNumberFormatStyle uStyle = UNUM_DECIMAL; + const UChar* uCurrency = nullptr; + uint32_t uMinimumIntegerDigits = 1; + uint32_t uMinimumFractionDigits = 0; + uint32_t uMaximumFractionDigits = 3; + int32_t uMinimumSignificantDigits = -1; + int32_t uMaximumSignificantDigits = -1; + bool uUseGrouping = true; + + // Sprinkle appropriate rooting flavor over things the GC might care about. + RootedString currency(cx); + AutoStableStringChars stableChars(cx); + + // We don't need to look at numberingSystem - it can only be set via + // the Unicode locale extension and is therefore already set on locale. + + if (!GetProperty(cx, internals, internals, cx->names().style, &value)) + return nullptr; + JSAutoByteString style(cx, value.toString()); + if (!style) + return nullptr; + + if (equal(style, "currency")) { + if (!GetProperty(cx, internals, internals, cx->names().currency, &value)) + return nullptr; + currency = value.toString(); + MOZ_ASSERT(currency->length() == 3, + "IsWellFormedCurrencyCode permits only length-3 strings"); + if (!currency->ensureFlat(cx) || !stableChars.initTwoByte(cx, currency)) + return nullptr; + // uCurrency remains owned by stableChars. + uCurrency = Char16ToUChar(stableChars.twoByteRange().begin().get()); + if (!uCurrency) + return nullptr; + + if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay, &value)) + return nullptr; + JSAutoByteString currencyDisplay(cx, value.toString()); + if (!currencyDisplay) + return nullptr; + if (equal(currencyDisplay, "code")) { + uStyle = UNUM_CURRENCY_ISO; + } else if (equal(currencyDisplay, "symbol")) { + uStyle = UNUM_CURRENCY; + } else { + MOZ_ASSERT(equal(currencyDisplay, "name")); + uStyle = UNUM_CURRENCY_PLURAL; + } + } else if (equal(style, "percent")) { + uStyle = UNUM_PERCENT; + } else { + MOZ_ASSERT(equal(style, "decimal")); + uStyle = UNUM_DECIMAL; + } + + RootedId id(cx, NameToId(cx->names().minimumSignificantDigits)); + bool hasP; + if (!HasProperty(cx, internals, id, &hasP)) + return nullptr; + if (hasP) { + if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits, + &value)) + { + return nullptr; + } + uMinimumSignificantDigits = int32_t(value.toNumber()); + if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits, + &value)) + { + return nullptr; + } + uMaximumSignificantDigits = int32_t(value.toNumber()); + } else { + if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits, + &value)) + { + return nullptr; + } + uMinimumIntegerDigits = int32_t(value.toNumber()); + if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits, + &value)) + { + return nullptr; + } + uMinimumFractionDigits = int32_t(value.toNumber()); + if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits, + &value)) + { + return nullptr; + } + uMaximumFractionDigits = int32_t(value.toNumber()); + } + + if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value)) + return nullptr; + uUseGrouping = value.toBoolean(); + + UErrorCode status = U_ZERO_ERROR; + UNumberFormat* nf = unum_open(uStyle, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return nullptr; + } + ScopedICUObject<UNumberFormat, unum_close> toClose(nf); + + if (uCurrency) { + unum_setTextAttribute(nf, UNUM_CURRENCY_CODE, uCurrency, 3, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return nullptr; + } + } + if (uMinimumSignificantDigits != -1) { + unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true); + unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits); + unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits); + } else { + unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits); + unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits); + unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits); + } + unum_setAttribute(nf, UNUM_GROUPING_USED, uUseGrouping); + unum_setAttribute(nf, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP); + + return toClose.forget(); +} + +static bool +intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result) +{ + // FormatNumber doesn't consider -0.0 to be negative. + if (IsNegativeZero(x)) + x = 0.0; + + Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx); + if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE)) + return false; + UErrorCode status = U_ZERO_ERROR; + int size = unum_formatDouble(nf, x, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE, + nullptr, &status); + if (status == U_BUFFER_OVERFLOW_ERROR) { + if (!chars.resize(size)) + return false; + status = U_ZERO_ERROR; + unum_formatDouble(nf, x, Char16ToUChar(chars.begin()), size, nullptr, &status); + } + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size); + if (!str) + return false; + + result.setString(str); + return true; +} + +bool +js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(args[0].isObject()); + MOZ_ASSERT(args[1].isNumber()); + + RootedObject numberFormat(cx, &args[0].toObject()); + + // Obtain a UNumberFormat object, cached if possible. + bool isNumberFormatInstance = numberFormat->getClass() == &NumberFormatClass; + UNumberFormat* nf; + if (isNumberFormatInstance) { + void* priv = + numberFormat->as<NativeObject>().getReservedSlot(UNUMBER_FORMAT_SLOT).toPrivate(); + nf = static_cast<UNumberFormat*>(priv); + if (!nf) { + nf = NewUNumberFormat(cx, numberFormat); + if (!nf) + return false; + numberFormat->as<NativeObject>().setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nf)); + } + } else { + // There's no good place to cache the ICU number format for an object + // that has been initialized as a NumberFormat but is not a + // NumberFormat instance. One possibility might be to add a + // NumberFormat instance as an internal property to each such object. + nf = NewUNumberFormat(cx, numberFormat); + if (!nf) + return false; + } + + // Use the UNumberFormat to actually format the number. + RootedValue result(cx); + bool success = intl_FormatNumber(cx, nf, args[1].toNumber(), &result); + + if (!isNumberFormatInstance) + unum_close(nf); + if (!success) + return false; + args.rval().set(result); + return true; +} + + +/******************** DateTimeFormat ********************/ + +static void dateTimeFormat_finalize(FreeOp* fop, JSObject* obj); + +static const uint32_t UDATE_FORMAT_SLOT = 0; +static const uint32_t DATE_TIME_FORMAT_SLOTS_COUNT = 1; + +static const ClassOps DateTimeFormatClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + dateTimeFormat_finalize +}; + +static const Class DateTimeFormatClass = { + js_Object_str, + JSCLASS_HAS_RESERVED_SLOTS(DATE_TIME_FORMAT_SLOTS_COUNT) | + JSCLASS_FOREGROUND_FINALIZE, + &DateTimeFormatClassOps +}; + +#if JS_HAS_TOSOURCE +static bool +dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(cx->names().DateTimeFormat); + return true; +} +#endif + +static const JSFunctionSpec dateTimeFormat_static_methods[] = { + JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_DateTimeFormat_supportedLocalesOf", 1, 0), + JS_FS_END +}; + +static const JSFunctionSpec dateTimeFormat_methods[] = { + JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions", 0, 0), + JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 0, 0), +#if JS_HAS_TOSOURCE + JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0), +#endif + JS_FS_END +}; + +/** + * DateTimeFormat constructor. + * Spec: ECMAScript Internationalization API Specification, 12.1 + */ +static bool +DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct) +{ + RootedObject obj(cx); + + if (!construct) { + // 12.1.2.1 step 3 + JSObject* intl = cx->global()->getOrCreateIntlObject(cx); + if (!intl) + return false; + RootedValue self(cx, args.thisv()); + if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { + // 12.1.2.1 step 4 + obj = ToObject(cx, self); + if (!obj) + return false; + + // 12.1.2.1 step 5 + bool extensible; + if (!IsExtensible(cx, obj, &extensible)) + return false; + if (!extensible) + return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); + } else { + // 12.1.2.1 step 3.a + construct = true; + } + } + if (construct) { + // 12.1.3.1 paragraph 2 + RootedObject proto(cx, cx->global()->getOrCreateDateTimeFormatPrototype(cx)); + if (!proto) + return false; + obj = NewObjectWithGivenProto(cx, &DateTimeFormatClass, proto); + if (!obj) + return false; + + obj->as<NativeObject>().setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); + } + + // 12.1.2.1 steps 1 and 2; 12.1.3.1 steps 1 and 2 + RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); + RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); + + // 12.1.2.1 step 6; 12.1.3.1 step 3 + if (!IntlInitialize(cx, obj, cx->names().InitializeDateTimeFormat, locales, options)) + return false; + + // 12.1.2.1 steps 3.a and 7 + args.rval().setObject(*obj); + return true; +} + +static bool +DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return DateTimeFormat(cx, args, args.isConstructing()); +} + +bool +js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it + // cannot be used with "new", but it still has to be treated as a + // constructor. + return DateTimeFormat(cx, args, true); +} + +static void +dateTimeFormat_finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(fop->onMainThread()); + + // This is-undefined check shouldn't be necessary, but for internal + // brokenness in object allocation code. For the moment, hack around it by + // explicitly guarding against the possibility of the reserved slot not + // containing a private. See bug 949220. + const Value& slot = obj->as<NativeObject>().getReservedSlot(UDATE_FORMAT_SLOT); + if (!slot.isUndefined()) { + if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate())) + udat_close(df); + } +} + +static JSObject* +CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global) +{ + RootedFunction ctor(cx); + ctor = global->createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0); + if (!ctor) + return nullptr; + + RootedNativeObject proto(cx, global->createBlankPrototype(cx, &DateTimeFormatClass)); + if (!proto) + return nullptr; + proto->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); + + if (!LinkConstructorAndPrototype(cx, ctor, proto)) + return nullptr; + + // 12.2.2 + if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods)) + return nullptr; + + // 12.3.2 and 12.3.3 + if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods)) + return nullptr; + + // Install a getter for DateTimeFormat.prototype.format that returns a + // formatting function bound to a specified DateTimeFormat object (suitable + // for passing to methods like Array.prototype.map). + RootedValue getter(cx); + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().DateTimeFormatFormatGet, + &getter)) + { + return nullptr; + } + if (!DefineProperty(cx, proto, cx->names().format, UndefinedHandleValue, + JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()), + nullptr, JSPROP_GETTER | JSPROP_SHARED)) + { + return nullptr; + } + + RootedValue options(cx); + if (!CreateDefaultOptions(cx, &options)) + return nullptr; + + // 12.2.1 and 12.3 + if (!IntlInitialize(cx, proto, cx->names().InitializeDateTimeFormat, UndefinedHandleValue, + options)) + { + return nullptr; + } + + // 8.1 + RootedValue ctorValue(cx, ObjectValue(*ctor)); + if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0)) + return nullptr; + + return proto; +} + +bool +js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + RootedValue result(cx); + if (!intl_availableLocales(cx, udat_countAvailable, udat_getAvailable, &result)) + return false; + args.rval().set(result); + return true; +} + +// ICU returns old-style keyword values; map them to BCP 47 equivalents +// (see http://bugs.icu-project.org/trac/ticket/9620). +static const char* +bcp47CalendarName(const char* icuName) +{ + if (equal(icuName, "ethiopic-amete-alem")) + return "ethioaa"; + if (equal(icuName, "gregorian")) + return "gregory"; + if (equal(icuName, "islamic-civil")) + return "islamicc"; + return icuName; +} + +bool +js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString()); + + JSAutoByteString locale(cx, args[0].toString()); + if (!locale) + return false; + + RootedObject calendars(cx, NewDenseEmptyArray(cx)); + if (!calendars) + return false; + uint32_t index = 0; + + // We need the default calendar for the locale as the first result. + UErrorCode status = U_ZERO_ERROR; + RootedString jscalendar(cx); + { + UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status); + + // This correctly handles nullptr |cal| when opening failed. + ScopedICUObject<UCalendar, ucal_close> closeCalendar(cal); + + const char* calendar = ucal_getType(cal, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar)); + if (!jscalendar) + return false; + } + + RootedValue element(cx, StringValue(jscalendar)); + if (!DefineElement(cx, calendars, index++, element)) + return false; + + // Now get the calendars that "would make a difference", i.e., not the default. + UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + ScopedICUObject<UEnumeration, uenum_close> toClose(values); + + uint32_t count = uenum_count(values, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + for (; count > 0; count--) { + const char* calendar = uenum_next(values, nullptr, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar)); + if (!jscalendar) + return false; + element = StringValue(jscalendar); + if (!DefineElement(cx, calendars, index++, element)) + return false; + } + + args.rval().setObject(*calendars); + return true; +} + +template<typename Char> +static constexpr Char +ToUpperASCII(Char c) +{ + return ('a' <= c && c <= 'z') + ? (c & ~0x20) + : c; +} + +static_assert(ToUpperASCII('a') == 'A', "verifying 'a' uppercases correctly"); +static_assert(ToUpperASCII('m') == 'M', "verifying 'm' uppercases correctly"); +static_assert(ToUpperASCII('z') == 'Z', "verifying 'z' uppercases correctly"); +static_assert(ToUpperASCII(u'a') == u'A', "verifying u'a' uppercases correctly"); +static_assert(ToUpperASCII(u'k') == u'K', "verifying u'k' uppercases correctly"); +static_assert(ToUpperASCII(u'z') == u'Z', "verifying u'z' uppercases correctly"); + +template<typename Char1, typename Char2> +static bool +EqualCharsIgnoreCaseASCII(const Char1* s1, const Char2* s2, size_t len) +{ + for (const Char1* s1end = s1 + len; s1 < s1end; s1++, s2++) { + if (ToUpperASCII(*s1) != ToUpperASCII(*s2)) + return false; + } + return true; +} + +template<typename Char> +static js::HashNumber +HashStringIgnoreCaseASCII(const Char* s, size_t length) +{ + uint32_t hash = 0; + for (size_t i = 0; i < length; i++) + hash = mozilla::AddToHash(hash, ToUpperASCII(s[i])); + return hash; +} + +js::SharedIntlData::TimeZoneHasher::Lookup::Lookup(JSFlatString* timeZone) + : isLatin1(timeZone->hasLatin1Chars()), length(timeZone->length()) +{ + if (isLatin1) { + latin1Chars = timeZone->latin1Chars(nogc); + hash = HashStringIgnoreCaseASCII(latin1Chars, length); + } else { + twoByteChars = timeZone->twoByteChars(nogc); + hash = HashStringIgnoreCaseASCII(twoByteChars, length); + } +} + +bool +js::SharedIntlData::TimeZoneHasher::match(TimeZoneName key, const Lookup& lookup) +{ + if (key->length() != lookup.length) + return false; + + // Compare time zone names ignoring ASCII case differences. + if (key->hasLatin1Chars()) { + const Latin1Char* keyChars = key->latin1Chars(lookup.nogc); + if (lookup.isLatin1) + return EqualCharsIgnoreCaseASCII(keyChars, lookup.latin1Chars, lookup.length); + return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length); + } + + const char16_t* keyChars = key->twoByteChars(lookup.nogc); + if (lookup.isLatin1) + return EqualCharsIgnoreCaseASCII(lookup.latin1Chars, keyChars, lookup.length); + return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length); +} + +static bool +IsLegacyICUTimeZone(const char* timeZone) +{ + for (const auto& legacyTimeZone : js::timezone::legacyICUTimeZones) { + if (equal(timeZone, legacyTimeZone)) + return true; + } + return false; +} + +bool +js::SharedIntlData::ensureTimeZones(JSContext* cx) +{ + if (timeZoneDataInitialized) + return true; + + // If initTimeZones() was called previously, but didn't complete due to + // OOM, clear all sets/maps and start from scratch. + if (availableTimeZones.initialized()) + availableTimeZones.finish(); + if (!availableTimeZones.init()) { + ReportOutOfMemory(cx); + return false; + } + + UErrorCode status = U_ZERO_ERROR; + UEnumeration* values = ucal_openTimeZones(&status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + ScopedICUObject<UEnumeration, uenum_close> toClose(values); + + RootedAtom timeZone(cx); + while (true) { + int32_t size; + const char* rawTimeZone = uenum_next(values, &size, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + if (rawTimeZone == nullptr) + break; + + // Skip legacy ICU time zone names. + if (IsLegacyICUTimeZone(rawTimeZone)) + continue; + + MOZ_ASSERT(size >= 0); + timeZone = Atomize(cx, rawTimeZone, size_t(size)); + if (!timeZone) + return false; + + TimeZoneHasher::Lookup lookup(timeZone); + TimeZoneSet::AddPtr p = availableTimeZones.lookupForAdd(lookup); + + // ICU shouldn't report any duplicate time zone names, but if it does, + // just ignore the duplicate name. + if (!p && !availableTimeZones.add(p, timeZone)) { + ReportOutOfMemory(cx); + return false; + } + } + + if (ianaZonesTreatedAsLinksByICU.initialized()) + ianaZonesTreatedAsLinksByICU.finish(); + if (!ianaZonesTreatedAsLinksByICU.init()) { + ReportOutOfMemory(cx); + return false; + } + + for (const char* rawTimeZone : timezone::ianaZonesTreatedAsLinksByICU) { + MOZ_ASSERT(rawTimeZone != nullptr); + timeZone = Atomize(cx, rawTimeZone, strlen(rawTimeZone)); + if (!timeZone) + return false; + + TimeZoneHasher::Lookup lookup(timeZone); + TimeZoneSet::AddPtr p = ianaZonesTreatedAsLinksByICU.lookupForAdd(lookup); + MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaZonesTreatedAsLinksByICU"); + + if (!ianaZonesTreatedAsLinksByICU.add(p, timeZone)) { + ReportOutOfMemory(cx); + return false; + } + } + + if (ianaLinksCanonicalizedDifferentlyByICU.initialized()) + ianaLinksCanonicalizedDifferentlyByICU.finish(); + if (!ianaLinksCanonicalizedDifferentlyByICU.init()) { + ReportOutOfMemory(cx); + return false; + } + + RootedAtom linkName(cx); + RootedAtom& target = timeZone; + for (const auto& linkAndTarget : timezone::ianaLinksCanonicalizedDifferentlyByICU) { + const char* rawLinkName = linkAndTarget.link; + const char* rawTarget = linkAndTarget.target; + + MOZ_ASSERT(rawLinkName != nullptr); + linkName = Atomize(cx, rawLinkName, strlen(rawLinkName)); + if (!linkName) + return false; + + MOZ_ASSERT(rawTarget != nullptr); + target = Atomize(cx, rawTarget, strlen(rawTarget)); + if (!target) + return false; + + TimeZoneHasher::Lookup lookup(linkName); + TimeZoneMap::AddPtr p = ianaLinksCanonicalizedDifferentlyByICU.lookupForAdd(lookup); + MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaLinksCanonicalizedDifferentlyByICU"); + + if (!ianaLinksCanonicalizedDifferentlyByICU.add(p, linkName, target)) { + ReportOutOfMemory(cx); + return false; + } + } + + MOZ_ASSERT(!timeZoneDataInitialized, "ensureTimeZones is neither reentrant nor thread-safe"); + timeZoneDataInitialized = true; + + return true; +} + +bool +js::SharedIntlData::validateTimeZoneName(JSContext* cx, HandleString timeZone, + MutableHandleString result) +{ + if (!ensureTimeZones(cx)) + return false; + + Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx)); + if (!timeZoneFlat) + return false; + + TimeZoneHasher::Lookup lookup(timeZoneFlat); + if (TimeZoneSet::Ptr p = availableTimeZones.lookup(lookup)) + result.set(*p); + + return true; +} + +bool +js::SharedIntlData::tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, HandleString timeZone, + MutableHandleString result) +{ + if (!ensureTimeZones(cx)) + return false; + + Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx)); + if (!timeZoneFlat) + return false; + + TimeZoneHasher::Lookup lookup(timeZoneFlat); + MOZ_ASSERT(availableTimeZones.has(lookup), "Invalid time zone name"); + + if (TimeZoneMap::Ptr p = ianaLinksCanonicalizedDifferentlyByICU.lookup(lookup)) { + // The effectively supported time zones aren't known at compile time, + // when + // 1. SpiderMonkey was compiled with "--with-system-icu". + // 2. ICU's dynamic time zone data loading feature was used. + // (ICU supports loading time zone files at runtime through the + // ICU_TIMEZONE_FILES_DIR environment variable.) + // Ensure ICU supports the new target zone before applying the update. + TimeZoneName targetTimeZone = p->value(); + TimeZoneHasher::Lookup targetLookup(targetTimeZone); + if (availableTimeZones.has(targetLookup)) + result.set(targetTimeZone); + } else if (TimeZoneSet::Ptr p = ianaZonesTreatedAsLinksByICU.lookup(lookup)) { + result.set(*p); + } + + return true; +} + +void +js::SharedIntlData::destroyInstance() +{ + availableTimeZones.finish(); + ianaZonesTreatedAsLinksByICU.finish(); + ianaLinksCanonicalizedDifferentlyByICU.finish(); +} + +void +js::SharedIntlData::trace(JSTracer* trc) +{ + // Atoms are always tenured. + if (!trc->runtime()->isHeapMinorCollecting()) { + availableTimeZones.trace(trc); + ianaZonesTreatedAsLinksByICU.trace(trc); + ianaLinksCanonicalizedDifferentlyByICU.trace(trc); + } +} + +size_t +js::SharedIntlData::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + return availableTimeZones.sizeOfExcludingThis(mallocSizeOf) + + ianaZonesTreatedAsLinksByICU.sizeOfExcludingThis(mallocSizeOf) + + ianaLinksCanonicalizedDifferentlyByICU.sizeOfExcludingThis(mallocSizeOf); +} + +bool +js::intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString()); + + SharedIntlData& sharedIntlData = cx->sharedIntlData; + + RootedString timeZone(cx, args[0].toString()); + RootedString validatedTimeZone(cx); + if (!sharedIntlData.validateTimeZoneName(cx, timeZone, &validatedTimeZone)) + return false; + + if (validatedTimeZone) + args.rval().setString(validatedTimeZone); + else + args.rval().setNull(); + + return true; +} + +bool +js::intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString()); + + SharedIntlData& sharedIntlData = cx->sharedIntlData; + + // Some time zone names are canonicalized differently by ICU -- handle + // those first: + RootedString timeZone(cx, args[0].toString()); + RootedString ianaTimeZone(cx); + if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA(cx, timeZone, &ianaTimeZone)) + return false; + + if (ianaTimeZone) { + args.rval().setString(ianaTimeZone); + return true; + } + + AutoStableStringChars stableChars(cx); + if (!stableChars.initTwoByte(cx, timeZone)) + return false; + + mozilla::Range<const char16_t> tzchars = stableChars.twoByteRange(); + + Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx); + if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE)) + return false; + + UBool* isSystemID = nullptr; + UErrorCode status = U_ZERO_ERROR; + int32_t size = ucal_getCanonicalTimeZoneID(Char16ToUChar(tzchars.begin().get()), + tzchars.length(), Char16ToUChar(chars.begin()), + INITIAL_CHAR_BUFFER_SIZE, isSystemID, &status); + if (status == U_BUFFER_OVERFLOW_ERROR) { + MOZ_ASSERT(size >= 0); + if (!chars.resize(size_t(size))) + return false; + status = U_ZERO_ERROR; + ucal_getCanonicalTimeZoneID(Char16ToUChar(tzchars.begin().get()), tzchars.length(), + Char16ToUChar(chars.begin()), size, isSystemID, &status); + } + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + MOZ_ASSERT(size >= 0); + JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size)); + if (!str) + return false; + args.rval().setString(str); + return true; +} + +bool +js::intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + // The current default might be stale, because JS::ResetTimeZone() doesn't + // immediately update ICU's default time zone. So perform an update if + // needed. + js::ResyncICUDefaultTimeZone(); + + Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx); + if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE)) + return false; + + UErrorCode status = U_ZERO_ERROR; + int32_t size = ucal_getDefaultTimeZone(Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE, + &status); + if (status == U_BUFFER_OVERFLOW_ERROR) { + MOZ_ASSERT(size >= 0); + if (!chars.resize(size_t(size))) + return false; + status = U_ZERO_ERROR; + ucal_getDefaultTimeZone(Char16ToUChar(chars.begin()), size, &status); + } + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + MOZ_ASSERT(size >= 0); + JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size)); + if (!str) + return false; + args.rval().setString(str); + return true; +} + +bool +js::intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + UErrorCode status = U_ZERO_ERROR; + const UChar* uTimeZone = nullptr; + int32_t uTimeZoneLength = 0; + const char* rootLocale = ""; + UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, rootLocale, UCAL_DEFAULT, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + ScopedICUObject<UCalendar, ucal_close> toClose(cal); + + int32_t offset = ucal_get(cal, UCAL_ZONE_OFFSET, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + args.rval().setInt32(offset); + return true; +} + +bool +js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(args[0].isString()); + MOZ_ASSERT(args[1].isString()); + + JSAutoByteString locale(cx, args[0].toString()); + if (!locale) + return false; + + JSFlatString* skeletonFlat = args[1].toString()->ensureFlat(cx); + if (!skeletonFlat) + return false; + + AutoStableStringChars stableChars(cx); + if (!stableChars.initTwoByte(cx, skeletonFlat)) + return false; + + mozilla::Range<const char16_t> skeletonChars = stableChars.twoByteRange(); + uint32_t skeletonLen = u_strlen(Char16ToUChar(skeletonChars.begin().get())); + + UErrorCode status = U_ZERO_ERROR; + UDateTimePatternGenerator* gen = udatpg_open(icuLocale(locale.ptr()), &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + ScopedICUObject<UDateTimePatternGenerator, udatpg_close> toClose(gen); + + int32_t size = udatpg_getBestPattern(gen, Char16ToUChar(skeletonChars.begin().get()), + skeletonLen, nullptr, 0, &status); + if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + ScopedJSFreePtr<UChar> pattern(cx->pod_malloc<UChar>(size + 1)); + if (!pattern) + return false; + pattern[size] = '\0'; + status = U_ZERO_ERROR; + udatpg_getBestPattern(gen, Char16ToUChar(skeletonChars.begin().get()), + skeletonLen, pattern, size, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + RootedString str(cx, JS_NewUCStringCopyZ(cx, reinterpret_cast<char16_t*>(pattern.get()))); + if (!str) + return false; + args.rval().setString(str); + return true; +} + +/** + * Returns a new UDateFormat with the locale and date-time formatting options + * of the given DateTimeFormat. + */ +static UDateFormat* +NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat) +{ + RootedValue value(cx); + + RootedObject internals(cx, GetInternals(cx, dateTimeFormat)); + if (!internals) + return nullptr; + + if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) + return nullptr; + JSAutoByteString locale(cx, value.toString()); + if (!locale) + return nullptr; + + // We don't need to look at calendar and numberingSystem - they can only be + // set via the Unicode locale extension and are therefore already set on + // locale. + + if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value)) + return nullptr; + + AutoStableStringChars timeZoneChars(cx); + Rooted<JSFlatString*> timeZoneFlat(cx, value.toString()->ensureFlat(cx)); + if (!timeZoneFlat || !timeZoneChars.initTwoByte(cx, timeZoneFlat)) + return nullptr; + + const UChar* uTimeZone = Char16ToUChar(timeZoneChars.twoByteRange().begin().get()); + uint32_t uTimeZoneLength = u_strlen(uTimeZone); + + if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) + return nullptr; + + AutoStableStringChars patternChars(cx); + Rooted<JSFlatString*> patternFlat(cx, value.toString()->ensureFlat(cx)); + if (!patternFlat || !patternChars.initTwoByte(cx, patternFlat)) + return nullptr; + + const UChar* uPattern = Char16ToUChar(patternChars.twoByteRange().begin().get()); + uint32_t uPatternLength = u_strlen(uPattern); + + UErrorCode status = U_ZERO_ERROR; + UDateFormat* df = + udat_open(UDAT_PATTERN, UDAT_PATTERN, icuLocale(locale.ptr()), uTimeZone, uTimeZoneLength, + uPattern, uPatternLength, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return nullptr; + } + + // ECMAScript requires the Gregorian calendar to be used from the beginning + // of ECMAScript time. + UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(df)); + ucal_setGregorianChange(cal, StartOfTime, &status); + + // An error here means the calendar is not Gregorian, so we don't care. + + return df; +} + +static bool +intl_FormatDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result) +{ + if (!IsFinite(x)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE); + return false; + } + + Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx); + if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE)) + return false; + UErrorCode status = U_ZERO_ERROR; + int size = udat_format(df, x, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE, + nullptr, &status); + if (status == U_BUFFER_OVERFLOW_ERROR) { + if (!chars.resize(size)) + return false; + status = U_ZERO_ERROR; + udat_format(df, x, Char16ToUChar(chars.begin()), size, nullptr, &status); + } + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size); + if (!str) + return false; + + result.setString(str); + + return true; +} + +using FieldType = ImmutablePropertyNamePtr JSAtomState::*; + +static FieldType +GetFieldTypeForFormatField(UDateFormatField fieldName) +{ + // See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This + // switch is deliberately exhaustive: cases might have to be added/removed + // if this code is compiled with a different ICU with more + // UDateFormatField enum initializers. Please guard such cases with + // appropriate ICU version-testing #ifdefs, should cross-version divergence + // occur. + switch (fieldName) { + case UDAT_ERA_FIELD: + return &JSAtomState::era; + case UDAT_YEAR_FIELD: + case UDAT_YEAR_WOY_FIELD: + case UDAT_EXTENDED_YEAR_FIELD: + case UDAT_YEAR_NAME_FIELD: + return &JSAtomState::year; + + case UDAT_MONTH_FIELD: + case UDAT_STANDALONE_MONTH_FIELD: + return &JSAtomState::month; + + case UDAT_DATE_FIELD: + case UDAT_JULIAN_DAY_FIELD: + return &JSAtomState::day; + + case UDAT_HOUR_OF_DAY1_FIELD: + case UDAT_HOUR_OF_DAY0_FIELD: + case UDAT_HOUR1_FIELD: + case UDAT_HOUR0_FIELD: + return &JSAtomState::hour; + + case UDAT_MINUTE_FIELD: + return &JSAtomState::minute; + + case UDAT_SECOND_FIELD: + return &JSAtomState::second; + + case UDAT_DAY_OF_WEEK_FIELD: + case UDAT_STANDALONE_DAY_FIELD: + case UDAT_DOW_LOCAL_FIELD: + case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD: + return &JSAtomState::weekday; + + case UDAT_AM_PM_FIELD: + return &JSAtomState::dayPeriod; + + case UDAT_TIMEZONE_FIELD: + return &JSAtomState::timeZoneName; + + case UDAT_FRACTIONAL_SECOND_FIELD: + case UDAT_DAY_OF_YEAR_FIELD: + case UDAT_WEEK_OF_YEAR_FIELD: + case UDAT_WEEK_OF_MONTH_FIELD: + case UDAT_MILLISECONDS_IN_DAY_FIELD: + case UDAT_TIMEZONE_RFC_FIELD: + case UDAT_TIMEZONE_GENERIC_FIELD: + case UDAT_QUARTER_FIELD: + case UDAT_STANDALONE_QUARTER_FIELD: + case UDAT_TIMEZONE_SPECIAL_FIELD: + case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: + case UDAT_TIMEZONE_ISO_FIELD: + case UDAT_TIMEZONE_ISO_LOCAL_FIELD: +#ifndef U_HIDE_INTERNAL_API + case UDAT_RELATED_YEAR_FIELD: +#endif +#ifndef U_HIDE_DRAFT_API + case UDAT_AM_PM_MIDNIGHT_NOON_FIELD: + case UDAT_FLEXIBLE_DAY_PERIOD_FIELD: +#endif +#ifndef U_HIDE_INTERNAL_API + case UDAT_TIME_SEPARATOR_FIELD: +#endif + // These fields are all unsupported. + return nullptr; + + case UDAT_FIELD_COUNT: + MOZ_ASSERT_UNREACHABLE("format field sentinel value returned by " + "iterator!"); + } + + MOZ_ASSERT_UNREACHABLE("unenumerated, undocumented format field returned " + "by iterator"); + return nullptr; +} + +static bool +intl_FormatToPartsDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result) +{ + if (!IsFinite(x)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE); + return false; + } + + Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx); + if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE)) + return false; + + UErrorCode status = U_ZERO_ERROR; + UFieldPositionIterator* fpositer = ufieldpositer_open(&status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + auto closeFieldPosIter = MakeScopeExit([&]() { ufieldpositer_close(fpositer); }); + + int resultSize = + udat_formatForFields(df, x, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE, + fpositer, &status); + if (status == U_BUFFER_OVERFLOW_ERROR) { + if (!chars.resize(resultSize)) + return false; + status = U_ZERO_ERROR; + udat_formatForFields(df, x, Char16ToUChar(chars.begin()), resultSize, fpositer, &status); + } + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx)); + if (!partsArray) + return false; + if (resultSize == 0) { + // An empty string contains no parts, so avoid extra work below. + result.setObject(*partsArray); + return true; + } + + RootedString overallResult(cx, NewStringCopyN<CanGC>(cx, chars.begin(), resultSize)); + if (!overallResult) + return false; + + size_t lastEndIndex = 0; + + uint32_t partIndex = 0; + RootedObject singlePart(cx); + RootedValue partType(cx); + RootedString partSubstr(cx); + RootedValue val(cx); + + auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) { + singlePart = NewBuiltinClassInstance<PlainObject>(cx); + if (!singlePart) + return false; + + partType = StringValue(cx->names().*type); + if (!DefineProperty(cx, singlePart, cx->names().type, partType)) + return false; + + partSubstr = SubstringKernel(cx, overallResult, beginIndex, endIndex - beginIndex); + if (!partSubstr) + return false; + + val = StringValue(partSubstr); + if (!DefineProperty(cx, singlePart, cx->names().value, val)) + return false; + + val = ObjectValue(*singlePart); + if (!DefineElement(cx, partsArray, partIndex, val)) + return false; + + lastEndIndex = endIndex; + partIndex++; + return true; + }; + + int32_t fieldInt, beginIndexInt, endIndexInt; + while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt, &endIndexInt)) >= 0) { + MOZ_ASSERT(beginIndexInt >= 0); + MOZ_ASSERT(endIndexInt >= 0); + MOZ_ASSERT(beginIndexInt <= endIndexInt, + "field iterator returning invalid range"); + + size_t beginIndex(beginIndexInt); + size_t endIndex(endIndexInt); + + // Technically this isn't guaranteed. But it appears true in pratice, + // and http://bugs.icu-project.org/trac/ticket/12024 is expected to + // correct the documentation lapse. + MOZ_ASSERT(lastEndIndex <= beginIndex, + "field iteration didn't return fields in order start to " + "finish as expected"); + + if (FieldType type = GetFieldTypeForFormatField(static_cast<UDateFormatField>(fieldInt))) { + if (lastEndIndex < beginIndex) { + if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) + return false; + } + + if (!AppendPart(type, beginIndex, endIndex)) + return false; + } + } + + // Append any final literal. + if (lastEndIndex < overallResult->length()) { + if (!AppendPart(&JSAtomState::literal, lastEndIndex, overallResult->length())) + return false; + } + + result.setObject(*partsArray); + return true; +} + +bool +js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 3); + MOZ_ASSERT(args[0].isObject()); + MOZ_ASSERT(args[1].isNumber()); + MOZ_ASSERT(args[2].isBoolean()); + + RootedObject dateTimeFormat(cx, &args[0].toObject()); + + // Obtain a UDateFormat object, cached if possible. + bool isDateTimeFormatInstance = dateTimeFormat->getClass() == &DateTimeFormatClass; + UDateFormat* df; + if (isDateTimeFormatInstance) { + void* priv = + dateTimeFormat->as<NativeObject>().getReservedSlot(UDATE_FORMAT_SLOT).toPrivate(); + df = static_cast<UDateFormat*>(priv); + if (!df) { + df = NewUDateFormat(cx, dateTimeFormat); + if (!df) + return false; + dateTimeFormat->as<NativeObject>().setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(df)); + } + } else { + // There's no good place to cache the ICU date-time format for an object + // that has been initialized as a DateTimeFormat but is not a + // DateTimeFormat instance. One possibility might be to add a + // DateTimeFormat instance as an internal property to each such object. + df = NewUDateFormat(cx, dateTimeFormat); + if (!df) + return false; + } + + // Use the UDateFormat to actually format the time stamp. + RootedValue result(cx); + bool success = args[2].toBoolean() + ? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), &result) + : intl_FormatDateTime(cx, df, args[1].toNumber(), &result); + + if (!isDateTimeFormatInstance) + udat_close(df); + if (!success) + return false; + args.rval().set(result); + return true; +} + +bool +js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + + JSAutoByteString locale(cx, args[0].toString()); + if (!locale) + return false; + + UErrorCode status = U_ZERO_ERROR; + const UChar* uTimeZone = nullptr; + int32_t uTimeZoneLength = 0; + UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, locale.ptr(), UCAL_DEFAULT, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + ScopedICUObject<UCalendar, ucal_close> toClose(cal); + + RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx)); + if (!info) + return false; + + RootedValue v(cx); + int32_t firstDayOfWeek = ucal_getAttribute(cal, UCAL_FIRST_DAY_OF_WEEK); + v.setInt32(firstDayOfWeek); + + if (!DefineProperty(cx, info, cx->names().firstDayOfWeek, v)) + return false; + + int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK); + v.setInt32(minDays); + if (!DefineProperty(cx, info, cx->names().minDays, v)) + return false; + + UCalendarWeekdayType prevDayType = ucal_getDayOfWeekType(cal, UCAL_SATURDAY, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + RootedValue weekendStart(cx), weekendEnd(cx); + + for (int i = UCAL_SUNDAY; i <= UCAL_SATURDAY; i++) { + UCalendarDaysOfWeek dayOfWeek = static_cast<UCalendarDaysOfWeek>(i); + UCalendarWeekdayType type = ucal_getDayOfWeekType(cal, dayOfWeek, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + if (prevDayType != type) { + switch (type) { + case UCAL_WEEKDAY: + // If the first Weekday after Weekend is Sunday (1), + // then the last Weekend day is Saturday (7). + // Otherwise we'll just take the previous days number. + weekendEnd.setInt32(i == 1 ? 7 : i - 1); + break; + case UCAL_WEEKEND: + weekendStart.setInt32(i); + break; + case UCAL_WEEKEND_ONSET: + case UCAL_WEEKEND_CEASE: + // At the time this code was added, ICU apparently never behaves this way, + // so just throw, so that users will report a bug and we can decide what to + // do. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + default: + break; + } + } + + prevDayType = type; + } + + MOZ_ASSERT(weekendStart.isInt32()); + MOZ_ASSERT(weekendEnd.isInt32()); + + if (!DefineProperty(cx, info, cx->names().weekendStart, weekendStart)) + return false; + + if (!DefineProperty(cx, info, cx->names().weekendEnd, weekendEnd)) + return false; + + args.rval().setObject(*info); + return true; +} + +/******************** Intl ********************/ + +const Class js::IntlClass = { + js_Object_str, + JSCLASS_HAS_CACHED_PROTO(JSProto_Intl) +}; + +#if JS_HAS_TOSOURCE +static bool +intl_toSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(cx->names().Intl); + return true; +} +#endif + +static const JSFunctionSpec intl_static_methods[] = { +#if JS_HAS_TOSOURCE + JS_FN(js_toSource_str, intl_toSource, 0, 0), +#endif + JS_SELF_HOSTED_FN("getCanonicalLocales", "Intl_getCanonicalLocales", 1, 0), + JS_FS_END +}; + +/** + * Initializes the Intl Object and its standard built-in properties. + * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1 + */ +bool +GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global) +{ + RootedObject proto(cx, global->getOrCreateObjectPrototype(cx)); + if (!proto) + return false; + + // The |Intl| object is just a plain object with some "static" function + // properties and some constructor properties. + RootedObject intl(cx, NewObjectWithGivenProto(cx, &IntlClass, proto, SingletonObject)); + if (!intl) + return false; + + // Add the static functions. + if (!JS_DefineFunctions(cx, intl, intl_static_methods)) + return false; + + // Add the constructor properties, computing and returning the relevant + // prototype objects needed below. + RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global)); + if (!collatorProto) + return false; + RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global)); + if (!dateTimeFormatProto) + return false; + RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global)); + if (!numberFormatProto) + return false; + + // The |Intl| object is fully set up now, so define the global property. + RootedValue intlValue(cx, ObjectValue(*intl)); + if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr, + JSPROP_RESOLVING)) + { + return false; + } + + // Now that the |Intl| object is successfully added, we can OOM-safely fill + // in all relevant reserved global slots. + + // Cache the various prototypes, for use in creating instances of these + // objects with the proper [[Prototype]] as "the original value of + // |Intl.Collator.prototype|" and similar. For builtin classes like + // |String.prototype| we have |JSProto_*| that enables + // |getPrototype(JSProto_*)|, but that has global-object-property-related + // baggage we don't need or want, so we use one-off reserved slots. + global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto)); + global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto)); + global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto)); + + // Also cache |Intl| to implement spec language that conditions behavior + // based on values being equal to "the standard built-in |Intl| object". + // Use |setConstructor| to correspond with |JSProto_Intl|. + // + // XXX We should possibly do a one-off reserved slot like above. + global->setConstructor(JSProto_Intl, ObjectValue(*intl)); + return true; +} + +JSObject* +js::InitIntlClass(JSContext* cx, HandleObject obj) +{ + Handle<GlobalObject*> global = obj.as<GlobalObject>(); + if (!GlobalObject::initIntlObject(cx, global)) + return nullptr; + + return &global->getConstructor(JSProto_Intl).toObject(); +} |