Bug 1436276. Bindings should create their return promises in the current compartment even when called over Xrays. r=bholley
authorBoris Zbarsky <bzbarsky@mit.edu>
Sat, 10 Feb 2018 01:34:10 -0500
changeset 403230 fa2042520eb9252f535ba369b7dcb678c1c96703
parent 403229 1f3a5b496acd2288cc8cf0c32af86cb35157ea4e
child 403231 4ad4e15fed408dd842664fd7117d27290d294133
push id99753
push userbzbarsky@mozilla.com
push dateSat, 10 Feb 2018 06:36:44 +0000
treeherdermozilla-inbound@fa2042520eb9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs1436276
milestone60.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 1436276. Bindings should create their return promises in the current compartment even when called over Xrays. r=bholley These are cases that are implementing the "convert an exception to a Promise" steps of the Web IDL spec. Typically the exception is thrown in the current compartment; the Promise returned should simply match that. Otherwise we can end up with a situation in which the promise doesn't actaully have access to its rejection value, which will cause problems if someone uses then() on the promise: the return value of the then() call will get a sanitized exception instead of the real one. We _could_ try to match the actual compartment of the exception, in theory. But it's not clear why this would be preferable to using the current compartment, even if there were cases in which the exception _doesn't_ match the current compartment. Which there likely are not. MozReview-Commit-ID: Ac2BHIHxfvY
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/test/TestFunctions.cpp
dom/bindings/test/TestFunctions.h
dom/promise/tests/chrome.ini
dom/promise/tests/file_promise_argument_tests.js
dom/promise/tests/file_promise_retval_tests.js
dom/promise/tests/mochitest.ini
dom/promise/tests/test_promise_argument_xrays.html
dom/promise/tests/test_promise_retval.html
dom/promise/tests/test_promise_retval_xrays.html
dom/webidl/TestFunctions.webidl
js/xpconnect/src/Sandbox.cpp
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -2908,62 +2908,57 @@ GenericBindingGetter(JSContext* cx, unsi
   }
 #endif
   return ok;
 }
 
 bool
 GenericPromiseReturningBindingGetter(JSContext* cx, unsigned argc, JS::Value* vp)
 {
-  // Make sure to save the callee before someone maybe messes with rval().
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  JS::Rooted<JSObject*> callee(cx, &args.callee());
 
   // We could invoke GenericBindingGetter here, but that involves an
   // extra call.  Manually inline it instead.
   const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     ThrowInvalidThis(cx, args, false, protoID);
-    return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                     args.rval());
+    return ConvertExceptionToPromise(cx, args.rval());
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
   // NOTE: we want to leave obj in its initial compartment, so don't want to
   // pass it to UnwrapObject.
   JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
     binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
     nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
                                                                    self,
                                                                    protoID,
                                                                    info->depth);
     if (NS_FAILED(rv)) {
       ThrowInvalidThis(cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                        protoID);
-      return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                       args.rval());
+      return ConvertExceptionToPromise(cx, args.rval());
     }
   }
   MOZ_ASSERT(info->type() == JSJitInfo::Getter);
   JSJitGetterOp getter = info->getter;
   bool ok = getter(cx, obj, self, JSJitGetterCallArgs(args));
   if (ok) {
 #ifdef DEBUG
     AssertReturnTypeMatchesJitinfo(info, args.rval());
 #endif
     return true;
   }
 
   // Promise-returning getters always return objects
   MOZ_ASSERT(info->returnType() == JSVAL_TYPE_OBJECT);
-  return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                   args.rval());
+  return ConvertExceptionToPromise(cx, args.rval());
 }
 
 bool
 GenericBindingSetter(JSContext* cx, unsigned argc, JS::Value* vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
@@ -3039,114 +3034,99 @@ GenericBindingMethod(JSContext* cx, unsi
   }
 #endif
   return ok;
 }
 
 bool
 GenericPromiseReturningBindingMethod(JSContext* cx, unsigned argc, JS::Value* vp)
 {
-  // Make sure to save the callee before someone maybe messes with rval().
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  JS::Rooted<JSObject*> callee(cx, &args.callee());
 
   // We could invoke GenericBindingMethod here, but that involves an
   // extra call.  Manually inline it instead.
   const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     ThrowInvalidThis(cx, args, false, protoID);
-    return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                     args.rval());
+    return ConvertExceptionToPromise(cx, args.rval());
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
   // NOTE: we want to leave obj in its initial compartment, so don't want to
   // pass it to UnwrapObject.
   JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
     binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
     nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
                                                                    self,
                                                                    protoID,
                                                                    info->depth);
     if (NS_FAILED(rv)) {
       ThrowInvalidThis(cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                        protoID);
-      return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                       args.rval());
+      return ConvertExceptionToPromise(cx, args.rval());
     }
   }
   MOZ_ASSERT(info->type() == JSJitInfo::Method);
   JSJitMethodOp method = info->method;
   bool ok = method(cx, obj, self, JSJitMethodCallArgs(args));
   if (ok) {
 #ifdef DEBUG
     AssertReturnTypeMatchesJitinfo(info, args.rval());
 #endif
     return true;
   }
 
   // Promise-returning methods always return objects
   MOZ_ASSERT(info->returnType() == JSVAL_TYPE_OBJECT);
-  return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                   args.rval());
+  return ConvertExceptionToPromise(cx, args.rval());
 }
 
 bool
 StaticMethodPromiseWrapper(JSContext* cx, unsigned argc, JS::Value* vp)
 {
-  // Make sure to save the callee before someone maybe messes with rval().
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  JS::Rooted<JSObject*> callee(cx, &args.callee());
 
   const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   MOZ_ASSERT(info);
   MOZ_ASSERT(info->type() == JSJitInfo::StaticMethod);
 
   bool ok = info->staticMethod(cx, argc, vp);
   if (ok) {
     return true;
   }
 
-  return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                   args.rval());
+  return ConvertExceptionToPromise(cx, args.rval());
 }
 
 bool
 ConvertExceptionToPromise(JSContext* cx,
-                          JSObject* promiseScope,
                           JS::MutableHandle<JS::Value> rval)
 {
-  {
-    JSAutoCompartment ac(cx, promiseScope);
-
-    JS::Rooted<JS::Value> exn(cx);
-    if (!JS_GetPendingException(cx, &exn)) {
-      // This is very important: if there is no pending exception here but we're
-      // ending up in this code, that means the callee threw an uncatchable
-      // exception.  Just propagate that out as-is.
-      return false;
-    }
-
-    JS_ClearPendingException(cx);
-
-    JSObject* promise = JS::CallOriginalPromiseReject(cx, exn);
-    if (!promise) {
-      // We just give up.  Put the exception back.
-      JS_SetPendingException(cx, exn);
-      return false;
-    }
-
-    rval.setObject(*promise);
+  JS::Rooted<JS::Value> exn(cx);
+  if (!JS_GetPendingException(cx, &exn)) {
+    // This is very important: if there is no pending exception here but we're
+    // ending up in this code, that means the callee threw an uncatchable
+    // exception.  Just propagate that out as-is.
+    return false;
   }
 
-  // Now make sure we rewrap promise back into the compartment we want
-  return JS_WrapValue(cx, rval);
+  JS_ClearPendingException(cx);
+
+  JSObject* promise = JS::CallOriginalPromiseReject(cx, exn);
+  if (!promise) {
+    // We just give up.  Put the exception back.
+    JS_SetPendingException(cx, exn);
+    return false;
+  }
+
+  rval.setObject(*promise);
+  return true;
 }
 
 /* static */
 void
 CreateGlobalOptions<nsGlobalWindowInner>::TraceGlobal(JSTracer* aTrc, JSObject* aObj)
 {
   xpc::TraceXPCGlobal(aTrc, aObj);
 }
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -3242,21 +3242,18 @@ bool
 StaticMethodPromiseWrapper(JSContext* cx, unsigned argc, JS::Value* vp);
 
 // ConvertExceptionToPromise should only be called when we have an error
 // condition (e.g. returned false from a JSAPI method).  Note that there may be
 // no exception on cx, in which case this is an uncatchable failure that will
 // simply be propagated.  Otherwise this method will attempt to convert the
 // exception to a Promise rejected with the exception that it will store in
 // rval.
-//
-// promiseScope should be the scope in which the Promise should be created.
 bool
 ConvertExceptionToPromise(JSContext* cx,
-                          JSObject* promiseScope,
                           JS::MutableHandle<JS::Value> rval);
 
 #ifdef DEBUG
 void
 AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitinfo,
                                JS::Handle<JS::Value> aValue);
 #endif
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -8838,48 +8838,40 @@ class CGGenericPromiseReturningMethod(CG
     """
     A class for generating the C++ code for an IDL method that returns a Promise.
 
     Does not handle cross-origin this.
     """
     def __init__(self, descriptor):
         unwrapFailureCode = dedent("""
             ThrowInvalidThis(cx, args, %%(securityError)s, "%s");\n
-            return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                             args.rval());\n""" %
+            return ConvertExceptionToPromise(cx, args.rval());\n""" %
                                    descriptor.interface.identifier.name)
 
         name = "genericPromiseReturningMethod"
-        customCallArgs = dedent("""
-            JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-            // Make sure to save the callee before someone maybe messes with rval().
-            JS::Rooted<JSObject*> callee(cx, &args.callee());
-        """)
 
         CGAbstractBindingMethod.__init__(self, descriptor, name,
                                          JSNativeArguments(),
-                                         callArgs=customCallArgs,
                                          unwrapFailureCode=unwrapFailureCode)
 
     def generate_code(self):
         return CGGeneric(dedent("""
             const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
             MOZ_ASSERT(info->type() == JSJitInfo::Method);
             JSJitMethodOp method = info->method;
             bool ok = method(cx, obj, self, JSJitMethodCallArgs(args));
             if (ok) {
             #ifdef DEBUG
               AssertReturnTypeMatchesJitinfo(info, args.rval());
             #endif
               return true;
             }
 
             MOZ_ASSERT(info->returnType() == JSVAL_TYPE_OBJECT);
-            return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                             args.rval());
+            return ConvertExceptionToPromise(cx, args.rval());
             """))
 
 
 class CGSpecializedMethod(CGAbstractStaticMethod):
     """
     A class for generating the C++ code for a specialized method that the JIT
     can call with lower overhead.
     """
@@ -8915,25 +8907,21 @@ class CGMethodPromiseWrapper(CGAbstractS
         name = self.makeName(methodToWrap.name)
         args = list(methodToWrap.args)
         CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args,
                                         canRunScript=True)
 
     def definition_body(self):
         return fill(
             """
-            // Make sure to save the callee before someone maybe messes
-            // with rval().
-            JS::Rooted<JSObject*> callee(cx, &args.callee());
             bool ok = ${methodName}(${args});
             if (ok) {
               return true;
             }
-            return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                             args.rval());
+            return ConvertExceptionToPromise(cx, args.rval());
             """,
             methodName=self.method.name,
             args=", ".join(arg.name for arg in self.args))
 
     @staticmethod
     def makeName(methodName):
         return methodName + "_promiseWrapper"
 
@@ -9206,31 +9194,23 @@ class CGGenericPromiseReturningGetter(CG
     A class for generating the C++ code for an IDL getter that returns a Promise.
 
     Does not handle cross-origin this or [LenientThis].
     """
     def __init__(self, descriptor):
         unwrapFailureCode = fill(
             """
             ThrowInvalidThis(cx, args, %%(securityError)s, "${iface}");
-            return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                             args.rval());
+            return ConvertExceptionToPromise(cx, args.rval());
             """,
             iface=descriptor.interface.identifier.name)
         name = "genericPromiseReturningGetter"
-        customCallArgs = dedent(
-            """
-            JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-            // Make sure to save the callee before someone maybe messes with rval().
-            JS::Rooted<JSObject*> callee(cx, &args.callee());
-            """)
 
         CGAbstractBindingMethod.__init__(self, descriptor, name,
                                          JSNativeArguments(),
-                                         callArgs=customCallArgs,
                                          unwrapFailureCode=unwrapFailureCode)
 
     def generate_code(self):
         return CGGeneric(dedent(
             """
             const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
             MOZ_ASSERT(info->type() == JSJitInfo::Getter);
             JSJitGetterOp getter = info->getter;
@@ -9238,18 +9218,17 @@ class CGGenericPromiseReturningGetter(CG
             if (ok) {
             #ifdef DEBUG
               AssertReturnTypeMatchesJitinfo(info, args.rval());
             #endif
               return true;
             }
 
             MOZ_ASSERT(info->returnType() == JSVAL_TYPE_OBJECT);
-            return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
-                                             args.rval());
+            return ConvertExceptionToPromise(cx, args.rval());
             """))
 
 
 class CGSpecializedGetter(CGAbstractStaticMethod):
     """
     A class for generating the code for a specialized attribute getter
     that the JIT can call with lower overhead.
     """
@@ -9383,24 +9362,17 @@ class CGGetterPromiseWrapper(CGAbstractS
 
     def definition_body(self):
         return fill(
             """
             bool ok = ${getterName}(${args});
             if (ok) {
               return true;
             }
-            JS::Rooted<JSObject*> globalForPromise(cx);
-            // We can't use xpc::XrayAwareCalleeGlobal here because we have no
-            // callee.  Use our hacky version instead.
-            if (!xpc::XrayAwareCalleeGlobalForSpecializedGetters(cx, obj,
-                                                                 &globalForPromise)) {
-              return false;
-            }
-            return ConvertExceptionToPromise(cx, globalForPromise, args.rval());
+            return ConvertExceptionToPromise(cx, args.rval());
             """,
             getterName=self.getter.name,
             args=", ".join(arg.name for arg in self.args))
 
     @staticmethod
     def makeName(getterName):
         return getterName + "_promiseWrapper"
 
--- a/dom/bindings/test/TestFunctions.cpp
+++ b/dom/bindings/test/TestFunctions.cpp
@@ -96,16 +96,23 @@ TestFunctions::TestThrowNsresult(ErrorRe
 void
 TestFunctions::TestThrowNsresultFromNative(ErrorResult& aError)
 {
   nsCOMPtr<mozITestInterfaceJS> impl =
     do_CreateInstance("@mozilla.org/dom/test-interface-js;1");
   aError = impl->TestThrowNsresultFromNative();
 }
 
+already_AddRefed<Promise>
+TestFunctions::ThrowToRejectPromise(GlobalObject& aGlobal, ErrorResult& aError)
+{
+  aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  return nullptr;
+}
+
 bool
 TestFunctions::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
                           JS::MutableHandle<JSObject*> aWrapper)
 {
   return TestFunctionsBinding::Wrap(aCx, this, aGivenProto, aWrapper);
 }
 
 }
--- a/dom/bindings/test/TestFunctions.h
+++ b/dom/bindings/test/TestFunctions.h
@@ -37,16 +37,19 @@ public:
 
   void GetStringDataAsAString(nsAString& aString);
   void GetStringDataAsAString(uint32_t aLength, nsAString& aString);
   void GetStringDataAsDOMString(const Optional<uint32_t>& aLength,
                                 DOMString& aString);
 
   void TestThrowNsresult(ErrorResult& aError);
   void TestThrowNsresultFromNative(ErrorResult& aError);
+  static already_AddRefed<Promise>
+  ThrowToRejectPromise(GlobalObject& aGlobal,
+                       ErrorResult& aError);
 
   bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
                   JS::MutableHandle<JSObject*> aWrapper);
 private:
   nsString mStringData;
 };
 
 } // namespace dom
--- a/dom/promise/tests/chrome.ini
+++ b/dom/promise/tests/chrome.ini
@@ -3,8 +3,11 @@
 [test_on_new_promise.html]
 [test_on_promise_settled.html]
 [test_on_promise_settled_duplicates.html]
 [test_promise_xrays.html]
 support-files = file_promise_xrays.html
 [test_promise_argument_xrays.html]
 support-files = file_promise_xrays.html file_promise_argument_tests.js
 skip-if = debug == false
+[test_promise_retval_xrays.html]
+support-files = file_promise_xrays.html file_promise_retval_tests.js
+skip-if = debug == false
--- a/dom/promise/tests/file_promise_argument_tests.js
+++ b/dom/promise/tests/file_promise_argument_tests.js
@@ -9,17 +9,17 @@
  * 3) A function named getPromise.  This function is given a global object and a
  *    single argument to use for getting the promise.  The getPromise function
  *    is expected to trigger the canonical Promise.resolve for the given global
  *    with the given argument in some way that depends on the test, and return
  *    the result.
  * 4) A subframe (frames[0]) which can be used as a second global for creating
  *    promises.
  */
-var label = parent;
+var label = "parent";
 
 function passBasicPromise() {
   var p1 = Promise.resolve();
   verifyPromiseGlobal(p1, window, "Promise.resolve return value 1");
   var p2 = getPromise(window, p1);
   is(p1, p2, "Basic promise should just pass on through");
   return p2;
 }
new file mode 100644
--- /dev/null
+++ b/dom/promise/tests/file_promise_retval_tests.js
@@ -0,0 +1,41 @@
+/*
+ * This file is meant to provide common infrastructure for several consumers.
+ * The consumer is expected to define the following things:
+ *
+ * 1) A verifyPromiseGlobal function which does whatever test the consumer
+ *    wants.  This function is passed a promise and the global whose
+ *    TestFunctions was used to get the promise.
+ * 2) A expectedExceptionGlobal function which is handed the global whose
+ *    TestFunctions was used to trigger the exception and should return the
+ *    global the exception is expected to live in.
+ * 3) A subframe (frames[0]) which can be used as a second global for creating
+ *    promises.
+ */
+var label = "parent";
+
+function testThrownException(global) {
+  var p = global.TestFunctions.throwToRejectPromise();
+  verifyPromiseGlobal(p, global, "throwToRejectPromise return value");
+  return p.then(() => {}).catch((err) => {
+    var expected = expectedExceptionGlobal(global)
+    is(SpecialPowers.unwrap(SpecialPowers.Cu.getGlobalForObject(err)),
+       expected,
+       "Should have an exception object from the right global too");
+    ok(err instanceof expected.DOMException,
+       "Should have a DOMException here");
+    is(Object.getPrototypeOf(err), expected.DOMException.prototype,
+       "Should have a DOMException from the right global");
+    is(err.name, "InvalidStateError", "Should have the right DOMException");
+  });
+}
+
+function runPromiseRetvalTests(finishFunc) {
+  Promise.resolve()
+    .then(testThrownException.bind(undefined, window))
+    .then(testThrownException.bind(undefined, frames[0]))
+    .then(finishFunc)
+    .catch(function(e) {
+      ok(false, `Exception thrown: ${e}@${location.pathname}:${e.lineNumber}:${e.columnNumber}`);
+      finishFunc();
+    });
+}
--- a/dom/promise/tests/mochitest.ini
+++ b/dom/promise/tests/mochitest.ini
@@ -18,8 +18,11 @@ support-files = file_promise_and_timeout
 [test_webassembly_compile.html]
 support-files = test_webassembly_compile_sample.wasm test_webassembly_compile_worker.js test_webassembly_compile_worker_terminate.js
 [test_promise_argument.html]
 support-files = file_promise_argument_tests.js
 skip-if = debug == false
 [test_promise_callback_retval.html]
 support-files = file_promise_argument_tests.js
 skip-if = debug == false
+[test_promise_retval.html]
+support-files = file_promise_retval_tests.js
+skip-if = debug == false
--- a/dom/promise/tests/test_promise_argument_xrays.html
+++ b/dom/promise/tests/test_promise_argument_xrays.html
@@ -74,16 +74,17 @@ function nextTest() {
   if (tests.length == 0) {
     SimpleTest.finish();
     return;
   }
   tests.shift()();
 }
 
 addLoadEvent(function() {
+  frames[0].label = "child";
   SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]},
                             nextTest);
 });
 
 </script>
 </pre>
 </body>
 </html>
copy from dom/promise/tests/test_promise_argument.html
copy to dom/promise/tests/test_promise_retval.html
--- a/dom/promise/tests/test_promise_argument.html
+++ b/dom/promise/tests/test_promise_retval.html
@@ -1,43 +1,45 @@
 <!DOCTYPE HTML>
 <html>
 <!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1323324
+https://bugzilla.mozilla.org/show_bug.cgi?id=1436276.
 -->
 <head>
   <meta charset="utf-8">
-  <title>Test for Bug 1323324</title>
+  <title>Test for Bug 1436276.</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <script src="file_promise_argument_tests.js"></script>
+  <script src="file_promise_retval_tests.js"></script>
   <script type="application/javascript">
-  /** Test for Bug 1323324 **/
+  /** Test for Bug 1436276. **/
   SimpleTest.waitForExplicitFinish();
 
-  var globalWrapper;
   function verifyPromiseGlobal(p, global, msg) {
     // SpecialPowers.Cu.getGlobalForObject returns a SpecialPowers wrapper for
     // the actual global.  We want to grab the underlying object.
-    globalWrapper = SpecialPowers.Cu.getGlobalForObject(p);
+    var globalWrapper = SpecialPowers.Cu.getGlobalForObject(p);
     is(SpecialPowers.unwrap(globalWrapper), global,
        msg + " should come from " + global.label);
   }
 
-  const isXrayArgumentTest = false;
+  function expectedExceptionGlobal(global) {
+    // We should end up with an exception from "global".
+    return global;
+  }
 
   function getPromise(global, arg) {
     return global.TestFunctions.passThroughPromise(arg);
   }
 
   addLoadEvent(function() {
     frames[0].label = "child";
     SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]},
-                              runPromiseArgumentTests.bind(undefined,
-                                                           SimpleTest.finish));
+                              runPromiseRetvalTests.bind(undefined,
+							 SimpleTest.finish));
   });
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1323324">Mozilla Bug 1323324</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   <!-- A subframe so we have another global to work with -->
copy from dom/promise/tests/test_promise_argument_xrays.html
copy to dom/promise/tests/test_promise_retval_xrays.html
--- a/dom/promise/tests/test_promise_argument_xrays.html
+++ b/dom/promise/tests/test_promise_retval_xrays.html
@@ -1,29 +1,29 @@
 <!DOCTYPE HTML>
 <html>
 <!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1233324
+https://bugzilla.mozilla.org/show_bug.cgi?id=1436276.
 -->
 <head>
   <meta charset="utf-8">
-  <title>Test for Bug 1233324</title>
+  <title>Test for Bug 1436276.</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1233324">Mozilla Bug 1233324</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1436276.">Mozilla Bug 1436276.</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 <iframe id="t" src="http://example.org/chrome/dom/promise/tests/file_promise_xrays.html"></iframe>
 </div>
 
 <pre id="test">
-<script src="file_promise_argument_tests.js"></script>
+<script src="file_promise_retval_tests.js"></script>
 <script type="application/javascript">
 
 var win = $("t").contentWindow;
 
 /** Test for Bug 1233324 **/
 SimpleTest.waitForExplicitFinish();
 
 function testLoadComplete() {
@@ -49,41 +49,46 @@ function verifyPromiseGlobal(p, _, msg) 
   // the actual global.  We want to grab the underlying object.
   var global = SpecialPowers.unwrap(SpecialPowers.Cu.getGlobalForObject(p));
 
   // We expect our global to always be "window" here, because we're working over
   // Xrays.
   is(global, window, msg + " should come from " + window.label);
 }
 
-const isXrayArgumentTest = true;
+function expectedExceptionGlobal(_) {
+  // We should end up with an exception from "window" no matter what global
+  // was involved to start with, because we're working over Xrays.
+  return window;
+}
 
 function getPromise(global, arg) {
   return global.TestFunctions.passThroughPromise(arg);
 }
 
-function testPromiseArgumentConversions() {
-  runPromiseArgumentTests(nextTest);
+function testPromiseRetvals() {
+  runPromiseRetvalTests(nextTest);
 }
 
 var tests = [
   testLoadComplete,
   testHaveXray,
-  testPromiseArgumentConversions,
+  testPromiseRetvals,
 ];
 
 function nextTest() {
   if (tests.length == 0) {
     SimpleTest.finish();
     return;
   }
   tests.shift()();
 }
 
 addLoadEvent(function() {
+  frames[0].label = "child";
   SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]},
                             nextTest);
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/webidl/TestFunctions.webidl
+++ b/dom/webidl/TestFunctions.webidl
@@ -39,9 +39,13 @@ interface TestFunctions {
   // stringbuffers.  If length not passed, use our full length.
   DOMString getStringDataAsDOMString(optional unsigned long length);
 
   // Functions that just punch through to mozITestInterfaceJS.idl
   [Throws]
   void testThrowNsresult();
   [Throws]
   void testThrowNsresultFromNative();
+
+  // Throws an InvalidStateError to auto-create a rejected promise.
+  [Throws]
+  static Promise<any> throwToRejectPromise();
 };
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -309,22 +309,21 @@ SandboxFetch(JSContext* cx, JS::HandleOb
 
     args.rval().setObject(*response->PromiseObj());
     return true;
 }
 
 static bool SandboxFetchPromise(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    RootedObject callee(cx, &args.callee());
     RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
     if (SandboxFetch(cx, scope, args)) {
         return true;
     }
-    return ConvertExceptionToPromise(cx, scope, args.rval());
+    return ConvertExceptionToPromise(cx, args.rval());
 }
 
 
 static bool
 SandboxCreateFetch(JSContext* cx, HandleObject obj)
 {
     MOZ_ASSERT(JS_IsGlobalObject(obj));