diff --git a/b2g/components/MozKeyboard.js b/b2g/components/MozKeyboard.js index 99571a586f5..20b348aee31 100644 --- a/b2g/components/MozKeyboard.js +++ b/b2g/components/MozKeyboard.js @@ -545,8 +545,8 @@ MozInputContext.prototype = { getText: function ic_getText(offset, length) { let self = this; - return this.createPromise(function(resolver) { - let resolverId = self.getPromiseResolverId(resolver); + return this.createPromise(function(resolve, reject) { + let resolverId = self.getPromiseResolverId({ resolve: resolve, reject: reject }); cpmm.sendAsyncMessage('Keyboard:GetText', { contextId: self._contextId, requestId: resolverId, @@ -574,8 +574,8 @@ MozInputContext.prototype = { setSelectionRange: function ic_setSelectionRange(start, length) { let self = this; - return this.createPromise(function(resolver) { - let resolverId = self.getPromiseResolverId(resolver); + return this.createPromise(function(resolve, reject) { + let resolverId = self.getPromiseResolverId({ resolve: resolve, reject: reject }); cpmm.sendAsyncMessage("Keyboard:SetSelectionRange", { contextId: self._contextId, requestId: resolverId, @@ -603,8 +603,8 @@ MozInputContext.prototype = { replaceSurroundingText: function ic_replaceSurrText(text, offset, length) { let self = this; - return this.createPromise(function(resolver) { - let resolverId = self.getPromiseResolverId(resolver); + return this.createPromise(function(resolve, reject) { + let resolverId = self.getPromiseResolverId({ resolve: resolve, reject: reject }); cpmm.sendAsyncMessage('Keyboard:ReplaceSurroundingText', { contextId: self._contextId, requestId: resolverId, @@ -621,8 +621,8 @@ MozInputContext.prototype = { sendKey: function ic_sendKey(keyCode, charCode, modifiers) { let self = this; - return this.createPromise(function(resolver) { - let resolverId = self.getPromiseResolverId(resolver); + return this.createPromise(function(resolve, reject) { + let resolverId = self.getPromiseResolverId({ resolve: resolve, reject: reject }); cpmm.sendAsyncMessage('Keyboard:SendKey', { contextId: self._contextId, requestId: resolverId, @@ -635,8 +635,8 @@ MozInputContext.prototype = { setComposition: function ic_setComposition(text, cursor, clauses) { let self = this; - return this.createPromise(function(resolver) { - let resolverId = self.getPromiseResolverId(resolver); + return this.createPromise(function(resolve, reject) { + let resolverId = self.getPromiseResolverId({ resolve: resolve, reject: reject }); cpmm.sendAsyncMessage('Keyboard:SetComposition', { contextId: self._contextId, requestId: resolverId, @@ -649,8 +649,8 @@ MozInputContext.prototype = { endComposition: function ic_endComposition(text) { let self = this; - return this.createPromise(function(resolver) { - let resolverId = self.getPromiseResolverId(resolver); + return this.createPromise(function(resolve, reject) { + let resolverId = self.getPromiseResolverId({ resolve: resolve, reject: reject }); cpmm.sendAsyncMessage('Keyboard:EndComposition', { contextId: self._contextId, requestId: resolverId, diff --git a/dom/base/DOMRequestHelper.jsm b/dom/base/DOMRequestHelper.jsm index c1578e42e8f..8bc7c1cecee 100644 --- a/dom/base/DOMRequestHelper.jsm +++ b/dom/base/DOMRequestHelper.jsm @@ -221,7 +221,8 @@ DOMRequestIpcHelper.prototype = { forEachPromiseResolver: function(aCallback) { Object.keys(this._requests).forEach(function(k) { - if (this.getPromiseResolver(k) instanceof this._window.PromiseResolver) { + if ("resolve" in this.getPromiseResolver(k) && + "reject" in this.getPromiseResolver(k)) { aCallback(k); } }, this); diff --git a/dom/base/test/test_domrequesthelper.xul b/dom/base/test/test_domrequesthelper.xul index edd3dd423d2..2acf0eb3827 100644 --- a/dom/base/test/test_domrequesthelper.xul +++ b/dom/base/test/test_domrequesthelper.xul @@ -30,9 +30,8 @@ function createPromise() { ok(Promise, "Promise object should exist"); - var promise = dummy.createPromise(function(r) { - ok(r, "received PromiseResolver"); - r.resolve(true); + var promise = dummy.createPromise(function(resolve, reject) { + resolve(true); }); ok(promise instanceof Promise, "returned a Promise"); promise.then(runTest); @@ -41,7 +40,8 @@ function getResolver() { var id; var resolver; - var promise = dummy.createPromise(function(r) { + var promise = dummy.createPromise(function(resolve, reject) { + var r = { resolve: resolve, reject: reject }; id = dummy.getPromiseResolverId(r); resolver = r; ok(typeof id === "string", "id should be string"); @@ -55,7 +55,8 @@ function removeResolver() { var id; - var promise = dummy.createPromise(function(r) { + var promise = dummy.createPromise(function(resolve, reject) { + var r = { resolve: resolve, reject: reject }; id = dummy.getPromiseResolverId(r); ok(typeof id === "string", "id should be string"); @@ -74,7 +75,8 @@ function takeResolver() { var id; var resolver; - var promise = dummy.createPromise(function(r) { + var promise = dummy.createPromise(function(resolve, reject) { + var r = { resolve: resolve, reject: reject }; id = dummy.getPromiseResolverId(r); resolver = r; ok(typeof id === "string", "id should be string"); diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index 88de734c25c..1fcbb4fb514 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -9,7 +9,6 @@ #include "jsfriendapi.h" #include "mozilla/dom/OwningNonNull.h" #include "mozilla/dom/PromiseBinding.h" -#include "mozilla/dom/PromiseResolver.h" #include "mozilla/Preferences.h" #include "PromiseCallback.h" #include "nsContentUtils.h" @@ -52,6 +51,58 @@ private: nsRefPtr mPromise; }; +// This class processes the promise's callbacks with promise's result. +class PromiseResolverTask MOZ_FINAL : public nsRunnable +{ +public: + PromiseResolverTask(Promise* aPromise, + JS::Handle aValue, + Promise::PromiseState aState) + : mPromise(aPromise) + , mValue(aValue) + , mState(aState) + { + MOZ_ASSERT(aPromise); + MOZ_ASSERT(mState != Promise::Pending); + MOZ_COUNT_CTOR(PromiseResolverTask); + + JSContext* cx = nsContentUtils::GetSafeJSContext(); + + /* It's safe to use unsafeGet() here: the unsafeness comes from the + * possibility of updating the value of mJSObject without triggering the + * barriers. However if the value will always be marked, post barriers + * unnecessary. */ + JS_AddNamedValueRootRT(JS_GetRuntime(cx), mValue.unsafeGet(), + "PromiseResolverTask.mValue"); + } + + ~PromiseResolverTask() + { + MOZ_COUNT_DTOR(PromiseResolverTask); + + JSContext* cx = nsContentUtils::GetSafeJSContext(); + + /* It's safe to use unsafeGet() here: the unsafeness comes from the + * possibility of updating the value of mJSObject without triggering the + * barriers. However if the value will always be marked, post barriers + * unnecessary. */ + JS_RemoveValueRootRT(JS_GetRuntime(cx), mValue.unsafeGet()); + } + + NS_IMETHOD Run() + { + mPromise->RunResolveTask( + JS::Handle::fromMarkedLocation(mValue.address()), + mState, Promise::SyncTask); + return NS_OK; + } + +private: + nsRefPtr mPromise; + JS::Heap mValue; + Promise::PromiseState mState; +}; + // Promise NS_IMPL_CYCLE_COLLECTION_CLASS(Promise) @@ -59,7 +110,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(Promise) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise) tmp->MaybeReportRejected(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolver) NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks); NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks); tmp->mResult = JS::UndefinedValue(); @@ -68,7 +118,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveCallbacks); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectCallbacks); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS @@ -93,12 +142,11 @@ Promise::Promise(nsPIDOMWindow* aWindow) , mState(Pending) , mTaskPending(false) , mHadRejectCallback(false) + , mResolvePending(false) { MOZ_COUNT_CTOR(Promise); mozilla::HoldJSObjects(this); SetIsDOMBinding(); - - mResolver = new PromiseResolver(this); } Promise::~Promise() @@ -152,6 +200,67 @@ EnterCompartment(Maybe& aAc, JSContext* aCx, } } +enum { + SLOT_PROMISE = 0, + SLOT_TASK +}; + +/* static */ bool +Promise::JSCallback(JSContext *aCx, unsigned aArgc, JS::Value *aVp) +{ + JS::CallArgs args = CallArgsFromVp(aArgc, aVp); + + JS::Rooted v(aCx, + js::GetFunctionNativeReserved(&args.callee(), + SLOT_PROMISE)); + MOZ_ASSERT(v.isObject()); + + Promise* promise; + if (NS_FAILED(UNWRAP_OBJECT(Promise, aCx, &v.toObject(), promise))) { + return Throw(aCx, NS_ERROR_UNEXPECTED); + } + + Optional > value(aCx); + if (aArgc) { + value.Value() = args[0]; + } + + v = js::GetFunctionNativeReserved(&args.callee(), SLOT_TASK); + PromiseCallback::Task task = static_cast(v.toInt32()); + + if (task == PromiseCallback::Resolve) { + promise->MaybeResolve(aCx, value); + } else { + promise->MaybeReject(aCx, value); + } + + return true; +} + +/* static */ JSObject* +Promise::CreateFunction(JSContext* aCx, JSObject* aParent, Promise* aPromise, + int32_t aTask) +{ + JSFunction* func = js::NewFunctionWithReserved(aCx, JSCallback, + 1 /* nargs */, 0 /* flags */, + aParent, nullptr); + if (!func) { + return nullptr; + } + + JS::Rooted obj(aCx, JS_GetFunctionObject(func)); + + JS::Rooted promiseObj(aCx); + if (!dom::WrapNewBindingObject(aCx, obj, aPromise, &promiseObj)) { + return nullptr; + } + + js::SetFunctionNativeReserved(obj, SLOT_PROMISE, promiseObj); + js::SetFunctionNativeReserved(obj, SLOT_TASK, JS::Int32Value(aTask)); + + return obj; +} + /* static */ already_AddRefed Promise::Constructor(const GlobalObject& aGlobal, PromiseInit& aInit, ErrorResult& aRv) @@ -165,7 +274,23 @@ Promise::Constructor(const GlobalObject& aGlobal, nsRefPtr promise = new Promise(window); - aInit.Call(promise, *promise->mResolver, aRv, + JS::Rooted resolveFunc(cx, + CreateFunction(cx, aGlobal.Get(), promise, + PromiseCallback::Resolve)); + if (!resolveFunc) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + JS::Rooted rejectFunc(cx, + CreateFunction(cx, aGlobal.Get(), promise, + PromiseCallback::Reject)); + if (!rejectFunc) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + aInit.Call(promise, resolveFunc, rejectFunc, aRv, CallbackObject::eRethrowExceptions); aRv.WouldReportJSException(); @@ -175,7 +300,7 @@ Promise::Constructor(const GlobalObject& aGlobal, Maybe ac; EnterCompartment(ac, cx, value); - promise->mResolver->Reject(cx, value); + promise->MaybeReject(cx, value); } return promise.forget(); @@ -194,7 +319,7 @@ Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx, nsRefPtr promise = new Promise(window); Optional > value(aCx, aValue); - promise->mResolver->Resolve(aCx, value); + promise->MaybeResolve(aCx, value); return promise.forget(); } @@ -211,7 +336,7 @@ Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx, nsRefPtr promise = new Promise(window); Optional > value(aCx, aValue); - promise->mResolver->Reject(aCx, value); + promise->MaybeReject(aCx, value); return promise.forget(); } @@ -222,14 +347,14 @@ Promise::Then(const Optional >& aResolveCallback, nsRefPtr promise = new Promise(GetParentObject()); nsRefPtr resolveCb = - PromiseCallback::Factory(promise->mResolver, + PromiseCallback::Factory(promise, aResolveCallback.WasPassed() ? &aResolveCallback.Value() : nullptr, PromiseCallback::Resolve); nsRefPtr rejectCb = - PromiseCallback::Factory(promise->mResolver, + PromiseCallback::Factory(promise, aRejectCallback.WasPassed() ? &aRejectCallback.Value() : nullptr, @@ -260,9 +385,9 @@ Promise::AppendCallbacks(PromiseCallback* aResolveCallback, mRejectCallbacks.AppendElement(aRejectCallback); } - // If promise's state is resolved, queue a task to process promise's resolve - // callbacks with promise's result. If promise's state is rejected, queue a task - // to process promise's reject callbacks with promise's result. + // If promise's state is resolved, queue a task to process our resolve + // callbacks with promise's result. If promise's state is rejected, queue a + // task to process our reject callbacks with promise's result. if (mState != Pending && !mTaskPending) { nsRefPtr task = new PromiseTask(this); NS_DispatchToCurrentThread(task); @@ -315,5 +440,91 @@ Promise::MaybeReportRejected() win)); } +void +Promise::MaybeResolve(JSContext* aCx, + const Optional >& aValue, + PromiseTaskSync aAsynchronous) +{ + if (mResolvePending) { + return; + } + + ResolveInternal(aCx, aValue, aAsynchronous); +} + +void +Promise::MaybeReject(JSContext* aCx, + const Optional >& aValue, + PromiseTaskSync aAsynchronous) +{ + if (mResolvePending) { + return; + } + + RejectInternal(aCx, aValue, aAsynchronous); +} + +void +Promise::ResolveInternal(JSContext* aCx, + const Optional >& aValue, + PromiseTaskSync aAsynchronous) +{ + mResolvePending = true; + + // TODO: Bug 879245 - Then-able objects + if (aValue.WasPassed() && aValue.Value().isObject()) { + JS::Rooted valueObj(aCx, &aValue.Value().toObject()); + Promise* nextPromise; + nsresult rv = UNWRAP_OBJECT(Promise, aCx, valueObj, nextPromise); + + if (NS_SUCCEEDED(rv)) { + nsRefPtr resolveCb = new ResolvePromiseCallback(this); + nsRefPtr rejectCb = new RejectPromiseCallback(this); + nextPromise->AppendCallbacks(resolveCb, rejectCb); + return; + } + } + + // If the synchronous flag is set, process our resolve callbacks with + // value. Otherwise, the synchronous flag is unset, queue a task to process + // own resolve callbacks with value. Otherwise, the synchronous flag is + // unset, queue a task to process our resolve callbacks with value. + RunResolveTask(aValue.WasPassed() ? aValue.Value() : JS::UndefinedHandleValue, + Resolved, aAsynchronous); +} + +void +Promise::RejectInternal(JSContext* aCx, + const Optional >& aValue, + PromiseTaskSync aAsynchronous) +{ + mResolvePending = true; + + // If the synchronous flag is set, process our reject callbacks with + // value. Otherwise, the synchronous flag is unset, queue a task to process + // promise's reject callbacks with value. + RunResolveTask(aValue.WasPassed() ? aValue.Value() : JS::UndefinedHandleValue, + Rejected, aAsynchronous); +} + +void +Promise::RunResolveTask(JS::Handle aValue, + PromiseState aState, + PromiseTaskSync aAsynchronous) +{ + // If the synchronous flag is unset, queue a task to process our + // accept callbacks with value. + if (aAsynchronous == AsyncTask) { + nsRefPtr task = + new PromiseResolverTask(this, aValue, aState); + NS_DispatchToCurrentThread(task); + return; + } + + SetResult(aValue); + SetState(aState); + RunTask(); +} + } // namespace dom } // namespace mozilla diff --git a/dom/promise/Promise.h b/dom/promise/Promise.h index 1ef7c6500af..26c6f8c1f8b 100644 --- a/dom/promise/Promise.h +++ b/dom/promise/Promise.h @@ -23,14 +23,15 @@ namespace dom { class PromiseInit; class PromiseCallback; class AnyCallback; -class PromiseResolver; class Promise MOZ_FINAL : public nsISupports, public nsWrapperCache { friend class PromiseTask; - friend class PromiseResolver; friend class PromiseResolverTask; + friend class ResolvePromiseCallback; + friend class RejectPromiseCallback; + friend class WrapperPromiseCallback; public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -79,6 +80,11 @@ private: Rejected }; + enum PromiseTaskSync { + SyncTask, + AsyncTask + }; + void SetState(PromiseState aState) { MOZ_ASSERT(mState == Pending); @@ -97,6 +103,10 @@ private: // appended by then(), catch() or done(). void RunTask(); + void RunResolveTask(JS::Handle aValue, + Promise::PromiseState aState, + PromiseTaskSync aAsynchronous); + void AppendCallbacks(PromiseCallback* aResolveCallback, PromiseCallback* aRejectCallback); @@ -104,9 +114,29 @@ private: // report it to the error console. void MaybeReportRejected(); - nsRefPtr mWindow; + void MaybeResolve(JSContext* aCx, + const Optional >& aValue, + PromiseTaskSync aSync = AsyncTask); + void MaybeReject(JSContext* aCx, + const Optional >& aValue, + PromiseTaskSync aSync = AsyncTask); - nsRefPtr mResolver; + void ResolveInternal(JSContext* aCx, + const Optional >& aValue, + PromiseTaskSync aSync = AsyncTask); + + void RejectInternal(JSContext* aCx, + const Optional >& aValue, + PromiseTaskSync aSync = AsyncTask); + + // Static methods for the PromiseInit functions. + static bool + JSCallback(JSContext *aCx, unsigned aArgc, JS::Value *aVp); + static JSObject* + CreateFunction(JSContext* aCx, JSObject* aParent, Promise* aPromise, + int32_t aTask); + + nsRefPtr mWindow; nsTArray > mResolveCallbacks; nsTArray > mRejectCallbacks; @@ -115,6 +145,8 @@ private: PromiseState mState; bool mTaskPending; bool mHadRejectCallback; + + bool mResolvePending; }; } // namespace dom diff --git a/dom/promise/PromiseCallback.cpp b/dom/promise/PromiseCallback.cpp index dcbe42dd2f0..f8cb9b9c9a1 100644 --- a/dom/promise/PromiseCallback.cpp +++ b/dom/promise/PromiseCallback.cpp @@ -6,7 +6,6 @@ #include "PromiseCallback.h" #include "mozilla/dom/Promise.h" -#include "mozilla/dom/PromiseResolver.h" namespace mozilla { namespace dom { @@ -51,7 +50,7 @@ EnterCompartment(Maybe& aAc, JSContext* aCx, NS_IMPL_CYCLE_COLLECTION_INHERITED_1(ResolvePromiseCallback, PromiseCallback, - mResolver) + mPromise) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ResolvePromiseCallback) NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) @@ -59,10 +58,10 @@ NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) NS_IMPL_ADDREF_INHERITED(ResolvePromiseCallback, PromiseCallback) NS_IMPL_RELEASE_INHERITED(ResolvePromiseCallback, PromiseCallback) -ResolvePromiseCallback::ResolvePromiseCallback(PromiseResolver* aResolver) - : mResolver(aResolver) +ResolvePromiseCallback::ResolvePromiseCallback(Promise* aPromise) + : mPromise(aPromise) { - MOZ_ASSERT(aResolver); + MOZ_ASSERT(aPromise); MOZ_COUNT_CTOR(ResolvePromiseCallback); } @@ -79,14 +78,14 @@ ResolvePromiseCallback::Call(const Optional >& aValue) Maybe ac; EnterCompartment(ac, cx, aValue); - mResolver->ResolveInternal(cx, aValue, PromiseResolver::SyncTask); + mPromise->ResolveInternal(cx, aValue, Promise::SyncTask); } // RejectPromiseCallback NS_IMPL_CYCLE_COLLECTION_INHERITED_1(RejectPromiseCallback, PromiseCallback, - mResolver) + mPromise) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(RejectPromiseCallback) NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) @@ -94,10 +93,10 @@ NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) NS_IMPL_ADDREF_INHERITED(RejectPromiseCallback, PromiseCallback) NS_IMPL_RELEASE_INHERITED(RejectPromiseCallback, PromiseCallback) -RejectPromiseCallback::RejectPromiseCallback(PromiseResolver* aResolver) - : mResolver(aResolver) +RejectPromiseCallback::RejectPromiseCallback(Promise* aPromise) + : mPromise(aPromise) { - MOZ_ASSERT(aResolver); + MOZ_ASSERT(aPromise); MOZ_COUNT_CTOR(RejectPromiseCallback); } @@ -114,14 +113,14 @@ RejectPromiseCallback::Call(const Optional >& aValue) Maybe ac; EnterCompartment(ac, cx, aValue); - mResolver->RejectInternal(cx, aValue, PromiseResolver::SyncTask); + mPromise->RejectInternal(cx, aValue, Promise::SyncTask); } // WrapperPromiseCallback NS_IMPL_CYCLE_COLLECTION_INHERITED_2(WrapperPromiseCallback, PromiseCallback, - mNextResolver, mCallback) + mNextPromise, mCallback) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WrapperPromiseCallback) NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) @@ -129,12 +128,12 @@ NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) NS_IMPL_ADDREF_INHERITED(WrapperPromiseCallback, PromiseCallback) NS_IMPL_RELEASE_INHERITED(WrapperPromiseCallback, PromiseCallback) -WrapperPromiseCallback::WrapperPromiseCallback(PromiseResolver* aNextResolver, +WrapperPromiseCallback::WrapperPromiseCallback(Promise* aNextPromise, AnyCallback* aCallback) - : mNextResolver(aNextResolver) + : mNextPromise(aNextPromise) , mCallback(aCallback) { - MOZ_ASSERT(aNextResolver); + MOZ_ASSERT(aNextPromise); MOZ_COUNT_CTOR(WrapperPromiseCallback); } @@ -155,7 +154,7 @@ WrapperPromiseCallback::Call(const Optional >& aValue) // If invoking callback threw an exception, run resolver's reject with the // thrown exception as argument and the synchronous flag set. Optional > value(cx, - mCallback->Call(mNextResolver->GetParentObject(), aValue, rv, + mCallback->Call(mNextPromise->GetParentObject(), aValue, rv, CallbackObject::eRethrowExceptions)); rv.WouldReportJSException(); @@ -166,7 +165,7 @@ WrapperPromiseCallback::Call(const Optional >& aValue) Maybe ac2; EnterCompartment(ac2, cx, value); - mNextResolver->RejectInternal(cx, value, PromiseResolver::SyncTask); + mNextPromise->RejectInternal(cx, value, Promise::SyncTask); return; } @@ -174,7 +173,7 @@ WrapperPromiseCallback::Call(const Optional >& aValue) // set. Maybe ac2; EnterCompartment(ac2, cx, value); - mNextResolver->ResolveInternal(cx, value, PromiseResolver::SyncTask); + mNextPromise->ResolveInternal(cx, value, Promise::SyncTask); } // SimpleWrapperPromiseCallback @@ -211,23 +210,23 @@ SimpleWrapperPromiseCallback::Call(const Optional >& aValu } /* static */ PromiseCallback* -PromiseCallback::Factory(PromiseResolver* aNextResolver, - AnyCallback* aCallback, Task aTask) +PromiseCallback::Factory(Promise* aNextPromise, AnyCallback* aCallback, + Task aTask) { - MOZ_ASSERT(aNextResolver); + MOZ_ASSERT(aNextPromise); // If we have a callback and a next resolver, we have to exec the callback and // then propagate the return value to the next resolver->resolve(). if (aCallback) { - return new WrapperPromiseCallback(aNextResolver, aCallback); + return new WrapperPromiseCallback(aNextPromise, aCallback); } if (aTask == Resolve) { - return new ResolvePromiseCallback(aNextResolver); + return new ResolvePromiseCallback(aNextPromise); } if (aTask == Reject) { - return new RejectPromiseCallback(aNextResolver); + return new RejectPromiseCallback(aNextPromise); } MOZ_ASSERT(false, "This should not happen"); diff --git a/dom/promise/PromiseCallback.h b/dom/promise/PromiseCallback.h index 14aa3145edc..38089466eed 100644 --- a/dom/promise/PromiseCallback.h +++ b/dom/promise/PromiseCallback.h @@ -13,8 +13,6 @@ namespace mozilla { namespace dom { -class PromiseResolver; - // This is the base class for any PromiseCallback. // It's a logical step in the promise chain of callbacks. class PromiseCallback : public nsISupports @@ -35,13 +33,12 @@ public: // This factory returns a PromiseCallback object with refcount of 0. static PromiseCallback* - Factory(PromiseResolver* aNextResolver, AnyCallback* aCallback, - Task aTask); + Factory(Promise* aNextPromise, AnyCallback* aCallback, Task aTask); }; // WrapperPromiseCallback execs a JS Callback with a value, and then the return -// value is sent to the aNextResolver->resolve() or to aNextResolver->Reject() -// if the JS Callback throws. +// value is sent to the aNextPromise->resolveFunction() or to +// aNextPromise->RejectFunction() if the JS Callback throws. class WrapperPromiseCallback MOZ_FINAL : public PromiseCallback { public: @@ -51,12 +48,11 @@ public: void Call(const Optional >& aValue) MOZ_OVERRIDE; - WrapperPromiseCallback(PromiseResolver* aNextResolver, - AnyCallback* aCallback); + WrapperPromiseCallback(Promise* aNextPromise, AnyCallback* aCallback); ~WrapperPromiseCallback(); private: - nsRefPtr mNextResolver; + nsRefPtr mNextPromise; nsRefPtr mCallback; }; @@ -79,8 +75,8 @@ private: nsRefPtr mCallback; }; -// ResolvePromiseCallback calls aResolver->Resolve() with the value received by -// Call(). +// ResolvePromiseCallback calls aPromise->ResolveFunction() with the value +// received by Call(). class ResolvePromiseCallback MOZ_FINAL : public PromiseCallback { public: @@ -90,15 +86,15 @@ public: void Call(const Optional >& aValue) MOZ_OVERRIDE; - ResolvePromiseCallback(PromiseResolver* aResolver); + ResolvePromiseCallback(Promise* aPromise); ~ResolvePromiseCallback(); private: - nsRefPtr mResolver; + nsRefPtr mPromise; }; -// RejectPromiseCallback calls aResolver->Reject() with the value received by -// Call(). +// RejectPromiseCallback calls aPromise->RejectFunction() with the value +// received by Call(). class RejectPromiseCallback MOZ_FINAL : public PromiseCallback { public: @@ -108,11 +104,11 @@ public: void Call(const Optional >& aValue) MOZ_OVERRIDE; - RejectPromiseCallback(PromiseResolver* aResolver); + RejectPromiseCallback(Promise* aPromise); ~RejectPromiseCallback(); private: - nsRefPtr mResolver; + nsRefPtr mPromise; }; } // namespace dom diff --git a/dom/promise/PromiseResolver.cpp b/dom/promise/PromiseResolver.cpp deleted file mode 100644 index 7221990b45b..00000000000 --- a/dom/promise/PromiseResolver.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "mozilla/dom/PromiseResolver.h" -#include "mozilla/dom/PromiseBinding.h" -#include "mozilla/dom/Promise.h" -#include "nsThreadUtils.h" -#include "PromiseCallback.h" - -namespace mozilla { -namespace dom { - -// PromiseResolverTask - -// This class processes the promise's callbacks with promise's result. -class PromiseResolverTask MOZ_FINAL : public nsRunnable -{ -public: - PromiseResolverTask(PromiseResolver* aResolver, - const JS::Handle aValue, - Promise::PromiseState aState) - : mResolver(aResolver) - , mValue(aValue) - , mState(aState) - { - MOZ_ASSERT(aResolver); - MOZ_ASSERT(mState != Promise::Pending); - MOZ_COUNT_CTOR(PromiseResolverTask); - - JSContext* cx = nsContentUtils::GetSafeJSContext(); - JS_AddNamedValueRootRT(JS_GetRuntime(cx), &mValue, - "PromiseResolverTask.mValue"); - } - - ~PromiseResolverTask() - { - MOZ_COUNT_DTOR(PromiseResolverTask); - - JSContext* cx = nsContentUtils::GetSafeJSContext(); - JS_RemoveValueRootRT(JS_GetRuntime(cx), &mValue); - } - - NS_IMETHOD Run() - { - mResolver->RunTask(JS::Handle::fromMarkedLocation(&mValue), - mState, PromiseResolver::SyncTask); - return NS_OK; - } - -private: - nsRefPtr mResolver; - JS::Value mValue; - Promise::PromiseState mState; -}; - -// PromiseResolver - -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(PromiseResolver, mPromise) - -NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PromiseResolver, AddRef) -NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PromiseResolver, Release) - -PromiseResolver::PromiseResolver(Promise* aPromise) - : mPromise(aPromise) - , mResolvePending(false) -{ - MOZ_COUNT_CTOR(PromiseResolver); - SetIsDOMBinding(); -} - -PromiseResolver::~PromiseResolver() -{ - MOZ_COUNT_DTOR(PromiseResolver); -} - -JSObject* -PromiseResolver::WrapObject(JSContext* aCx, JS::Handle aScope) -{ - return PromiseResolverBinding::Wrap(aCx, aScope, this); -} - -void -PromiseResolver::Resolve(JSContext* aCx, - const Optional >& aValue, - PromiseTaskSync aAsynchronous) -{ - if (mResolvePending) { - return; - } - - ResolveInternal(aCx, aValue, aAsynchronous); -} - -void -PromiseResolver::ResolveInternal(JSContext* aCx, - const Optional >& aValue, - PromiseTaskSync aAsynchronous) -{ - mResolvePending = true; - - // TODO: Bug 879245 - Then-able objects - if (aValue.WasPassed() && aValue.Value().isObject()) { - JS::Rooted valueObj(aCx, &aValue.Value().toObject()); - Promise* nextPromise; - nsresult rv = UNWRAP_OBJECT(Promise, aCx, valueObj, nextPromise); - - if (NS_SUCCEEDED(rv)) { - nsRefPtr resolveCb = new ResolvePromiseCallback(this); - nsRefPtr rejectCb = new RejectPromiseCallback(this); - nextPromise->AppendCallbacks(resolveCb, rejectCb); - return; - } - } - - // If the synchronous flag is set, process promise's resolve callbacks with - // value. Otherwise, the synchronous flag is unset, queue a task to process - // promise's resolve callbacks with value. Otherwise, the synchronous flag is - // unset, queue a task to process promise's resolve callbacks with value. - RunTask(aValue.WasPassed() ? aValue.Value() : JS::UndefinedHandleValue, - Promise::Resolved, aAsynchronous); -} - -void -PromiseResolver::Reject(JSContext* aCx, - const Optional >& aValue, - PromiseTaskSync aAsynchronous) -{ - if (mResolvePending) { - return; - } - - RejectInternal(aCx, aValue, aAsynchronous); -} - -void -PromiseResolver::RejectInternal(JSContext* aCx, - const Optional >& aValue, - PromiseTaskSync aAsynchronous) -{ - mResolvePending = true; - - // If the synchronous flag is set, process promise's reject callbacks with - // value. Otherwise, the synchronous flag is unset, queue a task to process - // promise's reject callbacks with value. - RunTask(aValue.WasPassed() ? aValue.Value() : JS::UndefinedHandleValue, - Promise::Rejected, aAsynchronous); -} - -void -PromiseResolver::RunTask(JS::Handle aValue, - Promise::PromiseState aState, - PromiseTaskSync aAsynchronous) -{ - // If the synchronous flag is unset, queue a task to process promise's - // accept callbacks with value. - if (aAsynchronous == AsyncTask) { - nsRefPtr task = - new PromiseResolverTask(this, aValue, aState); - NS_DispatchToCurrentThread(task); - return; - } - - mPromise->SetResult(aValue); - mPromise->SetState(aState); - mPromise->RunTask(); - mPromise = nullptr; -} - -} // namespace dom -} // namespace mozilla diff --git a/dom/promise/PromiseResolver.h b/dom/promise/PromiseResolver.h deleted file mode 100644 index f6b8975964c..00000000000 --- a/dom/promise/PromiseResolver.h +++ /dev/null @@ -1,74 +0,0 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef mozilla_dom_PromiseResolver_h -#define mozilla_dom_PromiseResolver_h - -#include "mozilla/dom/Promise.h" -#include "mozilla/Attributes.h" -#include "mozilla/dom/BindingDeclarations.h" -#include "nsCycleCollectionParticipant.h" -#include "nsWrapperCache.h" -#include "js/TypeDecls.h" - -namespace mozilla { -namespace dom { - -class PromiseResolver MOZ_FINAL : public nsWrapperCache -{ - friend class PromiseResolverTask; - friend class WrapperPromiseCallback; - friend class ResolvePromiseCallback; - friend class RejectPromiseCallback; - -private: - enum PromiseTaskSync { - SyncTask, - AsyncTask - }; - -public: - NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PromiseResolver) - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PromiseResolver) - - PromiseResolver(Promise* aPromise); - virtual ~PromiseResolver(); - - Promise* GetParentObject() const - { - return mPromise; - } - - virtual JSObject* - WrapObject(JSContext* aCx, JS::Handle aScope) MOZ_OVERRIDE; - - void Resolve(JSContext* aCx, const Optional >& aValue, - PromiseTaskSync aSync = AsyncTask); - - void Reject(JSContext* aCx, const Optional >& aValue, - PromiseTaskSync aSync = AsyncTask); - -private: - void ResolveInternal(JSContext* aCx, - const Optional >& aValue, - PromiseTaskSync aSync = AsyncTask); - - void RejectInternal(JSContext* aCx, - const Optional >& aValue, - PromiseTaskSync aSync = AsyncTask); - - void RunTask(JS::Handle aValue, - Promise::PromiseState aState, PromiseTaskSync aSync); - - nsRefPtr mPromise; - - bool mResolvePending; -}; - -} // namespace dom -} // namespace mozilla - -#endif // mozilla_dom_PromiseResolver_h diff --git a/dom/promise/moz.build b/dom/promise/moz.build index 69bcf602d9f..600cc09007f 100644 --- a/dom/promise/moz.build +++ b/dom/promise/moz.build @@ -12,12 +12,10 @@ MODULE = 'dom' EXPORTS.mozilla.dom += [ 'Promise.h', - 'PromiseResolver.h', ] CPP_SOURCES += [ 'Promise.cpp', - 'PromiseResolver.cpp', 'PromiseCallback.cpp', ] diff --git a/dom/promise/tests/test_bug883683.html b/dom/promise/tests/test_bug883683.html index b1bdcf22bf3..c0e3d16f795 100644 --- a/dom/promise/tests/test_bug883683.html +++ b/dom/promise/tests/test_bug883683.html @@ -23,10 +23,10 @@ function runTest() { [{}, {}, {}, {}, {}].reduce(Promise.resolve); ok(true, "No leaks with resolve?"); - [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r) { throw a; }); }); + [{}, {}, {}, {}, {}].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(r) { }); }); + [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { }); }); ok(true, "No leaks with empty promise?"); SimpleTest.finish(); diff --git a/dom/promise/tests/test_promise.html b/dom/promise/tests/test_promise.html index 5666b6fa2da..0b67e17d145 100644 --- a/dom/promise/tests/test_promise.html +++ b/dom/promise/tests/test_promise.html @@ -19,12 +19,11 @@ function promiseResolve() { ok(Promise, "Promise object should exist"); - var promise = new Promise(function(resolver) { - ok(resolver, "PromiseResolver exists"); - ok("reject" in resolver, "PromiseResolver.reject exists"); - ok("resolve" in resolver, "PromiseResolver.resolve exists"); + var promise = new Promise(function(resolve, reject) { + ok(resolve, "Promise.resolve exists"); + ok(reject, "Promise.reject exists"); - resolver.resolve(42); + resolve(42); }).then(function(what) { ok(true, "Then - resolveCb has been called"); is(what, 42, "ResolveCb received 42"); @@ -36,8 +35,8 @@ function promiseResolve() { } function promiseReject() { - var promise = new Promise(function(resolver) { - resolver.reject(42); + var promise = new Promise(function(resolve, reject) { + reject(42); }).then(function(what) { ok(false, "Then - resolveCb has been called"); runTest(); @@ -49,7 +48,7 @@ function promiseReject() { } function promiseException() { - var promise = new Promise(function(resolver) { + var promise = new Promise(function(resolve, reject) { throw 42; }).then(function(what) { ok(false, "Then - resolveCb has been called"); @@ -62,9 +61,9 @@ function promiseException() { } function promiseGC() { - var resolver; - var promise = new Promise(function(r) { - resolver = r; + var resolve; + var promise = new Promise(function(r1, r2) { + resolve = r1; }).then(function(what) { ok(true, "Then - promise is still alive"); runTest(); @@ -76,14 +75,14 @@ function promiseGC() { SpecialPowers.forceGC(); SpecialPowers.forceCC(); - resolver.resolve(42); + resolve(42); } function promiseAsync() { var global = "foo"; - var f = new Promise(function(r) { + var f = new Promise(function(r1, r2) { is(global, "foo", "Global should be foo"); - r.resolve(42); + r1(42); is(global, "foo", "Global should still be foo"); setTimeout(function() { is(global, "bar", "Global should still be bar!"); @@ -97,8 +96,8 @@ function promiseAsync() { function promiseDoubleThen() { var steps = 0; - var promise = new Promise(function(resolver) { - resolver.resolve(42); + var promise = new Promise(function(r1, r2) { + r1(42); }); promise.then(function(what) { @@ -120,8 +119,8 @@ function promiseDoubleThen() { } function promiseThenException() { - var promise = new Promise(function(resolver) { - resolver.resolve(42); + var promise = new Promise(function(resolve, reject) { + resolve(42); }); promise.then(function(what) { @@ -134,8 +133,8 @@ function promiseThenException() { } function promiseThenCatchThen() { - var promise = new Promise(function(resolver) { - resolver.resolve(42); + var promise = new Promise(function(resolve, reject) { + resolve(42); }); var promise2 = promise.then(function(what) { @@ -166,8 +165,8 @@ function promiseThenCatchThen() { } function promiseRejectThenCatchThen() { - var promise = new Promise(function(resolver) { - resolver.reject(42); + var promise = new Promise(function(resolve, reject) { + reject(42); }); var promise2 = promise.then(function(what) { @@ -194,8 +193,8 @@ function promiseRejectThenCatchThen() { } function promiseRejectThenCatchThen2() { - var promise = new Promise(function(resolver) { - resolver.reject(42); + var promise = new Promise(function(resolve, reject) { + reject(42); }); promise.then(function(what) { @@ -214,8 +213,8 @@ function promiseRejectThenCatchThen2() { } function promiseRejectThenCatchExceptionThen() { - var promise = new Promise(function(resolver) { - resolver.reject(42); + var promise = new Promise(function(resolve, reject) { + reject(42); }); promise.then(function(what) { @@ -237,8 +236,8 @@ function promiseRejectThenCatchExceptionThen() { function promiseThenCatchOrderingResolve() { var global = 0; - var f = new Promise(function(r) { - r.resolve(42); + var f = new Promise(function(r1, r2) { + r1(42); }); f.then(function() { @@ -260,8 +259,8 @@ function promiseThenCatchOrderingResolve() { function promiseThenCatchOrderingReject() { var global = 0; - var f = new Promise(function(r) { - r.reject(42); + var f = new Promise(function(r1, r2) { + r2(42); }) f.then(function() {}, function() { @@ -282,10 +281,10 @@ function promiseThenCatchOrderingReject() { } function promiseNestedPromise() { - new Promise(function(resolver) { - resolver.resolve(new Promise(function(r) { + new Promise(function(resolve, reject) { + resolve(new Promise(function(resolve, reject) { ok(true, "Nested promise is executed"); - r.resolve(42); + resolve(42); })); }).then(function(value) { is(value, 42, "Nested promise is executed and then == 42"); @@ -294,10 +293,10 @@ function promiseNestedPromise() { } function promiseNestedNestedPromise() { - new Promise(function(resolver) { - resolver.resolve(new Promise(function(r) { + new Promise(function(resolve, reject) { + resolve(new Promise(function(resolve, reject) { ok(true, "Nested promise is executed"); - r.resolve(42); + resolve(42); }).then(function(what) { return what+1; })); }).then(function(value) { is(value, 43, "Nested promise is executed and then == 43"); @@ -306,12 +305,12 @@ function promiseNestedNestedPromise() { } function promiseWrongNestedPromise() { - new Promise(function(resolver) { - resolver.resolve(new Promise(function(r) { + new Promise(function(resolve, reject) { + resolve(new Promise(function(r, r2) { ok(true, "Nested promise is executed"); - r.resolve(42); + r(42); })); - resolver.reject(42); + reject(42); }).then(function(value) { is(value, 42, "Nested promise is executed and then == 42"); runTest(); @@ -321,12 +320,12 @@ function promiseWrongNestedPromise() { } function promiseLoop() { - new Promise(function(resolver) { - resolver.resolve(new Promise(function(r) { + new Promise(function(resolve, reject) { + resolve(new Promise(function(r1, r2) { ok(true, "Nested promise is executed"); - r.resolve(new Promise(function(r) { + r1(new Promise(function(r1, r2) { ok(true, "Nested nested promise is executed"); - r.resolve(42); + r1(42); })); })); }).then(function(value) { @@ -356,9 +355,9 @@ function promiseResolve() { } function promiseResolveNestedPromise() { - var promise = Promise.resolve(new Promise(function(r) { + var promise = Promise.resolve(new Promise(function(r, r2) { ok(true, "Nested promise is executed"); - r.resolve(42); + r(42); }, function() { ok(false, "This should not be called"); })).then(function(what) { diff --git a/dom/promise/tests/test_resolve.html b/dom/promise/tests/test_resolve.html index 01d79bcece5..67f4ca6412c 100644 --- a/dom/promise/tests/test_resolve.html +++ b/dom/promise/tests/test_resolve.html @@ -44,13 +44,13 @@ function runTest() { var test = tests.pop(); - new Promise(function(resolver) { - resolver.resolve(test); + new Promise(function(resolve, reject) { + resolve(test); }).then(function(what) { ok(test === what, "What is: " + what); }, cbError).then(function() { - new Promise(function(resolver) { - resolver.reject(test) + new Promise(function(resolve, reject) { + reject(test) }).then(cbError, function(what) { ok(test === what, "What is: " + what); }).then(runTest, cbError); diff --git a/dom/tests/browser/test-console-api.html b/dom/tests/browser/test-console-api.html index 6e9de2f647d..7d6e4537da7 100644 --- a/dom/tests/browser/test-console-api.html +++ b/dom/tests/browser/test-console-api.html @@ -49,7 +49,7 @@ function nativeCallback() { SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, function() { - new Promise(r => r.resolve(42)).then(console.log); + new Promise(function(resolve, reject) { resolve(42); }).then(console.log); }); } diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 6046cf0cd29..46df3af911b 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -383,7 +383,6 @@ var interfaceNamesInGlobalScope = "ProcessingInstruction", "ProgressEvent", {name: "Promise", b2g: false, release: false}, - {name: "PromiseResolver", b2g: false, release: false}, "PropertyNodeList", "Range", "RecordErrorEvent", diff --git a/dom/webidl/Promise.webidl b/dom/webidl/Promise.webidl index 3b5f5d4efff..0b74626649d 100644 --- a/dom/webidl/Promise.webidl +++ b/dom/webidl/Promise.webidl @@ -7,14 +7,10 @@ * http://dom.spec.whatwg.org/#promises */ -[Func="mozilla::dom::Promise::EnabledForScope"] -interface PromiseResolver { - // TODO bug 875289 - void fulfill(optional any value); - void resolve(optional any value); - void reject(optional any value); -}; - -callback PromiseInit = void (PromiseResolver resolver); +// TODO We use object instead Function. There is an open issue on WebIDL to +// have different types for "platform-provided function" and "user-provided +// function"; for now, we just use "object". +callback PromiseInit = void (object resolve, object reject); callback AnyCallback = any (optional any value); [Func="mozilla::dom::Promise::EnabledForScope", Constructor(PromiseInit init)]