summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/Intl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/Intl.cpp')
-rw-r--r--js/src/builtin/Intl.cpp486
1 files changed, 471 insertions, 15 deletions
diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp
index d231d7bec..622e773e0 100644
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -10,6 +10,7 @@
#include "builtin/Intl.h"
+#include "mozilla/Casting.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Range.h"
#include "mozilla/ScopeExit.h"
@@ -22,6 +23,7 @@
#include "jsobj.h"
#include "builtin/IntlTimeZoneData.h"
+#include "unicode/plurrule.h"
#include "unicode/ucal.h"
#include "unicode/ucol.h"
#include "unicode/udat.h"
@@ -29,6 +31,7 @@
#include "unicode/uenum.h"
#include "unicode/unum.h"
#include "unicode/unumsys.h"
+#include "unicode/upluralrules.h"
#include "unicode/ustring.h"
#include "vm/DateTime.h"
#include "vm/GlobalObject.h"
@@ -43,6 +46,7 @@
using namespace js;
+using mozilla::AssertedCast;
using mozilla::IsFinite;
using mozilla::IsNegativeZero;
using mozilla::MakeScopeExit;
@@ -963,6 +967,89 @@ js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
}
/**
+ *
+ * This creates new UNumberFormat with calculated digit formatting
+ * properties for PluralRules.
+ *
+ * This is similar to NewUNumberFormat but doesn't allow for currency or
+ * percent types.
+ *
+ */
+static UNumberFormat*
+NewUNumberFormatForPluralRules(JSContext* cx, HandleObject pluralRules)
+{
+ RootedObject internals(cx, GetInternals(cx, pluralRules));
+ if (!internals)
+ return nullptr;
+
+ RootedValue value(cx);
+
+ if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
+ return nullptr;
+ JSAutoByteString locale(cx, value.toString());
+ if (!locale)
+ return nullptr;
+
+ uint32_t uMinimumIntegerDigits = 1;
+ uint32_t uMinimumFractionDigits = 0;
+ uint32_t uMaximumFractionDigits = 3;
+ int32_t uMinimumSignificantDigits = -1;
+ int32_t uMaximumSignificantDigits = -1;
+
+ 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 = value.toInt32();
+
+ if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
+ &value))
+ return nullptr;
+ uMaximumSignificantDigits = value.toInt32();
+ } else {
+ if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
+ &value))
+ return nullptr;
+ uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
+
+ if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
+ &value))
+ return nullptr;
+ uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
+
+ if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
+ &value))
+ return nullptr;
+ uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
+ }
+
+ UErrorCode status = U_ZERO_ERROR;
+ UNumberFormat* nf = unum_open(UNUM_DECIMAL, 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 (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);
+ }
+
+ return toClose.forget();
+}
+
+
+/**
* Returns a new UNumberFormat with the locale and number formatting options
* of the given NumberFormat.
*/
@@ -1044,35 +1131,25 @@ NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
if (hasP) {
if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
&value))
- {
return nullptr;
- }
- uMinimumSignificantDigits = int32_t(value.toNumber());
+ uMinimumSignificantDigits = value.toInt32();
if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
&value))
- {
return nullptr;
- }
- uMaximumSignificantDigits = int32_t(value.toNumber());
+ uMaximumSignificantDigits = value.toInt32();
} else {
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value))
- {
return nullptr;
- }
- uMinimumIntegerDigits = int32_t(value.toNumber());
+ uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
&value))
- {
return nullptr;
- }
- uMinimumFractionDigits = int32_t(value.toNumber());
+ uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
&value))
- {
return nullptr;
- }
- uMaximumFractionDigits = int32_t(value.toNumber());
+ uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
}
if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value))
@@ -2323,6 +2400,381 @@ js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp)
return true;
}
+/**************** PluralRules *****************/
+
+static void pluralRules_finalize(FreeOp* fop, JSObject* obj);
+
+static const uint32_t UPLURAL_RULES_SLOT = 0;
+static const uint32_t PLURAL_RULES_SLOTS_COUNT = 1;
+
+static const ClassOps PluralRulesClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ pluralRules_finalize
+};
+
+static const Class PluralRulesClass = {
+ js_Object_str,
+ JSCLASS_HAS_RESERVED_SLOTS(PLURAL_RULES_SLOTS_COUNT) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &PluralRulesClassOps
+};
+
+#if JS_HAS_TOSOURCE
+static bool
+pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setString(cx->names().PluralRules);
+ return true;
+}
+#endif
+
+static const JSFunctionSpec pluralRules_static_methods[] = {
+ JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_PluralRules_supportedLocalesOf", 1, 0),
+ JS_FS_END
+};
+
+static const JSFunctionSpec pluralRules_methods[] = {
+ JS_SELF_HOSTED_FN("resolvedOptions", "Intl_PluralRules_resolvedOptions", 0, 0),
+ JS_SELF_HOSTED_FN("select", "Intl_PluralRules_select", 1, 0),
+#if JS_HAS_TOSOURCE
+ JS_FN(js_toSource_str, pluralRules_toSource, 0, 0),
+#endif
+ JS_FS_END
+};
+
+/**
+ * PluralRules constructor.
+ * Spec: ECMAScript 402 API, PluralRules, 1.1
+ */
+static bool
+PluralRules(JSContext* cx, const CallArgs& args, bool construct)
+{
+ RootedObject obj(cx);
+
+ if (!construct) {
+ JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
+ if (!intl)
+ return false;
+ RootedValue self(cx, args.thisv());
+ if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
+ obj = ToObject(cx, self);
+ if (!obj)
+ return false;
+
+ bool extensible;
+ if (!IsExtensible(cx, obj, &extensible))
+ return false;
+ if (!extensible)
+ return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
+ } else {
+ construct = true;
+ }
+ }
+ if (construct) {
+ RootedObject proto(cx, GlobalObject::getOrCreatePluralRulesPrototype(cx, cx->global()));
+ if (!proto)
+ return false;
+ obj = NewObjectWithGivenProto(cx, &PluralRulesClass, proto);
+ if (!obj)
+ return false;
+
+ obj->as<NativeObject>().setReservedSlot(UPLURAL_RULES_SLOT, PrivateValue(nullptr));
+ }
+
+ RootedValue locales(cx, args.get(0));
+ RootedValue options(cx, args.get(1));
+
+ if (!IntlInitialize(cx, obj, cx->names().InitializePluralRules, locales, options))
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+PluralRules(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return PluralRules(cx, args, args.isConstructing());
+}
+
+bool
+js::intl_PluralRules(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+ return PluralRules(cx, args, true);
+}
+
+static void
+pluralRules_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(UPLURAL_RULES_SLOT);
+ if (!slot.isUndefined()) {
+ if (UPluralRules* pr = static_cast<UPluralRules*>(slot.toPrivate()))
+ uplrules_close(pr);
+ }
+}
+
+static JSObject*
+CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+{
+ RootedFunction ctor(cx);
+ ctor = global->createConstructor(cx, &PluralRules, cx->names().PluralRules, 0);
+ if (!ctor)
+ return nullptr;
+
+ RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &PluralRulesClass));
+ if (!proto)
+ return nullptr;
+ proto->setReservedSlot(UPLURAL_RULES_SLOT, PrivateValue(nullptr));
+
+ if (!LinkConstructorAndPrototype(cx, ctor, proto))
+ return nullptr;
+
+ if (!JS_DefineFunctions(cx, ctor, pluralRules_static_methods))
+ return nullptr;
+
+ if (!JS_DefineFunctions(cx, proto, pluralRules_methods))
+ return nullptr;
+
+ RootedValue options(cx);
+ if (!CreateDefaultOptions(cx, &options))
+ return nullptr;
+
+ if (!IntlInitialize(cx, proto, cx->names().InitializePluralRules, UndefinedHandleValue,
+ options))
+ {
+ return nullptr;
+ }
+
+ RootedValue ctorValue(cx, ObjectValue(*ctor));
+ if (!DefineProperty(cx, Intl, cx->names().PluralRules, ctorValue, nullptr, nullptr, 0))
+ return nullptr;
+
+ return proto;
+}
+
+bool
+js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 0);
+
+ RootedValue result(cx);
+ // We're going to use ULocale availableLocales as per ICU recommendation:
+ // https://ssl.icu-project.org/trac/ticket/12756
+ if (!intl_availableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
+ return false;
+ args.rval().set(result);
+ return true;
+}
+
+bool
+js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject pluralRules(cx, &args[0].toObject());
+
+ UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
+ if (!nf)
+ return false;
+
+ ScopedICUObject<UNumberFormat, unum_close> closeNumberFormat(nf);
+
+ RootedObject internals(cx, GetInternals(cx, pluralRules));
+ if (!internals)
+ return false;
+
+ RootedValue value(cx);
+
+ if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
+ return false;
+ JSAutoByteString locale(cx, value.toString());
+ if (!locale)
+ return false;
+
+ if (!GetProperty(cx, internals, internals, cx->names().type, &value))
+ return false;
+ JSAutoByteString type(cx, value.toString());
+ if (!type)
+ return false;
+
+ double x = args[1].toNumber();
+
+ // We need a NumberFormat in order to format the number
+ // using the number formatting options (minimum/maximum*Digits)
+ // before we push the result to PluralRules
+ //
+ // This should be fixed in ICU 59 and we'll be able to switch to that
+ // API: http://bugs.icu-project.org/trac/ticket/12763
+ //
+ RootedValue fmtNumValue(cx);
+ if (!intl_FormatNumber(cx, nf, x, &fmtNumValue))
+ return false;
+ RootedString fmtNumValueString(cx, fmtNumValue.toString());
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, fmtNumValueString))
+ return false;
+
+ const UChar* uFmtNumValue = Char16ToUChar(stableChars.twoByteRange().begin().get());
+
+ UErrorCode status = U_ZERO_ERROR;
+
+ UFormattable* fmt = unum_parseToUFormattable(nf, nullptr, uFmtNumValue,
+ stableChars.twoByteRange().length(), 0, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ ScopedICUObject<UFormattable, ufmt_close> closeUFormattable(fmt);
+
+ double y = ufmt_getDouble(fmt, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ UPluralType category;
+
+ if (equal(type, "cardinal")) {
+ category = UPLURAL_TYPE_CARDINAL;
+ } else {
+ MOZ_ASSERT(equal(type, "ordinal"));
+ category = UPLURAL_TYPE_ORDINAL;
+ }
+
+ UPluralRules* pr = uplrules_openForType(icuLocale(locale.ptr()), category, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
+
+ Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+ if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+ return false;
+
+ int size = uplrules_select(pr, y, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE, &status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ if (!chars.resize(size))
+ return false;
+ status = U_ZERO_ERROR;
+ uplrules_select(pr, y, Char16ToUChar(chars.begin()), size, &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;
+
+ args.rval().setString(str);
+ return true;
+}
+
+bool
+js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+
+ JSAutoByteString locale(cx, args[0].toString());
+ if (!locale)
+ return false;
+
+ JSAutoByteString type(cx, args[1].toString());
+ if (!type)
+ return false;
+
+ UErrorCode status = U_ZERO_ERROR;
+
+ UPluralType category;
+
+ if (equal(type, "cardinal")) {
+ category = UPLURAL_TYPE_CARDINAL;
+ } else {
+ MOZ_ASSERT(equal(type, "ordinal"));
+ category = UPLURAL_TYPE_ORDINAL;
+ }
+
+ UPluralRules* pr = uplrules_openForType(
+ icuLocale(locale.ptr()),
+ category,
+ &status
+ );
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
+
+ // We should get a C API for that in ICU 59 and switch to it
+ // https://ssl.icu-project.org/trac/ticket/12772
+ //
+ icu::StringEnumeration* kwenum =
+ reinterpret_cast<icu::PluralRules*>(pr)->getKeywords(status);
+ UEnumeration* ue = uenum_openFromStringEnumeration(kwenum, &status);
+
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ ScopedICUObject<UEnumeration, uenum_close> closeEnum(ue);
+
+ RootedObject res(cx, NewDenseEmptyArray(cx));
+ if (!res)
+ return false;
+
+ RootedValue element(cx);
+ uint32_t i = 0;
+ int32_t catSize;
+ const char* cat;
+
+ do {
+ cat = uenum_next(ue, &catSize, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ if (!cat)
+ break;
+
+ JSString* str = NewStringCopyN<CanGC>(cx, cat, catSize);
+ if (!str)
+ return false;
+
+ element.setString(str);
+ if (!DefineElement(cx, res, i, element))
+ return false;
+ i++;
+ } while (true);
+
+ args.rval().setObject(*res);
+ return true;
+}
+
bool
js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp)
{
@@ -2761,6 +3213,9 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
if (!numberFormatProto)
return false;
+ RootedObject pluralRulesProto(cx, CreatePluralRulesPrototype(cx, intl, global));
+ if (!pluralRulesProto)
+ return false;
// The |Intl| object is fully set up now, so define the global property.
RootedValue intlValue(cx, ObjectValue(*intl));
@@ -2782,6 +3237,7 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
+ global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto));
// Also cache |Intl| to implement spec language that conditions behavior
// based on values being equal to "the standard built-in |Intl| object".