Bug 1270146 - Add PluralRules API. r?waldo draft
authorZibi Braniecki <gandalf@mozilla.com>
Tue, 20 Dec 2016 11:54:44 -0800
changeset 451663 ad6e67c980c3c1291cd6a72c6e4c8c33a25a2c1b
parent 451656 7083c0d30e75fc102c715887af9faec933e936f8
child 540085 3b8842ebe9e63b95cb685729b3a0219419c31fc1
push id39243
push userzbraniecki@mozilla.com
push dateTue, 20 Dec 2016 19:55:10 +0000
reviewerswaldo
bugs1270146
milestone53.0a1
Bug 1270146 - Add PluralRules API. r?waldo MozReview-Commit-ID: 2WCcMjiGjwZ
config/check_spidermonkey_style.py
js/public/Class.h
js/src/builtin/Intl.cpp
js/src/builtin/Intl.h
js/src/builtin/Intl.js
js/src/tests/Intl/NumberFormat/options-emulate-undefined.js
js/src/tests/Intl/PluralRules/browser.js
js/src/tests/Intl/PluralRules/pluralrules.js
js/src/tests/Intl/PluralRules/select.js
js/src/tests/Intl/PluralRules/shell.js
js/src/tests/Intl/PluralRules/supportedLocalesOf.js
js/src/vm/CommonPropertyNames.h
js/src/vm/GlobalObject.h
js/src/vm/SelfHosting.cpp
--- 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
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
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),