Bug 1170760 part 11. Add subclassing support to Promise::Reject. r=baku,efaust
authorBoris Zbarsky <bzbarsky@mit.edu>
Wed, 25 Nov 2015 15:48:09 -0500
changeset 308969 28e2db073b6593b647df66864fce8ecac935a3f5
parent 308968 f540f2da98a582b15bc2032f62d2db6e9c039f3a
child 308970 1e144edfd1d846ea8612a02fee5704ec28b06cc4
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, efaust
bugs1170760
milestone45.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 1170760 part 11. Add subclassing support to Promise::Reject. r=baku,efaust
browser/base/content/sync/aboutSyncTabs.js
dom/promise/Promise.cpp
dom/promise/Promise.h
dom/promise/tests/test_bug883683.html
dom/promise/tests/test_promise_xrays.html
dom/webidl/Promise.webidl
services/cloudsync/CloudSyncBookmarks.jsm
testing/web-platform/tests/js/builtins/Promise-subclassing.html
--- a/browser/base/content/sync/aboutSyncTabs.js
+++ b/browser/base/content/sync/aboutSyncTabs.js
@@ -268,17 +268,17 @@ var RemoteTabViewer = {
           };
           let tabEnt = this.createItem(tabAttrs);
           list.appendChild(tabEnt);
         }
       }
     }.bind(this);
 
     return CloudSync().tabs.getRemoteTabs()
-                           .then(updateTabList, Promise.reject);
+                           .then(updateTabList, Promise.reject.bind(Promise));
   },
 
   adjustContextMenu: function (event) {
     let mode = "all";
     switch (this._tabsList.selectedItems.length) {
       case 0:
         break;
       case 1:
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -1087,32 +1087,66 @@ Promise::Resolve(nsIGlobalObject* aGloba
   if (aRv.Failed()) {
     return nullptr;
   }
 
   promise->MaybeResolveInternal(aCx, aValue);
   return promise.forget();
 }
 
-/* static */ already_AddRefed<Promise>
+/* static */ void
 Promise::Reject(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
-                JS::Handle<JS::Value> aValue, ErrorResult& aRv)
+                JS::Handle<JS::Value> aValue,
+                JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv)
 {
+  // Implementation of
+  // http://www.ecma-international.org/ecma-262/6.0/#sec-promise.reject
+
+  JSContext* cx = aGlobal.Context();
+
   nsCOMPtr<nsIGlobalObject> global =
     do_QueryInterface(aGlobal.GetAsSupports());
   if (!global) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
+    return;
+  }
+
+  // Steps 1 and 2.
+  if (!aThisv.isObject()) {
+    aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
+    return;
+  }
+
+  // Step 3.
+  PromiseCapability capability(cx);
+  NewPromiseCapability(cx, global, aThisv, false, capability, aRv);
+  // Step 4.
+  if (aRv.Failed()) {
+    return;
   }
 
-  RefPtr<Promise> p = Reject(global, aGlobal.Context(), aValue, aRv);
+  // Step 5.
+  Promise* p = capability.mNativePromise;
   if (p) {
+    p->MaybeRejectInternal(cx, aValue);
     p->mRejectionStack = p->mAllocationStack;
+  } else {
+    JS::Rooted<JS::Value> value(cx, aValue);
+    JS::Rooted<JS::Value> ignored(cx);
+    if (!JS::Call(cx, JS::UndefinedHandleValue /* thisVal */,
+                  capability.mReject, JS::HandleValueArray(value),
+                  &ignored)) {
+      // Step 6.
+      aRv.NoteJSContextException();
+      return;
+    }
   }
-  return p.forget();
+
+  // Step 7.
+  aRetval.set(capability.PromiseValue());
 }
 
 /* static */ already_AddRefed<Promise>
 Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
                 JS::Handle<JS::Value> aValue, ErrorResult& aRv)
 {
   RefPtr<Promise> promise = Create(aGlobal, aRv);
   if (aRv.Failed()) {
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -167,19 +167,20 @@ public:
   Resolve(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
           JS::Handle<JS::Value> aValue,
           JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv);
 
   static already_AddRefed<Promise>
   Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
           JS::Handle<JS::Value> aValue, ErrorResult& aRv);
 
-  static already_AddRefed<Promise>
+  static void
   Reject(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
-         JS::Handle<JS::Value> aValue, ErrorResult& aRv);
+         JS::Handle<JS::Value> aValue,
+         JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv);
 
   static already_AddRefed<Promise>
   Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
          JS::Handle<JS::Value> aValue, ErrorResult& aRv);
 
   already_AddRefed<Promise>
   Then(JSContext* aCx, AnyCallback* aResolveCallback,
        AnyCallback* aRejectCallback, ErrorResult& aRv);
--- a/dom/promise/tests/test_bug883683.html
+++ b/dom/promise/tests/test_bug883683.html
@@ -12,17 +12,17 @@
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script type="application/javascript"><!--
 
 function runTest() {
-  [{}, {}, {}, {}, {}].reduce(Promise.reject);
+  [{}, {}, {}, {}, {}].reduce(Promise.reject.bind(Promise));
   ok(true, "No leaks with reject?");
 
   [{}, {}, {}, {}, {}].reduce(Promise.resolve.bind(Promise));
   ok(true, "No leaks with resolve?");
 
   [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { throw a; }); });
   ok(true, "No leaks with exception?");
 
--- a/dom/promise/tests/test_promise_xrays.html
+++ b/dom/promise/tests/test_promise_xrays.html
@@ -200,31 +200,45 @@ function testResolve3() {
       is(arg, 5, "Promise.resolve with chrome Promise should work");
     },
     function(e) {
       ok(false, "Promise.resolve with chrome Promise should not fail");
     }
   ).then(nextTest);
 }
 
+function testReject1() {
+  var p = win.Promise.reject(5);
+  ok(p instanceof win.Promise, "Promise.reject should return a promise");
+  p.then(
+    function(arg) {
+      ok(false, "Promise should be rejected");
+    },
+    function(e) {
+      is(e, 5, "Should get correct Promise.reject value");
+    }
+  ).then(nextTest);
+}
+
 var tests = [
   testLoadComplete,
   testHaveXray,
   testRace1,
   testRace2,
   testRace3,
   testRace4,
   testAll1,
   testAll2,
   testAll3,
   testAll4,
   testAll5,
   testResolve1,
   testResolve2,
   testResolve3,
+  testReject1,
 ];
 
 function nextTest() {
   if (tests.length == 0) {
     SimpleTest.finish();
     return;
   }
   tests.shift()();
--- a/dom/webidl/Promise.webidl
+++ b/dom/webidl/Promise.webidl
@@ -20,18 +20,18 @@ callback AnyCallback = any (any value);
  Exposed=(Window,Worker,System)]
 // Need to escape "Promise" so it's treated as an identifier.
 interface _Promise {
   // Have to use "any" (or "object", but "any" is simpler) as the type to
   // support the subclassing behavior, since nothing actually requires the
   // return value of PromiseSubclass.resolve/reject to be a Promise object.
   [NewObject, Throws]
   static any resolve(optional any value);
-  [NewObject]
-  static Promise<void> reject(optional any value);
+  [NewObject, Throws]
+  static any reject(optional any value);
 
   // The [TreatNonCallableAsNull] annotation is required since then() should do
   // nothing instead of throwing errors when non-callable arguments are passed.
   [NewObject, MethodIdentityTestable]
   Promise<any> then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null,
                     [TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
 
   [NewObject]
--- a/services/cloudsync/CloudSyncBookmarks.jsm
+++ b/services/cloudsync/CloudSyncBookmarks.jsm
@@ -289,33 +289,33 @@ var RootFolder = function (rootId, rootN
       results = Array.prototype.concat.apply([], results);
       let promises = [];
       results.map(function (result) {
         let promise = PlacesWrapper.localIdToGuid(result.parent).then(
           function (guidResult) {
             result.parent = guidResult;
             return Promise.resolve(result);
           },
-          Promise.reject
+          Promise.reject.bind(Promise)
         );
         promises.push(promise);
       });
       return Promise.all(promises);
     }
 
     function getAnnos(results) {
       results = Array.prototype.concat.apply([], results);
       let promises = [];
       results.map(function (result) {
         let promise = PlacesWrapper.getItemAnnotationsForLocalId(result.id).then(
           function (annos) {
             result.annos = annos;
             return Promise.resolve(result);
           },
-          Promise.reject
+          Promise.reject.bind(Promise)
         );
         promises.push(promise);
       });
       return Promise.all(promises);
     }
 
     let promises = [
       getFolders(folders),
@@ -347,17 +347,17 @@ var RootFolder = function (rootId, rootN
     function getParentGuids(results) {
       let promises = [];
       results.map(function (result) {
         let promise = PlacesWrapper.localIdToGuid(result.parent).then(
           function (guidResult) {
             result.parent = guidResult;
             return Promise.resolve(result);
           },
-          Promise.reject
+          Promise.reject.bind(Promise)
         );
         promises.push(promise);
       });
       return Promise.all(promises);
     }
 
     PlacesWrapper.getItemsByGuid(guids, types)
                  .then(getParentGuids)
@@ -554,17 +554,17 @@ var RootFolder = function (rootId, rootN
         }
       }
 
       for (let item of items) {
         if (!item || 'object' !== typeof(item)) {
           continue;
         }
 
-        let promise = exists(item).then(handleSortedItem, Promise.reject);
+        let promise = exists(item).then(handleSortedItem, Promise.reject.bind(Promise));
         promises.push(promise);
       }
 
       return Promise.all(promises);
     }
 
     let processNewFolders = function () {
       let newFolderGuids = Object.keys(newFolders);
@@ -583,28 +583,28 @@ var RootFolder = function (rootId, rootN
       let promises = [];
       for (let guid of newFolderRoots) {
         let root = newFolders[guid];
         let promise = Promise.resolve();
         promise = promise.then(
           function () {
             return _createItem(root);
           },
-          Promise.reject
+          Promise.reject.bind(Promise)
         );
         let items = [].concat(root._children);
 
         while (items.length) {
           let item = newFolders[items.shift()];
           items = items.concat(item._children);
           promise = promise.then(
             function () {
               return _createItem(item);
             },
-            Promise.reject
+            Promise.reject.bind(Promise)
           );
         }
         promises.push(promise);
       }
 
       return Promise.all(promises);
     }
 
--- a/testing/web-platform/tests/js/builtins/Promise-subclassing.html
+++ b/testing/web-platform/tests/js/builtins/Promise-subclassing.html
@@ -201,9 +201,20 @@ promise_test(function testPromiseResolve
   assert_false(v instanceof LoggingPromise);
 
   var results = Promise.all([p, q, r, s, u, v]);
   return results.then(function(arg) {
     assert_array_equals(arg, [5, 5, 5, 6, 6, 6]);
   });
 }, "Promise.resolve behavior");
 
+promise_test(function testPromiseReject() {
+  clearLog();
+  var p = LoggingPromise.reject(5);
+  var log = takeLog();
+  assert_array_equals(log, ["Reject 1", "Constructor 1"]);
+
+  return p.catch(function(arg) {
+    assert_equals(arg, 5);
+  });
+}, "Promise.reject behavior");
+
 </script>