--- a/config/check_spidermonkey_style.py
+++ b/config/check_spidermonkey_style.py
@@ -76,25 +76,27 @@ included_inclnames_to_ignore = set([
'prlink.h', # NSPR
'prlock.h', # NSPR
'prprf.h', # NSPR
'prthread.h', # NSPR
'prtypes.h', # NSPR
'selfhosted.out.h', # generated in $OBJDIR
'shellmoduleloader.out.h', # generated in $OBJDIR
'unicode/timezone.h', # ICU
+ 'unicode/plurrule.h', # ICU
'unicode/ucal.h', # ICU
'unicode/uclean.h', # ICU
'unicode/ucol.h', # ICU
'unicode/udat.h', # ICU
'unicode/udatpg.h', # ICU
'unicode/uenum.h', # ICU
'unicode/unorm2.h', # ICU
'unicode/unum.h', # ICU
'unicode/unumsys.h', # ICU
+ 'unicode/upluralrules.h', # ICU
'unicode/ustring.h', # ICU
'unicode/utypes.h', # ICU
'vtune/VTuneWrapper.h' # VTune
])
# These files have additional constraints on where they are #included, so we
# ignore #includes of them when checking #include ordering.
oddly_ordered_inclnames = set([
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -731,17 +731,17 @@ struct JSClass {
// with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
// previously allowed, but is now an ES5 violation and thus unsupported.
//
// JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
// the beginning of every global object's slots for use by the
// application.
#define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
#define JSCLASS_GLOBAL_SLOT_COUNT \
- (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 39)
+ (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 40)
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
#define JSCLASS_GLOBAL_FLAGS \
JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
#define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp) \
(((clasp)->flags & JSCLASS_IS_GLOBAL) \
&& JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -6,52 +6,56 @@
/*
* The Intl module specified by standard ECMA-402,
* ECMAScript Internationalization API Specification.
*/
#include "builtin/Intl.h"
+#include "mozilla/Casting.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Range.h"
#include <string.h>
#include "jsapi.h"
#include "jsatom.h"
#include "jscntxt.h"
#include "jsobj.h"
#include "jsstr.h"
#include "builtin/IntlTimeZoneData.h"
#include "ds/Sort.h"
#if ENABLE_INTL_API
+#include "unicode/plurrule.h"
#include "unicode/ucal.h"
#include "unicode/ucol.h"
#include "unicode/udat.h"
#include "unicode/udatpg.h"
#include "unicode/uenum.h"
#include "unicode/unum.h"
#include "unicode/unumsys.h"
+#include "unicode/upluralrules.h"
#include "unicode/ustring.h"
#endif
#include "vm/DateTime.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/Stack.h"
#include "vm/StringBuffer.h"
#include "vm/Unicode.h"
#include "jsobjinlines.h"
#include "vm/NativeObject-inl.h"
using namespace js;
+using mozilla::AssertedCast;
using mozilla::IsFinite;
using mozilla::IsNaN;
using mozilla::IsNegativeZero;
using mozilla::PodCopy;
using mozilla::Range;
using mozilla::RangedPtr;
/*
@@ -74,25 +78,56 @@ using mozilla::RangedPtr;
* possible. The functions using them should never be called, so they assert
* and return error codes. Signatures adapted from ICU header files locid.h,
* numsys.h, ucal.h, ucol.h, udat.h, udatpg.h, uenum.h, unum.h; see the ICU
* directory for license.
*/
namespace {
-typedef bool UBool;
-typedef char16_t UChar;
-typedef double UDate;
-
enum UErrorCode {
U_ZERO_ERROR,
U_BUFFER_OVERFLOW_ERROR,
};
+}
+
+namespace icu {
+
+class StringEnumeration {
+ public:
+ explicit StringEnumeration();
+};
+
+StringEnumeration::StringEnumeration()
+{
+ MOZ_CRASH("StringEnumeration::StringEnumeration: Intl API disabled");
+}
+
+class PluralRules {
+public:
+
+ StringEnumeration* getKeywords(UErrorCode& status) const;
+
+};
+
+StringEnumeration*
+PluralRules::getKeywords(UErrorCode& status) const
+{
+ MOZ_CRASH("PluralRules::getKeywords: Intl API disabled");
+}
+
+} // icu namespace
+
+namespace {
+
+typedef bool UBool;
+typedef char16_t UChar;
+typedef double UDate;
+
inline UBool
U_FAILURE(UErrorCode code)
{
MOZ_CRASH("U_FAILURE: Intl API disabled");
}
inline const UChar*
Char16ToUChar(const char16_t* chars)
@@ -113,16 +148,42 @@ UCharToChar16(UChar* chars)
}
inline const char16_t*
UCharToChar16(const UChar* chars)
{
MOZ_CRASH("UCharToChar16: Intl API disabled");
}
+const char*
+uloc_getAvailable(int32_t n)
+{
+ MOZ_CRASH("uloc_getAvailable: Intl API disabled");
+}
+
+int32_t
+uloc_countAvailable()
+{
+ MOZ_CRASH("uloc_countAvailable: Intl API disabled");
+}
+
+struct UFormattable;
+
+void
+ufmt_close(UFormattable* fmt)
+{
+ MOZ_CRASH("ufmt_close: Intl API disabled");
+}
+
+double
+ufmt_getDouble(UFormattable* fmt, UErrorCode *status)
+{
+ MOZ_CRASH("ufmt_getDouble: Intl API disabled");
+}
+
struct UEnumeration;
int32_t
uenum_count(UEnumeration* en, UErrorCode* status)
{
MOZ_CRASH("uenum_count: Intl API disabled");
}
@@ -133,16 +194,22 @@ uenum_next(UEnumeration* en, int32_t* re
}
void
uenum_close(UEnumeration* en)
{
MOZ_CRASH("uenum_close: Intl API disabled");
}
+UEnumeration*
+uenum_openFromStringEnumeration(icu::StringEnumeration* adopted, UErrorCode* ec)
+{
+ MOZ_CRASH("uenum_openFromStringEnumeration: Intl API disabled");
+}
+
struct UCollator;
enum UColAttribute {
UCOL_ALTERNATE_HANDLING,
UCOL_CASE_FIRST,
UCOL_CASE_LEVEL,
UCOL_NORMALIZATION_MODE,
UCOL_STRENGTH,
@@ -312,16 +379,27 @@ unum_close(UNumberFormat* fmt)
void
unum_setTextAttribute(UNumberFormat* fmt, UNumberFormatTextAttribute tag, const UChar* newValue,
int32_t newValueLength, UErrorCode* status)
{
MOZ_CRASH("unum_setTextAttribute: Intl API disabled");
}
+UFormattable*
+unum_parseToUFormattable(const UNumberFormat* fmt,
+ UFormattable *result,
+ const UChar* text,
+ int32_t textLength,
+ int32_t* parsePos, /* 0 = start */
+ UErrorCode* status)
+{
+ MOZ_CRASH("unum_parseToUFormattable: Intl API disabled");
+}
+
typedef void* UNumberingSystem;
UNumberingSystem*
unumsys_open(const char* locale, UErrorCode* status)
{
MOZ_CRASH("unumsys_open: Intl API disabled");
}
@@ -676,16 +754,44 @@ udat_close(UDateFormat* format)
int32_t
udat_getSymbols(const UDateFormat *fmt, UDateFormatSymbolType type, int32_t symbolIndex,
UChar *result, int32_t resultLength, UErrorCode *status)
{
MOZ_CRASH("udat_getSymbols: Intl API disabled");
}
+typedef void* UPluralRules;
+
+enum UPluralType {
+ UPLURAL_TYPE_CARDINAL,
+ UPLURAL_TYPE_ORDINAL
+};
+
+void
+uplrules_close(UPluralRules *uplrules)
+{
+ MOZ_CRASH("uplrules_close: Intl API disabled");
+}
+
+UPluralRules*
+uplrules_openForType(const char *locale, UPluralType type, UErrorCode *status)
+{
+ MOZ_CRASH("uplrules_openForType: Intl API disabled");
+}
+
+int32_t
+uplrules_select(const UPluralRules *uplrules,
+ double number,
+ UChar *keyword, int32_t capacity,
+ UErrorCode *status)
+{
+ MOZ_CRASH("uplrules_select: Intl API disabled");
+}
+
} // anonymous namespace
#endif
/******************** Common to Intl constructors ********************/
static bool
@@ -1607,16 +1713,110 @@ js::intl_numberingSystem(JSContext* cx,
RootedString jsname(cx, JS_NewStringCopyZ(cx, name));
if (!jsname)
return false;
args.rval().setString(jsname);
return true;
}
+
+/**
+ *
+ * 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.
*/
static UNumberFormat*
NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
{
RootedValue value(cx);
@@ -1690,42 +1890,42 @@ NewUNumberFormat(JSContext* cx, HandleOb
if (!HasProperty(cx, internals, id, &hasP))
return nullptr;
if (hasP) {
if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
&value))
{
return nullptr;
}
- uMinimumSignificantDigits = int32_t(value.toNumber());
+ 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))
return nullptr;
uUseGrouping = value.toBoolean();
UErrorCode status = U_ZERO_ERROR;
UNumberFormat* nf = unum_open(uStyle, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status);
@@ -3422,16 +3622,391 @@ js::intl_FormatDateTime(JSContext* cx, u
if (!isDateTimeFormatInstance)
udat_close(df);
if (!success)
return false;
args.rval().set(result);
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 = cx->global()->getOrCreateIntlObject(cx);
+ 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, cx->global()->getOrCreatePluralRulesPrototype(cx));
+ 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, global->createBlankPrototype(cx, &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)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
JSAutoByteString locale(cx, args[0].toString());
if (!locale)
@@ -3911,16 +4486,19 @@ GlobalObject::initIntlObject(JSContext*
if (!collatorProto)
return false;
RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
if (!dateTimeFormatProto)
return false;
RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
if (!numberFormatProto)
return false;
+ 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));
if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr,
JSPROP_RESOLVING))
{
return false;
}
@@ -3932,16 +4510,17 @@ GlobalObject::initIntlObject(JSContext*
// objects with the proper [[Prototype]] as "the original value of
// |Intl.Collator.prototype|" and similar. For builtin classes like
// |String.prototype| we have |JSProto_*| that enables
// |getPrototype(JSProto_*)|, but that has global-object-property-related
// baggage we don't need or want, so we use one-off reserved slots.
global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
+ 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".
// Use |setConstructor| to correspond with |JSProto_Intl|.
//
// XXX We should possibly do a one-off reserved slot like above.
global->setConstructor(JSProto_Intl, ObjectValue(*intl));
return true;
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -358,16 +358,64 @@ intl_patternForSkeleton(JSContext* cx, u
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
*
* Usage: formatted = intl_FormatDateTime(dateTimeFormat, x)
*/
extern MOZ_MUST_USE bool
intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);
+/******************** PluralRules ********************/
+
+/**
+ * Returns a new PluralRules instance.
+ * Self-hosted code cannot cache this constructor (as it does for others in
+ * Utilities.js) because it is initialized after self-hosted code is compiled.
+ *
+ * Usage: pluralRules = intl_PluralRules(locales, options)
+ */
+extern MOZ_MUST_USE bool
+intl_PluralRules(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns an object indicating the supported locales for plural rules
+ * by having a true-valued property for each such locale with the
+ * canonicalized language tag as the property name. The object has no
+ * prototype.
+ *
+ * Usage: availableLocales = intl_PluralRules_availableLocales()
+ */
+extern MOZ_MUST_USE bool
+intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns a plural rule for the number x according to the effective
+ * locale and the formatting options of the given PluralRules.
+ *
+ * A plural rule is a grammatical category that expresses count distinctions
+ * (such as "one", "two", "few" etc.).
+ *
+ * Usage: rule = intl_SelectPluralRule(pluralRules, x)
+ */
+extern MOZ_MUST_USE bool
+intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns an array of plural rules categories for a given
+ * locale and type.
+ *
+ * Usage: categories = intl_GetPluralCategories(locale, type)
+ *
+ * Example:
+ *
+ * intl_getPluralCategories('pl', 'cardinal'); // ['one', 'few', 'many', 'other']
+ */
+extern MOZ_MUST_USE bool
+intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp);
+
/**
* Returns a plain object with calendar information for a single valid locale
* (callers must perform this validation). The object will have these
* properties:
*
* firstDayOfWeek
* an integer in the range 1=Sunday to 7=Saturday indicating the day
* considered the first day of the week in calendars, e.g. 1 for en-US,
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -16,16 +16,19 @@
intl_CompareStrings: false,
intl_NumberFormat_availableLocales: false,
intl_numberingSystem: false,
intl_FormatNumber: false,
intl_DateTimeFormat_availableLocales: false,
intl_availableCalendars: false,
intl_patternForSkeleton: false,
intl_FormatDateTime: false,
+ intl_SelectPluralRule: false,
+ intl_GetPluralCategories: false,
+ intl_GetCalendarInfo: false,
*/
/*
* The Intl module specified by standard ECMA-402,
* ECMAScript Internationalization API Specification.
*/
@@ -834,16 +837,17 @@ function BestAvailableLocale(availableLo
/**
* Identical to BestAvailableLocale, but does not consider the default locale
* during computation.
*/
function BestAvailableLocaleIgnoringDefault(availableLocales, locale) {
return BestAvailableLocaleHelper(availableLocales, locale, false);
}
+var noRelevantExtensionKeys = [];
/**
* Compares a BCP 47 language priority list against the set of locales in
* availableLocales and determines the best available language to meet the
* request. Options specified through Unicode extension subsequences are
* ignored in the lookup, but information about such subsequences is returned
* separately.
*
@@ -1247,17 +1251,19 @@ function initializeIntlObject(obj) {
/**
* Mark |internals| as having the given type and lazy data.
*/
function setLazyData(internals, type, lazyData)
{
assert(internals.type === "partial", "can't set lazy data for anything but a newborn");
- assert(type === "Collator" || type === "DateTimeFormat" || type == "NumberFormat", "bad type");
+ assert(type === "Collator" || type === "DateTimeFormat" ||
+ type == "NumberFormat" || type === "PluralRules",
+ "bad type");
assert(IsObject(lazyData), "non-object lazy data");
// Set in reverse order so that the .type change is a barrier.
internals.lazyData = lazyData;
internals.type = type;
}
@@ -1297,17 +1303,19 @@ function maybeInternalProperties(interna
* Return whether |obj| has an[[initializedIntlObject]] property set to true.
*/
function isInitializedIntlObject(obj) {
#ifdef DEBUG
var internals = callFunction(std_WeakMap_get, internalsMap, obj);
if (IsObject(internals)) {
assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type");
var type = internals.type;
- assert(type === "partial" || type === "Collator" || type === "DateTimeFormat" || type === "NumberFormat", "unexpected type");
+ assert(type === "partial" || type === "Collator" ||
+ type === "DateTimeFormat" || type === "NumberFormat" || type === "PluralRules",
+ "unexpected type");
assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData");
assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps");
} else {
assert(internals === undefined, "bad mapping for |obj|");
}
#endif
return callFunction(std_WeakMap_has, internalsMap, obj);
}
@@ -1354,16 +1362,18 @@ function getInternals(obj)
return internals.internalProps;
var internalProps;
var type = internals.type;
if (type === "Collator")
internalProps = resolveCollatorInternals(lazyData)
else if (type === "DateTimeFormat")
internalProps = resolveDateTimeFormatInternals(lazyData)
+ else if (type === "PluralRules")
+ internalProps = resolvePluralRulesInternals(lazyData)
else
internalProps = resolveNumberFormatInternals(lazyData);
setInternalProperties(internals, internalProps);
return internalProps;
}
/********** Intl.Collator **********/
@@ -1753,67 +1763,59 @@ function resolveNumberFormatInternals(la
// Step 3.
var requestedLocales = lazyNumberFormatData.requestedLocales;
// Compute options that impact interpretation of locale.
// Step 6.
var opt = lazyNumberFormatData.opt;
- // Compute effective locale.
+ var NumberFormat = numberFormatInternalProperties;
+
// Step 9.
- var NumberFormat = numberFormatInternalProperties;
+ var localeData = NumberFormat.localeData;
// Step 10.
- var localeData = NumberFormat.localeData;
-
- // Step 11.
var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat),
lazyNumberFormatData.requestedLocales,
lazyNumberFormatData.opt,
NumberFormat.relevantExtensionKeys,
localeData);
- // Steps 12-13. (Step 14 is not relevant to our implementation.)
+ // Steps 11-12. (Step 13 is not relevant to our implementation.)
internalProps.locale = r.locale;
internalProps.numberingSystem = r.nu;
// Compute formatting options.
- // Step 16.
+ // Step 15.
var s = lazyNumberFormatData.style;
internalProps.style = s;
- // Steps 20, 22.
+ // Steps 19, 21.
if (s === "currency") {
internalProps.currency = lazyNumberFormatData.currency;
internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay;
}
- // Step 24.
internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits;
-
- // Steps 27.
internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits;
-
- // Step 30.
internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits;
- // Step 33.
if ("minimumSignificantDigits" in lazyNumberFormatData) {
// Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the
// actual presence (versus undefined-ness) of these properties.
assert("maximumSignificantDigits" in lazyNumberFormatData, "min/max sig digits mismatch");
internalProps.minimumSignificantDigits = lazyNumberFormatData.minimumSignificantDigits;
internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits;
}
- // Step 35.
+ // Step 27.
internalProps.useGrouping = lazyNumberFormatData.useGrouping;
- // Step 42.
+ // Step 34.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
// object using |setInternalProperties|.
return internalProps;
}
@@ -1831,16 +1833,52 @@ function getNumberFormatInternals(obj, m
return internalProps;
// Otherwise it's time to fully create them.
internalProps = resolveNumberFormatInternals(internals.lazyData);
setInternalProperties(internals, internalProps);
return internalProps;
}
+/**
+ * Applies digit options used for number formatting onto the intl object.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 11.1.1.
+ */
+function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault) {
+ // We skip Step 1 because we set the properties on a lazyData object.
+
+ // Step 2-3.
+ assert(IsObject(options), "SetNumberFormatDigitOptions");
+ assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions");
+
+
+ // Steps 4-6.
+ const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
+ const mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
+ const mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20);
+
+ // Steps 7-8.
+ let mnsd = options.minimumSignificantDigits;
+ let mxsd = options.maximumSignificantDigits;
+
+ // Steps 9-11.
+ lazyData.minimumIntegerDigits = mnid;
+ lazyData.minimumFractionDigits = mnfd;
+ lazyData.maximumFractionDigits = mxfd;
+
+ // Step 12.
+ if (mnsd !== undefined || mxsd !== undefined) {
+ mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
+ mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
+ lazyData.minimumSignificantDigits = mnsd;
+ lazyData.maximumSignificantDigits = mxsd;
+ }
+}
+
/**
* Initializes an object as a NumberFormat.
*
* This method is complicated a moderate bit by its implementing initialization
* as a *lazy* concept. Everything that must happen now, does -- but we defer
* all the work we can until the object is actually used as a NumberFormat.
* This later work occurs in |resolveNumberFormatInternals|; steps not noted
@@ -1880,17 +1918,17 @@ function InitializeNumberFormat(numberFo
// // optional
// minimumSignificantDigits: integer ∈ [1, 21],
// maximumSignificantDigits: integer ∈ [1, 21],
//
// useGrouping: true / false,
// }
//
// Note that lazy data is only installed as a final step of initialization,
- // so every Collator lazy data object has *all* these properties, never a
+ // so every NumberFormat lazy data object has *all* these properties, never a
// subset of them.
var lazyNumberFormatData = std_Object_create(null);
// Step 3.
var requestedLocales = CanonicalizeLocaleList(locales);
lazyNumberFormatData.requestedLocales = requestedLocales;
// Steps 4-5.
@@ -1910,77 +1948,59 @@ function InitializeNumberFormat(numberFo
var opt = new Record();
lazyNumberFormatData.opt = opt;
// Steps 7-8.
var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
// Compute formatting options.
- // Step 15.
+ // Step 14.
var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
lazyNumberFormatData.style = s;
- // Steps 17-20.
+ // Steps 16-19.
var c = GetOption(options, "currency", "string", undefined, undefined);
if (c !== undefined && !IsWellFormedCurrencyCode(c))
ThrowRangeError(JSMSG_INVALID_CURRENCY_CODE, c);
var cDigits;
if (s === "currency") {
if (c === undefined)
ThrowTypeError(JSMSG_UNDEFINED_CURRENCY);
// Steps 20.a-c.
c = toASCIIUpperCase(c);
lazyNumberFormatData.currency = c;
cDigits = CurrencyDigits(c);
}
- // Step 21.
+ // Step 20.
var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol");
if (s === "currency")
lazyNumberFormatData.currencyDisplay = cd;
- // Step 23.
- var mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
- lazyNumberFormatData.minimumIntegerDigits = mnid;
-
- // Steps 25-26.
- var mnfdDefault = (s === "currency") ? cDigits : 0;
- var mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
- lazyNumberFormatData.minimumFractionDigits = mnfd;
-
- // Steps 28-29.
- var mxfdDefault;
- if (s === "currency")
- mxfdDefault = std_Math_max(mnfd, cDigits);
- else if (s === "percent")
- mxfdDefault = std_Math_max(mnfd, 0);
- else
- mxfdDefault = std_Math_max(mnfd, 3);
- var mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20, mxfdDefault);
- lazyNumberFormatData.maximumFractionDigits = mxfd;
-
- // Steps 31-32.
- var mnsd = options.minimumSignificantDigits;
- var mxsd = options.maximumSignificantDigits;
-
- // Step 33.
- if (mnsd !== undefined || mxsd !== undefined) {
- mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
- mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
- lazyNumberFormatData.minimumSignificantDigits = mnsd;
- lazyNumberFormatData.maximumSignificantDigits = mxsd;
+ // Steps 22-24.
+ SetNumberFormatDigitOptions(lazyNumberFormatData, options, s === "currency" ? cDigits: 0);
+
+ // Step 25.
+ if (lazyNumberFormatData.maximumFractionDigits === undefined) {
+ let mxfdDefault = s === "currency"
+ ? cDigits
+ : s === "percent"
+ ? 0
+ : 3;
+ lazyNumberFormatData.maximumFractionDigits =
+ std_Math_max(lazyNumberFormatData.minimumFractionDigits, mxfdDefault);
}
- // Step 34.
+ // Steps 26.
var g = GetOption(options, "useGrouping", "boolean", undefined, true);
lazyNumberFormatData.useGrouping = g;
- // Step 43.
+ // Steps 35-36.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
setLazyData(internals, "NumberFormat", lazyNumberFormatData);
}
/**
@@ -2659,17 +2679,16 @@ function ToDateTimeOptions(options, requ
_DefineDataProperty(options, "minute", "numeric");
_DefineDataProperty(options, "second", "numeric");
}
// Step 9.
return options;
}
-
/**
* Compares the date and time components requested by options with the available
* date and time formats in formats, and selects the best match according
* to a specified basic matching algorithm.
*
* Spec: ECMAScript Internationalization API Specification, 12.1.1.
*/
function BasicFormatMatcher(options, formats) {
@@ -2980,16 +2999,244 @@ function resolveICUPattern(pattern, resu
if (c === "h" || c === "K")
_DefineDataProperty(result, "hour12", true);
else if (c === "H" || c === "k")
_DefineDataProperty(result, "hour12", false);
}
}
}
+/********** Intl.PluralRules **********/
+
+/**
+ * PluralRules internal properties.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.3.3.
+ */
+var pluralRulesInternalProperties = {
+ _availableLocales: null,
+ availableLocales: function()
+ {
+ var locales = this._availableLocales;
+ if (locales)
+ return locales;
+
+ locales = intl_PluralRules_availableLocales();
+ addSpecialMissingLanguageTags(locales);
+ return (this._availableLocales = locales);
+ }
+};
+
+/**
+ * Compute an internal properties object from |lazyPluralRulesData|.
+ */
+function resolvePluralRulesInternals(lazyPluralRulesData) {
+ assert(IsObject(lazyPluralRulesData), "lazy data not an object?");
+
+ var internalProps = std_Object_create(null);
+
+ var requestedLocales = lazyPluralRulesData.requestedLocales;
+
+ var PluralRules = pluralRulesInternalProperties;
+
+ // Step 13.
+ const r = ResolveLocale(callFunction(PluralRules.availableLocales, PluralRules),
+ lazyPluralRulesData.requestedLocales,
+ lazyPluralRulesData.opt,
+ noRelevantExtensionKeys, undefined);
+
+ // Step 14.
+ internalProps.locale = r.locale;
+ internalProps.type = lazyPluralRulesData.type;
+
+ internalProps.pluralCategories = intl_GetPluralCategories(
+ internalProps.locale,
+ internalProps.type);
+
+ internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;
+ internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits;
+ internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits;
+
+ if ("minimumSignificantDigits" in lazyPluralRulesData) {
+ assert("maximumSignificantDigits" in lazyPluralRulesData, "min/max sig digits mismatch");
+ internalProps.minimumSignificantDigits = lazyPluralRulesData.minimumSignificantDigits;
+ internalProps.maximumSignificantDigits = lazyPluralRulesData.maximumSignificantDigits;
+ }
+
+ return internalProps;
+}
+
+/**
+ * Returns an object containing the PluralRules internal properties of |obj|,
+ * or throws a TypeError if |obj| isn't PluralRules-initialized.
+ */
+function getPluralRulesInternals(obj, methodName) {
+ var internals = getIntlObjectInternals(obj, "PluralRules", methodName);
+ assert(internals.type === "PluralRules", "bad type escaped getIntlObjectInternals");
+
+ var internalProps = maybeInternalProperties(internals);
+ if (internalProps)
+ return internalProps;
+
+ internalProps = resolvePluralRulesInternals(internals.lazyData);
+ setInternalProperties(internals, internalProps);
+ return internalProps;
+}
+
+/**
+ * Initializes an object as a PluralRules.
+ *
+ * This method is complicated a moderate bit by its implementing initialization
+ * as a *lazy* concept. Everything that must happen now, does -- but we defer
+ * all the work we can until the object is actually used as a PluralRules.
+ * This later work occurs in |resolvePluralRulesInternals|; steps not noted
+ * here occur there.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.1.1.
+ */
+function InitializePluralRules(pluralRules, locales, options) {
+ assert(IsObject(pluralRules), "InitializePluralRules");
+
+ // Step 1.
+ if (isInitializedIntlObject(pluralRules))
+ ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+
+ let internals = initializeIntlObject(pluralRules);
+
+ // Lazy PluralRules data has the following structure:
+ //
+ // {
+ // requestedLocales: List of locales,
+ // type: "cardinal" / "ordinal",
+ //
+ // opt: // opt object computer in InitializePluralRules
+ // {
+ // localeMatcher: "lookup" / "best fit",
+ // }
+ //
+ // minimumIntegerDigits: integer ∈ [1, 21],
+ // minimumFractionDigits: integer ∈ [0, 20],
+ // maximumFractionDigits: integer ∈ [0, 20],
+ //
+ // // optional
+ // minimumSignificantDigits: integer ∈ [1, 21],
+ // maximumSignificantDigits: integer ∈ [1, 21],
+ // }
+ //
+ // Note that lazy data is only installed as a final step of initialization,
+ // so every PluralRules lazy data object has *all* these properties, never a
+ // subset of them.
+ const lazyPluralRulesData = std_Object_create(null);
+
+ // Step 3.
+ let requestedLocales = CanonicalizeLocaleList(locales);
+ lazyPluralRulesData.requestedLocales = requestedLocales;
+
+ // Steps 4-5.
+ if (options === undefined)
+ options = {};
+ else
+ options = ToObject(options);
+
+ // Step 6.
+ const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
+ lazyPluralRulesData.type = type;
+
+ // Step 8.
+ let opt = new Record();
+ lazyPluralRulesData.opt = opt;
+
+ // Steps 9-10.
+ let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
+ opt.localeMatcher = matcher;
+
+
+ // Step 11.
+ SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0);
+
+ // Step 12.
+ if (lazyPluralRulesData.maximumFractionDigits === undefined) {
+ lazyPluralRulesData.maximumFractionDigits =
+ std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3);
+ }
+
+ setLazyData(internals, "PluralRules", lazyPluralRulesData)
+}
+
+/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.3.2.
+ */
+function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ // Step 1.
+ var availableLocales = callFunction(pluralRulesInternalProperties.availableLocales,
+ pluralRulesInternalProperties);
+ // Step 2.
+ let requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
+ * Returns a String value representing the plural category matching
+ * the number passed as value according to the
+ * effective locale and the formatting options of this PluralRules.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.4.3.
+ */
+function Intl_PluralRules_select(value) {
+ // Step 1.
+ let pluralRules = this;
+ // Step 2.
+ let internals = getPluralRulesInternals(pluralRules, "select");
+
+ // Steps 3-4.
+ let n = ToNumber(value);
+
+ // Step 5.
+ return intl_SelectPluralRule(pluralRules, n);
+}
+
+/**
+ * Returns the resolved options for a PluralRules object.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.4.4.
+ */
+function Intl_PluralRules_resolvedOptions() {
+ var internals = getPluralRulesInternals(this, "resolvedOptions");
+
+ var result = {
+ locale: internals.locale,
+ type: internals.type,
+ pluralCategories: callFunction(std_Array_slice, internals.pluralCategories, 0),
+ minimumIntegerDigits: internals.minimumIntegerDigits,
+ minimumFractionDigits: internals.minimumFractionDigits,
+ maximumFractionDigits: internals.maximumFractionDigits,
+ };
+
+ var optionalProperties = [
+ "minimumSignificantDigits",
+ "maximumSignificantDigits"
+ ];
+
+ for (var i = 0; i < optionalProperties.length; i++) {
+ var p = optionalProperties[i];
+ if (callFunction(std_Object_hasOwnProperty, internals, p))
+ _DefineDataProperty(result, p, internals[p]);
+ }
+ return result;
+}
+
+
function Intl_getCanonicalLocales(locales) {
let codes = CanonicalizeLocaleList(locales);
let result = [];
let len = codes.length;
let k = 0;
while (k < len) {
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/NumberFormat/options-emulate-undefined.js
@@ -0,0 +1,13 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// objectEmulatingUndefined is only available when running tests in the shell,
+// not the browser
+if (typeof objectEmulatingUndefined === "function") {
+ let nf = new Intl.NumberFormat('en-US', objectEmulatingUndefined());
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/PluralRules/pluralrules.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the format function with a diverse set of locales and options.
+
+var pr;
+
+pr = new Intl.PluralRules("en-us");
+assertEq(pr.resolvedOptions().locale, "en-US");
+assertEq(pr.resolvedOptions().type, "cardinal");
+assertEq(pr.resolvedOptions().pluralCategories.length, 2);
+
+pr = new Intl.PluralRules("de", {type: 'cardinal'});
+assertEq(pr.resolvedOptions().pluralCategories.length, 2);
+
+pr = new Intl.PluralRules("de", {type: 'ordinal'});
+assertEq(pr.resolvedOptions().pluralCategories.length, 1);
+
+reportCompare(0, 0, 'ok');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/PluralRules/select.js
@@ -0,0 +1,60 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the format function with a diverse set of locales and options.
+
+var pr;
+
+pr = new Intl.PluralRules("en-us");
+assertEq(pr.select(0), "other");
+assertEq(pr.select(0.5), "other");
+assertEq(pr.select(1.2), "other");
+assertEq(pr.select(1.5), "other");
+assertEq(pr.select(1.7), "other");
+assertEq(pr.select(-1), "one");
+assertEq(pr.select(1), "one");
+assertEq(pr.select("1"), "one");
+assertEq(pr.select(123456789.123456789), "other");
+
+pr = new Intl.PluralRules("de", {type: "cardinal"});
+assertEq(pr.select(0), "other");
+assertEq(pr.select(0.5), "other");
+assertEq(pr.select(1.2), "other");
+assertEq(pr.select(1.5), "other");
+assertEq(pr.select(1.7), "other");
+assertEq(pr.select(-1), "one");
+
+pr = new Intl.PluralRules("de", {type: "ordinal"});
+assertEq(pr.select(0), "other");
+assertEq(pr.select(0.5), "other");
+assertEq(pr.select(1.2), "other");
+assertEq(pr.select(1.5), "other");
+assertEq(pr.select(1.7), "other");
+assertEq(pr.select(-1), "other");
+
+pr = new Intl.PluralRules("pl", {type: "cardinal"});
+assertEq(pr.select(0), "many");
+assertEq(pr.select(0.5), "other");
+assertEq(pr.select(1), "one");
+
+pr = new Intl.PluralRules("pl", {type: "cardinal", maximumFractionDigits: 0});
+assertEq(pr.select(1.1), "one");
+
+pr = new Intl.PluralRules("pl", {type: "cardinal", maximumFractionDigits: 1});
+assertEq(pr.select(1.1), "other");
+
+var weirdCases = [
+ NaN,
+ Infinity,
+ "word",
+ [0,2],
+ {},
+];
+
+for (let c of weirdCases) {
+ assertEq(pr.select(c), "other");
+};
+
+reportCompare(0, 0, 'ok');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/PluralRules/supportedLocalesOf.js
@@ -0,0 +1,373 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||xulRuntime.shell)
+// -- test in browser only that ICU has locale data for all Mozilla languages
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This array contains the locales that ICU supports in
+// number formatting whose languages Mozilla localizes Firefox into.
+// Current as of ICU 50.1.2 and Firefox March 2013.
+var locales = [
+ "af",
+ "af-NA",
+ "af-ZA",
+ "ar",
+ "ar-001",
+ "ar-AE",
+ "ar-BH",
+ "ar-DJ",
+ "ar-DZ",
+ "ar-EG",
+ "ar-EH",
+ "ar-ER",
+ "ar-IL",
+ "ar-IQ",
+ "ar-JO",
+ "ar-KM",
+ "ar-KW",
+ "ar-LB",
+ "ar-LY",
+ "ar-MA",
+ "ar-MR",
+ "ar-OM",
+ "ar-PS",
+ "ar-QA",
+ "ar-SA",
+ "ar-SD",
+ "ar-SO",
+ "ar-SY",
+ "ar-TD",
+ "ar-TN",
+ "ar-YE",
+ "as",
+ "as-IN",
+ "be",
+ "be-BY",
+ "bg",
+ "bg-BG",
+ "bn",
+ "bn-BD",
+ "bn-IN",
+ "br",
+ "br-FR",
+ "bs",
+ "bs-Cyrl",
+ "bs-Cyrl-BA",
+ "bs-Latn",
+ "bs-Latn-BA",
+ "ca",
+ "ca-AD",
+ "ca-ES",
+ "cs",
+ "cs-CZ",
+ "cy",
+ "cy-GB",
+ "da",
+ "da-DK",
+ "de",
+ "de-AT",
+ "de-BE",
+ "de-CH",
+ "de-DE",
+ "de-LI",
+ "de-LU",
+ "el",
+ "el-CY",
+ "el-GR",
+ "en",
+ "en-150",
+ "en-AG",
+ "en-AS",
+ "en-AU",
+ "en-BB",
+ "en-BE",
+ "en-BM",
+ "en-BS",
+ "en-BW",
+ "en-BZ",
+ "en-CA",
+ "en-CM",
+ "en-DM",
+ "en-FJ",
+ "en-FM",
+ "en-GB",
+ "en-GD",
+ "en-GG",
+ "en-GH",
+ "en-GI",
+ "en-GM",
+ "en-GU",
+ "en-GY",
+ "en-HK",
+ "en-IE",
+ "en-IM",
+ "en-IN",
+ "en-JE",
+ "en-JM",
+ "en-KE",
+ "en-KI",
+ "en-KN",
+ "en-KY",
+ "en-LC",
+ "en-LR",
+ "en-LS",
+ "en-MG",
+ "en-MH",
+ "en-MP",
+ "en-MT",
+ "en-MU",
+ "en-MW",
+ "en-NA",
+ "en-NG",
+ "en-NZ",
+ "en-PG",
+ "en-PH",
+ "en-PK",
+ "en-PR",
+ "en-PW",
+ "en-SB",
+ "en-SC",
+ "en-SG",
+ "en-SL",
+ "en-SS",
+ "en-SZ",
+ "en-TC",
+ "en-TO",
+ "en-TT",
+ "en-TZ",
+ "en-UG",
+ "en-UM",
+ "en-US",
+ "en-US-posix",
+ "en-VC",
+ "en-VG",
+ "en-VI",
+ "en-VU",
+ "en-WS",
+ "en-ZA",
+ "en-ZM",
+ "en-ZW",
+ "eo",
+ "es",
+ "es-419",
+ "es-AR",
+ "es-BO",
+ "es-CL",
+ "es-CO",
+ "es-CR",
+ "es-CU",
+ "es-DO",
+ "es-EA",
+ "es-EC",
+ "es-ES",
+ "es-GQ",
+ "es-GT",
+ "es-HN",
+ "es-IC",
+ "es-MX",
+ "es-NI",
+ "es-PA",
+ "es-PE",
+ "es-PH",
+ "es-PR",
+ "es-PY",
+ "es-SV",
+ "es-US",
+ "es-UY",
+ "es-VE",
+ "et",
+ "et-EE",
+ "eu",
+ "eu-ES",
+ "fa",
+ "fa-AF",
+ "fa-IR",
+ "ff",
+ "ff-SN",
+ "fi",
+ "fi-FI",
+ "fr",
+ "fr-BE",
+ "fr-BF",
+ "fr-BI",
+ "fr-BJ",
+ "fr-BL",
+ "fr-CA",
+ "fr-CD",
+ "fr-CF",
+ "fr-CG",
+ "fr-CH",
+ "fr-CI",
+ "fr-CM",
+ "fr-DJ",
+ "fr-DZ",
+ "fr-FR",
+ "fr-GA",
+ "fr-GF",
+ "fr-GN",
+ "fr-GP",
+ "fr-GQ",
+ "fr-HT",
+ "fr-KM",
+ "fr-LU",
+ "fr-MA",
+ "fr-MC",
+ "fr-MF",
+ "fr-MG",
+ "fr-ML",
+ "fr-MQ",
+ "fr-MR",
+ "fr-MU",
+ "fr-NC",
+ "fr-NE",
+ "fr-PF",
+ "fr-RE",
+ "fr-RW",
+ "fr-SC",
+ "fr-SN",
+ "fr-SY",
+ "fr-TD",
+ "fr-TG",
+ "fr-TN",
+ "fr-VU",
+ "fr-YT",
+ "ga",
+ "ga-IE",
+ "gl",
+ "gl-ES",
+ "gu",
+ "gu-IN",
+ "he",
+ "he-IL",
+ "hi",
+ "hi-IN",
+ "hr",
+ "hr-BA",
+ "hr-HR",
+ "hu",
+ "hu-HU",
+ "hy",
+ "hy-AM",
+ "id",
+ "id-ID",
+ "is",
+ "is-IS",
+ "it",
+ "it-CH",
+ "it-IT",
+ "it-SM",
+ "ja",
+ "ja-JP",
+ "kk",
+ "kk-Cyrl",
+ "kk-Cyrl-KZ",
+ "km",
+ "km-KH",
+ "kn",
+ "kn-IN",
+ "ko",
+ "ko-KP",
+ "ko-KR",
+ "lt",
+ "lt-LT",
+ "lv",
+ "lv-LV",
+ "mk",
+ "mk-MK",
+ "ml",
+ "ml-IN",
+ "mr",
+ "mr-IN",
+ "nb",
+ "nb-NO",
+ "nl",
+ "nl-AW",
+ "nl-BE",
+ "nl-CW",
+ "nl-NL",
+ "nl-SR",
+ "nl-SX",
+ "nn",
+ "nn-NO",
+ "or",
+ "or-IN",
+ "pa",
+ "pa-Arab",
+ "pa-Arab-PK",
+ "pa-Guru",
+ "pa-Guru-IN",
+ "pl",
+ "pl-PL",
+ "pt",
+ "pt-AO",
+ "pt-BR",
+ "pt-CV",
+ "pt-GW",
+ "pt-MO",
+ "pt-MZ",
+ "pt-PT",
+ "pt-ST",
+ "pt-TL",
+ "rm",
+ "rm-CH",
+ "ro",
+ "ro-MD",
+ "ro-RO",
+ "ru",
+ "ru-BY",
+ "ru-KG",
+ "ru-KZ",
+ "ru-MD",
+ "ru-RU",
+ "ru-UA",
+ "si",
+ "si-LK",
+ "sk",
+ "sk-SK",
+ "sl",
+ "sl-SI",
+ "sq",
+ "sq-AL",
+ "sq-MK",
+ "sr",
+ "sr-Cyrl",
+ "sr-Cyrl-BA",
+ "sr-Cyrl-ME",
+ "sr-Cyrl-RS",
+ "sr-Latn",
+ "sr-Latn-BA",
+ "sr-Latn-ME",
+ "sr-Latn-RS",
+ "sv",
+ "sv-AX",
+ "sv-FI",
+ "sv-SE",
+ "te",
+ "te-IN",
+ "th",
+ "th-TH",
+ "tr",
+ "tr-CY",
+ "tr-TR",
+ "uk",
+ "uk-UA",
+ "vi",
+ "vi-VN",
+ "zh",
+ "zh-Hans",
+ "zh-Hans-CN",
+ "zh-Hans-HK",
+ "zh-Hans-MO",
+ "zh-Hans-SG",
+ "zh-Hant",
+ "zh-Hant-HK",
+ "zh-Hant-MO",
+ "zh-Hant-TW",
+];
+
+const result = Intl.PluralRules.supportedLocalesOf(locales);
+
+assertEqArray(locales, result);
+
+reportCompare(0, 0, 'ok');
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -152,16 +152,17 @@
macro(includes, includes, "includes") \
macro(incumbentGlobal, incumbentGlobal, "incumbentGlobal") \
macro(index, index, "index") \
macro(infinity, infinity, "infinity") \
macro(Infinity, Infinity, "Infinity") \
macro(InitializeCollator, InitializeCollator, "InitializeCollator") \
macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \
macro(InitializeNumberFormat, InitializeNumberFormat, "InitializeNumberFormat") \
+ macro(InitializePluralRules, InitializePluralRules, "InitializePluralRules") \
macro(innermost, innermost, "innermost") \
macro(inNursery, inNursery, "inNursery") \
macro(input, input, "input") \
macro(int8, int8, "int8") \
macro(int16, int16, "int16") \
macro(int32, int32, "int32") \
macro(Int8x16, Int8x16, "Int8x16") \
macro(Int16x8, Int16x8, "Int16x8") \
@@ -243,16 +244,18 @@
macro(optimizedOut, optimizedOut, "optimizedOut") \
macro(other, other, "other") \
macro(outOfMemory, outOfMemory, "out of memory") \
macro(ownKeys, ownKeys, "ownKeys") \
macro(parseFloat, parseFloat, "parseFloat") \
macro(parseInt, parseInt, "parseInt") \
macro(pattern, pattern, "pattern") \
macro(pending, pending, "pending") \
+ macro(PluralRules, PluralRules, "PluralRules") \
+ macro(PluralRulesSelect, PluralRulesSelect, "Intl_PluralRules_Select") \
macro(percentSign, percentSign, "percentSign") \
macro(plusSign, plusSign, "plusSign") \
macro(preventExtensions, preventExtensions, "preventExtensions") \
macro(promise, promise, "promise") \
macro(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \
macro(proto, proto, "__proto__") \
macro(prototype, prototype, "prototype") \
macro(proxy, proxy, "proxy") \
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -99,16 +99,17 @@ class GlobalObject : public NativeObject
STAR_GENERATOR_FUNCTION,
ASYNC_FUNCTION_PROTO,
ASYNC_FUNCTION,
MAP_ITERATOR_PROTO,
SET_ITERATOR_PROTO,
COLLATOR_PROTO,
NUMBER_FORMAT_PROTO,
DATE_TIME_FORMAT_PROTO,
+ PLURAL_RULES_PROTO,
MODULE_PROTO,
IMPORT_ENTRY_PROTO,
EXPORT_ENTRY_PROTO,
REGEXP_STATICS,
WARNED_ONCE_FLAGS,
RUNTIME_CODEGEN_ENABLED,
DEBUGGERS,
INTRINSICS,
@@ -481,16 +482,20 @@ class GlobalObject : public NativeObject
JSObject* getOrCreateNumberFormatPrototype(JSContext* cx) {
return getOrCreateObject(cx, NUMBER_FORMAT_PROTO, initIntlObject);
}
JSObject* getOrCreateDateTimeFormatPrototype(JSContext* cx) {
return getOrCreateObject(cx, DATE_TIME_FORMAT_PROTO, initIntlObject);
}
+ JSObject* getOrCreatePluralRulesPrototype(JSContext* cx) {
+ return getOrCreateObject(cx, PLURAL_RULES_PROTO, initIntlObject);
+ }
+
static bool ensureModulePrototypesCreated(JSContext *cx, Handle<GlobalObject*> global);
JSObject* maybeGetModulePrototype() {
Value value = getSlot(MODULE_PROTO);
return value.isUndefined() ? nullptr : &value.toObject();
}
JSObject* maybeGetImportEntryPrototype() {
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2518,16 +2518,19 @@ static const JSFunctionSpec intrinsic_fu
JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
JS_FN("intl_ComputeDisplayNames", intl_ComputeDisplayNames, 3,0),
JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
+ JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
+ JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
+ JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
JS_INLINABLE_FN("IsRegExpObject",
intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
IsRegExpObject),
JS_FN("CallRegExpMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<RegExpObject>>, 2,0),
JS_INLINABLE_FN("RegExpMatcher", RegExpMatcher, 4,0,
RegExpMatcher),