Bug 1328386 - Part 7: Implement legacy constructor semantics for Intl.DateTimeFormat per ECMA-402, 4th edition. r=Waldo
authorAndré Bargull <andre.bargull@gmail.com>
Mon, 23 Jan 2017 08:33:47 -0800
changeset 331144 60adc4589abc3de850e379496a143dc6dc208cac
parent 331143 91080dad0ff8d9211948e470500a12cbb3bcf515
child 331145 e15e0f265264c7ec5bdcb643f75cacbbc29f16ba
push id31261
push usercbook@mozilla.com
push dateThu, 26 Jan 2017 11:32:02 +0000
treeherdermozilla-central@a338e596b1d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1328386
milestone54.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 1328386 - Part 7: Implement legacy constructor semantics for Intl.DateTimeFormat 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/DateTimeFormat/call.js
js/src/tests/Intl/DateTimeFormat/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 + 40)
+    (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 41)
 #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
@@ -2404,74 +2404,44 @@ static const JSPropertySpec dateTimeForm
 /**
  * 12.2.1 Intl.DateTimeFormat([ locales [, options]])
  *
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool
 DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct)
 {
-    RootedObject obj(cx);
-
-    // We're following ECMA-402 1st Edition when DateTimeFormat is called
-    // because of backward compatibility issues.
-    // See https://github.com/tc39/ecma402/issues/57
-    if (!construct) {
-        // ES Intl 1st ed., 12.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::getOrCreateDateTimeFormatPrototype(cx, cx->global());
+        if (!proto)
             return false;
-        RootedValue self(cx, args.thisv());
-        if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
-            // ES Intl 1st ed., 12.1.2.1 step 4
-            obj = ToObject(cx, self);
-            if (!obj)
-                return false;
-
-            // ES Intl 1st ed., 12.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., 12.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::getOrCreateDateTimeFormatPrototype(cx, cx->global());
-            if (!proto)
-                return false;
-        }
-
-        obj = NewObjectWithGivenProto<DateTimeFormatObject>(cx, proto);
-        if (!obj)
-            return false;
-
-        obj->as<DateTimeFormatObject>().setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT,
-                                                        PrivateValue(nullptr));
-    }
-
+    Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
+    dateTimeFormat = NewObjectWithGivenProto<DateTimeFormatObject>(cx, proto);
+    if (!dateTimeFormat)
+        return false;
+
+    dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT,
+                                    PrivateValue(nullptr));
+
+    RootedValue thisValue(cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv());
     RootedValue locales(cx, args.get(0));
     RootedValue options(cx, args.get(1));
 
     // Step 3.
-    if (!IntlInitialize(cx, obj, cx->names().InitializeDateTimeFormat, locales, options))
-        return false;
-
-    args.rval().setObject(*obj);
-    return true;
+    return LegacyIntlInitialize(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat,
+                                thisValue, locales, options, args.rval());
 }
 
 static bool
 DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return DateTimeFormat(cx, args, args.isConstructing());
 }
@@ -2501,17 +2471,18 @@ DateTimeFormatObject::finalize(FreeOp* f
         obj->as<DateTimeFormatObject>().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT);
     if (!slot.isUndefined()) {
         if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate()))
             udat_close(df);
     }
 }
 
 static JSObject*
-CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
+                              MutableHandleObject constructor)
 {
     RootedFunction ctor(cx);
     ctor = GlobalObject::createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
     if (!ctor)
         return nullptr;
 
     Rooted<DateTimeFormatObject*> proto(cx);
     proto = GlobalObject::createBlankPrototype<DateTimeFormatObject>(cx, global);
@@ -2534,27 +2505,30 @@ CreateDateTimeFormatPrototype(JSContext*
     if (!JS_DefineProperties(cx, proto, dateTimeFormat_properties))
         return nullptr;
 
     RootedValue options(cx);
     if (!CreateDefaultOptions(cx, &options))
         return nullptr;
 
     // 12.2.1 and 12.3
-    if (!IntlInitialize(cx, proto, cx->names().InitializeDateTimeFormat, UndefinedHandleValue,
-                        options))
+    RootedValue thisOrResult(cx, ObjectValue(*proto));
+    if (!LegacyIntlInitialize(cx, proto, cx->names().InitializeDateTimeFormat, thisOrResult,
+                              UndefinedHandleValue, options, &thisOrResult))
     {
         return nullptr;
     }
+    MOZ_ASSERT(&thisOrResult.toObject() == proto);
 
     // 8.1
     RootedValue ctorValue(cx, ObjectValue(*ctor));
     if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0))
         return nullptr;
 
+    constructor.set(ctor);
     return proto;
 }
 
 bool
 js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
@@ -3124,17 +3098,17 @@ js::intl_patternForSkeleton(JSContext* c
     return true;
 }
 
 /**
  * Returns a new UDateFormat with the locale and date-time formatting options
  * of the given DateTimeFormat.
  */
 static UDateFormat*
-NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat)
+NewUDateFormat(JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat)
 {
     RootedValue value(cx);
 
     RootedObject internals(cx, GetInternals(cx, dateTimeFormat));
     if (!internals)
        return nullptr;
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
@@ -3433,55 +3407,34 @@ bool
 js::intl_FormatDateTime(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 dateTimeFormat(cx, &args[0].toObject());
-
-    // Obtain a UDateFormat object, cached if possible.
-    bool isDateTimeFormatInstance = dateTimeFormat->is<DateTimeFormatObject>();
-    UDateFormat* df;
-    if (isDateTimeFormatInstance) {
-        void* priv =
-            dateTimeFormat->as<DateTimeFormatObject>().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT)
-                                                      .toPrivate();
-        df = static_cast<UDateFormat*>(priv);
-        if (!df) {
-            df = NewUDateFormat(cx, dateTimeFormat);
-            if (!df)
-                return false;
-            dateTimeFormat->as<DateTimeFormatObject>().setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT,
-                                                                       PrivateValue(df));
-        }
-    } else {
-        // There's no good place to cache the ICU date-time format for an object
-        // that has been initialized as a DateTimeFormat but is not a
-        // DateTimeFormat instance. One possibility might be to add a
-        // DateTimeFormat instance as an internal property to each such object.
+    Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
+    dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>();
+
+    // Obtain a cached UDateFormat object.
+    void* priv =
+        dateTimeFormat->getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT).toPrivate();
+    UDateFormat* df = static_cast<UDateFormat*>(priv);
+    if (!df) {
         df = NewUDateFormat(cx, dateTimeFormat);
         if (!df)
             return false;
+        dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(df));
     }
 
     // Use the UDateFormat to actually format the time stamp.
-    RootedValue result(cx);
-    bool success = args[2].toBoolean()
-                   ? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), &result)
-                   : intl_FormatDateTime(cx, df, args[1].toNumber(), &result);
-
-    if (!isDateTimeFormatInstance)
-        udat_close(df);
-    if (!success)
-        return false;
-    args.rval().set(result);
-    return true;
+    return args[2].toBoolean()
+           ? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), args.rval())
+           : intl_FormatDateTime(cx, df, args[1].toNumber(), args.rval());
 }
 
 
 /**************** PluralRules *****************/
 
 const ClassOps PluralRulesObject::classOps_ = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
@@ -4339,17 +4292,18 @@ GlobalObject::initIntlObject(JSContext* 
     if (!JS_DefineFunctions(cx, intl, intl_static_methods))
         return false;
 
     // 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));
+    RootedObject dateTimeFormatProto(cx), dateTimeFormat(cx);
+    dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat);
     if (!dateTimeFormatProto)
         return false;
     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.
@@ -4365,16 +4319,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, ObjectValue(*dateTimeFormat));
     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|.
     //
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -2310,52 +2310,78 @@ function resolveDateTimeFormatInternals(
 
     // The caller is responsible for associating |internalProps| with the right
     // object using |setInternalProperties|.
     return internalProps;
 }
 
 
 /**
- * Returns an object containing the DateTimeFormat internal properties of |obj|,
- * or throws a TypeError if |obj| isn't DateTimeFormat-initialized.
+ * Returns an object containing the DateTimeFormat internal properties of |obj|.
  */
 function getDateTimeFormatInternals(obj, methodName) {
+    assert(IsObject(obj), "getDateTimeFormatInternals called with non-object");
+    assert(IsDateTimeFormat(obj), "getDateTimeFormatInternals called with non-DateTimeFormat");
+
     var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName);
     assert(internals.type === "DateTimeFormat", "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 = resolveDateTimeFormatInternals(internals.lazyData);
     setInternalProperties(internals, internalProps);
     return internalProps;
 }
 
 
 /**
+ * UnwrapDateTimeFormat(dtf)
+ */
+function UnwrapDateTimeFormat(dtf, methodName) {
+    // Step 1.
+    if ((!IsObject(dtf) || !IsDateTimeFormat(dtf)) &&
+        dtf instanceof GetDateTimeFormatConstructor())
+    {
+        dtf = dtf[intlFallbackSymbol()];
+    }
+
+    // Step 2.
+    if (!IsObject(dtf) || !IsDateTimeFormat(dtf)) {
+        ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "DateTimeFormat", methodName,
+                       "DateTimeFormat");
+    }
+
+    // Step 3.
+    return dtf;
+}
+
+
+/**
  * Initializes an object as a DateTimeFormat.
  *
  * 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 DateTimeFormat.
  * This later work occurs in |resolveDateTimeFormatInternals|; steps not noted
  * here occur there.
  *
  * Spec: ECMAScript Internationalization API Specification, 12.1.1.
  */
-function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
-    assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat");
-
-    // Step 1.
-    if (isInitializedIntlObject(dateTimeFormat))
-        ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options) {
+    assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat called with non-Object");
+    assert(IsDateTimeFormat(dateTimeFormat),
+           "InitializeDateTimeFormat called with non-DateTimeFormat");
+
+    // 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(dateTimeFormat), "dateTimeFormat mustn't be initialized");
 
     // Step 2.
     var internals = initializeIntlObject(dateTimeFormat);
 
     // Lazy DateTimeFormat data has the following structure:
     //
     //   {
     //     requestedLocales: List of locales,
@@ -2460,16 +2486,28 @@ function InitializeDateTimeFormat(dateTi
     if (hr12 !== undefined)
         formatOpt.hour12 = hr12;
 
     // Step 31.
     //
     // We've done everything that must be done now: mark the lazy data as fully
     // computed and install it.
     setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData);
+
+    if (dateTimeFormat !== thisValue && thisValue instanceof GetDateTimeFormatConstructor()) {
+        if (!IsObject(thisValue))
+            ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue);
+
+        _DefineDataProperty(thisValue, intlFallbackSymbol(), dateTimeFormat,
+                            ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
+
+        return thisValue;
+    }
+
+    return dateTimeFormat;
 }
 
 
 // Intl.DateTimeFormat and ICU skeletons and patterns
 // ==================================================
 //
 // Different locales have different ways to display dates using the same
 // basic components. For example, en-US might use "Sept. 24, 2012" while
@@ -2869,56 +2907,63 @@ function dateTimeFormatFormatToBind() {
 /**
  * Returns a function bound to this DateTimeFormat that returns a String value
  * representing the result of calling ToNumber(date) according to the
  * effective locale and the formatting options of this DateTimeFormat.
  *
  * Spec: ECMAScript Internationalization API Specification, 12.3.2.
  */
 function Intl_DateTimeFormat_format_get() {
-    // Check "this DateTimeFormat object" per introduction of section 12.3.
-    var internals = getDateTimeFormatInternals(this, "format");
-
-    // Step 1.
+    // Steps 1-3.
+    var dtf = UnwrapDateTimeFormat(this, "format");
+
+    var internals = getDateTimeFormatInternals(dtf, "format");
+
+    // Step 4.
     if (internals.boundFormat === undefined) {
-        // Step 1.a.
+        // Step 4.a.
         var F = dateTimeFormatFormatToBind;
 
-        // Step 1.b-d.
-        var bf = callFunction(FunctionBind, F, this);
+        // Steps 4.b-d.
+        var bf = callFunction(FunctionBind, F, dtf);
         internals.boundFormat = bf;
     }
 
-    // Step 2.
+    // Step 5.
     return internals.boundFormat;
 }
 _SetCanonicalName(Intl_DateTimeFormat_format_get, "get format");
 
 
 function Intl_DateTimeFormat_formatToParts() {
-    // Check "this DateTimeFormat object" per introduction of section 12.3.
-    getDateTimeFormatInternals(this, "formatToParts");
-
-    // Steps 1.a.i-ii
+    // Steps 1-3.
+    var dtf = UnwrapDateTimeFormat(this, "formatToParts");
+
+    // Ensure the DateTimeFormat internals are resolved.
+    getDateTimeFormatInternals(dtf, "formatToParts");
+
+    // Steps 4-5.
     var date = arguments.length > 0 ? arguments[0] : undefined;
     var x = (date === undefined) ? std_Date_now() : ToNumber(date);
 
-    // Step 1.a.iii.
-    return intl_FormatDateTime(this, x, /* formatToParts = */ true);
+    // Step 6.
+    return intl_FormatDateTime(dtf, x, /* formatToParts = */ true);
 }
 
 
 /**
  * Returns the resolved options for a DateTimeFormat object.
  *
  * Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4.
  */
 function Intl_DateTimeFormat_resolvedOptions() {
-    // Check "this DateTimeFormat object" per introduction of section 12.3.
-    var internals = getDateTimeFormatInternals(this, "resolvedOptions");
+    // Invoke |UnwrapDateTimeFormat| per introduction of section 12.3.
+    var dtf = UnwrapDateTimeFormat(this, "resolvedOptions");
+
+    var internals = getDateTimeFormatInternals(dtf, "resolvedOptions");
 
     var result = {
         locale: internals.locale,
         calendar: internals.calendar,
         numberingSystem: internals.numberingSystem,
         timeZone: internals.timeZone
     };
     resolveICUPattern(internals.pattern, result);
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -24,16 +24,17 @@
     _(AtomicsAdd)                   \
     _(AtomicsSub)                   \
     _(AtomicsAnd)                   \
     _(AtomicsOr)                    \
     _(AtomicsXor)                   \
     _(AtomicsIsLockFree)            \
                                     \
     _(IntlIsCollator)               \
+    _(IntlIsDateTimeFormat)         \
     _(IntlIsNumberFormat)           \
     _(IntlIsPluralRules)            \
                                     \
     _(MathAbs)                      \
     _(MathFloor)                    \
     _(MathCeil)                     \
     _(MathRound)                    \
     _(MathClz32)                    \
--- 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::IntlIsDateTimeFormat:
+        return inlineHasClass(callInfo, &DateTimeFormatObject::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);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/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.DateTimeFormat.call(Object.create(Intl.DateTimeFormat.prototype)))[0];
+
+// Invoking [[Call]] for Intl.DateTimeFormat returns a new instance unless called
+// with an instance inheriting from Intl.DateTimeFormat.prototype.
+for (let thisValue of thisValues()) {
+    let obj = Intl.DateTimeFormat.call(thisValue);
+
+    if (!Intl.DateTimeFormat.prototype.isPrototypeOf(thisValue)) {
+        assertEq(Object.is(obj, thisValue), false);
+        assertEq(obj instanceof Intl.DateTimeFormat, true);
+        if (IsObject(thisValue))
+            assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+    } else {
+        assertEq(Object.is(obj, thisValue), true);
+        assertEq(obj instanceof Intl.DateTimeFormat, true);
+        assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+    }
+}
+
+// Intl.DateTimeFormat uses the legacy Intl constructor compromise semantics.
+// - Test when InstanceofOperator(thisValue, %DateTimeFormat%) returns true.
+for (let thisValue of thisValues()) {
+    let hasInstanceCalled = false;
+    Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
+        value() {
+            assertEq(hasInstanceCalled, false);
+            hasInstanceCalled = true;
+            return true;
+        }, configurable: true
+    });
+    if (!IsObject(thisValue)) {
+        // A TypeError is thrown when Intl.DateTimeFormat tries to install the
+        // [[FallbackSymbol]] property on |thisValue|.
+        assertThrowsInstanceOf(() => Intl.DateTimeFormat.call(thisValue), TypeError);
+        delete Intl.DateTimeFormat[Symbol.hasInstance];
+    } else {
+        let obj = Intl.DateTimeFormat.call(thisValue);
+        delete Intl.DateTimeFormat[Symbol.hasInstance];
+        assertEq(Object.is(obj, thisValue), true);
+        assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+    }
+    assertEq(hasInstanceCalled, true);
+}
+// - Test when InstanceofOperator(thisValue, %DateTimeFormat%) returns false.
+for (let thisValue of thisValues()) {
+    let hasInstanceCalled = false;
+    Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
+        value() {
+            assertEq(hasInstanceCalled, false);
+            hasInstanceCalled = true;
+            return false;
+        }, configurable: true
+    });
+    let obj = Intl.DateTimeFormat.call(thisValue);
+    delete Intl.DateTimeFormat[Symbol.hasInstance];
+    assertEq(Object.is(obj, thisValue), false);
+    assertEq(obj instanceof Intl.DateTimeFormat, 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.DateTimeFormat.prototype);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+
+    assertEq(Intl.DateTimeFormat.call(thisValue), thisValue);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+
+    assertThrowsInstanceOf(() => Intl.DateTimeFormat.call(thisValue), TypeError);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+
+// Throws an error when the thisValue is non-extensible.
+{
+    let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+    Object.preventExtensions(thisValue);
+
+    assertThrowsInstanceOf(() => Intl.DateTimeFormat.call(thisValue), TypeError);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+// [[FallbackSymbol]] is installed as a frozen property holding an Intl.DateTimeFormat instance.
+{
+    let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+    Intl.DateTimeFormat.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.DateTimeFormat, true);
+}
+
+// Ensure [[FallbackSymbol]] is installed last by changing the [[Prototype]]
+// during initialization.
+{
+    let thisValue = {};
+    let options = {
+        get hour12() {
+            Object.setPrototypeOf(thisValue, Intl.DateTimeFormat.prototype);
+            return false;
+        }
+    };
+    let obj = Intl.DateTimeFormat.call(thisValue, undefined, options);
+    assertEq(Object.is(obj, thisValue), true);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+{
+    let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+    let options = {
+        get hour12() {
+            Object.setPrototypeOf(thisValue, Object.prototype);
+            return false;
+        }
+    };
+    let obj = Intl.DateTimeFormat.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/DateTimeFormat/unwrapping.js
@@ -0,0 +1,224 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Test UnwrapDateTimeFormat operation.
+
+const dateTimeFormatFunctions = [];
+dateTimeFormatFunctions.push(Intl.DateTimeFormat.prototype.resolvedOptions);
+dateTimeFormatFunctions.push(Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format").get);
+dateTimeFormatFunctions.push(Intl.DateTimeFormat.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.DateTimeFormat.call(Object.create(Intl.DateTimeFormat.prototype)))[0];
+
+// Test Intl.DateTimeFormat.prototype methods.
+for (let dateTimeFormatFunction of dateTimeFormatFunctions) {
+    // Test a TypeError is thrown when the this-value isn't an initialized
+    // Intl.DateTimeFormat instance.
+    for (let thisValue of thisValues(Intl.DateTimeFormat)) {
+        assertThrowsInstanceOf(() => dateTimeFormatFunction.call(thisValue), TypeError);
+    }
+
+    // And test no error is thrown for initialized Intl.DateTimeFormat instances.
+    for (let thisValue of intlObjects(Intl.DateTimeFormat)) {
+        dateTimeFormatFunction.call(thisValue);
+    }
+
+    // Manually add [[FallbackSymbol]] to objects and then repeat the tests from above.
+    for (let thisValue of thisValues(Intl.DateTimeFormat)) {
+        assertThrowsInstanceOf(() => dateTimeFormatFunction.call({
+            __proto__: Intl.DateTimeFormat.prototype,
+            [intlFallbackSymbol]: thisValue,
+        }), TypeError);
+    }
+
+    for (let thisValue of intlObjects(Intl.DateTimeFormat)) {
+        dateTimeFormatFunction.call({
+            __proto__: Intl.DateTimeFormat.prototype,
+            [intlFallbackSymbol]: thisValue,
+        });
+    }
+
+    // Ensure [[FallbackSymbol]] isn't retrieved for Intl.DateTimeFormat instances.
+    for (let thisValue of intlObjects(Intl.DateTimeFormat)) {
+        Object.defineProperty(thisValue, intlFallbackSymbol, {
+            get() { assertEq(false, true); }
+        });
+        dateTimeFormatFunction.call(thisValue);
+    }
+
+    // Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.DateTimeFormat.prototype.
+    for (let thisValue of thisValues(Intl.DateTimeFormat)) {
+        if (!IsObject(thisValue) || Intl.DateTimeFormat.prototype.isPrototypeOf(thisValue))
+            continue;
+        Object.defineProperty(thisValue, intlFallbackSymbol, {
+            get() { assertEq(false, true); }
+        });
+        assertThrowsInstanceOf(() => dateTimeFormatFunction.call(thisValue), TypeError);
+    }
+
+    // Repeat the test from above, but also change Intl.DateTimeFormat[@@hasInstance]
+    // so it always returns |null|.
+    for (let thisValue of thisValues(Intl.DateTimeFormat)) {
+        let hasInstanceCalled = false, symbolGetterCalled = false;
+        Object.defineProperty(Intl.DateTimeFormat, 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(() => dateTimeFormatFunction.call(thisValue), TypeError);
+
+        delete Intl.DateTimeFormat[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.DateTimeFormat instances.
+{
+    // An actual Intl.DateTimeFormat instance.
+    let dateTimeFormat = new Intl.DateTimeFormat();
+
+    // An object initialized as a DateTimeFormat instance.
+    let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+    Intl.DateTimeFormat.call(thisValue);
+
+    // Object with [[FallbackSymbol]] set to DateTimeFormat instance.
+    let fakeObj = {
+        __proto__: Intl.DateTimeFormat.prototype,
+        [intlFallbackSymbol]: dateTimeFormat,
+    };
+
+    for (let number of [0, Date.now(), -Date.now()]) {
+        let expected = dateTimeFormat.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.DateTimeFormat instances.
+if ("formatToParts" in Intl.DateTimeFormat.prototype) {
+    // An actual Intl.DateTimeFormat instance.
+    let dateTimeFormat = new Intl.DateTimeFormat();
+
+    // An object initialized as a DateTimeFormat instance.
+    let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+    Intl.DateTimeFormat.call(thisValue);
+
+    // Object with [[FallbackSymbol]] set to DateTimeFormat instance.
+    let fakeObj = {
+        __proto__: Intl.DateTimeFormat.prototype,
+        [intlFallbackSymbol]: dateTimeFormat,
+    };
+
+    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, Date.now(), -Date.now()]) {
+        let expected = dateTimeFormat.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.DateTimeFormat instance.
+    let dateTimeFormat = new Intl.DateTimeFormat();
+
+    // An object initialized as a DateTimeFormat instance.
+    let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+    Intl.DateTimeFormat.call(thisValue);
+
+    // Object with [[FallbackSymbol]] set to DateTimeFormat instance.
+    let fakeObj = {
+        __proto__: Intl.DateTimeFormat.prototype,
+        [intlFallbackSymbol]: dateTimeFormat,
+    };
+
+    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 = dateTimeFormat.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
@@ -56,16 +56,17 @@ skip script test262/ch10/10.6/10.6-14-b-
 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
+skip script test262/intl402/ch12/12.1/12.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
@@ -98,16 +98,17 @@ class GlobalObject : public NativeObject
         STAR_GENERATOR_FUNCTION,
         ASYNC_FUNCTION_PROTO,
         ASYNC_FUNCTION,
         MAP_ITERATOR_PROTO,
         SET_ITERATOR_PROTO,
         COLLATOR_PROTO,
         NUMBER_FORMAT,
         NUMBER_FORMAT_PROTO,
+        DATE_TIME_FORMAT,
         DATE_TIME_FORMAT_PROTO,
         PLURAL_RULES_PROTO,
         MODULE_PROTO,
         IMPORT_ENTRY_PROTO,
         EXPORT_ENTRY_PROTO,
         REGEXP_STATICS,
         WARNED_ONCE_FLAGS,
         RUNTIME_CODEGEN_ENABLED,
@@ -490,16 +491,22 @@ class GlobalObject : public NativeObject
         return obj ? &obj->as<JSFunction>() : nullptr;
     }
 
     static JSObject*
     getOrCreateNumberFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, NUMBER_FORMAT_PROTO, initIntlObject);
     }
 
+    static JSFunction*
+    getOrCreateDateTimeFormatConstructor(JSContext* cx, Handle<GlobalObject*> global) {
+        JSObject* obj = getOrCreateObject(cx, global, DATE_TIME_FORMAT, initIntlObject);
+        return obj ? &obj->as<JSFunction>() : nullptr;
+    }
+
     static JSObject*
     getOrCreateDateTimeFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, DATE_TIME_FORMAT_PROTO, initIntlObject);
     }
 
     static JSObject*
     getOrCreatePluralRulesPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, PLURAL_RULES_PROTO, initIntlObject);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2497,22 +2497,28 @@ 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("IsDateTimeFormat",
+                    intrinsic_IsInstanceOfBuiltin<DateTimeFormatObject>, 1,0,
+                    IntlIsDateTimeFormat),
     JS_INLINABLE_FN("IsNumberFormat",
                     intrinsic_IsInstanceOfBuiltin<NumberFormatObject>, 1,0,
                     IntlIsNumberFormat),
     JS_INLINABLE_FN("IsPluralRules",
                     intrinsic_IsInstanceOfBuiltin<PluralRulesObject>, 1,0,
                     IntlIsPluralRules),
+    JS_FN("GetDateTimeFormatConstructor",
+          intrinsic_GetBuiltinIntlConstructor<GlobalObject::getOrCreateDateTimeFormatConstructor>,
+          0,0),
     JS_FN("GetNumberFormatConstructor",
           intrinsic_GetBuiltinIntlConstructor<GlobalObject::getOrCreateNumberFormatConstructor>,
           0,0),
 
     JS_INLINABLE_FN("IsRegExpObject",
                     intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
                     IsRegExpObject),
     JS_FN("CallRegExpMethodIfWrapped",