Bug 1019116 - Implement Promise.prototype.finally. r=till,bz
authorAndré Bargull <andre.bargull@gmail.com>
Tue, 01 Aug 2017 14:05:08 -0700
changeset 443981 af50babd996883c749f6608118fa69faee8db7de
parent 443980 919528a717da922e95f7788053af72c468db5efb
child 443982 2af1c7c2375f251cd8dd53b05d05087378ab709d
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill, bz
bugs1019116
milestone58.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 1019116 - Implement Promise.prototype.finally. r=till,bz
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
js/src/builtin/Promise.js
js/src/vm/SelfHosting.cpp
js/xpconnect/tests/chrome/test_xrayToJS.xul
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -2267,16 +2267,17 @@ PerformPromiseRace(JSContext *cx, JS::Fo
         // Step i.
         if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal))
             return false;
     }
 
     MOZ_ASSERT_UNREACHABLE("Shouldn't reach the end of PerformPromiseRace");
 }
 
+
 // ES2016, Sub-steps of 25.4.4.4 and 25.4.4.5.
 static MOZ_MUST_USE JSObject*
 CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue argVal,
                               ResolutionMode mode)
 {
     // Steps 1-2.
     if (!thisVal.isObject()) {
         const char* msg = mode == ResolveMode
@@ -2327,16 +2328,23 @@ CommonStaticResolveRejectImpl(JSContext*
     {
         return nullptr;
     }
 
     // Step 6 of Resolve, 4 of Reject.
     return promise;
 }
 
+MOZ_MUST_USE JSObject*
+js::PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value)
+{
+    RootedValue C(cx, ObjectValue(*constructor));
+    return CommonStaticResolveRejectImpl(cx, C, value, ResolveMode);
+}
+
 /**
  * ES2016, 25.4.4.4, Promise.reject.
  */
 bool
 js::Promise_reject(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedValue thisVal(cx, args.thisv());
@@ -3675,16 +3683,17 @@ static JSObject*
 CreatePromisePrototype(JSContext* cx, JSProtoKey key)
 {
     return GlobalObject::createBlankPrototype(cx, cx->global(), &PromiseObject::protoClass_);
 }
 
 static const JSFunctionSpec promise_methods[] = {
     JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0),
     JS_FN("then", Promise_then, 2, 0),
+    JS_SELF_HOSTED_FN("finally", "Promise_finally", 1, 0),
     JS_FS_END
 };
 
 static const JSPropertySpec promise_properties[] = {
     JS_STRING_SYM_PS(toStringTag, "Promise", JSPROP_READONLY),
     JS_PS_END
 };
 
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -119,16 +119,25 @@ GetWaitForAllPromise(JSContext* cx, cons
  * `promise` field that can contain null. That field is only ever used by
  * devtools, which have to treat these reactions specially.
  */
 MOZ_MUST_USE bool
 OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> promise,
                     HandleValue onFulfilled, HandleValue onRejected,
                     MutableHandleObject dependent, bool createDependent);
 
+/**
+ * PromiseResolve ( C, x )
+ *
+ * The abstract operation PromiseResolve, given a constructor and a value,
+ * returns a new promise resolved with that value.
+ */
+MOZ_MUST_USE JSObject*
+PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value);
+
 
 MOZ_MUST_USE PromiseObject*
 CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal);
 
 MOZ_MUST_USE bool
 AsyncFunctionReturned(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value);
 
 MOZ_MUST_USE bool
--- a/js/src/builtin/Promise.js
+++ b/js/src/builtin/Promise.js
@@ -2,8 +2,76 @@
  * 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/. */
 
 // ES6, 25.4.5.1.
 function Promise_catch(onRejected) {
     // Steps 1-2.
     return callContentFunction(this.then, this, undefined, onRejected);
 }
+
+// Promise.prototype.finally proposal, stage 3.
+// Promise.prototype.finally ( onFinally )
+function Promise_finally(onFinally) {
+    // Step 1.
+    var promise = this;
+
+    // Step 2.
+    if (!IsObject(promise))
+        ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Promise", "finally", "value");
+
+    // Step 3.
+    var C = SpeciesConstructor(promise, GetBuiltinConstructor("Promise"));
+
+    // Step 4.
+    assert(IsConstructor(C), "SpeciesConstructor returns a constructor function");
+
+    // Steps 5-6.
+    var thenFinally, catchFinally;
+    if (!IsCallable(onFinally)) {
+        thenFinally = onFinally;
+        catchFinally = onFinally;
+    } else {
+        // ThenFinally Function.
+        // The parentheses prevent the infering of a function name.
+        (thenFinally) = function(value) {
+            // Steps 1-2 (implicit).
+
+            // Step 3.
+            var result = onFinally();
+
+            // Steps 4-5 (implicit).
+
+            // Step 6.
+            var promise = PromiseResolve(C, result);
+
+            // Step 7.
+            // FIXME: spec issue - "be equivalent to a function that" is not a defined spec term.
+            // https://github.com/tc39/ecma262/issues/933
+
+            // Step 8.
+            return callContentFunction(promise.then, promise, function() { return value; });
+        };
+
+        // CatchFinally Function.
+        (catchFinally) = function(reason) {
+            // Steps 1-2 (implicit).
+
+            // Step 3.
+            var result = onFinally();
+
+            // Steps 4-5 (implicit).
+
+            // Step 6.
+            var promise = PromiseResolve(C, result);
+
+            // Step 7.
+            // FIXME: spec issue - "be equivalent to a function that" is not a defined spec term.
+            // https://github.com/tc39/ecma262/issues/933
+
+            // Step 8.
+            return callContentFunction(promise.then, promise, function() { throw reason; });
+        };
+    }
+
+    // Step 7.
+    return callContentFunction(promise.then, promise, thenFinally, catchFinally);
+}
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2087,16 +2087,31 @@ intrinsic_ModuleNamespaceExports(JSConte
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
     RootedModuleNamespaceObject namespace_(cx, &args[0].toObject().as<ModuleNamespaceObject>());
     args.rval().setObject(namespace_->exports());
     return true;
 }
 
+static bool
+intrinsic_PromiseResolve(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 2);
+
+    RootedObject constructor(cx, &args[0].toObject());
+    JSObject* promise = js::PromiseResolve(cx, constructor, args[1]);
+    if (!promise)
+        return false;
+
+    args.rval().setObject(*promise);
+    return true;
+}
+
 // The self-hosting global isn't initialized with the normal set of builtins.
 // Instead, individual C++-implemented functions that're required by
 // self-hosted code are defined as global functions. Accessing these
 // functions via a content compartment's builtins would be unsafe, because
 // content script might have changed the builtins' prototypes' members.
 // Installing the whole set of builtins in the self-hosting compartment, OTOH,
 // would be wasteful: it increases memory usage and initialization time for
 // self-hosting compartment.
@@ -2506,16 +2521,20 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("CreateNamespaceBinding", intrinsic_CreateNamespaceBinding, 3, 0),
     JS_FN("InstantiateModuleFunctionDeclarations",
           intrinsic_InstantiateModuleFunctionDeclarations, 1, 0),
     JS_FN("ExecuteModule", intrinsic_ExecuteModule, 1, 0),
     JS_FN("NewModuleNamespace", intrinsic_NewModuleNamespace, 2, 0),
     JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4, 0),
     JS_FN("ModuleNamespaceExports", intrinsic_ModuleNamespaceExports, 1, 0),
 
+    JS_FN("IsPromiseObject", intrinsic_IsInstanceOfBuiltin<PromiseObject>, 1, 0),
+    JS_FN("CallPromiseMethodIfWrapped", CallNonGenericSelfhostedMethod<Is<PromiseObject>>, 2, 0),
+    JS_FN("PromiseResolve", intrinsic_PromiseResolve, 2, 0),
+
     JS_FS_END
 };
 
 void
 js::FillSelfHostingCompileOptions(CompileOptions& options)
 {
     /*
      * In self-hosting mode, scripts use JSOP_GETINTRINSIC instead of
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -244,17 +244,17 @@ https://bugzilla.mozilla.org/show_bug.cg
      "flags", "global", "ignoreCase", "multiline", "source", "sticky", "unicode"];
   gConstructorProperties['RegExp'] =
     constructorProps(["input", "lastMatch", "lastParen",
                       "leftContext", "rightContext", "$1", "$2", "$3", "$4",
                       "$5", "$6", "$7", "$8", "$9", "$_", "$&", "$+",
                       "$`", "$'", Symbol.species])
 
   gPrototypeProperties['Promise'] =
-    ["constructor", "catch", "then", Symbol.toStringTag];
+    ["constructor", "catch", "then", "finally", Symbol.toStringTag];
   gConstructorProperties['Promise'] =
     constructorProps(["resolve", "reject", "all", "race", Symbol.species]);
 
   gPrototypeProperties['ArrayBuffer'] =
     ["constructor", "byteLength", "slice", Symbol.toStringTag];
   gConstructorProperties['ArrayBuffer'] =
     constructorProps(["isView", Symbol.species]);