Bug 1328386 - Part 6: Implement legacy constructor semantics for Intl.NumberFormat per ECMA-402, 4th edition. r=Waldo
authorAndré Bargull <andre.bargull@gmail.com>
Mon, 23 Jan 2017 08:33:43 -0800
changeset 466649 91080dad0ff8d9211948e470500a12cbb3bcf515
parent 466648 985a0c1baa892278e078fb4d796fc87a730aaeb4
child 466650 60adc4589abc3de850e379496a143dc6dc208cac
push id42948
push userbmo:gasolin@mozilla.com
push dateThu, 26 Jan 2017 07:49:21 +0000
reviewersWaldo
bugs1328386
milestone54.0a1
Bug 1328386 - Part 6: Implement legacy constructor semantics for Intl.NumberFormat per ECMA-402, 4th edition. r=Waldo
js/public/Class.h
js/src/builtin/Intl.cpp
js/src/builtin/Intl.js
js/src/jit/InlinableNatives.h
js/src/jit/MCallOptimize.cpp
js/src/tests/Intl/NumberFormat/call.js
js/src/tests/Intl/NumberFormat/unwrapping.js
js/src/tests/jstests.list
js/src/vm/GlobalObject.h
js/src/vm/SelfHosting.cpp
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -847,17 +847,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
@@ -801,17 +801,42 @@ IntlInitialize(JSContext* cx, HandleObje
     FixedInvokeArgs<3> args(cx);
 
     args[0].setObject(*obj);
     args[1].set(locales);
     args[2].set(options);
 
     RootedValue thisv(cx, NullValue());
     RootedValue ignored(cx);
-    return js::CallSelfHostedFunction(cx, initializer, thisv, args, &ignored);
+    if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, &ignored))
+        return false;
+
+    MOZ_ASSERT(ignored.isUndefined(),
+               "Unexpected return value from non-legacy Intl object initializer");
+    return true;
+}
+
+static bool
+LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
+                     HandleValue thisValue, HandleValue locales, HandleValue options,
+                     MutableHandleValue result)
+{
+    FixedInvokeArgs<4> args(cx);
+
+    args[0].setObject(*obj);
+    args[1].set(thisValue);
+    args[2].set(locales);
+    args[3].set(options);
+
+    RootedValue thisv(cx, NullValue());
+    if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, result))
+        return false;
+
+    MOZ_ASSERT(result.isObject(), "Legacy Intl object initializer must return an object");
+    return true;
 }
 
 static bool
 CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions)
 {
     RootedObject options(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
     if (!options)
         return false;
@@ -1421,74 +1446,43 @@ static const JSPropertySpec numberFormat
 /**
  * 11.2.1 Intl.NumberFormat([ locales [, options]])
  *
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool
 NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
 {
-    RootedObject obj(cx);
-
-    // We're following ECMA-402 1st Edition when NumberFormat is called
-    // because of backward compatibility issues.
-    // See https://github.com/tc39/ecma402/issues/57
-    if (!construct) {
-        // ES Intl 1st ed., 11.1.2.1 step 3
-        JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
-        if (!intl)
+    // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
+
+    // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
+    RootedObject proto(cx);
+    if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
+        return false;
+
+    if (!proto) {
+        proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
+        if (!proto)
             return false;
-        RootedValue self(cx, args.thisv());
-        if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
-            // ES Intl 1st ed., 11.1.2.1 step 4
-            obj = ToObject(cx, self);
-            if (!obj)
-                return false;
-
-            // ES Intl 1st ed., 11.1.2.1 step 5
-            bool extensible;
-            if (!IsExtensible(cx, obj, &extensible))
-                return false;
-            if (!extensible)
-                return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
-        } else {
-            // ES Intl 1st ed., 11.1.2.1 step 3.a
-            construct = true;
-        }
     }
 
-    if (construct) {
-        // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
-        RootedObject proto(cx);
-        if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
-            return false;
-
-        if (!proto) {
-            proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
-            if (!proto)
-                return false;
-        }
-
-        obj = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
-        if (!obj)
-            return false;
-
-        obj->as<NumberFormatObject>().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT,
-                                                      PrivateValue(nullptr));
-    }
-
+    Rooted<NumberFormatObject*> numberFormat(cx);
+    numberFormat = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
+    if (!numberFormat)
+        return false;
+
+    numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
+
+    RootedValue thisValue(cx, construct ? ObjectValue(*numberFormat) : args.thisv());
     RootedValue locales(cx, args.get(0));
     RootedValue options(cx, args.get(1));
 
     // Step 3.
-    if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options))
-        return false;
-
-    args.rval().setObject(*obj);
-    return true;
+    return LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue,
+                                locales, options, args.rval());
 }
 
 static bool
 NumberFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return NumberFormat(cx, args, args.isConstructing());
 }
@@ -1518,17 +1512,18 @@ NumberFormatObject::finalize(FreeOp* fop
         obj->as<NumberFormatObject>().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT);
     if (!slot.isUndefined()) {
         if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate()))
             unum_close(nf);
     }
 }
 
 static JSObject*
-CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
+                            MutableHandleObject constructor)
 {
     RootedFunction ctor(cx);
     ctor = GlobalObject::createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0);
     if (!ctor)
         return nullptr;
 
     RootedNativeObject proto(cx);
     proto = GlobalObject::createBlankPrototype<NumberFormatObject>(cx, global);
@@ -1569,27 +1564,30 @@ CreateNumberFormatPrototype(JSContext* c
     }
 #endif // defined(ICU_UNUM_HAS_FORMATDOUBLEFORFIELDS)
 
     RootedValue options(cx);
     if (!CreateDefaultOptions(cx, &options))
         return nullptr;
 
     // 11.2.1 and 11.3
-    if (!IntlInitialize(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue,
-                        options))
+    RootedValue thisOrResult(cx, ObjectValue(*proto));
+    if (!LegacyIntlInitialize(cx, proto, cx->names().InitializeNumberFormat, thisOrResult,
+                              UndefinedHandleValue, options, &thisOrResult))
     {
         return nullptr;
     }
+    MOZ_ASSERT(&thisOrResult.toObject() == proto);
 
     // 8.1
     RootedValue ctorValue(cx, ObjectValue(*ctor));
     if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0))
         return nullptr;
 
+    constructor.set(ctor);
     return proto;
 }
 
 bool
 js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
@@ -1708,17 +1706,17 @@ NewUNumberFormatForPluralRules(JSContext
 }
 
 
 /**
  * Returns a new UNumberFormat with the locale and number formatting options
  * of the given NumberFormat.
  */
 static UNumberFormat*
-NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
+NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat)
 {
     RootedValue value(cx);
 
     RootedObject internals(cx, GetInternals(cx, numberFormat));
     if (!internals)
        return nullptr;
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
@@ -2321,66 +2319,40 @@ bool
 js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 3);
     MOZ_ASSERT(args[0].isObject());
     MOZ_ASSERT(args[1].isNumber());
     MOZ_ASSERT(args[2].isBoolean());
 
-    RootedObject numberFormat(cx, &args[0].toObject());
-
-    // Obtain a UNumberFormat object, cached if possible.
-    bool isNumberFormatInstance = numberFormat->is<NumberFormatObject>();
-    UNumberFormat* nf;
-    if (isNumberFormatInstance) {
-        void* priv =
-            numberFormat->as<NumberFormatObject>().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT)
-                                                  .toPrivate();
-        nf = static_cast<UNumberFormat*>(priv);
-        if (!nf) {
-            nf = NewUNumberFormat(cx, numberFormat);
-            if (!nf)
-                return false;
-            numberFormat->as<NumberFormatObject>().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT,
-                                                                   PrivateValue(nf));
-        }
-    } else {
-        // There's no good place to cache the ICU number format for an object
-        // that has been initialized as a NumberFormat but is not a
-        // NumberFormat instance. One possibility might be to add a
-        // NumberFormat instance as an internal property to each such object.
+    Rooted<NumberFormatObject*> numberFormat(cx, &args[0].toObject().as<NumberFormatObject>());
+
+    // Obtain a cached UNumberFormat object.
+    void* priv =
+        numberFormat->getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT).toPrivate();
+    UNumberFormat* nf = static_cast<UNumberFormat*>(priv);
+    if (!nf) {
         nf = NewUNumberFormat(cx, numberFormat);
         if (!nf)
             return false;
+        numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nf));
     }
 
     // Use the UNumberFormat to actually format the number.
-    double d = args[1].toNumber();
-    RootedValue result(cx);
-
-    bool success;
 #if defined(ICU_UNUM_HAS_FORMATDOUBLEFORFIELDS)
     if (args[2].toBoolean()) {
-        success = intl_FormatNumberToParts(cx, nf, d, &result);
-    } else
+        return intl_FormatNumberToParts(cx, nf, args[1].toNumber(), args.rval());
+    }
+#else
+    MOZ_ASSERT(!args[2].toBoolean(),
+               "shouldn't be doing formatToParts without an ICU that "
+               "supports it");
 #endif // defined(ICU_UNUM_HAS_FORMATDOUBLEFORFIELDS)
-    {
-        MOZ_ASSERT(!args[2].toBoolean(),
-                   "shouldn't be doing formatToParts without an ICU that "
-                   "supports it");
-        success = intl_FormatNumber(cx, nf, d, &result);
-    }
-
-    if (!isNumberFormatInstance)
-        unum_close(nf);
-    if (!success)
-        return false;
-    args.rval().set(result);
-    return true;
+    return intl_FormatNumber(cx, nf, args[1].toNumber(), args.rval());
 }
 
 
 /******************** DateTimeFormat ********************/
 
 const ClassOps DateTimeFormatObject::classOps_ = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
@@ -4370,17 +4342,18 @@ GlobalObject::initIntlObject(JSContext* 
     // Add the constructor properties, computing and returning the relevant
     // prototype objects needed below.
     RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global));
     if (!collatorProto)
         return false;
     RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
     if (!dateTimeFormatProto)
         return false;
-    RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
+    RootedObject numberFormatProto(cx), numberFormat(cx);
+    numberFormatProto = CreateNumberFormatPrototype(cx, intl, global, &numberFormat);
     if (!numberFormatProto)
         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))
     {
@@ -4393,16 +4366,17 @@ GlobalObject::initIntlObject(JSContext* 
     // Cache the various prototypes, for use in creating instances of these
     // 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, ObjectValue(*numberFormat));
     global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
 
     // 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));
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -1847,34 +1847,55 @@ function resolveNumberFormatInternals(la
 
     // The caller is responsible for associating |internalProps| with the right
     // object using |setInternalProperties|.
     return internalProps;
 }
 
 
 /**
- * Returns an object containing the NumberFormat internal properties of |obj|,
- * or throws a TypeError if |obj| isn't NumberFormat-initialized.
+ * Returns an object containing the NumberFormat internal properties of |obj|.
  */
 function getNumberFormatInternals(obj, methodName) {
+    assert(IsObject(obj), "getNumberFormatInternals called with non-object");
+    assert(IsNumberFormat(obj), "getNumberFormatInternals called with non-NumberFormat");
+
     var internals = getIntlObjectInternals(obj, "NumberFormat", methodName);
     assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals");
 
     // If internal properties have already been computed, use them.
     var internalProps = maybeInternalProperties(internals);
     if (internalProps)
         return internalProps;
 
     // Otherwise it's time to fully create them.
     internalProps = resolveNumberFormatInternals(internals.lazyData);
     setInternalProperties(internals, internalProps);
     return internalProps;
 }
 
+
+/**
+ * UnwrapNumberFormat(nf)
+ */
+function UnwrapNumberFormat(nf, methodName) {
+    // Step 1.
+    if ((!IsObject(nf) || !IsNumberFormat(nf)) && nf instanceof GetNumberFormatConstructor()) {
+        nf = nf[intlFallbackSymbol()];
+    }
+
+    // Step 2.
+    if (!IsObject(nf) || !IsNumberFormat(nf))
+        ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "NumberFormat", methodName, "NumberFormat");
+
+    // Step 3.
+    return nf;
+}
+
+
 /**
  * Applies digit options used for number formatting onto the intl object.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.1.1.
  */
 function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault, mxfdDefault) {
     // We skip Step 1 because we set the properties on a lazyData object.
 
@@ -1915,22 +1936,23 @@ function SetNumberFormatDigitOptions(laz
  * 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
  * here occur there.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.1.1.
  */
-function InitializeNumberFormat(numberFormat, locales, options) {
-    assert(IsObject(numberFormat), "InitializeNumberFormat");
-
-    // Step 1.
-    if (isInitializedIntlObject(numberFormat))
-        ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
+    assert(IsObject(numberFormat), "InitializeNumberFormat called with non-object");
+    assert(IsNumberFormat(numberFormat), "InitializeNumberFormat called with non-NumberFormat");
+
+    // Steps 1-2 (These steps are no longer required and should be removed
+    // from the spec; https://github.com/tc39/ecma402/issues/115).
+    assert(!isInitializedIntlObject(numberFormat), "numberFormat mustn't be initialized");
 
     // Step 2.
     var internals = initializeIntlObject(numberFormat);
 
     // Lazy NumberFormat data has the following structure:
     //
     //   {
     //     requestedLocales: List of locales,
@@ -2026,16 +2048,28 @@ function InitializeNumberFormat(numberFo
     var g = GetOption(options, "useGrouping", "boolean", undefined, true);
     lazyNumberFormatData.useGrouping = g;
 
     // 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);
+
+    if (numberFormat !== thisValue && thisValue instanceof GetNumberFormatConstructor()) {
+        if (!IsObject(thisValue))
+            ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue);
+
+        _DefineDataProperty(thisValue, intlFallbackSymbol(), numberFormat,
+                            ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
+
+        return thisValue;
+    }
+
+    return numberFormat;
 }
 
 
 /**
  * Returns the number of decimal digits to be used for the given currency.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.1.1.
  */
@@ -2122,57 +2156,62 @@ function numberFormatFormatToBind(value)
 /**
  * Returns a function bound to this NumberFormat that returns a String value
  * representing the result of calling ToNumber(value) according to the
  * effective locale and the formatting options of this NumberFormat.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.3.2.
  */
 function Intl_NumberFormat_format_get() {
-    // Check "this NumberFormat object" per introduction of section 11.3.
-    var internals = getNumberFormatInternals(this, "format");
-
-    // Step 1.
+    // Steps 1-3.
+    var nf = UnwrapNumberFormat(this, "format");
+
+    var internals = getNumberFormatInternals(nf, "format");
+
+    // Step 4.
     if (internals.boundFormat === undefined) {
-        // Step 1.a.
+        // Step 4.a.
         var F = numberFormatFormatToBind;
 
-        // Step 1.b-d.
-        var bf = callFunction(FunctionBind, F, this);
+        // Steps 4.b-d.
+        var bf = callFunction(FunctionBind, F, nf);
         internals.boundFormat = bf;
     }
-    // Step 2.
+
+    // Step 5.
     return internals.boundFormat;
 }
 _SetCanonicalName(Intl_NumberFormat_format_get, "get format");
 
 
 function Intl_NumberFormat_formatToParts(value) {
-    // Step 1.
-    var nf = this;
-
-    // Steps 2-3.
+    // Steps 1-3.
+    var nf = UnwrapNumberFormat(this, "formatToParts");
+
+    // Ensure the NumberFormat internals are resolved.
     getNumberFormatInternals(nf, "formatToParts");
 
     // Step 4.
     var x = ToNumber(value);
 
     // Step 5.
     return intl_FormatNumber(nf, x, /* formatToParts = */ true);
 }
 
 
 /**
  * Returns the resolved options for a NumberFormat object.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4.
  */
 function Intl_NumberFormat_resolvedOptions() {
-    // Check "this NumberFormat object" per introduction of section 11.3.
-    var internals = getNumberFormatInternals(this, "resolvedOptions");
+    // Invoke |UnwrapNumberFormat| per introduction of section 11.3.
+    var nf = UnwrapNumberFormat(this, "resolvedOptions");
+
+    var internals = getNumberFormatInternals(nf, "resolvedOptions");
 
     var result = {
         locale: internals.locale,
         numberingSystem: internals.numberingSystem,
         style: internals.style,
         minimumIntegerDigits: internals.minimumIntegerDigits,
         minimumFractionDigits: internals.minimumFractionDigits,
         maximumFractionDigits: internals.maximumFractionDigits,
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -24,16 +24,17 @@
     _(AtomicsAdd)                   \
     _(AtomicsSub)                   \
     _(AtomicsAnd)                   \
     _(AtomicsOr)                    \
     _(AtomicsXor)                   \
     _(AtomicsIsLockFree)            \
                                     \
     _(IntlIsCollator)               \
+    _(IntlIsNumberFormat)           \
     _(IntlIsPluralRules)            \
                                     \
     _(MathAbs)                      \
     _(MathFloor)                    \
     _(MathCeil)                     \
     _(MathRound)                    \
     _(MathClz32)                    \
     _(MathSqrt)                     \
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -106,16 +106,18 @@ IonBuilder::inlineNativeCall(CallInfo& c
       case InlinableNative::AtomicsXor:
         return inlineAtomicsBinop(callInfo, inlNative);
       case InlinableNative::AtomicsIsLockFree:
         return inlineAtomicsIsLockFree(callInfo);
 
       // Intl natives.
       case InlinableNative::IntlIsCollator:
         return inlineHasClass(callInfo, &CollatorObject::class_);
+      case InlinableNative::IntlIsNumberFormat:
+        return inlineHasClass(callInfo, &NumberFormatObject::class_);
       case InlinableNative::IntlIsPluralRules:
         return inlineHasClass(callInfo, &PluralRulesObject::class_);
 
       // Math natives.
       case InlinableNative::MathAbs:
         return inlineMathAbs(callInfo);
       case InlinableNative::MathFloor:
         return inlineMathFloor(callInfo);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/NumberFormat/call.js
@@ -0,0 +1,167 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+function IsConstructor(o) {
+  try {
+    new (new Proxy(o, {construct: () => ({})}));
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+function IsObject(o) {
+    return Object(o) === o;
+}
+
+function thisValues() {
+    const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
+
+    return [
+        // Primitive values.
+        ...[undefined, null, true, "abc", Symbol(), 123],
+
+        // Object values.
+        ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+        // Intl objects.
+        ...[].concat(...intlConstructors.map(ctor => [
+            // Instance of an Intl constructor.
+            new ctor(),
+
+            // Instance of a subclassed Intl constructor.
+            new class extends ctor {},
+
+            // Object inheriting from an Intl constructor prototype.
+            Object.create(ctor.prototype),
+
+            // Intl object not inheriting from its default prototype.
+            Object.setPrototypeOf(new ctor(), Object.prototype),
+        ])),
+    ];
+}
+
+const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
+
+// Invoking [[Call]] for Intl.NumberFormat returns a new instance unless called
+// with an instance inheriting from Intl.NumberFormat.prototype.
+for (let thisValue of thisValues()) {
+    let obj = Intl.NumberFormat.call(thisValue);
+
+    if (!Intl.NumberFormat.prototype.isPrototypeOf(thisValue)) {
+        assertEq(Object.is(obj, thisValue), false);
+        assertEq(obj instanceof Intl.NumberFormat, true);
+        if (IsObject(thisValue))
+            assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+    } else {
+        assertEq(Object.is(obj, thisValue), true);
+        assertEq(obj instanceof Intl.NumberFormat, true);
+        assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+    }
+}
+
+// Intl.NumberFormat uses the legacy Intl constructor compromise semantics.
+// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns true.
+for (let thisValue of thisValues()) {
+    let hasInstanceCalled = false;
+    Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+        value() {
+            assertEq(hasInstanceCalled, false);
+            hasInstanceCalled = true;
+            return true;
+        }, configurable: true
+    });
+    if (!IsObject(thisValue)) {
+        // A TypeError is thrown when Intl.NumberFormat tries to install the
+        // [[FallbackSymbol]] property on |thisValue|.
+        assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
+        delete Intl.NumberFormat[Symbol.hasInstance];
+    } else {
+        let obj = Intl.NumberFormat.call(thisValue);
+        delete Intl.NumberFormat[Symbol.hasInstance];
+        assertEq(Object.is(obj, thisValue), true);
+        assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+    }
+    assertEq(hasInstanceCalled, true);
+}
+// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns false.
+for (let thisValue of thisValues()) {
+    let hasInstanceCalled = false;
+    Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+        value() {
+            assertEq(hasInstanceCalled, false);
+            hasInstanceCalled = true;
+            return false;
+        }, configurable: true
+    });
+    let obj = Intl.NumberFormat.call(thisValue);
+    delete Intl.NumberFormat[Symbol.hasInstance];
+    assertEq(Object.is(obj, thisValue), false);
+    assertEq(obj instanceof Intl.NumberFormat, true);
+    if (IsObject(thisValue))
+        assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+    assertEq(hasInstanceCalled, true);
+}
+
+// Throws an error when attempting to install [[FallbackSymbol]] twice.
+{
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+
+    assertEq(Intl.NumberFormat.call(thisValue), thisValue);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+
+    assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+
+// Throws an error when the thisValue is non-extensible.
+{
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    Object.preventExtensions(thisValue);
+
+    assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+// [[FallbackSymbol]] is installed as a frozen property holding an Intl.NumberFormat instance.
+{
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    Intl.NumberFormat.call(thisValue);
+
+    let desc = Object.getOwnPropertyDescriptor(thisValue, intlFallbackSymbol);
+    assertEq(desc !== undefined, true);
+    assertEq(desc.writable, false);
+    assertEq(desc.enumerable, false);
+    assertEq(desc.configurable, false);
+    assertEq(desc.value instanceof Intl.NumberFormat, true);
+}
+
+// Ensure [[FallbackSymbol]] is installed last by changing the [[Prototype]]
+// during initialization.
+{
+    let thisValue = {};
+    let options = {
+        get useGrouping() {
+            Object.setPrototypeOf(thisValue, Intl.NumberFormat.prototype);
+            return false;
+        }
+    };
+    let obj = Intl.NumberFormat.call(thisValue, undefined, options);
+    assertEq(Object.is(obj, thisValue), true);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+{
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    let options = {
+        get useGrouping() {
+            Object.setPrototypeOf(thisValue, Object.prototype);
+            return false;
+        }
+    };
+    let obj = Intl.NumberFormat.call(thisValue, undefined, options);
+    assertEq(Object.is(obj, thisValue), false);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/NumberFormat/unwrapping.js
@@ -0,0 +1,226 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Test UnwrapNumberFormat operation.
+
+const numberFormatFunctions = [];
+numberFormatFunctions.push(Intl.NumberFormat.prototype.resolvedOptions);
+numberFormatFunctions.push(Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get);
+// "formatToParts" isn't yet enabled by default.
+if ("formatToParts" in Intl.NumberFormat.prototype)
+    numberFormatFunctions.push(Intl.NumberFormat.prototype.formatToParts);
+
+function IsConstructor(o) {
+  try {
+    new (new Proxy(o, {construct: () => ({})}));
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+function IsObject(o) {
+    return Object(o) === o;
+}
+
+function intlObjects(ctor) {
+    return [
+        // Instance of an Intl constructor.
+        new ctor(),
+
+        // Instance of a subclassed Intl constructor.
+        new class extends ctor {},
+
+        // Intl object not inheriting from its default prototype.
+        Object.setPrototypeOf(new ctor(), Object.prototype),
+    ];
+}
+
+function thisValues(C) {
+    const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
+
+    return [
+        // Primitive values.
+        ...[undefined, null, true, "abc", Symbol(), 123],
+
+        // Object values.
+        ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+        // Intl objects.
+        ...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)),
+
+        // Object inheriting from an Intl constructor prototype.
+        ...intlConstructors.map(ctor => Object.create(ctor.prototype)),
+    ];
+}
+
+const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
+
+// Test Intl.NumberFormat.prototype methods.
+for (let numberFormatFunction of numberFormatFunctions) {
+    // Test a TypeError is thrown when the this-value isn't an initialized
+    // Intl.NumberFormat instance.
+    for (let thisValue of thisValues(Intl.NumberFormat)) {
+        assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
+    }
+
+    // And test no error is thrown for initialized Intl.NumberFormat instances.
+    for (let thisValue of intlObjects(Intl.NumberFormat)) {
+        numberFormatFunction.call(thisValue);
+    }
+
+    // Manually add [[FallbackSymbol]] to objects and then repeat the tests from above.
+    for (let thisValue of thisValues(Intl.NumberFormat)) {
+        assertThrowsInstanceOf(() => numberFormatFunction.call({
+            __proto__: Intl.NumberFormat.prototype,
+            [intlFallbackSymbol]: thisValue,
+        }), TypeError);
+    }
+
+    for (let thisValue of intlObjects(Intl.NumberFormat)) {
+        numberFormatFunction.call({
+            __proto__: Intl.NumberFormat.prototype,
+            [intlFallbackSymbol]: thisValue,
+        });
+    }
+
+    // Ensure [[FallbackSymbol]] isn't retrieved for Intl.NumberFormat instances.
+    for (let thisValue of intlObjects(Intl.NumberFormat)) {
+        Object.defineProperty(thisValue, intlFallbackSymbol, {
+            get() { assertEq(false, true); }
+        });
+        numberFormatFunction.call(thisValue);
+    }
+
+    // Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.NumberFormat.prototype.
+    for (let thisValue of thisValues(Intl.NumberFormat)) {
+        if (!IsObject(thisValue) || Intl.NumberFormat.prototype.isPrototypeOf(thisValue))
+            continue;
+        Object.defineProperty(thisValue, intlFallbackSymbol, {
+            get() { assertEq(false, true); }
+        });
+        assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
+    }
+
+    // Repeat the test from above, but also change Intl.NumberFormat[@@hasInstance]
+    // so it always returns |null|.
+    for (let thisValue of thisValues(Intl.NumberFormat)) {
+        let hasInstanceCalled = false, symbolGetterCalled = false;
+        Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+            value() {
+                assertEq(hasInstanceCalled, false);
+                hasInstanceCalled = true;
+                return true;
+            }, configurable: true
+        });
+        let isUndefinedOrNull = thisValue !== undefined || thisValue !== null;
+        let symbolHolder;
+        if (!isUndefinedOrNull) {
+            symbolHolder = IsObject(thisValue) ? thisValue : Object.getPrototypeOf(thisValue);
+            Object.defineProperty(symbolHolder, intlFallbackSymbol, {
+                get() {
+                    assertEq(symbolGetterCalled, false);
+                    symbolGetterCalled = true;
+                    return null;
+                }, configurable: true
+            });
+        }
+
+        assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
+
+        delete Intl.NumberFormat[Symbol.hasInstance];
+        if (!isUndefinedOrNull && !IsObject(thisValue))
+            delete symbolHolder[intlFallbackSymbol];
+
+        assertEq(hasInstanceCalled, true);
+        assertEq(symbolGetterCalled, !isUndefinedOrNull);
+    }
+}
+
+// Test format() returns the correct result for objects initialized as Intl.NumberFormat instances.
+{
+    // An actual Intl.NumberFormat instance.
+    let numberFormat = new Intl.NumberFormat();
+
+    // An object initialized as a NumberFormat instance.
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    Intl.NumberFormat.call(thisValue);
+
+    // Object with [[FallbackSymbol]] set to NumberFormat instance.
+    let fakeObj = {
+        __proto__: Intl.NumberFormat.prototype,
+        [intlFallbackSymbol]: numberFormat,
+    };
+
+    for (let number of [0, 1, 1.5, Infinity, NaN]) {
+        let expected = numberFormat.format(number);
+        assertEq(thisValue.format(number), expected);
+        assertEq(thisValue[intlFallbackSymbol].format(number), expected);
+        assertEq(fakeObj.format(number), expected);
+    }
+}
+
+// Test formatToParts() returns the correct result for objects initialized as Intl.NumberFormat instances.
+if ("formatToParts" in Intl.NumberFormat.prototype) {
+    // An actual Intl.NumberFormat instance.
+    let numberFormat = new Intl.NumberFormat();
+
+    // An object initialized as a NumberFormat instance.
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    Intl.NumberFormat.call(thisValue);
+
+    // Object with [[FallbackSymbol]] set to NumberFormat instance.
+    let fakeObj = {
+        __proto__: Intl.NumberFormat.prototype,
+        [intlFallbackSymbol]: numberFormat,
+    };
+
+    function assertEqParts(actual, expected) {
+        assertEq(actual.length, expected.length, "parts count mismatch");
+        for (var i = 0; i < expected.length; i++) {
+            assertEq(actual[i].type, expected[i].type, "type mismatch at " + i);
+            assertEq(actual[i].value, expected[i].value, "value mismatch at " + i);
+        }
+    }
+
+    for (let number of [0, 1, 1.5, Infinity, NaN]) {
+        let expected = numberFormat.formatToParts(number);
+        assertEqParts(thisValue.formatToParts(number), expected);
+        assertEqParts(thisValue[intlFallbackSymbol].formatToParts(number), expected);
+        assertEqParts(fakeObj.formatToParts(number), expected);
+    }
+}
+
+// Test resolvedOptions() returns the same results.
+{
+    // An actual Intl.NumberFormat instance.
+    let numberFormat = new Intl.NumberFormat();
+
+    // An object initialized as a NumberFormat instance.
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    Intl.NumberFormat.call(thisValue);
+
+    // Object with [[FallbackSymbol]] set to NumberFormat instance.
+    let fakeObj = {
+        __proto__: Intl.NumberFormat.prototype,
+        [intlFallbackSymbol]: numberFormat,
+    };
+
+    function assertEqOptions(actual, expected) {
+        actual = Object.entries(actual);
+        expected = Object.entries(expected);
+
+        assertEq(actual.length, expected.length, "options count mismatch");
+        for (var i = 0; i < expected.length; i++) {
+            assertEq(actual[i][0], expected[i][0], "key mismatch at " + i);
+            assertEq(actual[i][1], expected[i][1], "value mismatch at " + i);
+        }
+    }
+
+    let expected = numberFormat.resolvedOptions();
+    assertEqOptions(thisValue.resolvedOptions(), expected);
+    assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected);
+    assertEqOptions(fakeObj.resolvedOptions(), expected);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -55,16 +55,17 @@ skip script test262/ch10/10.6/10.6-14-b-
 skip script test262/ch10/10.6/10.6-14-b-4-s.js
 skip script test262/ch10/10.6/10.6-13-b-1-s.js
 skip script test262/ch10/10.6/10.6-13-b-2-s.js
 
 # ES2017 Intl legacy constructor semantics changes made these tests invalid
 # (bug 1328386).
 skip script test262/intl402/ch10/10.1/10.1.1_1.js
 skip script test262/intl402/ch10/10.1/10.1.2_a.js
+skip script test262/intl402/ch11/11.1/11.1.1_1.js
 
 #######################################################################
 # Tests disabled due to jstest limitations wrt imported test262 tests #
 #######################################################################
 
 # These tests are disabled because jstest doesn't understand @negative (without
 # a pattern) yet.
 skip script test262/ch07/7.2/S7.2_A5_T1.js
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -96,16 +96,17 @@ class GlobalObject : public NativeObject
         STAR_GENERATOR_OBJECT_PROTO,
         STAR_GENERATOR_FUNCTION_PROTO,
         STAR_GENERATOR_FUNCTION,
         ASYNC_FUNCTION_PROTO,
         ASYNC_FUNCTION,
         MAP_ITERATOR_PROTO,
         SET_ITERATOR_PROTO,
         COLLATOR_PROTO,
+        NUMBER_FORMAT,
         NUMBER_FORMAT_PROTO,
         DATE_TIME_FORMAT_PROTO,
         PLURAL_RULES_PROTO,
         MODULE_PROTO,
         IMPORT_ENTRY_PROTO,
         EXPORT_ENTRY_PROTO,
         REGEXP_STATICS,
         WARNED_ONCE_FLAGS,
@@ -478,16 +479,22 @@ class GlobalObject : public NativeObject
         return &getPrototype(JSProto_Iterator).toObject();
     }
 
     static JSObject*
     getOrCreateCollatorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, COLLATOR_PROTO, initIntlObject);
     }
 
+    static JSFunction*
+    getOrCreateNumberFormatConstructor(JSContext* cx, Handle<GlobalObject*> global) {
+        JSObject* obj = getOrCreateObject(cx, global, NUMBER_FORMAT, initIntlObject);
+        return obj ? &obj->as<JSFunction>() : nullptr;
+    }
+
     static JSObject*
     getOrCreateNumberFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, NUMBER_FORMAT_PROTO, initIntlObject);
     }
 
     static JSObject*
     getOrCreateDateTimeFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, DATE_TIME_FORMAT_PROTO, initIntlObject);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1851,16 +1851,33 @@ intrinsic_RuntimeDefaultLocale(JSContext
     RootedString jslocale(cx, JS_NewStringCopyZ(cx, locale));
     if (!jslocale)
         return false;
 
     args.rval().setString(jslocale);
     return true;
 }
 
+using GetOrCreateIntlConstructor = JSFunction* (*)(JSContext*, Handle<GlobalObject*>);
+
+template <GetOrCreateIntlConstructor getOrCreateIntlConstructor>
+static bool
+intrinsic_GetBuiltinIntlConstructor(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 0);
+
+    JSFunction* constructor = getOrCreateIntlConstructor(cx, cx->global());
+    if (!constructor)
+        return false;
+
+    args.rval().setObject(*constructor);
+    return true;
+}
+
 static bool
 intrinsic_AddContentTelemetry(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
 
     int id = args[0].toInt32();
     MOZ_ASSERT(id < JS_TELEMETRY_END);
@@ -2480,19 +2497,25 @@ static const JSFunctionSpec intrinsic_fu
     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("IsCollator",
                     intrinsic_IsInstanceOfBuiltin<CollatorObject>, 1,0,
                     IntlIsCollator),
+    JS_INLINABLE_FN("IsNumberFormat",
+                    intrinsic_IsInstanceOfBuiltin<NumberFormatObject>, 1,0,
+                    IntlIsNumberFormat),
     JS_INLINABLE_FN("IsPluralRules",
                     intrinsic_IsInstanceOfBuiltin<PluralRulesObject>, 1,0,
                     IntlIsPluralRules),
+    JS_FN("GetNumberFormatConstructor",
+          intrinsic_GetBuiltinIntlConstructor<GlobalObject::getOrCreateNumberFormatConstructor>,
+          0,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),