Bug 1170760 part 10. Add subclassing support to Promise::Resolve. r=baku,efaust
authorBoris Zbarsky <bzbarsky@mit.edu>
Wed, 25 Nov 2015 15:48:09 -0500
changeset 308968 f540f2da98a582b15bc2032f62d2db6e9c039f3a
parent 308967 d282d800811ea5c01de02075b954077c8e1412c8
child 308969 28e2db073b6593b647df66864fce8ecac935a3f5
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 10. Add subclassing support to Promise::Resolve. r=baku,efaust
dom/bindings/Bindings.conf
dom/bindings/Codegen.py
dom/bindings/test/TestBindingHeader.h
dom/bindings/test/TestCodeGen.webidl
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
testing/web-platform/tests/js/builtins/Promise-subclassing.html
toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1944,17 +1944,23 @@ DOMInterfaces = {
         'headerFile': 'TestExampleProxyInterface-example.h',
         'register': False
         },
 
 'TestDeprecatedInterface' : {
         # Keep this in sync with TestExampleInterface
         'headerFile': 'TestBindingHeader.h',
         'register': False
-        }
+        },
+
+'TestInterfaceWithPromiseConstructorArg' : {
+        'headerFile': 'TestBindingHeader.h',
+        'register': False,
+        },
+
 }
 
 # These are temporary, until they've been converted to use new DOM bindings
 def addExternalIface(iface, nativeType=None, headerFile=None,
                      notflattened=False):
     if iface in DOMInterfaces:
         raise Exception('Interface declared both as WebIDL and External interface')
     domInterface = {
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -5088,41 +5088,134 @@ def getJSToNativeConversionInfo(type, de
             else:
                 declType = "NonNull<" + typeName + ">"
 
         templateBody = ""
         if forceOwningType:
             templateBody += 'static_assert(IsRefcounted<%s>::value, "We can only store refcounted classes.");' % typeName
 
         if isPromise:
+            # Per spec, what we're supposed to do is take the original
+            # Promise.resolve and call it with the original Promise as this
+            # value to make a Promise out of whatever value we actually have
+            # here.  The question is which global we should use.  There are
+            # several cases to consider:
+            #
+            # 1) Normal call to API with a Promise argument.  This is a case the
+            #    spec covers, and we should be using the current Realm's
+            #    Promise.  That means the current compartment.
+            # 2) Call to API with a Promise argument over Xrays.  In practice,
+            #    this sort of thing seems to be used for giving an API
+            #    implementation a way to wait for conclusion of an asyc
+            #    operation, _not_ to expose the Promise to content code.  So we
+            #    probably want to allow callers to use such an API in a
+            #    "natural" way, by passing chrome-side promises; indeed, that
+            #    may be all that the caller has to represent their async
+            #    operation.  That means we really need to do the
+            #    Promise.resolve() in the caller (chrome) compartment: if we do
+            #    it in the content compartment, we will try to call .then() on
+            #    the chrome promise while in the content compartment, which will
+            #    throw and we'll just get a rejected Promise.  Note that this is
+            #    also the reason why a caller who has a chrome Promise
+            #    representing an async operation can't itself convert it to a
+            #    content-side Promise (at least not without some serious
+            #    gyrations).
+            # 3) Promise return value from a callback or callback interface.
+            #    This is in theory a case the spec covers but in practice it
+            #    really doesn't define behavior here because it doesn't define
+            #    what Realm we're in after the callback returns, which is when
+            #    the argument conversion happens.  We will use the current
+            #    compartment, which is the compartment of the callable (which
+            #    may itself be a cross-compartment wrapper itself), which makes
+            #    as much sense as anything else. In practice, such an API would
+            #    once again be providing a Promise to signal completion of an
+            #    operation, which would then not be exposed to anyone other than
+            #    our own implementation code.
+            # 4) Return value from a JS-implemented interface.  In this case we
+            #    have a problem.  Our current compartment is the compartment of
+            #    the JS implementation.  But if the JS implementation returned
+            #    a page-side Promise (which is a totally sane thing to do, and
+            #    in fact the right thing to do given that this return value is
+            #    going right to content script) then we don't want to
+            #    Promise.resolve with our current compartment Promise, because
+            #    that will wrap it up in a chrome-side Promise, which is
+            #    decidedly _not_ what's desired here.  So in that case we
+            #    should really unwrap the return value and use the global of
+            #    the result.  CheckedUnwrap should be good enough for that; if
+            #    it fails, then we're failing unwrap while in a
+            #    system-privileged compartment, so presumably we have a dead
+            #    object wrapper.  Just error out.  Do NOT fall back to using
+            #    the current compartment instead: that will return a
+            #    system-privileged rejected (because getting .then inside
+            #    resolve() failed) Promise to the caller, which they won't be
+            #    able to touch.  That's not helpful.  If we error out, on the
+            #    other hand, they will get a content-side rejected promise.
+            #    Same thing if the value returned is not even an object.
+            if isCallbackReturnValue == "JSImpl":
+                # Case 4 above.  Note that globalObj defaults to the current
+                # compartment global.  Note that we don't use $*{exceptionCode}
+                # here because that will try to aRv.Throw(NS_ERROR_UNEXPECTED)
+                # which we don't really want here.
+                assert exceptionCode == "aRv.Throw(NS_ERROR_UNEXPECTED);\nreturn nullptr;\n"
+                getPromiseGlobal = fill(
+                    """
+                    if (!$${val}.isObject()) {
+                      aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}"));
+                      return nullptr;
+                    }
+                    JSObject* unwrappedVal = js::CheckedUnwrap(&$${val}.toObject());
+                    if (!unwrappedVal) {
+                      // A slight lie, but not much of one, for a dead object wrapper.
+                      aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}"));
+                      return nullptr;
+                    }
+                    globalObj = js::GetGlobalForObjectCrossCompartment(unwrappedVal);
+                    """,
+                    sourceDescription=sourceDescription)
+            else:
+                getPromiseGlobal = ""
+
             templateBody = fill(
                 """
-                { // Scope for our GlobalObject and ErrorResult
-
-                  // Might as well use CurrentGlobalOrNull here; that will at
-                  // least give us the same behavior as if the caller just called
-                  // Promise.resolve() themselves.
+                { // Scope for our GlobalObject, ErrorResult, JSAutoCompartment,
+                  // etc.
+
                   JS::Rooted<JSObject*> globalObj(cx, JS::CurrentGlobalOrNull(cx));
+                  $*{getPromiseGlobal}
+                  JSAutoCompartment ac(cx, globalObj);
                   GlobalObject promiseGlobal(cx, globalObj);
                   if (promiseGlobal.Failed()) {
                     $*{exceptionCode}
                   }
                   ErrorResult promiseRv;
                   JS::Handle<JSObject*> promiseCtor =
                     PromiseBinding::GetConstructorObjectHandle(cx, globalObj);
                   if (!promiseCtor) {
                     $*{exceptionCode}
                   }
                   JS::Rooted<JS::Value> resolveThisv(cx, JS::ObjectValue(*promiseCtor));
-                  $${declName} = Promise::Resolve(promiseGlobal, resolveThisv, $${val}, promiseRv);
+                  JS::Rooted<JS::Value> resolveResult(cx);
+                  JS::Rooted<JS::Value> valueToResolve(cx, $${val});
+                  if (!JS_WrapValue(cx, &valueToResolve)) {
+                    $*{exceptionCode}
+                  }
+                  Promise::Resolve(promiseGlobal, resolveThisv, valueToResolve,
+                                   &resolveResult, promiseRv);
                   if (promiseRv.MaybeSetPendingException(cx)) {
                     $*{exceptionCode}
                   }
+                  nsresult unwrapRv = UNWRAP_OBJECT(Promise, &resolveResult.toObject(), $${declName});
+                  if (NS_FAILED(unwrapRv)) { // Quite odd
+                    promiseRv.Throw(unwrapRv);
+                    promiseRv.MaybeSetPendingException(cx);
+                    $*{exceptionCode}
+                  }
                 }
                 """,
+                getPromiseGlobal=getPromiseGlobal,
                 exceptionCode=exceptionCode)
         elif not descriptor.skipGen and not descriptor.interface.isConsequential() and not descriptor.interface.isExternal():
             if failureCode is not None:
                 templateBody += str(CastableObjectUnwrapper(
                     descriptor,
                     "&${val}.toObject()",
                     "${declName}",
                     failureCode))
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -1366,12 +1366,24 @@ public:
   already_AddRefed<TestDeprecatedInterface>
     Constructor(const GlobalObject&, ErrorResult&);
 
   static void AlsoDeprecated(const GlobalObject&);
 
   virtual nsISupports* GetParentObject();
 };
 
+class TestInterfaceWithPromiseConstructorArg : public nsISupports, public nsWrapperCache
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  static
+  already_AddRefed<TestInterfaceWithPromiseConstructorArg>
+    Constructor(const GlobalObject&, Promise&, ErrorResult&);
+
+  virtual nsISupports* GetParentObject();
+};
+
 } // namespace dom
 } // namespace mozilla
 
 #endif /* TestBindingHeader_h */
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -50,16 +50,17 @@ callback interface TestCallbackInterface
   void passFloat32Array(Float32Array arg);
   void passFloat64Array(Float64Array arg);
   void passSequenceOfArrayBuffers(sequence<ArrayBuffer> arg);
   void passSequenceOfNullableArrayBuffers(sequence<ArrayBuffer?> arg);
   void passVariadicTypedArray(Float32Array... arg);
   void passVariadicNullableTypedArray(Float32Array?... arg);
   Uint8Array receiveUint8Array();
   attribute Uint8Array uint8ArrayAttr;
+  Promise<void> receivePromise();
 };
 
 callback interface TestSingleOperationCallbackInterface {
   TestInterface doSomething(short arg, sequence<double> anotherArg);
 };
 
 enum TestEnum {
   "1",
@@ -1029,16 +1030,19 @@ dictionary Dict : ParentDict {
 
   long dashed-name;
 
   required long requiredLong;
   required object requiredObject;
 
   CustomEventInit customEventInit;
   TestDictionaryTypedef dictionaryTypedef;
+
+  Promise<void> promise;
+  sequence<Promise<void>> promiseSequence;
 };
 
 dictionary ParentDict : GrandparentDict {
   long c = 5;
   TestInterface someInterface;
   TestInterface? someNullableInterface = null;
   TestExternalInterface someExternalInterface;
   any parentAny;
@@ -1156,8 +1160,12 @@ interface TestCppKeywordNamedMethodsInte
   long volatile();
 };
 
 [Deprecated="GetAttributeNode", Constructor()]
 interface TestDeprecatedInterface {
   static void alsoDeprecated();
 };
 
+
+[Constructor(Promise<void> promise)]
+interface TestInterfaceWithPromiseConstructorArg {
+};
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -1001,44 +1001,87 @@ Promise::NewPromiseCapability(JSContext*
   aCapability.mReject = v;
 
   // Step 10.
   aCapability.mPromise = promiseVal;
 
   // Step 11 doesn't need anything, since the PromiseCapability was passed in.
 }
 
-/* static */ already_AddRefed<Promise>
+/* static */ void
 Promise::Resolve(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)
 {
-  // If a Promise was passed, just return it.
-  if (aValue.isObject()) {
-    JS::Rooted<JSObject*> valueObj(aGlobal.Context(), &aValue.toObject());
-    Promise* nextPromise;
-    nsresult rv = UNWRAP_OBJECT(Promise, valueObj, nextPromise);
+  // Implementation of
+  // http://www.ecma-international.org/ecma-262/6.0/#sec-promise.resolve
 
-    if (NS_SUCCEEDED(rv)) {
-      RefPtr<Promise> addRefed = nextPromise;
-      return addRefed.forget();
-    }
-  }
+  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;
   }
 
-  RefPtr<Promise> p = Resolve(global, aGlobal.Context(), aValue, aRv);
+  // Step 3.  If a Promise was passed and matches our constructor, just return it.
+  if (aValue.isObject()) {
+    JS::Rooted<JSObject*> valueObj(cx, &aValue.toObject());
+    Promise* nextPromise;
+    nsresult rv = UNWRAP_OBJECT(Promise, valueObj, nextPromise);
+
+    if (NS_SUCCEEDED(rv)) {
+      JS::Rooted<JS::Value> constructor(cx);
+      if (!JS_GetProperty(cx, valueObj, "constructor", &constructor)) {
+        aRv.NoteJSContextException();
+        return;
+      }
+
+      // Cheat instead of calling JS_SameValue, since we know one's an object.
+      if (aThisv == constructor) {
+        aRetval.setObject(*valueObj);
+        return;
+      }
+    }
+  }
+
+  // Step 4.
+  PromiseCapability capability(cx);
+  NewPromiseCapability(cx, global, aThisv, false, capability, aRv);
+  // Step 5.
+  if (aRv.Failed()) {
+    return;
+  }
+
+  // Step 6.
+  Promise* p = capability.mNativePromise;
   if (p) {
+    p->MaybeResolveInternal(cx, aValue);
     p->mFullfillmentStack = p->mAllocationStack;
+  } else {
+    JS::Rooted<JS::Value> value(cx, aValue);
+    JS::Rooted<JS::Value> ignored(cx);
+    if (!JS::Call(cx, JS::UndefinedHandleValue /* thisVal */,
+                  capability.mResolve, JS::HandleValueArray(value),
+                  &ignored)) {
+      // Step 7.
+      aRv.NoteJSContextException();
+      return;
+    }
   }
-  return p.forget();
+
+  // Step 8.
+  aRetval.set(capability.PromiseValue());
 }
 
 /* static */ already_AddRefed<Promise>
 Promise::Resolve(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
@@ -158,19 +158,20 @@ public:
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   static already_AddRefed<Promise>
   Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
               ErrorResult& aRv, JS::Handle<JSObject*> aDesiredProto);
 
-  static already_AddRefed<Promise>
+  static void
   Resolve(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>
   Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
           JS::Handle<JS::Value> aValue, ErrorResult& aRv);
 
   static already_AddRefed<Promise>
   Reject(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
          JS::Handle<JS::Value> aValue, ErrorResult& aRv);
--- a/dom/promise/tests/test_bug883683.html
+++ b/dom/promise/tests/test_bug883683.html
@@ -15,17 +15,17 @@
 </div>
 <pre id="test">
 <script type="application/javascript"><!--
 
 function runTest() {
   [{}, {}, {}, {}, {}].reduce(Promise.reject);
   ok(true, "No leaks with reject?");
 
-  [{}, {}, {}, {}, {}].reduce(Promise.resolve);
+  [{}, {}, {}, {}, {}].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?");
 
   [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { }); });
   ok(true, "No leaks with empty promise?");
 
--- a/dom/promise/tests/test_promise_xrays.html
+++ b/dom/promise/tests/test_promise_xrays.html
@@ -168,28 +168,63 @@ function testAll5() {
       ok(arg instanceof win.Array, "Should get an Array from Promise.all (5)");
     },
     function(e) {
       ok(false, "testAll5 threw exception: " + e);
     }
   ).then(nextTest);
 }
 
+function testResolve1() {
+  var p = win.Promise.resolve(5);
+  ok(p instanceof win.Promise, "Promise.resolve should return a promise");
+  p.then(
+    function(arg) {
+      is(arg, 5, "Should get correct Promise.resolve value");
+    },
+    function(e) {
+      ok(false, "testAll5 threw exception: " + e);
+    }
+  ).then(nextTest);
+}
+
+function testResolve2() {
+  var p = win.Promise.resolve(5);
+  var q = win.Promise.resolve(p);
+  is(q, p, "Promise.resolve should just pass through Promise values");
+  nextTest();
+}
+
+function testResolve3() {
+  var p = win.Promise.resolve(Promise.resolve(5));
+  p.then(
+    function(arg) {
+      is(arg, 5, "Promise.resolve with chrome Promise should work");
+    },
+    function(e) {
+      ok(false, "Promise.resolve with chrome Promise should not fail");
+    }
+  ).then(nextTest);
+}
+
 var tests = [
   testLoadComplete,
   testHaveXray,
   testRace1,
   testRace2,
   testRace3,
   testRace4,
   testAll1,
   testAll2,
   testAll3,
   testAll4,
   testAll5,
+  testResolve1,
+  testResolve2,
+  testResolve3,
 ];
 
 function nextTest() {
   if (tests.length == 0) {
     SimpleTest.finish();
     return;
   }
   tests.shift()();
--- a/dom/webidl/Promise.webidl
+++ b/dom/webidl/Promise.webidl
@@ -15,23 +15,21 @@ callback PromiseInit = void (object reso
 [TreatNonCallableAsNull]
 callback AnyCallback = any (any value);
 
 // REMOVE THE RELEVANT ENTRY FROM test_interfaces.html WHEN THIS IS IMPLEMENTED IN JS.
 [Constructor(PromiseInit init),
  Exposed=(Window,Worker,System)]
 // Need to escape "Promise" so it's treated as an identifier.
 interface _Promise {
-  // Disable the static methods when the interface object is supposed to be
-  // disabled, just in case some code decides to walk over to .constructor from
-  // the proto of a promise object or someone screws up and manages to create a
-  // Promise object in this scope without having resolved the interface object
-  // first.
-  [NewObject]
-  static Promise<any> resolve(optional any value);
+  // 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);
 
   // 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);
--- a/testing/web-platform/tests/js/builtins/Promise-subclassing.html
+++ b/testing/web-platform/tests/js/builtins/Promise-subclassing.html
@@ -122,46 +122,88 @@ promise_test(function testBasicConstruct
   });
 }, "Basic constructor behavior");
 
 promise_test(function testPromiseRace() {
   clearLog();
   var p = LoggingPromise.race(new LoggingIterable([1, 2]));
   var log = takeLog();
   assert_array_equals(log, ["Race 1", "Constructor 1",
-                            "Next 1", "Resolve 1",
-                            "Next 2", "Resolve 2",
+                            "Next 1", "Resolve 1", "Constructor 2",
+                            "Then 1",
+                            "Next 2", "Resolve 2", "Constructor 3",
+                            "Then 2",
                             "Next 3"]);
   assert_true(p instanceof LoggingPromise);
   return p.then(function(arg) {
     assert_true(arg == 1 || arg == 2);
   });
 }, "Promise.race behavior");
 
 promise_test(function testPromiseRaceNoSpecies() {
   clearLog();
   var p = SpeciesLessPromise.race(new LoggingIterable([1, 2]));
   var log = takeLog();
   assert_array_equals(log, ["Race 1", "Constructor 1",
-                            "Next 1", "Resolve 1",
-                            "Next 2", "Resolve 2",
+                            "Next 1", "Resolve 1", "Constructor 2",
+                            "Then 1",
+                            "Next 2", "Resolve 2", "Constructor 3",
+                            "Then 2",
                             "Next 3"]);
   assert_true(p instanceof SpeciesLessPromise);
   return p.then(function(arg) {
     assert_true(arg == 1 || arg == 2);
   });
 }, "Promise.race without species behavior");
 
 promise_test(function testPromiseAll() {
   clearLog();
   var p = LoggingPromise.all(new LoggingIterable([1, 2]));
   var log = takeLog();
   assert_array_equals(log, ["All 1", "Constructor 1",
-                            "Next 1", "Resolve 1",
-                            "Next 2", "Resolve 2",
+                            "Next 1", "Resolve 1", "Constructor 2",
+                            "Then 1",
+                            "Next 2", "Resolve 2", "Constructor 3",
+                            "Then 2",
                             "Next 3"]);
   assert_true(p instanceof LoggingPromise);
   return p.then(function(arg) {
     assert_array_equals(arg, [1, 2]);
   });
 }, "Promise.all behavior");
 
+promise_test(function testPromiseResolve() {
+  clearLog();
+  var p = LoggingPromise.resolve(5);
+  var log = takeLog();
+  assert_array_equals(log, ["Resolve 1", "Constructor 1"]);
+  var q = LoggingPromise.resolve(p);
+  assert_equals(p, q,
+                "Promise.resolve with same constructor should preserve identity");
+  log = takeLog();
+  assert_array_equals(log, ["Resolve 1"]);
+
+  var r = Promise.resolve(p);
+  assert_not_equals(p, r,
+                    "Promise.resolve with different constructor should " +
+                    "create a new Promise instance (1)")
+  var s = Promise.resolve(6);
+  var u = LoggingPromise.resolve(s);
+  log = takeLog();
+  assert_array_equals(log, ["Resolve 1", "Constructor 1"]);
+  assert_not_equals(s, u,
+                    "Promise.resolve with different constructor should " +
+                    "create a new Promise instance (2)")
+
+  Object.defineProperty(s, "constructor", { value: LoggingPromise });
+  var v = LoggingPromise.resolve(s);
+  log = takeLog();
+  assert_array_equals(log, ["Resolve 1"]);
+  assert_equals(v, s, "Faking the .constructor should work");
+  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");
+
 </script>
--- a/toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js
+++ b/toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js
@@ -143,17 +143,17 @@ add_task(function* test_phase_removeBloc
     }
     do_print("Waiting (should lift very quickly)");
     yield lock.wait();
     do_remove_blocker(lock, blockers[0], false);
 
 
     do_print("Attempt to remove a blocker after wait");
     lock = makeLock(kind);
-    blocker = Promise.resolve;
+    blocker = Promise.resolve.bind(Promise);
     yield lock.wait();
     do_remove_blocker(lock, blocker, false);
 
     do_print("Attempt to remove non-registered blocker after wait()");
     do_remove_blocker(lock, "foo", false);
     do_remove_blocker(lock, null, false);
   }