Bug 1170760 part 13. Add subclassing support to Promise::Then/Catch. r=baku,efaust

This commit is contained in:
Boris Zbarsky 2015-11-25 15:48:10 -05:00
parent 3b6bc23534
commit 7429891baf
11 changed files with 595 additions and 60 deletions

View File

@ -13,6 +13,7 @@
#include "mozilla/dom/Event.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ScriptSettings.h"
#include "jsfriendapi.h"
using mozilla::dom::AnyCallback;
using mozilla::dom::DOMError;
@ -206,14 +207,16 @@ DOMRequest::RootResultVal()
mozilla::HoldJSObjects(this);
}
already_AddRefed<Promise>
void
DOMRequest::Then(JSContext* aCx, AnyCallback* aResolveCallback,
AnyCallback* aRejectCallback, mozilla::ErrorResult& aRv)
AnyCallback* aRejectCallback,
JS::MutableHandle<JS::Value> aRetval,
mozilla::ErrorResult& aRv)
{
if (!mPromise) {
mPromise = Promise::Create(DOMEventTargetHelper::GetParentObject(), aRv);
if (aRv.Failed()) {
return nullptr;
return;
}
if (mDone) {
// Since we create mPromise lazily, it's possible that the DOMRequest object
@ -228,7 +231,10 @@ DOMRequest::Then(JSContext* aCx, AnyCallback* aResolveCallback,
}
}
return mPromise->Then(aCx, aResolveCallback, aRejectCallback, aRv);
// Just use the global of the Promise itself as the callee global.
JS::Rooted<JSObject*> global(aCx, mPromise->GetWrapper());
global = js::GetGlobalForObjectCrossCompartment(global);
mPromise->Then(aCx, global, aResolveCallback, aRejectCallback, aRetval, aRv);
}
NS_IMPL_ISUPPORTS(DOMRequestService, nsIDOMRequestService)

View File

@ -74,9 +74,11 @@ public:
IMPL_EVENT_HANDLER(success)
IMPL_EVENT_HANDLER(error)
already_AddRefed<mozilla::dom::Promise>
void
Then(JSContext* aCx, AnyCallback* aResolveCallback,
AnyCallback* aRejectCallback, mozilla::ErrorResult& aRv);
AnyCallback* aRejectCallback,
JS::MutableHandle<JS::Value> aRetval,
mozilla::ErrorResult& aRv);
void FireSuccess(JS::Handle<JS::Value> aResult);
void FireError(const nsAString& aError);

View File

@ -7147,6 +7147,18 @@ class CGPerSignatureCall(CGThing):
if needsCx:
argsPre.append("cx")
# Hack for making Promise.prototype.then work well over Xrays.
if (not static and
(descriptor.name == "Promise" or
descriptor.name == "MozAbortablePromise") and
idlNode.isMethod() and
idlNode.identifier.name == "then"):
cgThings.append(CGGeneric(dedent(
"""
JS::Rooted<JSObject*> calleeGlobal(cx, xpc::XrayAwareCalleeGlobal(&args.callee()));
""")))
argsPre.append("calleeGlobal")
needsUnwrap = False
argsPost = []
if isConstructor:

View File

@ -1115,35 +1115,236 @@ Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
return promise.forget();
}
already_AddRefed<Promise>
Promise::Then(JSContext* aCx, AnyCallback* aResolveCallback,
AnyCallback* aRejectCallback, ErrorResult& aRv)
namespace {
void
SpeciesConstructor(JSContext* aCx,
JS::Handle<JSObject*> promise,
JS::Handle<JS::Value> defaultCtor,
JS::MutableHandle<JS::Value> ctor,
ErrorResult& aRv)
{
RefPtr<Promise> promise = Create(GetParentObject(), aRv);
if (aRv.Failed()) {
return nullptr;
// Implements
// http://www.ecma-international.org/ecma-262/6.0/#sec-speciesconstructor
// Step 1.
MOZ_ASSERT(promise);
// Step 2.
JS::Rooted<JS::Value> constructorVal(aCx);
if (!JS_GetProperty(aCx, promise, "constructor", &constructorVal)) {
// Step 3.
aRv.NoteJSContextException();
return;
}
// Step 4.
if (constructorVal.isUndefined()) {
ctor.set(defaultCtor);
return;
}
// Step 5.
if (!constructorVal.isObject()) {
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
return;
}
// Step 6.
JS::Rooted<jsid> species(aCx,
SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::species)));
JS::Rooted<JS::Value> speciesVal(aCx);
JS::Rooted<JSObject*> constructorObj(aCx, &constructorVal.toObject());
if (!JS_GetPropertyById(aCx, constructorObj, species, &speciesVal)) {
// Step 7.
aRv.NoteJSContextException();
return;
}
// Step 8.
if (speciesVal.isNullOrUndefined()) {
ctor.set(defaultCtor);
return;
}
// Step 9.
if (speciesVal.isObject() && JS::IsConstructor(&speciesVal.toObject())) {
ctor.set(speciesVal);
return;
}
// Step 10.
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
}
} // anonymous namespace
void
Promise::Then(JSContext* aCx, JS::Handle<JSObject*> aCalleeGlobal,
AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv)
{
// Implements
// http://www.ecma-international.org/ecma-262/6.0/#sec-promise.prototype.then
// Step 1.
JS::Rooted<JS::Value> promiseVal(aCx, JS::ObjectValue(*GetWrapper()));
if (!MaybeWrapObjectValue(aCx, &promiseVal)) {
aRv.NoteJSContextException();
return;
}
JS::Rooted<JSObject*> promiseObj(aCx, &promiseVal.toObject());
MOZ_ASSERT(promiseObj);
// Step 2 was done by the bindings.
// Step 3. We want to use aCalleeGlobal here because it will do the
// right thing for us via Xrays (where we won't find @@species on
// our promise constructor for now).
JS::Rooted<JSObject*> calleeGlobal(aCx, aCalleeGlobal);
JS::Rooted<JS::Value> defaultCtorVal(aCx);
{ // Scope for JSAutoCompartment
JSAutoCompartment ac(aCx, aCalleeGlobal);
JSObject* defaultCtor =
PromiseBinding::GetConstructorObject(aCx, calleeGlobal);
if (!defaultCtor) {
aRv.NoteJSContextException();
return;
}
defaultCtorVal.setObject(*defaultCtor);
}
if (!MaybeWrapObjectValue(aCx, &defaultCtorVal)) {
aRv.NoteJSContextException();
return;
}
JS::Rooted<JS::Value> constructor(aCx);
SpeciesConstructor(aCx, promiseObj, defaultCtorVal, &constructor, aRv);
if (aRv.Failed()) {
// Step 4.
return;
}
// Step 5.
GlobalObject globalObj(aCx, GetWrapper());
if (globalObj.Failed()) {
aRv.NoteJSContextException();
return;
}
nsCOMPtr<nsIGlobalObject> globalObject =
do_QueryInterface(globalObj.GetAsSupports());
if (!globalObject) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
PromiseCapability capability(aCx);
NewPromiseCapability(aCx, globalObject, constructor, false, capability, aRv);
if (aRv.Failed()) {
// Step 6.
return;
}
// Now step 7: start
// http://www.ecma-international.org/ecma-262/6.0/#sec-performpromisethen
// Step 1 and step 2 are just assertions.
// Step 3 and step 4 are kinda handled for us already; we use null
// to represent "Identity" and "Thrower".
// Steps 5 and 6. These branch based on whether we know we have a
// vanilla Promise or not.
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
if (capability.mNativePromise) {
Promise* promise = capability.mNativePromise;
RefPtr<PromiseCallback> resolveCb =
PromiseCallback::Factory(promise, global, aResolveCallback,
PromiseCallback::Resolve);
RefPtr<PromiseCallback> resolveCb =
PromiseCallback::Factory(promise, global, aResolveCallback,
PromiseCallback::Resolve);
RefPtr<PromiseCallback> rejectCb =
PromiseCallback::Factory(promise, global, aRejectCallback,
PromiseCallback::Reject);
RefPtr<PromiseCallback> rejectCb =
PromiseCallback::Factory(promise, global, aRejectCallback,
PromiseCallback::Reject);
AppendCallbacks(resolveCb, rejectCb);
AppendCallbacks(resolveCb, rejectCb);
} else {
JS::Rooted<JSObject*> resolveObj(aCx, &capability.mResolve.toObject());
RefPtr<AnyCallback> resolveFunc =
new AnyCallback(aCx, resolveObj, GetIncumbentGlobal());
return promise.forget();
JS::Rooted<JSObject*> rejectObj(aCx, &capability.mReject.toObject());
RefPtr<AnyCallback> rejectFunc =
new AnyCallback(aCx, rejectObj, GetIncumbentGlobal());
if (!capability.mPromise.isObject()) {
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
return;
}
JS::Rooted<JSObject*> newPromiseObj(aCx, &capability.mPromise.toObject());
// We want to store the reflector itself.
newPromiseObj = js::CheckedUnwrap(newPromiseObj);
if (!newPromiseObj) {
// Just throw something.
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
return;
}
RefPtr<PromiseCallback> resolveCb;
if (aResolveCallback) {
resolveCb = new WrapperPromiseCallback(global, aResolveCallback,
newPromiseObj,
resolveFunc, rejectFunc);
} else {
resolveCb = new InvokePromiseFuncCallback(global, newPromiseObj,
resolveFunc);
}
RefPtr<PromiseCallback> rejectCb;
if (aRejectCallback) {
rejectCb = new WrapperPromiseCallback(global, aRejectCallback,
newPromiseObj,
resolveFunc, rejectFunc);
} else {
rejectCb = new InvokePromiseFuncCallback(global, newPromiseObj,
rejectFunc);
}
AppendCallbacks(resolveCb, rejectCb);
}
aRetval.set(capability.PromiseValue());
}
already_AddRefed<Promise>
Promise::Catch(JSContext* aCx, AnyCallback* aRejectCallback, ErrorResult& aRv)
void
Promise::Catch(JSContext* aCx, AnyCallback* aRejectCallback,
JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aRv)
{
RefPtr<AnyCallback> resolveCb;
return Then(aCx, resolveCb, aRejectCallback, aRv);
// Implements
// http://www.ecma-international.org/ecma-262/6.0/#sec-promise.prototype.catch
// We can't call Promise::Then directly, because someone might have
// overridden Promise.prototype.then.
JS::Rooted<JS::Value> promiseVal(aCx, JS::ObjectValue(*GetWrapper()));
if (!MaybeWrapObjectValue(aCx, &promiseVal)) {
aRv.NoteJSContextException();
return;
}
JS::Rooted<JSObject*> promiseObj(aCx, &promiseVal.toObject());
MOZ_ASSERT(promiseObj);
JS::AutoValueArray<2> callbacks(aCx);
callbacks[0].setUndefined();
if (aRejectCallback) {
callbacks[1].setObject(*aRejectCallback->Callable());
// It could be in any compartment, so put it in ours.
if (!MaybeWrapObjectValue(aCx, callbacks[1])) {
aRv.NoteJSContextException();
return;
}
} else {
callbacks[1].setNull();
}
if (!JS_CallFunctionName(aCx, promiseObj, "then", callbacks, aRetval)) {
aRv.NoteJSContextException();
}
}
/**

View File

@ -181,12 +181,20 @@ public:
Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
JS::Handle<JS::Value> aValue, ErrorResult& aRv);
already_AddRefed<Promise>
Then(JSContext* aCx, AnyCallback* aResolveCallback,
AnyCallback* aRejectCallback, ErrorResult& aRv);
void
Then(JSContext* aCx,
// aCalleeGlobal may not be in the compartment of aCx, when called over
// Xrays.
JS::Handle<JSObject*> aCalleeGlobal,
AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aRv);
already_AddRefed<Promise>
Catch(JSContext* aCx, AnyCallback* aRejectCallback, ErrorResult& aRv);
void
Catch(JSContext* aCx,
AnyCallback* aRejectCallback,
JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aRv);
static void
All(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,

View File

@ -156,25 +156,113 @@ RejectPromiseCallback::Call(JSContext* aCx,
return NS_OK;
}
// InvokePromiseFuncCallback
NS_IMPL_CYCLE_COLLECTION_CLASS(InvokePromiseFuncCallback)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(InvokePromiseFuncCallback,
PromiseCallback)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromiseFunc)
tmp->mGlobal = nullptr;
tmp->mNextPromiseObj = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(InvokePromiseFuncCallback,
PromiseCallback)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromiseFunc)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(InvokePromiseFuncCallback)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mNextPromiseObj)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(InvokePromiseFuncCallback)
NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)
NS_IMPL_ADDREF_INHERITED(InvokePromiseFuncCallback, PromiseCallback)
NS_IMPL_RELEASE_INHERITED(InvokePromiseFuncCallback, PromiseCallback)
InvokePromiseFuncCallback::InvokePromiseFuncCallback(JS::Handle<JSObject*> aGlobal,
JS::Handle<JSObject*> aNextPromiseObj,
AnyCallback* aPromiseFunc)
: mGlobal(aGlobal)
, mNextPromiseObj(aNextPromiseObj)
, mPromiseFunc(aPromiseFunc)
{
MOZ_ASSERT(aGlobal);
MOZ_ASSERT(aNextPromiseObj);
MOZ_ASSERT(aPromiseFunc);
HoldJSObjects(this);
}
InvokePromiseFuncCallback::~InvokePromiseFuncCallback()
{
DropJSObjects(this);
}
nsresult
InvokePromiseFuncCallback::Call(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
// Run resolver's algorithm with value and the synchronous flag set.
JS::ExposeObjectToActiveJS(mGlobal);
JS::ExposeValueToActiveJS(aValue);
JSAutoCompartment ac(aCx, mGlobal);
JS::Rooted<JS::Value> value(aCx, aValue);
if (!JS_WrapValue(aCx, &value)) {
NS_WARNING("Failed to wrap value into the right compartment.");
return NS_ERROR_FAILURE;
}
ErrorResult rv;
JS::Rooted<JS::Value> ignored(aCx);
mPromiseFunc->Call(value, &ignored, rv);
// Useful exceptions already got reported.
rv.SuppressException();
return NS_OK;
}
Promise*
InvokePromiseFuncCallback::GetDependentPromise()
{
Promise* promise;
if (NS_SUCCEEDED(UNWRAP_OBJECT(Promise, mNextPromiseObj, promise))) {
return promise;
}
// Oh, well.
return nullptr;
}
// WrapperPromiseCallback
NS_IMPL_CYCLE_COLLECTION_CLASS(WrapperPromiseCallback)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WrapperPromiseCallback,
PromiseCallback)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextPromise)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveFunc)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectFunc)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
tmp->mGlobal = nullptr;
tmp->mNextPromiseObj = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WrapperPromiseCallback,
PromiseCallback)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveFunc)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectFunc)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WrapperPromiseCallback)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mNextPromiseObj)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WrapperPromiseCallback)
@ -195,6 +283,24 @@ WrapperPromiseCallback::WrapperPromiseCallback(Promise* aNextPromise,
HoldJSObjects(this);
}
WrapperPromiseCallback::WrapperPromiseCallback(JS::Handle<JSObject*> aGlobal,
AnyCallback* aCallback,
JS::Handle<JSObject*> aNextPromiseObj,
AnyCallback* aResolveFunc,
AnyCallback* aRejectFunc)
: mNextPromiseObj(aNextPromiseObj)
, mResolveFunc(aResolveFunc)
, mRejectFunc(aRejectFunc)
, mGlobal(aGlobal)
, mCallback(aCallback)
{
MOZ_ASSERT(mNextPromiseObj);
MOZ_ASSERT(aResolveFunc);
MOZ_ASSERT(aRejectFunc);
MOZ_ASSERT(aGlobal);
HoldJSObjects(this);
}
WrapperPromiseCallback::~WrapperPromiseCallback()
{
DropJSObjects(this);
@ -218,9 +324,16 @@ WrapperPromiseCallback::Call(JSContext* aCx,
// PromiseReactionTask step 6
JS::Rooted<JS::Value> retValue(aCx);
JSCompartment* compartment;
if (mNextPromise) {
compartment = mNextPromise->Compartment();
} else {
MOZ_ASSERT(mNextPromiseObj);
compartment = js::GetObjectCompartment(mNextPromiseObj);
}
mCallback->Call(value, &retValue, rv, "promise callback",
CallbackObject::eRethrowExceptions,
mNextPromise->Compartment());
compartment);
rv.WouldReportJSException();
@ -231,23 +344,43 @@ WrapperPromiseCallback::Call(JSContext* aCx,
// Convert the ErrorResult to a JS exception object that we can reject
// ourselves with. This will be exactly the exception that would get
// thrown from a binding method whose ErrorResult ended up with whatever
// is on "rv" right now.
JSAutoCompartment ac(aCx, mNextPromise->GlobalJSObject());
// is on "rv" right now. Do this in the promise reflector compartment.
Maybe<JSAutoCompartment> ac;
if (mNextPromise) {
ac.emplace(aCx, mNextPromise->GlobalJSObject());
} else {
ac.emplace(aCx, mNextPromiseObj);
}
DebugOnly<bool> conversionResult = ToJSValue(aCx, rv, &value);
MOZ_ASSERT(conversionResult);
}
mNextPromise->RejectInternal(aCx, value);
if (mNextPromise) {
mNextPromise->RejectInternal(aCx, value);
} else {
JS::Rooted<JS::Value> ignored(aCx);
ErrorResult rejectRv;
mRejectFunc->Call(value, &ignored, rejectRv);
// This reported any JS exceptions; we just have a pointless exception on
// there now.
rejectRv.SuppressException();
}
return NS_OK;
}
// If the return value is the same as the promise itself, throw TypeError.
if (retValue.isObject()) {
JS::Rooted<JSObject*> valueObj(aCx, &retValue.toObject());
Promise* returnedPromise;
nsresult r = UNWRAP_OBJECT(Promise, valueObj, returnedPromise);
if (NS_SUCCEEDED(r) && returnedPromise == mNextPromise) {
valueObj = js::CheckedUnwrap(valueObj);
JS::Rooted<JSObject*> nextPromiseObj(aCx);
if (mNextPromise) {
nextPromiseObj = mNextPromise->GetWrapper();
} else {
MOZ_ASSERT(mNextPromiseObj);
nextPromiseObj = mNextPromiseObj;
}
// XXXbz shouldn't this check be over in ResolveInternal anyway?
if (valueObj == nextPromiseObj) {
const char* fileName = nullptr;
uint32_t lineNumber = 0;
@ -296,7 +429,16 @@ WrapperPromiseCallback::Call(JSContext* aCx,
return NS_ERROR_OUT_OF_MEMORY;
}
mNextPromise->RejectInternal(aCx, typeError);
if (mNextPromise) {
mNextPromise->RejectInternal(aCx, typeError);
} else {
JS::Rooted<JS::Value> ignored(aCx);
ErrorResult rejectRv;
mRejectFunc->Call(typeError, &ignored, rejectRv);
// This reported any JS exceptions; we just have a pointless exception
// on there now.
rejectRv.SuppressException();
}
return NS_OK;
}
}
@ -307,7 +449,17 @@ WrapperPromiseCallback::Call(JSContext* aCx,
return NS_ERROR_FAILURE;
}
mNextPromise->ResolveInternal(aCx, retValue);
if (mNextPromise) {
mNextPromise->ResolveInternal(aCx, retValue);
} else {
JS::Rooted<JS::Value> ignored(aCx);
ErrorResult resolveRv;
mResolveFunc->Call(retValue, &ignored, resolveRv);
// This reported any JS exceptions; we just have a pointless exception
// on there now.
resolveRv.SuppressException();
}
return NS_OK;
}
@ -334,7 +486,17 @@ WrapperPromiseCallback::GetDependentPromise()
return promise;
}
return mNextPromise;
if (mNextPromise) {
return mNextPromise;
}
Promise* promise;
if (NS_SUCCEEDED(UNWRAP_OBJECT(Promise, mNextPromiseObj, promise))) {
return promise;
}
// Oh, well.
return nullptr;
}
// NativePromiseCallback

View File

@ -45,8 +45,13 @@ public:
};
// WrapperPromiseCallback execs a JS Callback with a value, and then the return
// value is sent to the aNextPromise->ResolveFunction() or to
// aNextPromise->RejectFunction() if the JS Callback throws.
// value is sent to either:
// a) If aNextPromise is non-null, the aNextPromise->ResolveFunction() or to
// aNextPromise->RejectFunction() if the JS Callback throws.
// or
// b) If aNextPromise is null, in which case aResolveFunc and aRejectFunc must
// be non-null, then to aResolveFunc, unless aCallback threw, in which case
// aRejectFunc.
class WrapperPromiseCallback final : public PromiseCallback
{
public:
@ -59,13 +64,28 @@ public:
Promise* GetDependentPromise() override;
// Constructor for when we know we have a vanilla Promise.
WrapperPromiseCallback(Promise* aNextPromise, JS::Handle<JSObject*> aGlobal,
AnyCallback* aCallback);
// Constructor for when all we have to work with are resolve/reject functions.
WrapperPromiseCallback(JS::Handle<JSObject*> aGlobal,
AnyCallback* aCallback,
JS::Handle<JSObject*> mNextPromiseObj,
AnyCallback* aResolveFunc,
AnyCallback* aRejectFunc);
private:
~WrapperPromiseCallback();
// Either mNextPromise is non-null or all three of mNextPromiseObj,
// mResolveFund and mRejectFunc must are non-null.
RefPtr<Promise> mNextPromise;
// mNextPromiseObj is the reflector itself; it may not be in the
// same compartment as anything else we have.
JS::Heap<JSObject*> mNextPromiseObj;
RefPtr<AnyCallback> mResolveFunc;
RefPtr<AnyCallback> mRejectFunc;
JS::Heap<JSObject*> mGlobal;
RefPtr<AnyCallback> mCallback;
};
@ -122,6 +142,32 @@ private:
JS::Heap<JSObject*> mGlobal;
};
// InvokePromiseFuncCallback calls the given function with the value
// received by Call().
class InvokePromiseFuncCallback final : public PromiseCallback
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(InvokePromiseFuncCallback,
PromiseCallback)
nsresult Call(JSContext* aCx,
JS::Handle<JS::Value> aValue) override;
Promise* GetDependentPromise() override;
InvokePromiseFuncCallback(JS::Handle<JSObject*> aGlobal,
JS::Handle<JSObject*> aNextPromiseObj,
AnyCallback* aPromiseFunc);
private:
~InvokePromiseFuncCallback();
JS::Heap<JSObject*> mGlobal;
JS::Heap<JSObject*> mNextPromiseObj;
RefPtr<AnyCallback> mPromiseFunc;
};
// NativePromiseCallback wraps a PromiseNativeHandler.
class NativePromiseCallback final : public PromiseCallback
{

View File

@ -218,6 +218,52 @@ function testReject1() {
).then(nextTest);
}
function testThen1() {
var p = win.Promise.resolve(5);
var q = p.then((x) => x*x);
ok(q instanceof win.Promise,
"Promise.then should return a promise from the right global");
q.then(
function(arg) {
is(arg, 25, "Promise.then should work");
},
function(e) {
ok(false, "Promise.then should not fail");
}
).then(nextTest);
}
function testThen2() {
var p = win.Promise.resolve(5);
var q = p.then((x) => Promise.resolve(x*x));
ok(q instanceof win.Promise,
"Promise.then should return a promise from the right global");
q.then(
function(arg) {
is(arg, 25, "Promise.then resolved with chrome promise should work");
},
function(e) {
ok(false, "Promise.then resolved with chrome promise should not fail");
}
).then(nextTest);
}
function testCatch1() {
var p = win.Promise.reject(5);
ok(p instanceof win.Promise, "Promise.resolve should return a promise");
var q = p.catch((x) => x*x);
ok(q instanceof win.Promise,
"Promise.catch should return a promise from the right global");
q.then(
function(arg) {
is(arg, 25, "Promise.catch should work");
},
function(e) {
ok(false, "Promise.catch should not fail");
}
).then(nextTest);
}
var tests = [
testLoadComplete,
testHaveXray,
@ -234,6 +280,9 @@ var tests = [
testResolve2,
testResolve3,
testReject1,
testThen1,
testThen2,
testCatch1,
];
function nextTest() {

View File

@ -20,9 +20,10 @@ interface DOMRequestShared {
interface DOMRequest : EventTarget {
// The [TreatNonCallableAsNull] annotation is required since then() should do
// nothing instead of throwing errors when non-callable arguments are passed.
[NewObject]
Promise<any> then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null,
[TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
// See documentation for Promise.then to see why we return "any".
[NewObject, Throws]
any then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null,
[TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
};
DOMRequest implements DOMRequestShared;

View File

@ -30,12 +30,15 @@ interface _Promise {
// The [TreatNonCallableAsNull] annotation is required since then() should do
// nothing instead of throwing errors when non-callable arguments are passed.
[NewObject]
Promise<any> then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null,
[TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
// 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.then/catch to be a Promise object.
[NewObject, Throws]
any then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null,
[TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
[NewObject]
Promise<any> catch([TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
[NewObject, Throws]
any catch([TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
// Have to use "any" (or "object", but "any" is simpler) as the type to
// support the subclassing behavior, since nothing actually requires the

View File

@ -9,6 +9,7 @@ var theLog = [];
var speciesGets = 0;
var speciesCalls = 0;
var constructorCalls = 0;
var constructorGets = 0;
var resolveCalls = 0;
var rejectCalls = 0;
var thenCalls = 0;
@ -22,7 +23,7 @@ function takeLog() {
theLog = [];
speciesGets = speciesCalls = constructorCalls = resolveCalls =
rejectCalls = thenCalls = catchCalls = allCalls = raceCalls =
nextCalls = 0;
nextCalls = constructorGets = 0;
return oldLog;
}
@ -37,6 +38,14 @@ function log(str) {
class LoggingPromise extends Promise {
constructor(func) {
super(func);
Object.defineProperty(this, "constructor",
{
get: function() {
++constructorGets;
log(`Constructor get ${constructorGets}`);
return Object.getPrototypeOf(this).constructor;
}
});
++constructorCalls;
log(`Constructor ${constructorCalls}`);
}
@ -128,9 +137,9 @@ promise_test(function testPromiseRace() {
var log = takeLog();
assert_array_equals(log, ["Race 1", "Constructor 1",
"Next 1", "Resolve 1", "Constructor 2",
"Then 1",
"Next 2", "Resolve 2", "Constructor 3",
"Then 2",
"Then 1", "Constructor get 1", "Species get 1", "Species call 1", "Constructor 3",
"Next 2", "Resolve 2", "Constructor 4",
"Then 2", "Constructor get 2", "Species get 2", "Species call 2", "Constructor 5",
"Next 3"]);
assert_true(p instanceof LoggingPromise);
return p.then(function(arg) {
@ -144,9 +153,9 @@ promise_test(function testPromiseRaceNoSpecies() {
var log = takeLog();
assert_array_equals(log, ["Race 1", "Constructor 1",
"Next 1", "Resolve 1", "Constructor 2",
"Then 1",
"Then 1", "Constructor get 1",
"Next 2", "Resolve 2", "Constructor 3",
"Then 2",
"Then 2", "Constructor get 2",
"Next 3"]);
assert_true(p instanceof SpeciesLessPromise);
return p.then(function(arg) {
@ -160,9 +169,9 @@ promise_test(function testPromiseAll() {
var log = takeLog();
assert_array_equals(log, ["All 1", "Constructor 1",
"Next 1", "Resolve 1", "Constructor 2",
"Then 1",
"Next 2", "Resolve 2", "Constructor 3",
"Then 2",
"Then 1", "Constructor get 1", "Species get 1", "Species call 1", "Constructor 3",
"Next 2", "Resolve 2", "Constructor 4",
"Then 2", "Constructor get 2", "Species get 2", "Species call 2", "Constructor 5",
"Next 3"]);
assert_true(p instanceof LoggingPromise);
return p.then(function(arg) {
@ -179,9 +188,11 @@ promise_test(function testPromiseResolve() {
assert_equals(p, q,
"Promise.resolve with same constructor should preserve identity");
log = takeLog();
assert_array_equals(log, ["Resolve 1"]);
assert_array_equals(log, ["Resolve 1", "Constructor get 1"]);
var r = Promise.resolve(p);
log = takeLog();
assert_array_equals(log, ["Constructor get 1"]);
assert_not_equals(p, r,
"Promise.resolve with different constructor should " +
"create a new Promise instance (1)")
@ -217,4 +228,38 @@ promise_test(function testPromiseReject() {
});
}, "Promise.reject behavior");
promise_test(function testPromiseThen() {
clearLog();
var p = LoggingPromise.resolve(5);
var log = takeLog();
assert_array_equals(log, ["Resolve 1", "Constructor 1"]);
var q = p.then((x) => x*x);
log = takeLog();
assert_array_equals(log, ["Then 1", "Constructor get 1", "Species get 1",
"Species call 1", "Constructor 1"]);
assert_true(q instanceof LoggingPromise);
return q.then(function(arg) {
assert_equals(arg, 25);
});
}, "Promise.then behavior");
promise_test(function testPromiseCatch() {
clearLog();
var p = LoggingPromise.reject(5);
var log = takeLog();
assert_array_equals(log, ["Reject 1", "Constructor 1"]);
var q = p.catch((x) => x*x);
log = takeLog();
assert_array_equals(log, ["Catch 1", "Then 1", "Constructor get 1",
"Species get 1", "Species call 1", "Constructor 1"]);
assert_true(q instanceof LoggingPromise);
return q.then(function(arg) {
assert_equals(arg, 25);
});
}, "Promise.catch behavior");
</script>