michael@0: /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "PromiseCallback.h" michael@0: #include "mozilla/dom/Promise.h" michael@0: #include "mozilla/dom/PromiseNativeHandler.h" michael@0: michael@0: #include "js/OldDebugAPI.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseCallback) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseCallback) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: PromiseCallback::PromiseCallback() michael@0: { michael@0: MOZ_COUNT_CTOR(PromiseCallback); michael@0: } michael@0: michael@0: PromiseCallback::~PromiseCallback() michael@0: { michael@0: MOZ_COUNT_DTOR(PromiseCallback); michael@0: } michael@0: michael@0: // ResolvePromiseCallback michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(ResolvePromiseCallback) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ResolvePromiseCallback, michael@0: PromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) michael@0: tmp->mGlobal = nullptr; michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ResolvePromiseCallback, michael@0: PromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ResolvePromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ResolvePromiseCallback) michael@0: NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(ResolvePromiseCallback, PromiseCallback) michael@0: NS_IMPL_RELEASE_INHERITED(ResolvePromiseCallback, PromiseCallback) michael@0: michael@0: ResolvePromiseCallback::ResolvePromiseCallback(Promise* aPromise, michael@0: JS::Handle aGlobal) michael@0: : mPromise(aPromise) michael@0: , mGlobal(aGlobal) michael@0: { michael@0: MOZ_ASSERT(aPromise); michael@0: MOZ_ASSERT(aGlobal); michael@0: MOZ_COUNT_CTOR(ResolvePromiseCallback); michael@0: HoldJSObjects(this); michael@0: } michael@0: michael@0: ResolvePromiseCallback::~ResolvePromiseCallback() michael@0: { michael@0: MOZ_COUNT_DTOR(ResolvePromiseCallback); michael@0: DropJSObjects(this); michael@0: } michael@0: michael@0: void michael@0: ResolvePromiseCallback::Call(JS::Handle aValue) michael@0: { michael@0: // Run resolver's algorithm with value and the synchronous flag set. michael@0: ThreadsafeAutoSafeJSContext cx; michael@0: michael@0: JSAutoCompartment ac(cx, mGlobal); michael@0: JS::Rooted value(cx, aValue); michael@0: if (!JS_WrapValue(cx, &value)) { michael@0: NS_WARNING("Failed to wrap value into the right compartment."); michael@0: return; michael@0: } michael@0: michael@0: mPromise->ResolveInternal(cx, value, Promise::SyncTask); michael@0: } michael@0: michael@0: // RejectPromiseCallback michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(RejectPromiseCallback) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(RejectPromiseCallback, michael@0: PromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) michael@0: tmp->mGlobal = nullptr; michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(RejectPromiseCallback, michael@0: PromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(RejectPromiseCallback) michael@0: NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(RejectPromiseCallback, michael@0: PromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(RejectPromiseCallback, PromiseCallback) michael@0: NS_IMPL_RELEASE_INHERITED(RejectPromiseCallback, PromiseCallback) michael@0: michael@0: RejectPromiseCallback::RejectPromiseCallback(Promise* aPromise, michael@0: JS::Handle aGlobal) michael@0: : mPromise(aPromise) michael@0: , mGlobal(aGlobal) michael@0: { michael@0: MOZ_ASSERT(aPromise); michael@0: MOZ_ASSERT(mGlobal); michael@0: MOZ_COUNT_CTOR(RejectPromiseCallback); michael@0: HoldJSObjects(this); michael@0: } michael@0: michael@0: RejectPromiseCallback::~RejectPromiseCallback() michael@0: { michael@0: MOZ_COUNT_DTOR(RejectPromiseCallback); michael@0: DropJSObjects(this); michael@0: } michael@0: michael@0: void michael@0: RejectPromiseCallback::Call(JS::Handle aValue) michael@0: { michael@0: // Run resolver's algorithm with value and the synchronous flag set. michael@0: ThreadsafeAutoSafeJSContext cx; michael@0: michael@0: JSAutoCompartment ac(cx, mGlobal); michael@0: JS::Rooted value(cx, aValue); michael@0: if (!JS_WrapValue(cx, &value)) { michael@0: NS_WARNING("Failed to wrap value into the right compartment."); michael@0: return; michael@0: } michael@0: michael@0: michael@0: mPromise->RejectInternal(cx, value, Promise::SyncTask); michael@0: } michael@0: michael@0: // WrapperPromiseCallback michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(WrapperPromiseCallback) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WrapperPromiseCallback, michael@0: PromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextPromise) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) michael@0: tmp->mGlobal = nullptr; michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WrapperPromiseCallback, michael@0: PromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextPromise) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WrapperPromiseCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WrapperPromiseCallback) michael@0: NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(WrapperPromiseCallback, PromiseCallback) michael@0: NS_IMPL_RELEASE_INHERITED(WrapperPromiseCallback, PromiseCallback) michael@0: michael@0: WrapperPromiseCallback::WrapperPromiseCallback(Promise* aNextPromise, michael@0: JS::Handle aGlobal, michael@0: AnyCallback* aCallback) michael@0: : mNextPromise(aNextPromise) michael@0: , mGlobal(aGlobal) michael@0: , mCallback(aCallback) michael@0: { michael@0: MOZ_ASSERT(aNextPromise); michael@0: MOZ_ASSERT(aGlobal); michael@0: MOZ_COUNT_CTOR(WrapperPromiseCallback); michael@0: HoldJSObjects(this); michael@0: } michael@0: michael@0: WrapperPromiseCallback::~WrapperPromiseCallback() michael@0: { michael@0: MOZ_COUNT_DTOR(WrapperPromiseCallback); michael@0: DropJSObjects(this); michael@0: } michael@0: michael@0: void michael@0: WrapperPromiseCallback::Call(JS::Handle aValue) michael@0: { michael@0: ThreadsafeAutoSafeJSContext cx; michael@0: michael@0: JSAutoCompartment ac(cx, mGlobal); michael@0: JS::Rooted value(cx, aValue); michael@0: if (!JS_WrapValue(cx, &value)) { michael@0: NS_WARNING("Failed to wrap value into the right compartment."); michael@0: return; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: michael@0: // If invoking callback threw an exception, run resolver's reject with the michael@0: // thrown exception as argument and the synchronous flag set. michael@0: JS::Rooted retValue(cx); michael@0: mCallback->Call(value, &retValue, rv, CallbackObject::eRethrowExceptions); michael@0: michael@0: rv.WouldReportJSException(); michael@0: michael@0: if (rv.Failed() && rv.IsJSException()) { michael@0: JS::Rooted value(cx); michael@0: rv.StealJSException(cx, &value); michael@0: michael@0: if (!JS_WrapValue(cx, &value)) { michael@0: NS_WARNING("Failed to wrap value into the right compartment."); michael@0: return; michael@0: } michael@0: michael@0: mNextPromise->RejectInternal(cx, value, Promise::SyncTask); michael@0: return; michael@0: } michael@0: michael@0: // If the return value is the same as the promise itself, throw TypeError. michael@0: if (retValue.isObject()) { michael@0: JS::Rooted valueObj(cx, &retValue.toObject()); michael@0: Promise* returnedPromise; michael@0: nsresult r = UNWRAP_OBJECT(Promise, valueObj, returnedPromise); michael@0: michael@0: if (NS_SUCCEEDED(r) && returnedPromise == mNextPromise) { michael@0: const char* fileName = nullptr; michael@0: uint32_t lineNumber = 0; michael@0: michael@0: // Try to get some information about the callback to report a sane error, michael@0: // but don't try too hard (only deals with scripted functions). michael@0: JS::Rooted unwrapped(cx, michael@0: js::CheckedUnwrap(mCallback->Callback())); michael@0: michael@0: if (unwrapped) { michael@0: JSAutoCompartment ac(cx, unwrapped); michael@0: if (JS_ObjectIsFunction(cx, unwrapped)) { michael@0: JS::Rooted asValue(cx, JS::ObjectValue(*unwrapped)); michael@0: JS::Rooted func(cx, JS_ValueToFunction(cx, asValue)); michael@0: michael@0: MOZ_ASSERT(func); michael@0: JSScript* script = JS_GetFunctionScript(cx, func); michael@0: if (script) { michael@0: fileName = JS_GetScriptFilename(script); michael@0: lineNumber = JS_GetScriptBaseLineNumber(cx, script); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // We're back in aValue's compartment here. michael@0: JS::Rooted stack(cx, JS_GetEmptyString(JS_GetRuntime(cx))); michael@0: JS::Rooted fn(cx, JS_NewStringCopyZ(cx, fileName)); michael@0: if (!fn) { michael@0: // Out of memory. Promise will stay unresolved. michael@0: JS_ClearPendingException(cx); michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted message(cx, michael@0: JS_NewStringCopyZ(cx, michael@0: "then() cannot return same Promise that it resolves.")); michael@0: if (!message) { michael@0: // Out of memory. Promise will stay unresolved. michael@0: JS_ClearPendingException(cx); michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted typeError(cx); michael@0: if (!JS::CreateTypeError(cx, stack, fn, lineNumber, 0, michael@0: nullptr, message, &typeError)) { michael@0: // Out of memory. Promise will stay unresolved. michael@0: JS_ClearPendingException(cx); michael@0: return; michael@0: } michael@0: michael@0: mNextPromise->RejectInternal(cx, typeError, Promise::SyncTask); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Otherwise, run resolver's resolve with value and the synchronous flag michael@0: // set. michael@0: if (!JS_WrapValue(cx, &retValue)) { michael@0: NS_WARNING("Failed to wrap value into the right compartment."); michael@0: return; michael@0: } michael@0: michael@0: mNextPromise->ResolveInternal(cx, retValue, Promise::SyncTask); michael@0: } michael@0: michael@0: // NativePromiseCallback michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(NativePromiseCallback, michael@0: PromiseCallback, mHandler) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(NativePromiseCallback) michael@0: NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(NativePromiseCallback, PromiseCallback) michael@0: NS_IMPL_RELEASE_INHERITED(NativePromiseCallback, PromiseCallback) michael@0: michael@0: NativePromiseCallback::NativePromiseCallback(PromiseNativeHandler* aHandler, michael@0: Promise::PromiseState aState) michael@0: : mHandler(aHandler) michael@0: , mState(aState) michael@0: { michael@0: MOZ_ASSERT(aHandler); michael@0: MOZ_COUNT_CTOR(NativePromiseCallback); michael@0: } michael@0: michael@0: NativePromiseCallback::~NativePromiseCallback() michael@0: { michael@0: MOZ_COUNT_DTOR(NativePromiseCallback); michael@0: } michael@0: michael@0: void michael@0: NativePromiseCallback::Call(JS::Handle aValue) michael@0: { michael@0: if (mState == Promise::Resolved) { michael@0: mHandler->ResolvedCallback(aValue); michael@0: return; michael@0: } michael@0: michael@0: if (mState == Promise::Rejected) { michael@0: mHandler->RejectedCallback(aValue); michael@0: return; michael@0: } michael@0: michael@0: NS_NOTREACHED("huh?"); michael@0: } michael@0: michael@0: /* static */ PromiseCallback* michael@0: PromiseCallback::Factory(Promise* aNextPromise, JS::Handle aGlobal, michael@0: AnyCallback* aCallback, Task aTask) michael@0: { michael@0: MOZ_ASSERT(aNextPromise); michael@0: michael@0: // If we have a callback and a next resolver, we have to exec the callback and michael@0: // then propagate the return value to the next resolver->resolve(). michael@0: if (aCallback) { michael@0: return new WrapperPromiseCallback(aNextPromise, aGlobal, aCallback); michael@0: } michael@0: michael@0: if (aTask == Resolve) { michael@0: return new ResolvePromiseCallback(aNextPromise, aGlobal); michael@0: } michael@0: michael@0: if (aTask == Reject) { michael@0: return new RejectPromiseCallback(aNextPromise, aGlobal); michael@0: } michael@0: michael@0: MOZ_ASSERT(false, "This should not happen"); michael@0: return nullptr; michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla