summaryrefslogtreecommitdiffstats
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/src/builtin/Intl.cpp102
-rw-r--r--js/src/jsfun.cpp50
-rw-r--r--js/src/tests/Intl/Collator/construct-newtarget.js81
-rw-r--r--js/src/tests/Intl/DateTimeFormat/construct-newtarget.js81
-rw-r--r--js/src/tests/Intl/NumberFormat/construct-newtarget.js81
-rw-r--r--js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js79
-rw-r--r--js/src/tests/ecma_2017/AsyncFunctions/subclass.js31
-rw-r--r--js/src/tests/ecma_6/Generators/construct-newtarget.js79
-rw-r--r--js/src/tests/ecma_6/Generators/subclass.js33
-rw-r--r--js/src/vm/AsyncFunction.cpp19
-rw-r--r--js/src/vm/AsyncFunction.h3
11 files changed, 586 insertions, 53 deletions
diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp
index 990a4acdb..3a20c487b 100644
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -765,42 +765,53 @@ static const JSFunctionSpec collator_methods[] = {
};
/**
- * 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;
@@ -808,15 +819,13 @@ Collator(JSContext* cx, const CallArgs& args, bool construct)
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;
}
@@ -833,6 +842,7 @@ 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);
@@ -1257,42 +1267,53 @@ static const JSFunctionSpec numberFormat_methods[] = {
};
/**
- * 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;
@@ -1300,15 +1321,13 @@ NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
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;
}
@@ -1325,6 +1344,7 @@ 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.
@@ -1725,42 +1745,53 @@ static const JSFunctionSpec dateTimeFormat_methods[] = {
};
/**
- * 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;
@@ -1768,15 +1799,13 @@ DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct)
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;
}
@@ -1793,6 +1822,7 @@ 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.
diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp
index c952441ad..9be02b217 100644
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1682,11 +1682,9 @@ const JSFunctionSpec js::function_methods[] = {
};
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)) {
@@ -1803,16 +1801,23 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener
* 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());
@@ -1921,24 +1926,47 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener
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;
diff --git a/js/src/tests/Intl/Collator/construct-newtarget.js b/js/src/tests/Intl/Collator/construct-newtarget.js
new file mode 100644
index 000000000..5db1abf37
--- /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);
diff --git a/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js b/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js
new file mode 100644
index 000000000..bbc08c79a
--- /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);
diff --git a/js/src/tests/Intl/NumberFormat/construct-newtarget.js b/js/src/tests/Intl/NumberFormat/construct-newtarget.js
new file mode 100644
index 000000000..0053b2737
--- /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);
diff --git a/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js b/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js
new file mode 100644
index 000000000..7d75d0c93
--- /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);
diff --git a/js/src/tests/ecma_2017/AsyncFunctions/subclass.js b/js/src/tests/ecma_2017/AsyncFunctions/subclass.js
new file mode 100644
index 000000000..da20ab19b
--- /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);
diff --git a/js/src/tests/ecma_6/Generators/construct-newtarget.js b/js/src/tests/ecma_6/Generators/construct-newtarget.js
new file mode 100644
index 000000000..f2087852b
--- /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);
diff --git a/js/src/tests/ecma_6/Generators/subclass.js b/js/src/tests/ecma_6/Generators/subclass.js
new file mode 100644
index 000000000..f93f4df4d
--- /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);
diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp
index bd0b4f32a..1e0c7d7c2 100644
--- a/js/src/vm/AsyncFunction.cpp
+++ b/js/src/vm/AsyncFunction.cpp
@@ -107,18 +107,15 @@ WrappedAsyncFunction(JSContext* cx, unsigned argc, Value* vp)
// 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))
@@ -141,6 +138,16 @@ js::WrapAsyncFunction(JSContext* cx, HandleFunction 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
diff --git a/js/src/vm/AsyncFunction.h b/js/src/vm/AsyncFunction.h
index ddf81a177..d7f2c1311 100644
--- a/js/src/vm/AsyncFunction.h
+++ b/js/src/vm/AsyncFunction.h
@@ -22,6 +22,9 @@ bool
IsWrappedAsyncFunction(JSFunction* fun);
JSObject*
+WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto);
+
+JSObject*
WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped);
MOZ_MUST_USE bool