michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * The Intl module specified by standard ECMA-402, michael@0: * ECMAScript Internationalization API Specification. michael@0: */ michael@0: michael@0: #include "builtin/Intl.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jsobj.h" michael@0: michael@0: #if ENABLE_INTL_API michael@0: #include "unicode/locid.h" michael@0: #include "unicode/numsys.h" michael@0: #include "unicode/ucal.h" michael@0: #include "unicode/ucol.h" michael@0: #include "unicode/udat.h" michael@0: #include "unicode/udatpg.h" michael@0: #include "unicode/uenum.h" michael@0: #include "unicode/unum.h" michael@0: #include "unicode/ustring.h" michael@0: #endif michael@0: #include "vm/DateTime.h" michael@0: #include "vm/GlobalObject.h" michael@0: #include "vm/Interpreter.h" michael@0: #include "vm/Stack.h" michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: #include "vm/ObjectImpl-inl.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using mozilla::IsFinite; michael@0: using mozilla::IsNegativeZero; michael@0: michael@0: #if ENABLE_INTL_API michael@0: using icu::Locale; michael@0: using icu::NumberingSystem; michael@0: #endif michael@0: michael@0: michael@0: /* michael@0: * Pervasive note: ICU functions taking a UErrorCode in/out parameter always michael@0: * test that parameter before doing anything, and will return immediately if michael@0: * the value indicates that a failure occurred in a prior ICU call, michael@0: * without doing anything else. See michael@0: * http://userguide.icu-project.org/design#TOC-Error-Handling michael@0: */ michael@0: michael@0: michael@0: /******************** ICU stubs ********************/ michael@0: michael@0: #if !ENABLE_INTL_API michael@0: michael@0: /* michael@0: * When the Internationalization API isn't enabled, we also shouldn't link michael@0: * against ICU. However, we still want to compile this code in order to prevent michael@0: * bit rot. The following stub implementations for ICU functions make this michael@0: * possible. The functions using them should never be called, so they assert michael@0: * and return error codes. Signatures adapted from ICU header files locid.h, michael@0: * numsys.h, ucal.h, ucol.h, udat.h, udatpg.h, uenum.h, unum.h; see the ICU michael@0: * directory for license. michael@0: */ michael@0: michael@0: static int32_t michael@0: u_strlen(const UChar *s) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("u_strlen: Intl API disabled"); michael@0: } michael@0: michael@0: struct UEnumeration; michael@0: michael@0: static int32_t michael@0: uenum_count(UEnumeration *en, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("uenum_count: Intl API disabled"); michael@0: } michael@0: michael@0: static const char * michael@0: uenum_next(UEnumeration *en, int32_t *resultLength, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("uenum_next: Intl API disabled"); michael@0: } michael@0: michael@0: static void michael@0: uenum_close(UEnumeration *en) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("uenum_close: Intl API disabled"); michael@0: } michael@0: michael@0: struct UCollator; michael@0: michael@0: enum UColAttribute { michael@0: UCOL_ALTERNATE_HANDLING, michael@0: UCOL_CASE_FIRST, michael@0: UCOL_CASE_LEVEL, michael@0: UCOL_NORMALIZATION_MODE, michael@0: UCOL_STRENGTH, michael@0: UCOL_NUMERIC_COLLATION, michael@0: }; michael@0: michael@0: enum UColAttributeValue { michael@0: UCOL_DEFAULT = -1, michael@0: UCOL_PRIMARY = 0, michael@0: UCOL_SECONDARY = 1, michael@0: UCOL_TERTIARY = 2, michael@0: UCOL_OFF = 16, michael@0: UCOL_ON = 17, michael@0: UCOL_SHIFTED = 20, michael@0: UCOL_LOWER_FIRST = 24, michael@0: UCOL_UPPER_FIRST = 25, michael@0: }; michael@0: michael@0: enum UCollationResult { michael@0: UCOL_EQUAL = 0, michael@0: UCOL_GREATER = 1, michael@0: UCOL_LESS = -1 michael@0: }; michael@0: michael@0: static int32_t michael@0: ucol_countAvailable(void) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucol_countAvailable: Intl API disabled"); michael@0: } michael@0: michael@0: static const char * michael@0: ucol_getAvailable(int32_t localeIndex) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucol_getAvailable: Intl API disabled"); michael@0: } michael@0: michael@0: static UCollator * michael@0: ucol_open(const char *loc, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucol_open: Intl API disabled"); michael@0: } michael@0: michael@0: static void michael@0: ucol_setAttribute(UCollator *coll, UColAttribute attr, UColAttributeValue value, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucol_setAttribute: Intl API disabled"); michael@0: } michael@0: michael@0: static UCollationResult michael@0: ucol_strcoll(const UCollator *coll, const UChar *source, int32_t sourceLength, michael@0: const UChar *target, int32_t targetLength) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucol_strcoll: Intl API disabled"); michael@0: } michael@0: michael@0: static void michael@0: ucol_close(UCollator *coll) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucol_close: Intl API disabled"); michael@0: } michael@0: michael@0: static UEnumeration * michael@0: ucol_getKeywordValuesForLocale(const char *key, const char *locale, UBool commonlyUsed, michael@0: UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucol_getKeywordValuesForLocale: Intl API disabled"); michael@0: } michael@0: michael@0: struct UParseError; michael@0: struct UFieldPosition; michael@0: typedef void *UNumberFormat; michael@0: michael@0: enum UNumberFormatStyle { michael@0: UNUM_DECIMAL = 1, michael@0: UNUM_CURRENCY, michael@0: UNUM_PERCENT, michael@0: UNUM_CURRENCY_ISO, michael@0: UNUM_CURRENCY_PLURAL, michael@0: }; michael@0: michael@0: enum UNumberFormatRoundingMode { michael@0: UNUM_ROUND_HALFUP, michael@0: }; michael@0: michael@0: enum UNumberFormatAttribute { michael@0: UNUM_GROUPING_USED, michael@0: UNUM_MIN_INTEGER_DIGITS, michael@0: UNUM_MAX_FRACTION_DIGITS, michael@0: UNUM_MIN_FRACTION_DIGITS, michael@0: UNUM_ROUNDING_MODE, michael@0: UNUM_SIGNIFICANT_DIGITS_USED, michael@0: UNUM_MIN_SIGNIFICANT_DIGITS, michael@0: UNUM_MAX_SIGNIFICANT_DIGITS, michael@0: }; michael@0: michael@0: enum UNumberFormatTextAttribute { michael@0: UNUM_CURRENCY_CODE, michael@0: }; michael@0: michael@0: static int32_t michael@0: unum_countAvailable(void) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("unum_countAvailable: Intl API disabled"); michael@0: } michael@0: michael@0: static const char * michael@0: unum_getAvailable(int32_t localeIndex) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("unum_getAvailable: Intl API disabled"); michael@0: } michael@0: michael@0: static UNumberFormat * michael@0: unum_open(UNumberFormatStyle style, const UChar *pattern, int32_t patternLength, michael@0: const char *locale, UParseError *parseErr, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("unum_open: Intl API disabled"); michael@0: } michael@0: michael@0: static void michael@0: unum_setAttribute(UNumberFormat *fmt, UNumberFormatAttribute attr, int32_t newValue) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("unum_setAttribute: Intl API disabled"); michael@0: } michael@0: michael@0: static int32_t michael@0: unum_formatDouble(const UNumberFormat *fmt, double number, UChar *result, michael@0: int32_t resultLength, UFieldPosition *pos, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("unum_formatDouble: Intl API disabled"); michael@0: } michael@0: michael@0: static void michael@0: unum_close(UNumberFormat *fmt) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("unum_close: Intl API disabled"); michael@0: } michael@0: michael@0: static void michael@0: unum_setTextAttribute(UNumberFormat *fmt, UNumberFormatTextAttribute tag, const UChar *newValue, michael@0: int32_t newValueLength, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("unum_setTextAttribute: Intl API disabled"); michael@0: } michael@0: michael@0: class Locale { michael@0: public: michael@0: Locale(const char *language, const char *country = 0, const char *variant = 0, michael@0: const char *keywordsAndValues = 0); michael@0: }; michael@0: michael@0: Locale::Locale(const char *language, const char *country, const char *variant, michael@0: const char *keywordsAndValues) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("Locale::Locale: Intl API disabled"); michael@0: } michael@0: michael@0: class NumberingSystem { michael@0: public: michael@0: static NumberingSystem *createInstance(const Locale &inLocale, UErrorCode &status); michael@0: const char *getName(); michael@0: }; michael@0: michael@0: NumberingSystem * michael@0: NumberingSystem::createInstance(const Locale &inLocale, UErrorCode &status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("NumberingSystem::createInstance: Intl API disabled"); michael@0: } michael@0: michael@0: const char * michael@0: NumberingSystem::getName() michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("NumberingSystem::getName: Intl API disabled"); michael@0: } michael@0: michael@0: typedef void *UCalendar; michael@0: michael@0: enum UCalendarType { michael@0: UCAL_TRADITIONAL, michael@0: UCAL_DEFAULT = UCAL_TRADITIONAL, michael@0: UCAL_GREGORIAN michael@0: }; michael@0: michael@0: static UCalendar * michael@0: ucal_open(const UChar *zoneID, int32_t len, const char *locale, michael@0: UCalendarType type, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucal_open: Intl API disabled"); michael@0: } michael@0: michael@0: static const char * michael@0: ucal_getType(const UCalendar *cal, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucal_getType: Intl API disabled"); michael@0: } michael@0: michael@0: static UEnumeration * michael@0: ucal_getKeywordValuesForLocale(const char *key, const char *locale, michael@0: UBool commonlyUsed, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucal_getKeywordValuesForLocale: Intl API disabled"); michael@0: } michael@0: michael@0: static void michael@0: ucal_close(UCalendar *cal) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucal_close: Intl API disabled"); michael@0: } michael@0: michael@0: typedef void *UDateTimePatternGenerator; michael@0: michael@0: static UDateTimePatternGenerator * michael@0: udatpg_open(const char *locale, UErrorCode *pErrorCode) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("udatpg_open: Intl API disabled"); michael@0: } michael@0: michael@0: static int32_t michael@0: udatpg_getBestPattern(UDateTimePatternGenerator *dtpg, const UChar *skeleton, michael@0: int32_t length, UChar *bestPattern, int32_t capacity, michael@0: UErrorCode *pErrorCode) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("udatpg_getBestPattern: Intl API disabled"); michael@0: } michael@0: michael@0: static void michael@0: udatpg_close(UDateTimePatternGenerator *dtpg) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("udatpg_close: Intl API disabled"); michael@0: } michael@0: michael@0: typedef void *UCalendar; michael@0: typedef void *UDateFormat; michael@0: michael@0: enum UDateFormatStyle { michael@0: UDAT_PATTERN = -2, michael@0: UDAT_IGNORE = UDAT_PATTERN michael@0: }; michael@0: michael@0: static int32_t michael@0: udat_countAvailable(void) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("udat_countAvailable: Intl API disabled"); michael@0: } michael@0: michael@0: static const char * michael@0: udat_getAvailable(int32_t localeIndex) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("udat_getAvailable: Intl API disabled"); michael@0: } michael@0: michael@0: static UDateFormat * michael@0: udat_open(UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const char *locale, michael@0: const UChar *tzID, int32_t tzIDLength, const UChar *pattern, michael@0: int32_t patternLength, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("udat_open: Intl API disabled"); michael@0: } michael@0: michael@0: static const UCalendar * michael@0: udat_getCalendar(const UDateFormat *fmt) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("udat_getCalendar: Intl API disabled"); michael@0: } michael@0: michael@0: static void michael@0: ucal_setGregorianChange(UCalendar *cal, UDate date, UErrorCode *pErrorCode) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("ucal_setGregorianChange: Intl API disabled"); michael@0: } michael@0: michael@0: static int32_t michael@0: udat_format(const UDateFormat *format, UDate dateToFormat, UChar *result, michael@0: int32_t resultLength, UFieldPosition *position, UErrorCode *status) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("udat_format: Intl API disabled"); michael@0: } michael@0: michael@0: static void michael@0: udat_close(UDateFormat *format) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("udat_close: Intl API disabled"); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: michael@0: /******************** Common to Intl constructors ********************/ michael@0: michael@0: static bool michael@0: IntlInitialize(JSContext *cx, HandleObject obj, Handle initializer, michael@0: HandleValue locales, HandleValue options) michael@0: { michael@0: RootedValue initializerValue(cx); michael@0: if (!GlobalObject::getIntrinsicValue(cx, cx->global(), initializer, &initializerValue)) michael@0: return false; michael@0: JS_ASSERT(initializerValue.isObject()); michael@0: JS_ASSERT(initializerValue.toObject().is()); michael@0: michael@0: InvokeArgs args(cx); michael@0: if (!args.init(3)) michael@0: return false; michael@0: michael@0: args.setCallee(initializerValue); michael@0: args.setThis(NullValue()); michael@0: args[0].setObject(*obj); michael@0: args[1].set(locales); michael@0: args[2].set(options); michael@0: michael@0: return Invoke(cx, args); michael@0: } michael@0: michael@0: // CountAvailable and GetAvailable describe the signatures used for ICU API michael@0: // to determine available locales for various functionality. michael@0: typedef int32_t michael@0: (* CountAvailable)(void); michael@0: michael@0: typedef const char * michael@0: (* GetAvailable)(int32_t localeIndex); michael@0: michael@0: static bool michael@0: intl_availableLocales(JSContext *cx, CountAvailable countAvailable, michael@0: GetAvailable getAvailable, MutableHandleValue result) michael@0: { michael@0: RootedObject locales(cx, NewObjectWithGivenProto(cx, &JSObject::class_, nullptr, nullptr)); michael@0: if (!locales) michael@0: return false; michael@0: michael@0: #if ENABLE_INTL_API michael@0: uint32_t count = countAvailable(); michael@0: RootedValue t(cx, BooleanValue(true)); michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: const char *locale = getAvailable(i); michael@0: ScopedJSFreePtr lang(JS_strdup(cx, locale)); michael@0: if (!lang) michael@0: return false; michael@0: char *p; michael@0: while ((p = strchr(lang, '_'))) michael@0: *p = '-'; michael@0: RootedAtom a(cx, Atomize(cx, lang, strlen(lang))); michael@0: if (!a) michael@0: return false; michael@0: if (!JSObject::defineProperty(cx, locales, a->asPropertyName(), t, michael@0: JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: #endif michael@0: result.setObject(*locales); michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Returns the object holding the internal properties for obj. michael@0: */ michael@0: static bool michael@0: GetInternals(JSContext *cx, HandleObject obj, MutableHandleObject internals) michael@0: { michael@0: RootedValue getInternalsValue(cx); michael@0: if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().getInternals, &getInternalsValue)) michael@0: return false; michael@0: JS_ASSERT(getInternalsValue.isObject()); michael@0: JS_ASSERT(getInternalsValue.toObject().is()); michael@0: michael@0: InvokeArgs args(cx); michael@0: if (!args.init(1)) michael@0: return false; michael@0: michael@0: args.setCallee(getInternalsValue); michael@0: args.setThis(NullValue()); michael@0: args[0].setObject(*obj); michael@0: michael@0: if (!Invoke(cx, args)) michael@0: return false; michael@0: internals.set(&args.rval().toObject()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: equal(const char *s1, const char *s2) michael@0: { michael@0: return !strcmp(s1, s2); michael@0: } michael@0: michael@0: static bool michael@0: equal(JSAutoByteString &s1, const char *s2) michael@0: { michael@0: return !strcmp(s1.ptr(), s2); michael@0: } michael@0: michael@0: static const char * michael@0: icuLocale(const char *locale) michael@0: { michael@0: if (equal(locale, "und")) michael@0: return ""; // ICU root locale michael@0: return locale; michael@0: } michael@0: michael@0: // Simple RAII for ICU objects. MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE michael@0: // unfortunately doesn't work because of namespace incompatibilities michael@0: // (TypeSpecificDelete cannot be in icu and mozilla at the same time) michael@0: // and because ICU declares both UNumberFormat and UDateTimePatternGenerator michael@0: // as void*. michael@0: template michael@0: class ScopedICUObject michael@0: { michael@0: T *ptr_; michael@0: void (* deleter_)(T*); michael@0: michael@0: public: michael@0: ScopedICUObject(T *ptr, void (*deleter)(T*)) michael@0: : ptr_(ptr), michael@0: deleter_(deleter) michael@0: {} michael@0: michael@0: ~ScopedICUObject() { michael@0: if (ptr_) michael@0: deleter_(ptr_); michael@0: } michael@0: michael@0: // In cases where an object should be deleted on abnormal exits, michael@0: // but returned to the caller if everything goes well, call forget() michael@0: // to transfer the object just before returning. michael@0: T *forget() { michael@0: T *tmp = ptr_; michael@0: ptr_ = nullptr; michael@0: return tmp; michael@0: } michael@0: }; michael@0: michael@0: // As a small optimization (not important for correctness), this is the inline michael@0: // capacity of a StringBuffer. michael@0: static const size_t INITIAL_STRING_BUFFER_SIZE = 32; michael@0: michael@0: michael@0: /******************** Collator ********************/ michael@0: michael@0: static void collator_finalize(FreeOp *fop, JSObject *obj); michael@0: michael@0: static const uint32_t UCOLLATOR_SLOT = 0; michael@0: static const uint32_t COLLATOR_SLOTS_COUNT = 1; michael@0: michael@0: static const Class CollatorClass = { michael@0: js_Object_str, michael@0: JSCLASS_HAS_RESERVED_SLOTS(COLLATOR_SLOTS_COUNT), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: collator_finalize michael@0: }; michael@0: michael@0: #if JS_HAS_TOSOURCE michael@0: static bool michael@0: collator_toSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setString(cx->names().Collator); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static const JSFunctionSpec collator_static_methods[] = { michael@0: JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_Collator_supportedLocalesOf", 1, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec collator_methods[] = { michael@0: JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0), michael@0: #if JS_HAS_TOSOURCE michael@0: JS_FN(js_toSource_str, collator_toSource, 0, 0), michael@0: #endif michael@0: JS_FS_END michael@0: }; michael@0: michael@0: /** michael@0: * Collator constructor. michael@0: * Spec: ECMAScript Internationalization API Specification, 10.1 michael@0: */ michael@0: static bool michael@0: Collator(JSContext *cx, CallArgs args, bool construct) michael@0: { michael@0: RootedObject obj(cx); michael@0: michael@0: if (!construct) { michael@0: // 10.1.2.1 step 3 michael@0: JSObject *intl = cx->global()->getOrCreateIntlObject(cx); michael@0: if (!intl) michael@0: return false; michael@0: RootedValue self(cx, args.thisv()); michael@0: if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { michael@0: // 10.1.2.1 step 4 michael@0: obj = ToObject(cx, self); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: // 10.1.2.1 step 5 michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return false; michael@0: if (!extensible) michael@0: return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); michael@0: } else { michael@0: // 10.1.2.1 step 3.a michael@0: construct = true; michael@0: } michael@0: } michael@0: if (construct) { michael@0: // 10.1.3.1 paragraph 2 michael@0: RootedObject proto(cx, cx->global()->getOrCreateCollatorPrototype(cx)); michael@0: if (!proto) michael@0: return false; michael@0: obj = NewObjectWithGivenProto(cx, &CollatorClass, proto, cx->global()); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: obj->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); michael@0: } michael@0: michael@0: // 10.1.2.1 steps 1 and 2; 10.1.3.1 steps 1 and 2 michael@0: RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); michael@0: RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); michael@0: michael@0: // 10.1.2.1 step 6; 10.1.3.1 step 3 michael@0: if (!IntlInitialize(cx, obj, cx->names().InitializeCollator, locales, options)) michael@0: return false; michael@0: michael@0: // 10.1.2.1 steps 3.a and 7 michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Collator(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return Collator(cx, args, args.isConstructing()); michael@0: } michael@0: michael@0: bool michael@0: js::intl_Collator(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 2); michael@0: // intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot michael@0: // be used with "new", but it still has to be treated as a constructor. michael@0: return Collator(cx, args, true); michael@0: } michael@0: michael@0: static void michael@0: collator_finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: UCollator *coll = static_cast(obj->getReservedSlot(UCOLLATOR_SLOT).toPrivate()); michael@0: if (coll) michael@0: ucol_close(coll); michael@0: } michael@0: michael@0: static JSObject * michael@0: InitCollatorClass(JSContext *cx, HandleObject Intl, Handle global) michael@0: { michael@0: RootedFunction ctor(cx, global->createConstructor(cx, &Collator, cx->names().Collator, 0)); michael@0: if (!ctor) michael@0: return nullptr; michael@0: michael@0: RootedObject proto(cx, global->as().getOrCreateCollatorPrototype(cx)); michael@0: if (!proto) michael@0: return nullptr; michael@0: if (!LinkConstructorAndPrototype(cx, ctor, proto)) michael@0: return nullptr; michael@0: michael@0: // 10.2.2 michael@0: if (!JS_DefineFunctions(cx, ctor, collator_static_methods)) michael@0: return nullptr; michael@0: michael@0: // 10.3.2 and 10.3.3 michael@0: if (!JS_DefineFunctions(cx, proto, collator_methods)) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * Install the getter for Collator.prototype.compare, which returns a bound michael@0: * comparison function for the specified Collator object (suitable for michael@0: * passing to methods like Array.prototype.sort). michael@0: */ michael@0: RootedValue getter(cx); michael@0: if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().CollatorCompareGet, &getter)) michael@0: return nullptr; michael@0: if (!JSObject::defineProperty(cx, proto, cx->names().compare, UndefinedHandleValue, michael@0: JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()), michael@0: nullptr, JSPROP_GETTER | JSPROP_SHARED)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: // 10.2.1 and 10.3 michael@0: if (!IntlInitialize(cx, proto, cx->names().InitializeCollator, UndefinedHandleValue, michael@0: UndefinedHandleValue)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: // 8.1 michael@0: RootedValue ctorValue(cx, ObjectValue(*ctor)); michael@0: if (!JSObject::defineProperty(cx, Intl, cx->names().Collator, ctorValue, michael@0: JS_PropertyStub, JS_StrictPropertyStub, 0)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: return ctor; michael@0: } michael@0: michael@0: bool michael@0: GlobalObject::initCollatorProto(JSContext *cx, Handle global) michael@0: { michael@0: RootedObject proto(cx, global->createBlankPrototype(cx, &CollatorClass)); michael@0: if (!proto) michael@0: return false; michael@0: proto->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); michael@0: global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*proto)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::intl_Collator_availableLocales(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 0); michael@0: michael@0: RootedValue result(cx); michael@0: if (!intl_availableLocales(cx, ucol_countAvailable, ucol_getAvailable, &result)) michael@0: return false; michael@0: args.rval().set(result); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::intl_availableCollations(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 1); michael@0: JS_ASSERT(args[0].isString()); michael@0: michael@0: JSAutoByteString locale(cx, args[0].toString()); michael@0: if (!locale) michael@0: return false; michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UEnumeration *values = ucol_getKeywordValuesForLocale("co", locale.ptr(), false, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: ScopedICUObject toClose(values, uenum_close); michael@0: michael@0: uint32_t count = uenum_count(values, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject collations(cx, NewDenseEmptyArray(cx)); michael@0: if (!collations) michael@0: return false; michael@0: michael@0: uint32_t index = 0; michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: const char *collation = uenum_next(values, nullptr, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: michael@0: // Per ECMA-402, 10.2.3, we don't include standard and search: michael@0: // "The values 'standard' and 'search' must not be used as elements in michael@0: // any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co michael@0: // array." michael@0: if (equal(collation, "standard") || equal(collation, "search")) michael@0: continue; michael@0: michael@0: // ICU returns old-style keyword values; map them to BCP 47 equivalents michael@0: // (see http://bugs.icu-project.org/trac/ticket/9620). michael@0: if (equal(collation, "dictionary")) michael@0: collation = "dict"; michael@0: else if (equal(collation, "gb2312han")) michael@0: collation = "gb2312"; michael@0: else if (equal(collation, "phonebook")) michael@0: collation = "phonebk"; michael@0: else if (equal(collation, "traditional")) michael@0: collation = "trad"; michael@0: michael@0: RootedString jscollation(cx, JS_NewStringCopyZ(cx, collation)); michael@0: if (!jscollation) michael@0: return false; michael@0: RootedValue element(cx, StringValue(jscollation)); michael@0: if (!JSObject::defineElement(cx, collations, index++, element)) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setObject(*collations); michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Returns a new UCollator with the locale and collation options michael@0: * of the given Collator. michael@0: */ michael@0: static UCollator * michael@0: NewUCollator(JSContext *cx, HandleObject collator) michael@0: { michael@0: RootedValue value(cx); michael@0: michael@0: RootedObject internals(cx); michael@0: if (!GetInternals(cx, collator, &internals)) michael@0: return nullptr; michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().locale, &value)) michael@0: return nullptr; michael@0: JSAutoByteString locale(cx, value.toString()); michael@0: if (!locale) michael@0: return nullptr; michael@0: michael@0: // UCollator options with default values. michael@0: UColAttributeValue uStrength = UCOL_DEFAULT; michael@0: UColAttributeValue uCaseLevel = UCOL_OFF; michael@0: UColAttributeValue uAlternate = UCOL_DEFAULT; michael@0: UColAttributeValue uNumeric = UCOL_OFF; michael@0: // Normalization is always on to meet the canonical equivalence requirement. michael@0: UColAttributeValue uNormalization = UCOL_ON; michael@0: UColAttributeValue uCaseFirst = UCOL_DEFAULT; michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().usage, &value)) michael@0: return nullptr; michael@0: JSAutoByteString usage(cx, value.toString()); michael@0: if (!usage) michael@0: return nullptr; michael@0: if (equal(usage, "search")) { michael@0: // ICU expects search as a Unicode locale extension on locale. michael@0: // Unicode locale extensions must occur before private use extensions. michael@0: const char *oldLocale = locale.ptr(); michael@0: const char *p; michael@0: size_t index; michael@0: size_t localeLen = strlen(oldLocale); michael@0: if ((p = strstr(oldLocale, "-x-"))) michael@0: index = p - oldLocale; michael@0: else michael@0: index = localeLen; michael@0: michael@0: const char *insert; michael@0: if ((p = strstr(oldLocale, "-u-")) && static_cast(p - oldLocale) < index) { michael@0: index = p - oldLocale + 2; michael@0: insert = "-co-search"; michael@0: } else { michael@0: insert = "-u-co-search"; michael@0: } michael@0: size_t insertLen = strlen(insert); michael@0: char *newLocale = cx->pod_malloc(localeLen + insertLen + 1); michael@0: if (!newLocale) michael@0: return nullptr; michael@0: memcpy(newLocale, oldLocale, index); michael@0: memcpy(newLocale + index, insert, insertLen); michael@0: memcpy(newLocale + index + insertLen, oldLocale + index, localeLen - index + 1); // '\0' michael@0: locale.clear(); michael@0: locale.initBytes(newLocale); michael@0: } michael@0: michael@0: // We don't need to look at the collation property - it can only be set michael@0: // via the Unicode locale extension and is therefore already set on michael@0: // locale. michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().sensitivity, &value)) michael@0: return nullptr; michael@0: JSAutoByteString sensitivity(cx, value.toString()); michael@0: if (!sensitivity) michael@0: return nullptr; michael@0: if (equal(sensitivity, "base")) { michael@0: uStrength = UCOL_PRIMARY; michael@0: } else if (equal(sensitivity, "accent")) { michael@0: uStrength = UCOL_SECONDARY; michael@0: } else if (equal(sensitivity, "case")) { michael@0: uStrength = UCOL_PRIMARY; michael@0: uCaseLevel = UCOL_ON; michael@0: } else { michael@0: JS_ASSERT(equal(sensitivity, "variant")); michael@0: uStrength = UCOL_TERTIARY; michael@0: } michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().ignorePunctuation, &value)) michael@0: return nullptr; michael@0: // According to the ICU team, UCOL_SHIFTED causes punctuation to be michael@0: // ignored. Looking at Unicode Technical Report 35, Unicode Locale Data michael@0: // Markup Language, "shifted" causes whitespace and punctuation to be michael@0: // ignored - that's a bit more than asked for, but there's no way to get michael@0: // less. michael@0: if (value.toBoolean()) michael@0: uAlternate = UCOL_SHIFTED; michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().numeric, &value)) michael@0: return nullptr; michael@0: if (!value.isUndefined() && value.toBoolean()) michael@0: uNumeric = UCOL_ON; michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().caseFirst, &value)) michael@0: return nullptr; michael@0: if (!value.isUndefined()) { michael@0: JSAutoByteString caseFirst(cx, value.toString()); michael@0: if (!caseFirst) michael@0: return nullptr; michael@0: if (equal(caseFirst, "upper")) michael@0: uCaseFirst = UCOL_UPPER_FIRST; michael@0: else if (equal(caseFirst, "lower")) michael@0: uCaseFirst = UCOL_LOWER_FIRST; michael@0: else michael@0: JS_ASSERT(equal(caseFirst, "false")); michael@0: } michael@0: michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UCollator *coll = ucol_open(icuLocale(locale.ptr()), &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return nullptr; michael@0: } michael@0: michael@0: ucol_setAttribute(coll, UCOL_STRENGTH, uStrength, &status); michael@0: ucol_setAttribute(coll, UCOL_CASE_LEVEL, uCaseLevel, &status); michael@0: ucol_setAttribute(coll, UCOL_ALTERNATE_HANDLING, uAlternate, &status); michael@0: ucol_setAttribute(coll, UCOL_NUMERIC_COLLATION, uNumeric, &status); michael@0: ucol_setAttribute(coll, UCOL_NORMALIZATION_MODE, uNormalization, &status); michael@0: ucol_setAttribute(coll, UCOL_CASE_FIRST, uCaseFirst, &status); michael@0: if (U_FAILURE(status)) { michael@0: ucol_close(coll); michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return nullptr; michael@0: } michael@0: michael@0: return coll; michael@0: } michael@0: michael@0: static bool michael@0: intl_CompareStrings(JSContext *cx, UCollator *coll, HandleString str1, HandleString str2, MutableHandleValue result) michael@0: { michael@0: JS_ASSERT(str1); michael@0: JS_ASSERT(str2); michael@0: michael@0: if (str1 == str2) { michael@0: result.setInt32(0); michael@0: return true; michael@0: } michael@0: michael@0: size_t length1 = str1->length(); michael@0: const jschar *chars1 = str1->getChars(cx); michael@0: if (!chars1) michael@0: return false; michael@0: size_t length2 = str2->length(); michael@0: const jschar *chars2 = str2->getChars(cx); michael@0: if (!chars2) michael@0: return false; michael@0: michael@0: UCollationResult uresult = ucol_strcoll(coll, JSCharToUChar(chars1), michael@0: length1, JSCharToUChar(chars2), michael@0: length2); michael@0: michael@0: int32_t res; michael@0: switch (uresult) { michael@0: case UCOL_LESS: res = -1; break; michael@0: case UCOL_EQUAL: res = 0; break; michael@0: case UCOL_GREATER: res = 1; break; michael@0: default: MOZ_ASSUME_UNREACHABLE("ucol_strcoll returned bad UCollationResult"); michael@0: } michael@0: result.setInt32(res); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::intl_CompareStrings(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 3); michael@0: JS_ASSERT(args[0].isObject()); michael@0: JS_ASSERT(args[1].isString()); michael@0: JS_ASSERT(args[2].isString()); michael@0: michael@0: RootedObject collator(cx, &args[0].toObject()); michael@0: michael@0: // Obtain a UCollator object, cached if possible. michael@0: // XXX Does this handle Collator instances from other globals correctly? michael@0: bool isCollatorInstance = collator->getClass() == &CollatorClass; michael@0: UCollator *coll; michael@0: if (isCollatorInstance) { michael@0: coll = static_cast(collator->getReservedSlot(UCOLLATOR_SLOT).toPrivate()); michael@0: if (!coll) { michael@0: coll = NewUCollator(cx, collator); michael@0: if (!coll) michael@0: return false; michael@0: collator->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(coll)); michael@0: } michael@0: } else { michael@0: // There's no good place to cache the ICU collator for an object michael@0: // that has been initialized as a Collator but is not a Collator michael@0: // instance. One possibility might be to add a Collator instance as an michael@0: // internal property to each such object. michael@0: coll = NewUCollator(cx, collator); michael@0: if (!coll) michael@0: return false; michael@0: } michael@0: michael@0: // Use the UCollator to actually compare the strings. michael@0: RootedString str1(cx, args[1].toString()); michael@0: RootedString str2(cx, args[2].toString()); michael@0: RootedValue result(cx); michael@0: bool success = intl_CompareStrings(cx, coll, str1, str2, &result); michael@0: michael@0: if (!isCollatorInstance) michael@0: ucol_close(coll); michael@0: if (!success) michael@0: return false; michael@0: args.rval().set(result); michael@0: return true; michael@0: } michael@0: michael@0: michael@0: /******************** NumberFormat ********************/ michael@0: michael@0: static void numberFormat_finalize(FreeOp *fop, JSObject *obj); michael@0: michael@0: static const uint32_t UNUMBER_FORMAT_SLOT = 0; michael@0: static const uint32_t NUMBER_FORMAT_SLOTS_COUNT = 1; michael@0: michael@0: static const Class NumberFormatClass = { michael@0: js_Object_str, michael@0: JSCLASS_HAS_RESERVED_SLOTS(NUMBER_FORMAT_SLOTS_COUNT), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: numberFormat_finalize michael@0: }; michael@0: michael@0: #if JS_HAS_TOSOURCE michael@0: static bool michael@0: numberFormat_toSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setString(cx->names().NumberFormat); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static const JSFunctionSpec numberFormat_static_methods[] = { michael@0: JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_NumberFormat_supportedLocalesOf", 1, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec numberFormat_methods[] = { michael@0: JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0, 0), michael@0: #if JS_HAS_TOSOURCE michael@0: JS_FN(js_toSource_str, numberFormat_toSource, 0, 0), michael@0: #endif michael@0: JS_FS_END michael@0: }; michael@0: michael@0: /** michael@0: * NumberFormat constructor. michael@0: * Spec: ECMAScript Internationalization API Specification, 11.1 michael@0: */ michael@0: static bool michael@0: NumberFormat(JSContext *cx, CallArgs args, bool construct) michael@0: { michael@0: RootedObject obj(cx); michael@0: michael@0: if (!construct) { michael@0: // 11.1.2.1 step 3 michael@0: JSObject *intl = cx->global()->getOrCreateIntlObject(cx); michael@0: if (!intl) michael@0: return false; michael@0: RootedValue self(cx, args.thisv()); michael@0: if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { michael@0: // 11.1.2.1 step 4 michael@0: obj = ToObject(cx, self); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: // 11.1.2.1 step 5 michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return false; michael@0: if (!extensible) michael@0: return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); michael@0: } else { michael@0: // 11.1.2.1 step 3.a michael@0: construct = true; michael@0: } michael@0: } michael@0: if (construct) { michael@0: // 11.1.3.1 paragraph 2 michael@0: RootedObject proto(cx, cx->global()->getOrCreateNumberFormatPrototype(cx)); michael@0: if (!proto) michael@0: return false; michael@0: obj = NewObjectWithGivenProto(cx, &NumberFormatClass, proto, cx->global()); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: obj->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); michael@0: } michael@0: michael@0: // 11.1.2.1 steps 1 and 2; 11.1.3.1 steps 1 and 2 michael@0: RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); michael@0: RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); michael@0: michael@0: // 11.1.2.1 step 6; 11.1.3.1 step 3 michael@0: if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options)) michael@0: return false; michael@0: michael@0: // 11.1.2.1 steps 3.a and 7 michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: NumberFormat(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return NumberFormat(cx, args, args.isConstructing()); michael@0: } michael@0: michael@0: bool michael@0: js::intl_NumberFormat(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 2); michael@0: // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it michael@0: // cannot be used with "new", but it still has to be treated as a michael@0: // constructor. michael@0: return NumberFormat(cx, args, true); michael@0: } michael@0: michael@0: static void michael@0: numberFormat_finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: UNumberFormat *nf = michael@0: static_cast(obj->getReservedSlot(UNUMBER_FORMAT_SLOT).toPrivate()); michael@0: if (nf) michael@0: unum_close(nf); michael@0: } michael@0: michael@0: static JSObject * michael@0: InitNumberFormatClass(JSContext *cx, HandleObject Intl, Handle global) michael@0: { michael@0: RootedFunction ctor(cx, global->createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0)); michael@0: if (!ctor) michael@0: return nullptr; michael@0: michael@0: RootedObject proto(cx, global->as().getOrCreateNumberFormatPrototype(cx)); michael@0: if (!proto) michael@0: return nullptr; michael@0: if (!LinkConstructorAndPrototype(cx, ctor, proto)) michael@0: return nullptr; michael@0: michael@0: // 11.2.2 michael@0: if (!JS_DefineFunctions(cx, ctor, numberFormat_static_methods)) michael@0: return nullptr; michael@0: michael@0: // 11.3.2 and 11.3.3 michael@0: if (!JS_DefineFunctions(cx, proto, numberFormat_methods)) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * Install the getter for NumberFormat.prototype.format, which returns a michael@0: * bound formatting function for the specified NumberFormat object (suitable michael@0: * for passing to methods like Array.prototype.map). michael@0: */ michael@0: RootedValue getter(cx); michael@0: if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().NumberFormatFormatGet, &getter)) michael@0: return nullptr; michael@0: if (!JSObject::defineProperty(cx, proto, cx->names().format, UndefinedHandleValue, michael@0: JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()), michael@0: nullptr, JSPROP_GETTER | JSPROP_SHARED)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: // 11.2.1 and 11.3 michael@0: if (!IntlInitialize(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue, michael@0: UndefinedHandleValue)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: // 8.1 michael@0: RootedValue ctorValue(cx, ObjectValue(*ctor)); michael@0: if (!JSObject::defineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, michael@0: JS_PropertyStub, JS_StrictPropertyStub, 0)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: return ctor; michael@0: } michael@0: michael@0: bool michael@0: GlobalObject::initNumberFormatProto(JSContext *cx, Handle global) michael@0: { michael@0: RootedObject proto(cx, global->createBlankPrototype(cx, &NumberFormatClass)); michael@0: if (!proto) michael@0: return false; michael@0: proto->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); michael@0: global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*proto)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::intl_NumberFormat_availableLocales(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 0); michael@0: michael@0: RootedValue result(cx); michael@0: if (!intl_availableLocales(cx, unum_countAvailable, unum_getAvailable, &result)) michael@0: return false; michael@0: args.rval().set(result); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::intl_numberingSystem(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 1); michael@0: JS_ASSERT(args[0].isString()); michael@0: michael@0: JSAutoByteString locale(cx, args[0].toString()); michael@0: if (!locale) michael@0: return false; michael@0: michael@0: // There's no C API for numbering system, so use the C++ API and hope it michael@0: // won't break. http://bugs.icu-project.org/trac/ticket/10039 michael@0: Locale ulocale(locale.ptr()); michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: NumberingSystem *numbers = NumberingSystem::createInstance(ulocale, status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: const char *name = numbers->getName(); michael@0: RootedString jsname(cx, JS_NewStringCopyZ(cx, name)); michael@0: delete numbers; michael@0: if (!jsname) michael@0: return false; michael@0: args.rval().setString(jsname); michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Returns a new UNumberFormat with the locale and number formatting options michael@0: * of the given NumberFormat. michael@0: */ michael@0: static UNumberFormat * michael@0: NewUNumberFormat(JSContext *cx, HandleObject numberFormat) michael@0: { michael@0: RootedValue value(cx); michael@0: michael@0: RootedObject internals(cx); michael@0: if (!GetInternals(cx, numberFormat, &internals)) michael@0: return nullptr; michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().locale, &value)) michael@0: return nullptr; michael@0: JSAutoByteString locale(cx, value.toString()); michael@0: if (!locale) michael@0: return nullptr; michael@0: michael@0: // UNumberFormat options with default values michael@0: UNumberFormatStyle uStyle = UNUM_DECIMAL; michael@0: const UChar *uCurrency = nullptr; michael@0: uint32_t uMinimumIntegerDigits = 1; michael@0: uint32_t uMinimumFractionDigits = 0; michael@0: uint32_t uMaximumFractionDigits = 3; michael@0: int32_t uMinimumSignificantDigits = -1; michael@0: int32_t uMaximumSignificantDigits = -1; michael@0: bool uUseGrouping = true; michael@0: michael@0: // Sprinkle appropriate rooting flavor over things the GC might care about. michael@0: RootedString currency(cx); michael@0: michael@0: // We don't need to look at numberingSystem - it can only be set via michael@0: // the Unicode locale extension and is therefore already set on locale. michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().style, &value)) michael@0: return nullptr; michael@0: JSAutoByteString style(cx, value.toString()); michael@0: if (!style) michael@0: return nullptr; michael@0: michael@0: if (equal(style, "currency")) { michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().currency, &value)) michael@0: return nullptr; michael@0: currency = value.toString(); michael@0: MOZ_ASSERT(currency->length() == 3, "IsWellFormedCurrencyCode permits only length-3 strings"); michael@0: // uCurrency remains owned by currency. michael@0: uCurrency = JSCharToUChar(JS_GetStringCharsZ(cx, currency)); michael@0: if (!uCurrency) michael@0: return nullptr; michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().currencyDisplay, &value)) michael@0: return nullptr; michael@0: JSAutoByteString currencyDisplay(cx, value.toString()); michael@0: if (!currencyDisplay) michael@0: return nullptr; michael@0: if (equal(currencyDisplay, "code")) { michael@0: uStyle = UNUM_CURRENCY_ISO; michael@0: } else if (equal(currencyDisplay, "symbol")) { michael@0: uStyle = UNUM_CURRENCY; michael@0: } else { michael@0: JS_ASSERT(equal(currencyDisplay, "name")); michael@0: uStyle = UNUM_CURRENCY_PLURAL; michael@0: } michael@0: } else if (equal(style, "percent")) { michael@0: uStyle = UNUM_PERCENT; michael@0: } else { michael@0: JS_ASSERT(equal(style, "decimal")); michael@0: uStyle = UNUM_DECIMAL; michael@0: } michael@0: michael@0: RootedId id(cx, NameToId(cx->names().minimumSignificantDigits)); michael@0: bool hasP; michael@0: if (!JSObject::hasProperty(cx, internals, id, &hasP)) michael@0: return nullptr; michael@0: if (hasP) { michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().minimumSignificantDigits, michael@0: &value)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: uMinimumSignificantDigits = int32_t(value.toNumber()); michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().maximumSignificantDigits, michael@0: &value)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: uMaximumSignificantDigits = int32_t(value.toNumber()); michael@0: } else { michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().minimumIntegerDigits, michael@0: &value)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: uMinimumIntegerDigits = int32_t(value.toNumber()); michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().minimumFractionDigits, michael@0: &value)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: uMinimumFractionDigits = int32_t(value.toNumber()); michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().maximumFractionDigits, michael@0: &value)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: uMaximumFractionDigits = int32_t(value.toNumber()); michael@0: } michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().useGrouping, &value)) michael@0: return nullptr; michael@0: uUseGrouping = value.toBoolean(); michael@0: michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UNumberFormat *nf = unum_open(uStyle, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return nullptr; michael@0: } michael@0: ScopedICUObject toClose(nf, unum_close); michael@0: michael@0: if (uCurrency) { michael@0: unum_setTextAttribute(nf, UNUM_CURRENCY_CODE, uCurrency, 3, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return nullptr; michael@0: } michael@0: } michael@0: if (uMinimumSignificantDigits != -1) { michael@0: unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true); michael@0: unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits); michael@0: unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits); michael@0: } else { michael@0: unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits); michael@0: unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits); michael@0: unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits); michael@0: } michael@0: unum_setAttribute(nf, UNUM_GROUPING_USED, uUseGrouping); michael@0: unum_setAttribute(nf, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP); michael@0: michael@0: return toClose.forget(); michael@0: } michael@0: michael@0: static bool michael@0: intl_FormatNumber(JSContext *cx, UNumberFormat *nf, double x, MutableHandleValue result) michael@0: { michael@0: // FormatNumber doesn't consider -0.0 to be negative. michael@0: if (IsNegativeZero(x)) michael@0: x = 0.0; michael@0: michael@0: StringBuffer chars(cx); michael@0: if (!chars.resize(INITIAL_STRING_BUFFER_SIZE)) michael@0: return false; michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: int size = unum_formatDouble(nf, x, JSCharToUChar(chars.begin()), michael@0: INITIAL_STRING_BUFFER_SIZE, nullptr, &status); michael@0: if (status == U_BUFFER_OVERFLOW_ERROR) { michael@0: if (!chars.resize(size)) michael@0: return false; michael@0: status = U_ZERO_ERROR; michael@0: unum_formatDouble(nf, x, JSCharToUChar(chars.begin()), michael@0: size, nullptr, &status); michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: michael@0: // Trim any unused characters. michael@0: if (!chars.resize(size)) michael@0: return false; michael@0: michael@0: RootedString str(cx, chars.finishString()); michael@0: if (!str) michael@0: return false; michael@0: michael@0: result.setString(str); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::intl_FormatNumber(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 2); michael@0: JS_ASSERT(args[0].isObject()); michael@0: JS_ASSERT(args[1].isNumber()); michael@0: michael@0: RootedObject numberFormat(cx, &args[0].toObject()); michael@0: michael@0: // Obtain a UNumberFormat object, cached if possible. michael@0: bool isNumberFormatInstance = numberFormat->getClass() == &NumberFormatClass; michael@0: UNumberFormat *nf; michael@0: if (isNumberFormatInstance) { michael@0: nf = static_cast(numberFormat->getReservedSlot(UNUMBER_FORMAT_SLOT).toPrivate()); michael@0: if (!nf) { michael@0: nf = NewUNumberFormat(cx, numberFormat); michael@0: if (!nf) michael@0: return false; michael@0: numberFormat->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nf)); michael@0: } michael@0: } else { michael@0: // There's no good place to cache the ICU number format for an object michael@0: // that has been initialized as a NumberFormat but is not a michael@0: // NumberFormat instance. One possibility might be to add a michael@0: // NumberFormat instance as an internal property to each such object. michael@0: nf = NewUNumberFormat(cx, numberFormat); michael@0: if (!nf) michael@0: return false; michael@0: } michael@0: michael@0: // Use the UNumberFormat to actually format the number. michael@0: RootedValue result(cx); michael@0: bool success = intl_FormatNumber(cx, nf, args[1].toNumber(), &result); michael@0: michael@0: if (!isNumberFormatInstance) michael@0: unum_close(nf); michael@0: if (!success) michael@0: return false; michael@0: args.rval().set(result); michael@0: return true; michael@0: } michael@0: michael@0: michael@0: /******************** DateTimeFormat ********************/ michael@0: michael@0: static void dateTimeFormat_finalize(FreeOp *fop, JSObject *obj); michael@0: michael@0: static const uint32_t UDATE_FORMAT_SLOT = 0; michael@0: static const uint32_t DATE_TIME_FORMAT_SLOTS_COUNT = 1; michael@0: michael@0: static const Class DateTimeFormatClass = { michael@0: js_Object_str, michael@0: JSCLASS_HAS_RESERVED_SLOTS(DATE_TIME_FORMAT_SLOTS_COUNT), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: dateTimeFormat_finalize michael@0: }; michael@0: michael@0: #if JS_HAS_TOSOURCE michael@0: static bool michael@0: dateTimeFormat_toSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setString(cx->names().DateTimeFormat); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static const JSFunctionSpec dateTimeFormat_static_methods[] = { michael@0: JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_DateTimeFormat_supportedLocalesOf", 1, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec dateTimeFormat_methods[] = { michael@0: JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions", 0, 0), michael@0: #if JS_HAS_TOSOURCE michael@0: JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0), michael@0: #endif michael@0: JS_FS_END michael@0: }; michael@0: michael@0: /** michael@0: * DateTimeFormat constructor. michael@0: * Spec: ECMAScript Internationalization API Specification, 12.1 michael@0: */ michael@0: static bool michael@0: DateTimeFormat(JSContext *cx, CallArgs args, bool construct) michael@0: { michael@0: RootedObject obj(cx); michael@0: michael@0: if (!construct) { michael@0: // 12.1.2.1 step 3 michael@0: JSObject *intl = cx->global()->getOrCreateIntlObject(cx); michael@0: if (!intl) michael@0: return false; michael@0: RootedValue self(cx, args.thisv()); michael@0: if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { michael@0: // 12.1.2.1 step 4 michael@0: obj = ToObject(cx, self); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: // 12.1.2.1 step 5 michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return false; michael@0: if (!extensible) michael@0: return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); michael@0: } else { michael@0: // 12.1.2.1 step 3.a michael@0: construct = true; michael@0: } michael@0: } michael@0: if (construct) { michael@0: // 12.1.3.1 paragraph 2 michael@0: RootedObject proto(cx, cx->global()->getOrCreateDateTimeFormatPrototype(cx)); michael@0: if (!proto) michael@0: return false; michael@0: obj = NewObjectWithGivenProto(cx, &DateTimeFormatClass, proto, cx->global()); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: obj->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); michael@0: } michael@0: michael@0: // 12.1.2.1 steps 1 and 2; 12.1.3.1 steps 1 and 2 michael@0: RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); michael@0: RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); michael@0: michael@0: // 12.1.2.1 step 6; 12.1.3.1 step 3 michael@0: if (!IntlInitialize(cx, obj, cx->names().InitializeDateTimeFormat, locales, options)) michael@0: return false; michael@0: michael@0: // 12.1.2.1 steps 3.a and 7 michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DateTimeFormat(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return DateTimeFormat(cx, args, args.isConstructing()); michael@0: } michael@0: michael@0: bool michael@0: js::intl_DateTimeFormat(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 2); michael@0: // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it michael@0: // cannot be used with "new", but it still has to be treated as a michael@0: // constructor. michael@0: return DateTimeFormat(cx, args, true); michael@0: } michael@0: michael@0: static void michael@0: dateTimeFormat_finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: UDateFormat *df = static_cast(obj->getReservedSlot(UDATE_FORMAT_SLOT).toPrivate()); michael@0: if (df) michael@0: udat_close(df); michael@0: } michael@0: michael@0: static JSObject * michael@0: InitDateTimeFormatClass(JSContext *cx, HandleObject Intl, Handle global) michael@0: { michael@0: RootedFunction ctor(cx, global->createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0)); michael@0: if (!ctor) michael@0: return nullptr; michael@0: michael@0: RootedObject proto(cx, global->as().getOrCreateDateTimeFormatPrototype(cx)); michael@0: if (!proto) michael@0: return nullptr; michael@0: if (!LinkConstructorAndPrototype(cx, ctor, proto)) michael@0: return nullptr; michael@0: michael@0: // 12.2.2 michael@0: if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods)) michael@0: return nullptr; michael@0: michael@0: // 12.3.2 and 12.3.3 michael@0: if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods)) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * Install the getter for DateTimeFormat.prototype.format, which returns a michael@0: * bound formatting function for the specified DateTimeFormat object michael@0: * (suitable for passing to methods like Array.prototype.map). michael@0: */ michael@0: RootedValue getter(cx); michael@0: if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().DateTimeFormatFormatGet, &getter)) michael@0: return nullptr; michael@0: if (!JSObject::defineProperty(cx, proto, cx->names().format, UndefinedHandleValue, michael@0: JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()), michael@0: nullptr, JSPROP_GETTER | JSPROP_SHARED)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: // 12.2.1 and 12.3 michael@0: if (!IntlInitialize(cx, proto, cx->names().InitializeDateTimeFormat, UndefinedHandleValue, michael@0: UndefinedHandleValue)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: // 8.1 michael@0: RootedValue ctorValue(cx, ObjectValue(*ctor)); michael@0: if (!JSObject::defineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, michael@0: JS_PropertyStub, JS_StrictPropertyStub, 0)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: return ctor; michael@0: } michael@0: michael@0: bool michael@0: GlobalObject::initDateTimeFormatProto(JSContext *cx, Handle global) michael@0: { michael@0: RootedObject proto(cx, global->createBlankPrototype(cx, &DateTimeFormatClass)); michael@0: if (!proto) michael@0: return false; michael@0: proto->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); michael@0: global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*proto)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::intl_DateTimeFormat_availableLocales(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 0); michael@0: michael@0: RootedValue result(cx); michael@0: if (!intl_availableLocales(cx, udat_countAvailable, udat_getAvailable, &result)) michael@0: return false; michael@0: args.rval().set(result); michael@0: return true; michael@0: } michael@0: michael@0: // ICU returns old-style keyword values; map them to BCP 47 equivalents michael@0: // (see http://bugs.icu-project.org/trac/ticket/9620). michael@0: static const char * michael@0: bcp47CalendarName(const char *icuName) michael@0: { michael@0: if (equal(icuName, "ethiopic-amete-alem")) michael@0: return "ethioaa"; michael@0: if (equal(icuName, "gregorian")) michael@0: return "gregory"; michael@0: if (equal(icuName, "islamic-civil")) michael@0: return "islamicc"; michael@0: return icuName; michael@0: } michael@0: michael@0: bool michael@0: js::intl_availableCalendars(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 1); michael@0: JS_ASSERT(args[0].isString()); michael@0: michael@0: JSAutoByteString locale(cx, args[0].toString()); michael@0: if (!locale) michael@0: return false; michael@0: michael@0: RootedObject calendars(cx, NewDenseEmptyArray(cx)); michael@0: if (!calendars) michael@0: return false; michael@0: uint32_t index = 0; michael@0: michael@0: // We need the default calendar for the locale as the first result. michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UCalendar *cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status); michael@0: const char *calendar = ucal_getType(cal, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: ucal_close(cal); michael@0: RootedString jscalendar(cx, JS_NewStringCopyZ(cx, bcp47CalendarName(calendar))); michael@0: if (!jscalendar) michael@0: return false; michael@0: RootedValue element(cx, StringValue(jscalendar)); michael@0: if (!JSObject::defineElement(cx, calendars, index++, element)) michael@0: return false; michael@0: michael@0: // Now get the calendars that "would make a difference", i.e., not the default. michael@0: UEnumeration *values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: ScopedICUObject toClose(values, uenum_close); michael@0: michael@0: uint32_t count = uenum_count(values, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: michael@0: for (; count > 0; count--) { michael@0: calendar = uenum_next(values, nullptr, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: michael@0: jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar)); michael@0: if (!jscalendar) michael@0: return false; michael@0: element = StringValue(jscalendar); michael@0: if (!JSObject::defineElement(cx, calendars, index++, element)) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setObject(*calendars); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::intl_patternForSkeleton(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 2); michael@0: JS_ASSERT(args[0].isString()); michael@0: JS_ASSERT(args[1].isString()); michael@0: michael@0: JSAutoByteString locale(cx, args[0].toString()); michael@0: if (!locale) michael@0: return false; michael@0: RootedString jsskeleton(cx, args[1].toString()); michael@0: const jschar *skeleton = JS_GetStringCharsZ(cx, jsskeleton); michael@0: if (!skeleton) michael@0: return false; michael@0: uint32_t skeletonLen = u_strlen(JSCharToUChar(skeleton)); michael@0: michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UDateTimePatternGenerator *gen = udatpg_open(icuLocale(locale.ptr()), &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: ScopedICUObject toClose(gen, udatpg_close); michael@0: michael@0: int32_t size = udatpg_getBestPattern(gen, JSCharToUChar(skeleton), michael@0: skeletonLen, nullptr, 0, &status); michael@0: if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: ScopedJSFreePtr pattern(cx->pod_malloc(size + 1)); michael@0: if (!pattern) michael@0: return false; michael@0: pattern[size] = '\0'; michael@0: status = U_ZERO_ERROR; michael@0: udatpg_getBestPattern(gen, JSCharToUChar(skeleton), michael@0: skeletonLen, pattern, size, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: michael@0: RootedString str(cx, JS_NewUCStringCopyZ(cx, reinterpret_cast(pattern.get()))); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Returns a new UDateFormat with the locale and date-time formatting options michael@0: * of the given DateTimeFormat. michael@0: */ michael@0: static UDateFormat * michael@0: NewUDateFormat(JSContext *cx, HandleObject dateTimeFormat) michael@0: { michael@0: RootedValue value(cx); michael@0: michael@0: RootedObject internals(cx); michael@0: if (!GetInternals(cx, dateTimeFormat, &internals)) michael@0: return nullptr; michael@0: michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().locale, &value)) { michael@0: return nullptr; michael@0: } michael@0: JSAutoByteString locale(cx, value.toString()); michael@0: if (!locale) michael@0: return nullptr; michael@0: michael@0: // UDateFormat options with default values. michael@0: const UChar *uTimeZone = nullptr; michael@0: uint32_t uTimeZoneLength = 0; michael@0: const UChar *uPattern = nullptr; michael@0: uint32_t uPatternLength = 0; michael@0: michael@0: // We don't need to look at calendar and numberingSystem - they can only be michael@0: // set via the Unicode locale extension and are therefore already set on michael@0: // locale. michael@0: michael@0: RootedId id(cx, NameToId(cx->names().timeZone)); michael@0: bool hasP; michael@0: if (!JSObject::hasProperty(cx, internals, id, &hasP)) michael@0: return nullptr; michael@0: if (hasP) { michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().timeZone, &value)) michael@0: return nullptr; michael@0: if (!value.isUndefined()) { michael@0: uTimeZone = JSCharToUChar(JS_GetStringCharsZ(cx, value.toString())); michael@0: if (!uTimeZone) michael@0: return nullptr; michael@0: uTimeZoneLength = u_strlen(uTimeZone); michael@0: } michael@0: } michael@0: if (!JSObject::getProperty(cx, internals, internals, cx->names().pattern, &value)) michael@0: return nullptr; michael@0: uPattern = JSCharToUChar(JS_GetStringCharsZ(cx, value.toString())); michael@0: if (!uPattern) michael@0: return nullptr; michael@0: uPatternLength = u_strlen(uPattern); michael@0: michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: michael@0: // If building with ICU headers before 50.1, use UDAT_IGNORE instead of michael@0: // UDAT_PATTERN. michael@0: UDateFormat *df = michael@0: udat_open(UDAT_PATTERN, UDAT_PATTERN, icuLocale(locale.ptr()), uTimeZone, uTimeZoneLength, michael@0: uPattern, uPatternLength, &status); michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return nullptr; michael@0: } michael@0: michael@0: // ECMAScript requires the Gregorian calendar to be used from the beginning michael@0: // of ECMAScript time. michael@0: UCalendar *cal = const_cast(udat_getCalendar(df)); michael@0: ucal_setGregorianChange(cal, StartOfTime, &status); michael@0: michael@0: // An error here means the calendar is not Gregorian, so we don't care. michael@0: michael@0: return df; michael@0: } michael@0: michael@0: static bool michael@0: intl_FormatDateTime(JSContext *cx, UDateFormat *df, double x, MutableHandleValue result) michael@0: { michael@0: if (!IsFinite(x)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE); michael@0: return false; michael@0: } michael@0: michael@0: StringBuffer chars(cx); michael@0: if (!chars.resize(INITIAL_STRING_BUFFER_SIZE)) michael@0: return false; michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: int size = udat_format(df, x, JSCharToUChar(chars.begin()), INITIAL_STRING_BUFFER_SIZE, nullptr, &status); michael@0: if (status == U_BUFFER_OVERFLOW_ERROR) { michael@0: if (!chars.resize(size)) michael@0: return false; michael@0: status = U_ZERO_ERROR; michael@0: udat_format(df, x, JSCharToUChar(chars.begin()), size, nullptr, &status); michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); michael@0: return false; michael@0: } michael@0: michael@0: // Trim any unused characters. michael@0: if (!chars.resize(size)) michael@0: return false; michael@0: michael@0: RootedString str(cx, chars.finishString()); michael@0: if (!str) michael@0: return false; michael@0: michael@0: result.setString(str); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::intl_FormatDateTime(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 2); michael@0: JS_ASSERT(args[0].isObject()); michael@0: JS_ASSERT(args[1].isNumber()); michael@0: michael@0: RootedObject dateTimeFormat(cx, &args[0].toObject()); michael@0: michael@0: // Obtain a UDateFormat object, cached if possible. michael@0: bool isDateTimeFormatInstance = dateTimeFormat->getClass() == &DateTimeFormatClass; michael@0: UDateFormat *df; michael@0: if (isDateTimeFormatInstance) { michael@0: df = static_cast(dateTimeFormat->getReservedSlot(UDATE_FORMAT_SLOT).toPrivate()); michael@0: if (!df) { michael@0: df = NewUDateFormat(cx, dateTimeFormat); michael@0: if (!df) michael@0: return false; michael@0: dateTimeFormat->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(df)); michael@0: } michael@0: } else { michael@0: // There's no good place to cache the ICU date-time format for an object michael@0: // that has been initialized as a DateTimeFormat but is not a michael@0: // DateTimeFormat instance. One possibility might be to add a michael@0: // DateTimeFormat instance as an internal property to each such object. michael@0: df = NewUDateFormat(cx, dateTimeFormat); michael@0: if (!df) michael@0: return false; michael@0: } michael@0: michael@0: // Use the UDateFormat to actually format the time stamp. michael@0: RootedValue result(cx); michael@0: bool success = intl_FormatDateTime(cx, df, args[1].toNumber(), &result); michael@0: michael@0: if (!isDateTimeFormatInstance) michael@0: udat_close(df); michael@0: if (!success) michael@0: return false; michael@0: args.rval().set(result); michael@0: return true; michael@0: } michael@0: michael@0: michael@0: /******************** Intl ********************/ michael@0: michael@0: const Class js::IntlClass = { michael@0: js_Object_str, michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_Intl), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: #if JS_HAS_TOSOURCE michael@0: static bool michael@0: intl_toSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setString(cx->names().Intl); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static const JSFunctionSpec intl_static_methods[] = { michael@0: #if JS_HAS_TOSOURCE michael@0: JS_FN(js_toSource_str, intl_toSource, 0, 0), michael@0: #endif michael@0: JS_FS_END michael@0: }; michael@0: michael@0: /** michael@0: * Initializes the Intl Object and its standard built-in properties. michael@0: * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1 michael@0: */ michael@0: JSObject * michael@0: js_InitIntlClass(JSContext *cx, HandleObject obj) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: Rooted global(cx, &obj->as()); michael@0: michael@0: // The constructors above need to be able to determine whether they've been michael@0: // called with this being "the standard built-in Intl object". The global michael@0: // object reserves slots to track standard built-in objects, but doesn't michael@0: // normally keep references to non-constructors. This makes sure there is one. michael@0: RootedObject Intl(cx, global->getOrCreateIntlObject(cx)); michael@0: if (!Intl) michael@0: return nullptr; michael@0: michael@0: RootedValue IntlValue(cx, ObjectValue(*Intl)); michael@0: if (!JSObject::defineProperty(cx, global, cx->names().Intl, IntlValue, michael@0: JS_PropertyStub, JS_StrictPropertyStub, 0)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!JS_DefineFunctions(cx, Intl, intl_static_methods)) michael@0: return nullptr; michael@0: michael@0: // Skip initialization of the Intl constructors during initialization of the michael@0: // self-hosting global as we may get here before self-hosted code is compiled, michael@0: // and no core code refers to the Intl classes. michael@0: if (!cx->runtime()->isSelfHostingGlobal(cx->global())) { michael@0: if (!InitCollatorClass(cx, Intl, global)) michael@0: return nullptr; michael@0: if (!InitNumberFormatClass(cx, Intl, global)) michael@0: return nullptr; michael@0: if (!InitDateTimeFormatClass(cx, Intl, global)) michael@0: return nullptr; michael@0: } michael@0: michael@0: global->setConstructor(JSProto_Intl, ObjectValue(*Intl)); michael@0: michael@0: return Intl; michael@0: } michael@0: michael@0: bool michael@0: GlobalObject::initIntlObject(JSContext *cx, Handle global) michael@0: { michael@0: RootedObject Intl(cx); michael@0: Intl = NewObjectWithGivenProto(cx, &IntlClass, global->getOrCreateObjectPrototype(cx), michael@0: global, SingletonObject); michael@0: if (!Intl) michael@0: return false; michael@0: michael@0: global->setConstructor(JSProto_Intl, ObjectValue(*Intl)); michael@0: return true; michael@0: }