Bug 1270146 - Add PluralRules API. r=Waldo
authorZibi Braniecki <gandalf@mozilla.com>
Tue, 20 Dec 2016 11:54:44 -0800
changeset 326616 80234a27490b3105612b1a78716f1f1c0987e5e3
parent 326615 1ffe78f2ab5a70c620f7fc5f6bf647570efb4e24
child 326617 e66794fae8f76b8416f3376cdba70b640b7fb7bd
push id31107
push userphilringnalda@gmail.com
push dateWed, 21 Dec 2016 04:13:35 +0000
treeherdermozilla-central@20774bffb62a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1270146
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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),