Bug 1318017 - Call GetPrototypeFromConstructor for generator, async function, and Intl constructors. r=arai
authorAndré Bargull <andre.bargull@gmail.com>
Mon, 21 Nov 2016 06:26:02 -0800
changeset 323726 e48faa1e47838383793dfd8c72b3da93aff7a643
parent 323725 6750f9bd442249d8f48cfb10c664ffcab4d5c144
child 323727 b4dcd4bb4c7344d828f1e66f9c81a87f5f6f0dd6
push id34512
push usercbook@mozilla.com
push dateTue, 22 Nov 2016 15:36:36 +0000
treeherderautoland@157d57933ec5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1318017
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 1318017 - Call GetPrototypeFromConstructor for generator, async function, and Intl constructors. r=arai
js/src/builtin/Intl.cpp
js/src/jsfun.cpp
js/src/tests/Intl/Collator/construct-newtarget.js
js/src/tests/Intl/DateTimeFormat/construct-newtarget.js
js/src/tests/Intl/NumberFormat/construct-newtarget.js
js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js
js/src/tests/ecma_2017/AsyncFunctions/subclass.js
js/src/tests/ecma_6/Generators/construct-newtarget.js
js/src/tests/ecma_6/Generators/subclass.js
js/src/vm/AsyncFunction.cpp
js/src/vm/AsyncFunction.h
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -763,84 +763,95 @@ static const JSFunctionSpec collator_met
     JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0),
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str, collator_toSource, 0, 0),
 #endif
     JS_FS_END
 };
 
 /**
- * Collator constructor.
- * Spec: ECMAScript Internationalization API Specification, 10.1
+ * 10.1.2 Intl.Collator([ locales [, options]])
+ *
+ * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool
 Collator(JSContext* cx, const CallArgs& args, bool construct)
 {
     RootedObject obj(cx);
 
+    // We're following ECMA-402 1st Edition when Collator is called because of
+    // backward compatibility issues.
+    // See https://github.com/tc39/ecma402/issues/57
     if (!construct) {
-        // 10.1.2.1 step 3
+        // ES Intl 1st ed., 10.1.2.1 step 3
         JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
         if (!intl)
             return false;
         RootedValue self(cx, args.thisv());
         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
-            // 10.1.2.1 step 4
+            // ES Intl 1st ed., 10.1.2.1 step 4
             obj = ToObject(cx, self);
             if (!obj)
                 return false;
 
-            // 10.1.2.1 step 5
+            // ES Intl 1st ed., 10.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 {
-            // 10.1.2.1 step 3.a
+            // ES Intl 1st ed., 10.1.2.1 step 3.a
             construct = true;
         }
     }
+
     if (construct) {
-        // 10.1.3.1 paragraph 2
-        RootedObject proto(cx, cx->global()->getOrCreateCollatorPrototype(cx));
-        if (!proto)
+        // Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
+        RootedObject proto(cx);
+        if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
             return false;
+
+        if (!proto) {
+            proto = cx->global()->getOrCreateCollatorPrototype(cx);
+            if (!proto)
+                return false;
+        }
+
         obj = NewObjectWithGivenProto(cx, &CollatorClass, proto);
         if (!obj)
             return false;
 
         obj->as<NativeObject>().setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr));
     }
 
-    // 10.1.2.1 steps 1 and 2; 10.1.3.1 steps 1 and 2
     RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
     RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
 
-    // 10.1.2.1 step 6; 10.1.3.1 step 3
+    // Step 6.
     if (!IntlInitialize(cx, obj, cx->names().InitializeCollator, locales, options))
         return false;
 
-    // 10.1.2.1 steps 3.a and 7
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
 Collator(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return Collator(cx, args, args.isConstructing());
 }
 
 bool
 js::intl_Collator(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
+    MOZ_ASSERT(!args.isConstructing());
     // intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot
     // be used with "new", but it still has to be treated as a constructor.
     return Collator(cx, args, true);
 }
 
 static void
 collator_finalize(FreeOp* fop, JSObject* obj)
 {
@@ -1255,84 +1266,95 @@ static const JSFunctionSpec numberFormat
     JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0, 0),
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str, numberFormat_toSource, 0, 0),
 #endif
     JS_FS_END
 };
 
 /**
- * NumberFormat constructor.
- * Spec: ECMAScript Internationalization API Specification, 11.1
+ * 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) {
-        // 11.1.2.1 step 3
+        // ES Intl 1st ed., 11.1.2.1 step 3
         JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
         if (!intl)
             return false;
         RootedValue self(cx, args.thisv());
         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
-            // 11.1.2.1 step 4
+            // ES Intl 1st ed., 11.1.2.1 step 4
             obj = ToObject(cx, self);
             if (!obj)
                 return false;
 
-            // 11.1.2.1 step 5
+            // 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 {
-            // 11.1.2.1 step 3.a
+            // ES Intl 1st ed., 11.1.2.1 step 3.a
             construct = true;
         }
     }
+
     if (construct) {
-        // 11.1.3.1 paragraph 2
-        RootedObject proto(cx, cx->global()->getOrCreateNumberFormatPrototype(cx));
-        if (!proto)
+        // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
+        RootedObject proto(cx);
+        if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
             return false;
+
+        if (!proto) {
+            proto = cx->global()->getOrCreateNumberFormatPrototype(cx);
+            if (!proto)
+                return false;
+        }
+
         obj = NewObjectWithGivenProto(cx, &NumberFormatClass, proto);
         if (!obj)
             return false;
 
         obj->as<NativeObject>().setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
     }
 
-    // 11.1.2.1 steps 1 and 2; 11.1.3.1 steps 1 and 2
     RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
     RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
 
-    // 11.1.2.1 step 6; 11.1.3.1 step 3
+    // Step 3.
     if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options))
         return false;
 
-    // 11.1.2.1 steps 3.a and 7
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
 NumberFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return NumberFormat(cx, args, args.isConstructing());
 }
 
 bool
 js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
+    MOZ_ASSERT(!args.isConstructing());
     // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it
     // cannot be used with "new", but it still has to be treated as a
     // constructor.
     return NumberFormat(cx, args, true);
 }
 
 static void
 numberFormat_finalize(FreeOp* fop, JSObject* obj)
@@ -1723,84 +1745,95 @@ static const JSFunctionSpec dateTimeForm
     JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 0, 0),
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0),
 #endif
     JS_FS_END
 };
 
 /**
- * DateTimeFormat constructor.
- * Spec: ECMAScript Internationalization API Specification, 12.1
+ * 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) {
-        // 12.1.2.1 step 3
+        // ES Intl 1st ed., 12.1.2.1 step 3
         JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
         if (!intl)
             return false;
         RootedValue self(cx, args.thisv());
         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
-            // 12.1.2.1 step 4
+            // ES Intl 1st ed., 12.1.2.1 step 4
             obj = ToObject(cx, self);
             if (!obj)
                 return false;
 
-            // 12.1.2.1 step 5
+            // 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 {
-            // 12.1.2.1 step 3.a
+            // ES Intl 1st ed., 12.1.2.1 step 3.a
             construct = true;
         }
     }
+
     if (construct) {
-        // 12.1.3.1 paragraph 2
-        RootedObject proto(cx, cx->global()->getOrCreateDateTimeFormatPrototype(cx));
-        if (!proto)
+        // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
+        RootedObject proto(cx);
+        if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
             return false;
+
+        if (!proto) {
+            proto = cx->global()->getOrCreateDateTimeFormatPrototype(cx);
+            if (!proto)
+                return false;
+        }
+
         obj = NewObjectWithGivenProto(cx, &DateTimeFormatClass, proto);
         if (!obj)
             return false;
 
         obj->as<NativeObject>().setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr));
     }
 
-    // 12.1.2.1 steps 1 and 2; 12.1.3.1 steps 1 and 2
     RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
     RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
 
-    // 12.1.2.1 step 6; 12.1.3.1 step 3
+    // Step 3.
     if (!IntlInitialize(cx, obj, cx->names().InitializeDateTimeFormat, locales, options))
         return false;
 
-    // 12.1.2.1 steps 3.a and 7
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
 DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return DateTimeFormat(cx, args, args.isConstructing());
 }
 
 bool
 js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
+    MOZ_ASSERT(!args.isConstructing());
     // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it
     // cannot be used with "new", but it still has to be treated as a
     // constructor.
     return DateTimeFormat(cx, args, true);
 }
 
 static void
 dateTimeFormat_finalize(FreeOp* fop, JSObject* obj)
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1625,21 +1625,19 @@ const JSFunctionSpec js::function_method
     JS_FN(js_call_str,       fun_call,       1,0),
     JS_FN("isGenerator",     fun_isGenerator,0,0),
     JS_SELF_HOSTED_FN("bind", "FunctionBind", 2, JSFUN_HAS_REST),
     JS_SYM_FN(hasInstance, fun_symbolHasInstance, 1, JSPROP_READONLY | JSPROP_PERMANENT),
     JS_FS_END
 };
 
 static bool
-FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind generatorKind,
+FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generatorKind,
                     FunctionAsyncKind asyncKind)
 {
-    CallArgs args = CallArgsFromVp(argc, vp);
-
     /* Block this call if security callbacks forbid it. */
     Rooted<GlobalObject*> global(cx, &args.callee().global());
     if (!GlobalObject::isRuntimeCodeGenEnabled(cx, global)) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_FUNCTION);
         return false;
     }
 
     bool isStarGenerator = generatorKind == StarGenerator;
@@ -1746,26 +1744,33 @@ FunctionConstructor(JSContext* cx, unsig
 
     /*
      * NB: (new Function) is not lexically closed by its caller, it's just an
      * anonymous function in the top-level scope that its constructor inhabits.
      * Thus 'var x = 42; f = new Function("return x"); print(f())' prints 42,
      * and so would a call to f from another top-level's script or function.
      */
     RootedAtom anonymousAtom(cx, cx->names().anonymous);
+
+    // ES2017, draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077
+    // 19.2.1.1.1 Runtime Semantics: CreateDynamicFunction, step 24.
     RootedObject proto(cx);
-    if (isStarGenerator) {
+    if (!isAsync) {
+        if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+            return false;
+    }
+
+    // 19.2.1.1.1, step 4.d, use %Generator% as the fallback prototype.
+    // Also use %Generator% for the unwrapped function of async functions.
+    if (!proto && isStarGenerator) {
         // Unwrapped function of async function should use GeneratorFunction,
         // while wrapped function isn't generator.
         proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global);
         if (!proto)
             return false;
-    } else {
-        if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
-            return false;
     }
 
     RootedObject globalLexical(cx, &global->lexicalEnvironment());
     AllocKind allocKind = isAsync ? AllocKind::FUNCTION_EXTENDED : AllocKind::FUNCTION;
     RootedFunction fun(cx, NewFunctionWithProto(cx, nullptr, 0,
                                                 JSFunction::INTERPRETED_LAMBDA, globalLexical,
                                                 anonymousAtom, proto,
                                                 allocKind, TenuredObject));
@@ -1864,34 +1869,57 @@ FunctionConstructor(JSContext* cx, unsig
         ok = frontend::CompileFunctionBody(cx, &fun, options, formals, srcBuf);
     args.rval().setObject(*fun);
     return ok;
 }
 
 bool
 js::Function(JSContext* cx, unsigned argc, Value* vp)
 {
-    return FunctionConstructor(cx, argc, vp, NotGenerator, SyncFunction);
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return FunctionConstructor(cx, args, NotGenerator, SyncFunction);
 }
 
 bool
 js::Generator(JSContext* cx, unsigned argc, Value* vp)
 {
-    return FunctionConstructor(cx, argc, vp, StarGenerator, SyncFunction);
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return FunctionConstructor(cx, args, StarGenerator, SyncFunction);
 }
 
 bool
 js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    if (!FunctionConstructor(cx, argc, vp, StarGenerator, AsyncFunction))
+
+    // Save the callee before its reset in FunctionConstructor().
+    RootedObject newTarget(cx);
+    if (args.isConstructing())
+        newTarget = &args.newTarget().toObject();
+    else
+        newTarget = &args.callee();
+
+    if (!FunctionConstructor(cx, args, StarGenerator, AsyncFunction))
         return false;
 
+    // ES2017, draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077
+    // 19.2.1.1.1 Runtime Semantics: CreateDynamicFunction, step 24.
+    RootedObject proto(cx);
+    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+        return false;
+
+    // 19.2.1.1.1, step 4.d, use %AsyncFunctionPrototype% as the fallback.
+    if (!proto) {
+        proto = GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global());
+        if (!proto)
+            return false;
+    }
+
     RootedFunction unwrapped(cx, &args.rval().toObject().as<JSFunction>());
-    RootedObject wrapped(cx, WrapAsyncFunction(cx, unwrapped));
+    RootedObject wrapped(cx, WrapAsyncFunctionWithProto(cx, unwrapped, proto));
     if (!wrapped)
         return false;
 
     args.rval().setObject(*wrapped);
     return true;
 }
 
 bool
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/Collator/construct-newtarget.js
@@ -0,0 +1,81 @@
+// |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/. */
+
+
+// Test subclassing %Intl.Collator% works correctly.
+class MyCollator extends Intl.Collator {}
+
+var obj = new MyCollator();
+assertEq(obj instanceof MyCollator, true);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), MyCollator.prototype);
+
+obj = Reflect.construct(MyCollator, []);
+assertEq(obj instanceof MyCollator, true);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), MyCollator.prototype);
+
+obj = Reflect.construct(MyCollator, [], MyCollator);
+assertEq(obj instanceof MyCollator, true);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), MyCollator.prototype);
+
+obj = Reflect.construct(MyCollator, [], Intl.Collator);
+assertEq(obj instanceof MyCollator, false);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype);
+
+
+// Set a different constructor as NewTarget.
+obj = Reflect.construct(MyCollator, [], Array);
+assertEq(obj instanceof MyCollator, false);
+assertEq(obj instanceof Intl.Collator, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+obj = Reflect.construct(Intl.Collator, [], Array);
+assertEq(obj instanceof Intl.Collator, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+
+// The prototype defaults to %CollatorPrototype% if null.
+function NewTargetNullPrototype() {}
+NewTargetNullPrototype.prototype = null;
+
+obj = Reflect.construct(Intl.Collator, [], NewTargetNullPrototype);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype);
+
+obj = Reflect.construct(MyCollator, [], NewTargetNullPrototype);
+assertEq(obj instanceof MyCollator, false);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype);
+
+
+// "prototype" property is retrieved exactly once.
+var trapLog = [], getLog = [];
+var ProxiedConstructor = new Proxy(Intl.Collator, new Proxy({
+    get(target, propertyKey, receiver) {
+        getLog.push(propertyKey);
+        return Reflect.get(target, propertyKey, receiver);
+    }
+}, {
+    get(target, propertyKey, receiver) {
+        trapLog.push(propertyKey);
+        return Reflect.get(target, propertyKey, receiver);
+    }
+}));
+
+obj = Reflect.construct(Intl.Collator, [], ProxiedConstructor);
+assertEqArray(trapLog, ["get"]);
+assertEqArray(getLog, ["prototype"]);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype);
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js
@@ -0,0 +1,81 @@
+// |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/. */
+
+
+// Test subclassing %Intl.DateTimeFormat% works correctly.
+class MyDateTimeFormat extends Intl.DateTimeFormat {}
+
+var obj = new MyDateTimeFormat();
+assertEq(obj instanceof MyDateTimeFormat, true);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype);
+
+obj = Reflect.construct(MyDateTimeFormat, []);
+assertEq(obj instanceof MyDateTimeFormat, true);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype);
+
+obj = Reflect.construct(MyDateTimeFormat, [], MyDateTimeFormat);
+assertEq(obj instanceof MyDateTimeFormat, true);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype);
+
+obj = Reflect.construct(MyDateTimeFormat, [], Intl.DateTimeFormat);
+assertEq(obj instanceof MyDateTimeFormat, false);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype);
+
+
+// Set a different constructor as NewTarget.
+obj = Reflect.construct(MyDateTimeFormat, [], Array);
+assertEq(obj instanceof MyDateTimeFormat, false);
+assertEq(obj instanceof Intl.DateTimeFormat, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+obj = Reflect.construct(Intl.DateTimeFormat, [], Array);
+assertEq(obj instanceof Intl.DateTimeFormat, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+
+// The prototype defaults to %DateTimeFormatPrototype% if null.
+function NewTargetNullPrototype() {}
+NewTargetNullPrototype.prototype = null;
+
+obj = Reflect.construct(Intl.DateTimeFormat, [], NewTargetNullPrototype);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype);
+
+obj = Reflect.construct(MyDateTimeFormat, [], NewTargetNullPrototype);
+assertEq(obj instanceof MyDateTimeFormat, false);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype);
+
+
+// "prototype" property is retrieved exactly once.
+var trapLog = [], getLog = [];
+var ProxiedConstructor = new Proxy(Intl.DateTimeFormat, new Proxy({
+    get(target, propertyKey, receiver) {
+        getLog.push(propertyKey);
+        return Reflect.get(target, propertyKey, receiver);
+    }
+}, {
+    get(target, propertyKey, receiver) {
+        trapLog.push(propertyKey);
+        return Reflect.get(target, propertyKey, receiver);
+    }
+}));
+
+obj = Reflect.construct(Intl.DateTimeFormat, [], ProxiedConstructor);
+assertEqArray(trapLog, ["get"]);
+assertEqArray(getLog, ["prototype"]);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype);
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/NumberFormat/construct-newtarget.js
@@ -0,0 +1,81 @@
+// |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/. */
+
+
+// Test subclassing %Intl.NumberFormat% works correctly.
+class MyNumberFormat extends Intl.NumberFormat {}
+
+var obj = new MyNumberFormat();
+assertEq(obj instanceof MyNumberFormat, true);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype);
+
+obj = Reflect.construct(MyNumberFormat, []);
+assertEq(obj instanceof MyNumberFormat, true);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype);
+
+obj = Reflect.construct(MyNumberFormat, [], MyNumberFormat);
+assertEq(obj instanceof MyNumberFormat, true);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype);
+
+obj = Reflect.construct(MyNumberFormat, [], Intl.NumberFormat);
+assertEq(obj instanceof MyNumberFormat, false);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype);
+
+
+// Set a different constructor as NewTarget.
+obj = Reflect.construct(MyNumberFormat, [], Array);
+assertEq(obj instanceof MyNumberFormat, false);
+assertEq(obj instanceof Intl.NumberFormat, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+obj = Reflect.construct(Intl.NumberFormat, [], Array);
+assertEq(obj instanceof Intl.NumberFormat, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+
+// The prototype defaults to %NumberFormatPrototype% if null.
+function NewTargetNullPrototype() {}
+NewTargetNullPrototype.prototype = null;
+
+obj = Reflect.construct(Intl.NumberFormat, [], NewTargetNullPrototype);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype);
+
+obj = Reflect.construct(MyNumberFormat, [], NewTargetNullPrototype);
+assertEq(obj instanceof MyNumberFormat, false);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype);
+
+
+// "prototype" property is retrieved exactly once.
+var trapLog = [], getLog = [];
+var ProxiedConstructor = new Proxy(Intl.NumberFormat, new Proxy({
+    get(target, propertyKey, receiver) {
+        getLog.push(propertyKey);
+        return Reflect.get(target, propertyKey, receiver);
+    }
+}, {
+    get(target, propertyKey, receiver) {
+        trapLog.push(propertyKey);
+        return Reflect.get(target, propertyKey, receiver);
+    }
+}));
+
+obj = Reflect.construct(Intl.NumberFormat, [], ProxiedConstructor);
+assertEqArray(trapLog, ["get"]);
+assertEqArray(getLog, ["prototype"]);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype);
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js
@@ -0,0 +1,79 @@
+/* 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/. */
+
+const AsyncFunction = async function(){}.constructor;
+
+
+// Test subclassing %AsyncFunction% works correctly.
+class MyAsync extends AsyncFunction {}
+
+var fn = new MyAsync();
+assertEq(fn instanceof MyAsync, true);
+assertEq(fn instanceof AsyncFunction, true);
+assertEq(Object.getPrototypeOf(fn), MyAsync.prototype);
+
+fn = Reflect.construct(MyAsync, []);
+assertEq(fn instanceof MyAsync, true);
+assertEq(fn instanceof AsyncFunction, true);
+assertEq(Object.getPrototypeOf(fn), MyAsync.prototype);
+
+fn = Reflect.construct(MyAsync, [], MyAsync);
+assertEq(fn instanceof MyAsync, true);
+assertEq(fn instanceof AsyncFunction, true);
+assertEq(Object.getPrototypeOf(fn), MyAsync.prototype);
+
+fn = Reflect.construct(MyAsync, [], AsyncFunction);
+assertEq(fn instanceof MyAsync, false);
+assertEq(fn instanceof AsyncFunction, true);
+assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype);
+
+
+// Set a different constructor as NewTarget.
+fn = Reflect.construct(MyAsync, [], Array);
+assertEq(fn instanceof MyAsync, false);
+assertEq(fn instanceof AsyncFunction, false);
+assertEq(Object.getPrototypeOf(fn), Array.prototype);
+
+fn = Reflect.construct(AsyncFunction, [], Array);
+assertEq(fn instanceof AsyncFunction, false);
+assertEq(Object.getPrototypeOf(fn), Array.prototype);
+
+
+// The prototype defaults to %AsyncFunctionPrototype% if null.
+function NewTargetNullPrototype() {}
+NewTargetNullPrototype.prototype = null;
+
+fn = Reflect.construct(AsyncFunction, [], NewTargetNullPrototype);
+assertEq(fn instanceof AsyncFunction, true);
+assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype);
+
+fn = Reflect.construct(MyAsync, [], NewTargetNullPrototype);
+assertEq(fn instanceof MyAsync, false);
+assertEq(fn instanceof AsyncFunction, true);
+assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype);
+
+
+// "prototype" property is retrieved exactly once.
+var trapLog = [], getLog = [];
+var ProxiedConstructor = new Proxy(AsyncFunction, new Proxy({
+    get(target, propertyKey, receiver) {
+        getLog.push(propertyKey);
+        return Reflect.get(target, propertyKey, receiver);
+    }
+}, {
+    get(target, propertyKey, receiver) {
+        trapLog.push(propertyKey);
+        return Reflect.get(target, propertyKey, receiver);
+    }
+}));
+
+fn = Reflect.construct(AsyncFunction, [], ProxiedConstructor);
+assertEqArray(trapLog, ["get"]);
+assertEqArray(getLog, ["prototype"]);
+assertEq(fn instanceof AsyncFunction, true);
+assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype);
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/AsyncFunctions/subclass.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+/* 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/. */
+
+const AsyncFunction = async function(){}.constructor;
+
+class MyAsync extends AsyncFunction {}
+
+// MyGen inherits from %AsyncFunction%.
+assertEq(Object.getPrototypeOf(MyAsync), AsyncFunction);
+
+// MyGen.prototype inherits from %AsyncFunctionPrototype%.
+assertEq(Object.getPrototypeOf(MyAsync.prototype), AsyncFunction.prototype);
+
+var fn = new MyAsync("return await 'ok';");
+
+// fn inherits from MyAsync.prototype.
+assertEq(Object.getPrototypeOf(fn), MyAsync.prototype);
+
+// Ensure the new async function can be executed.
+var promise = fn();
+
+// promise inherits from %Promise.prototype%.
+assertEq(Object.getPrototypeOf(promise), Promise.prototype);
+
+// Computes the expected result.
+assertEventuallyEq(promise, "ok");
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Generators/construct-newtarget.js
@@ -0,0 +1,79 @@
+/* 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/. */
+
+const GeneratorFunction = function*(){}.constructor;
+
+
+// Test subclassing %GeneratorFunction% works correctly.
+class MyGenerator extends GeneratorFunction {}
+
+var fn = new MyGenerator();
+assertEq(fn instanceof MyGenerator, true);
+assertEq(fn instanceof GeneratorFunction, true);
+assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype);
+
+fn = Reflect.construct(MyGenerator, []);
+assertEq(fn instanceof MyGenerator, true);
+assertEq(fn instanceof GeneratorFunction, true);
+assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype);
+
+fn = Reflect.construct(MyGenerator, [], MyGenerator);
+assertEq(fn instanceof MyGenerator, true);
+assertEq(fn instanceof GeneratorFunction, true);
+assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype);
+
+fn = Reflect.construct(MyGenerator, [], GeneratorFunction);
+assertEq(fn instanceof MyGenerator, false);
+assertEq(fn instanceof GeneratorFunction, true);
+assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype);
+
+
+// Set a different constructor as NewTarget.
+fn = Reflect.construct(MyGenerator, [], Array);
+assertEq(fn instanceof MyGenerator, false);
+assertEq(fn instanceof GeneratorFunction, false);
+assertEq(Object.getPrototypeOf(fn), Array.prototype);
+
+fn = Reflect.construct(GeneratorFunction, [], Array);
+assertEq(fn instanceof GeneratorFunction, false);
+assertEq(Object.getPrototypeOf(fn), Array.prototype);
+
+
+// The prototype defaults to %GeneratorFunctionPrototype% if null.
+function NewTargetNullPrototype() {}
+NewTargetNullPrototype.prototype = null;
+
+fn = Reflect.construct(GeneratorFunction, [], NewTargetNullPrototype);
+assertEq(fn instanceof GeneratorFunction, true);
+assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype);
+
+fn = Reflect.construct(MyGenerator, [], NewTargetNullPrototype);
+assertEq(fn instanceof MyGenerator, false);
+assertEq(fn instanceof GeneratorFunction, true);
+assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype);
+
+
+// "prototype" property is retrieved exactly once.
+var trapLog = [], getLog = [];
+var ProxiedConstructor = new Proxy(GeneratorFunction, new Proxy({
+    get(target, propertyKey, receiver) {
+        getLog.push(propertyKey);
+        return Reflect.get(target, propertyKey, receiver);
+    }
+}, {
+    get(target, propertyKey, receiver) {
+        trapLog.push(propertyKey);
+        return Reflect.get(target, propertyKey, receiver);
+    }
+}));
+
+fn = Reflect.construct(GeneratorFunction, [], ProxiedConstructor);
+assertEqArray(trapLog, ["get"]);
+assertEqArray(getLog, ["prototype"]);
+assertEq(fn instanceof GeneratorFunction, true);
+assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype);
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Generators/subclass.js
@@ -0,0 +1,33 @@
+/* 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/. */
+
+const GeneratorFunction = function*(){}.constructor;
+
+class MyGen extends GeneratorFunction {}
+
+// MyGen inherits from %GeneratorFunction%.
+assertEq(Object.getPrototypeOf(MyGen), GeneratorFunction);
+
+// MyGen.prototype inherits from %Generator%.
+assertEq(Object.getPrototypeOf(MyGen.prototype), GeneratorFunction.prototype);
+
+var fn = new MyGen("yield* [1, 2, 3]");
+
+// fn inherits from MyGen.prototype.
+assertEq(Object.getPrototypeOf(fn), MyGen.prototype);
+
+// fn.prototype inherits from %GeneratorPrototype%.
+assertEq(Object.getPrototypeOf(fn.prototype), GeneratorFunction.prototype.prototype);
+
+// Ensure the new generator function can be executed.
+var it = fn();
+
+// it inherits from fn.prototype.
+assertEq(Object.getPrototypeOf(it), fn.prototype);
+
+// Computes the expected result.
+assertEqArray([...it], [1, 2, 3]);
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
--- a/js/src/vm/AsyncFunction.cpp
+++ b/js/src/vm/AsyncFunction.cpp
@@ -105,28 +105,25 @@ WrappedAsyncFunction(JSContext* cx, unsi
 }
 
 // Async Functions proposal 2.1 steps 1, 3 (partially).
 // In the spec it creates a function, but we create 2 functions `unwrapped` and
 // `wrapped`.  `unwrapped` is a generator that corresponds to
 //  the async function's body, replacing `await` with `yield`.  `wrapped` is a
 // function that is visible to the outside, and handles yielded values.
 JSObject*
-js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped)
+js::WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto)
 {
     MOZ_ASSERT(unwrapped->isStarGenerator());
+    MOZ_ASSERT(proto, "We need an explicit prototype to avoid the default"
+                      "%FunctionPrototype% fallback in NewFunctionWithProto().");
 
     // Create a new function with AsyncFunctionPrototype, reusing the name and
     // the length of `unwrapped`.
 
-    // Step 1.
-    RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()));
-    if (!proto)
-        return nullptr;
-
     RootedAtom funName(cx, unwrapped->name());
     uint16_t length;
     if (!unwrapped->getLength(cx, &length))
         return nullptr;
 
     // Steps 3 (partially).
     RootedFunction wrapped(cx, NewFunctionWithProto(cx, WrappedAsyncFunction, length,
                                                     JSFunction::NATIVE_FUN, nullptr,
@@ -139,16 +136,26 @@ js::WrapAsyncFunction(JSContext* cx, Han
     // Link them to each other to make GetWrappedAsyncFunction and
     // GetUnwrappedAsyncFunction work.
     unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped));
     wrapped->setExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped));
 
     return wrapped;
 }
 
+JSObject*
+js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped)
+{
+    RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()));
+    if (!proto)
+        return nullptr;
+
+    return WrapAsyncFunctionWithProto(cx, unwrapped, proto);
+}
+
 enum class ResumeKind {
     Normal,
     Throw
 };
 
 // Async Functions proposal 2.2 steps 3.f, 3.g.
 // Async Functions proposal 2.2 steps 3.d-e, 3.g.
 // Implemented in js/src/builtin/Promise.cpp
--- a/js/src/vm/AsyncFunction.h
+++ b/js/src/vm/AsyncFunction.h
@@ -17,16 +17,19 @@ GetWrappedAsyncFunction(JSFunction* unwr
 
 JSFunction*
 GetUnwrappedAsyncFunction(JSFunction* wrapped);
 
 bool
 IsWrappedAsyncFunction(JSFunction* fun);
 
 JSObject*
+WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto);
+
+JSObject*
 WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped);
 
 MOZ_MUST_USE bool
 AsyncFunctionAwaitedFulfilled(JSContext* cx, Handle<PromiseObject*> resultPromise,
                               HandleValue generatorVal, HandleValue value);
 
 MOZ_MUST_USE bool
 AsyncFunctionAwaitedRejected(JSContext* cx, Handle<PromiseObject*> resultPromise,