summaryrefslogtreecommitdiff
path: root/dom/promise
diff options
context:
space:
mode:
Diffstat (limited to 'dom/promise')
-rw-r--r--dom/promise/Promise.cpp3264
-rw-r--r--dom/promise/Promise.h537
-rw-r--r--dom/promise/PromiseCallback.cpp575
-rw-r--r--dom/promise/PromiseCallback.h203
-rw-r--r--dom/promise/PromiseDebugging.cpp521
-rw-r--r--dom/promise/PromiseDebugging.h108
-rw-r--r--dom/promise/PromiseNativeHandler.h38
-rw-r--r--dom/promise/PromiseWorkerProxy.h226
-rw-r--r--dom/promise/moz.build37
-rw-r--r--dom/promise/tests/chrome.ini7
-rw-r--r--dom/promise/tests/file_promise_and_timeout_ordering.js18
-rw-r--r--dom/promise/tests/file_promise_xrays.html32
-rw-r--r--dom/promise/tests/mochitest.ini18
-rw-r--r--dom/promise/tests/promise_uncatchable_exception.js9
-rw-r--r--dom/promise/tests/test_bug883683.html41
-rw-r--r--dom/promise/tests/test_on_new_promise.html45
-rw-r--r--dom/promise/tests/test_on_promise_settled.html54
-rw-r--r--dom/promise/tests/test_on_promise_settled_duplicates.html59
-rw-r--r--dom/promise/tests/test_promise.html831
-rw-r--r--dom/promise/tests/test_promise_and_timeout_ordering.html15
-rw-r--r--dom/promise/tests/test_promise_and_timeout_ordering_workers.html13
-rw-r--r--dom/promise/tests/test_promise_uncatchable_exception.html35
-rw-r--r--dom/promise/tests/test_promise_utils.html320
-rw-r--r--dom/promise/tests/test_promise_xrays.html365
-rw-r--r--dom/promise/tests/test_resolve.html67
-rw-r--r--dom/promise/tests/test_resolver_return_value.html40
-rw-r--r--dom/promise/tests/test_species_getter.html24
-rw-r--r--dom/promise/tests/test_thenable_vs_promise_ordering.html27
-rw-r--r--dom/promise/tests/test_webassembly_compile.html174
-rw-r--r--dom/promise/tests/unit/test_monitor_uncaught.js274
-rw-r--r--dom/promise/tests/unit/xpcshell.ini5
31 files changed, 7982 insertions, 0 deletions
diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp
new file mode 100644
index 0000000000..bf1fa5f504
--- /dev/null
+++ b/dom/promise/Promise.cpp
@@ -0,0 +1,3264 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/Promise.h"
+
+#include "js/Debug.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/Preferences.h"
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/MediaStreamError.h"
+#include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "jsfriendapi.h"
+#include "js/StructuredClone.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsJSEnvironment.h"
+#include "nsJSPrincipals.h"
+#include "nsJSUtils.h"
+#include "nsPIDOMWindow.h"
+#include "PromiseCallback.h"
+#include "PromiseDebugging.h"
+#include "PromiseNativeHandler.h"
+#include "PromiseWorkerProxy.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WrapperFactory.h"
+#include "xpcpublic.h"
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+// Generator used by Promise::GetID.
+Atomic<uintptr_t> gIDGenerator(0);
+} // namespace
+
+using namespace workers;
+
+#ifndef SPIDERMONKEY_PROMISE
+// This class processes the promise's callbacks with promise's result.
+class PromiseReactionJob final : public Runnable
+{
+public:
+ PromiseReactionJob(Promise* aPromise,
+ PromiseCallback* aCallback,
+ const JS::Value& aValue)
+ : mPromise(aPromise)
+ , mCallback(aCallback)
+ , mValue(CycleCollectedJSContext::Get()->Context(), aValue)
+ {
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(aCallback);
+ MOZ_COUNT_CTOR(PromiseReactionJob);
+ }
+
+ virtual
+ ~PromiseReactionJob()
+ {
+ NS_ASSERT_OWNINGTHREAD(PromiseReactionJob);
+ MOZ_COUNT_DTOR(PromiseReactionJob);
+ }
+
+protected:
+ NS_IMETHOD
+ Run() override
+ {
+ NS_ASSERT_OWNINGTHREAD(PromiseReactionJob);
+
+ MOZ_ASSERT(mPromise->GetWrapper()); // It was preserved!
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mPromise->GetWrapper())) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JS::Value> value(cx, mValue);
+ if (!MaybeWrapValue(cx, &value)) {
+ NS_WARNING("Failed to wrap value into the right compartment.");
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> asyncStack(cx, mPromise->mAllocationStack);
+
+ {
+ Maybe<JS::AutoSetAsyncStackForNewCalls> sas;
+ if (asyncStack) {
+ sas.emplace(cx, asyncStack, "Promise");
+ }
+ mCallback->Call(cx, value);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ RefPtr<Promise> mPromise;
+ RefPtr<PromiseCallback> mCallback;
+ JS::PersistentRooted<JS::Value> mValue;
+ NS_DECL_OWNINGTHREAD;
+};
+
+/*
+ * Utilities for thenable callbacks.
+ *
+ * A thenable is a { then: function(resolve, reject) { } }.
+ * `then` is called with a resolve and reject callback pair.
+ * Since only one of these should be called at most once (first call wins), the
+ * two keep a reference to each other in SLOT_DATA. When either of them is
+ * called, the references are cleared. Further calls are ignored.
+ */
+namespace {
+void
+LinkThenableCallables(JSContext* aCx, JS::Handle<JSObject*> aResolveFunc,
+ JS::Handle<JSObject*> aRejectFunc)
+{
+ js::SetFunctionNativeReserved(aResolveFunc, Promise::SLOT_DATA,
+ JS::ObjectValue(*aRejectFunc));
+ js::SetFunctionNativeReserved(aRejectFunc, Promise::SLOT_DATA,
+ JS::ObjectValue(*aResolveFunc));
+}
+
+/*
+ * Returns false if callback was already called before, otherwise breaks the
+ * links and returns true.
+ */
+bool
+MarkAsCalledIfNotCalledBefore(JSContext* aCx, JS::Handle<JSObject*> aFunc)
+{
+ JS::Value otherFuncVal =
+ js::GetFunctionNativeReserved(aFunc, Promise::SLOT_DATA);
+
+ if (!otherFuncVal.isObject()) {
+ return false;
+ }
+
+ JSObject* otherFuncObj = &otherFuncVal.toObject();
+ MOZ_ASSERT(js::GetFunctionNativeReserved(otherFuncObj,
+ Promise::SLOT_DATA).isObject());
+
+ // Break both references.
+ js::SetFunctionNativeReserved(aFunc, Promise::SLOT_DATA,
+ JS::UndefinedValue());
+ js::SetFunctionNativeReserved(otherFuncObj, Promise::SLOT_DATA,
+ JS::UndefinedValue());
+
+ return true;
+}
+
+Promise*
+GetPromise(JSContext* aCx, JS::Handle<JSObject*> aFunc)
+{
+ JS::Value promiseVal = js::GetFunctionNativeReserved(aFunc,
+ Promise::SLOT_PROMISE);
+
+ MOZ_ASSERT(promiseVal.isObject());
+
+ Promise* promise;
+ UNWRAP_OBJECT(Promise, &promiseVal.toObject(), promise);
+ return promise;
+}
+} // namespace
+
+// Runnable to resolve thenables.
+// Equivalent to the specification's ResolvePromiseViaThenableTask.
+class PromiseResolveThenableJob final : public Runnable
+{
+public:
+ PromiseResolveThenableJob(Promise* aPromise,
+ JS::Handle<JSObject*> aThenable,
+ PromiseInit* aThen)
+ : mPromise(aPromise)
+ , mThenable(CycleCollectedJSContext::Get()->Context(), aThenable)
+ , mThen(aThen)
+ {
+ MOZ_ASSERT(aPromise);
+ MOZ_COUNT_CTOR(PromiseResolveThenableJob);
+ }
+
+ virtual
+ ~PromiseResolveThenableJob()
+ {
+ NS_ASSERT_OWNINGTHREAD(PromiseResolveThenableJob);
+ MOZ_COUNT_DTOR(PromiseResolveThenableJob);
+ }
+
+protected:
+ NS_IMETHOD
+ Run() override
+ {
+ NS_ASSERT_OWNINGTHREAD(PromiseResolveThenableJob);
+
+ MOZ_ASSERT(mPromise->GetWrapper()); // It was preserved!
+
+ AutoJSAPI jsapi;
+ // If we ever change which compartment we're working in here, make sure to
+ // fix the fast-path for resolved-with-a-Promise in ResolveInternal.
+ if (!jsapi.Init(mPromise->GetWrapper())) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> resolveFunc(cx,
+ mPromise->CreateThenableFunction(cx, mPromise, PromiseCallback::Resolve));
+
+ if (!resolveFunc) {
+ mPromise->HandleException(cx);
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> rejectFunc(cx,
+ mPromise->CreateThenableFunction(cx, mPromise, PromiseCallback::Reject));
+ if (!rejectFunc) {
+ mPromise->HandleException(cx);
+ return NS_OK;
+ }
+
+ LinkThenableCallables(cx, resolveFunc, rejectFunc);
+
+ ErrorResult rv;
+
+ JS::Rooted<JSObject*> rootedThenable(cx, mThenable);
+
+ mThen->Call(rootedThenable, resolveFunc, rejectFunc, rv,
+ "promise thenable", CallbackObject::eRethrowExceptions,
+ mPromise->Compartment());
+
+ rv.WouldReportJSException();
+ if (rv.Failed()) {
+ JS::Rooted<JS::Value> exn(cx);
+ { // Scope for JSAutoCompartment
+
+ // 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(cx, mPromise->GlobalJSObject());
+ DebugOnly<bool> conversionResult = ToJSValue(cx, rv, &exn);
+ MOZ_ASSERT(conversionResult);
+ }
+
+ bool couldMarkAsCalled = MarkAsCalledIfNotCalledBefore(cx, resolveFunc);
+
+ // If we could mark as called, neither of the callbacks had been called
+ // when the exception was thrown. So we can reject the Promise.
+ if (couldMarkAsCalled) {
+ bool ok = JS_WrapValue(cx, &exn);
+ MOZ_ASSERT(ok);
+ if (!ok) {
+ NS_WARNING("Failed to wrap value into the right compartment.");
+ }
+
+ mPromise->RejectInternal(cx, exn);
+ }
+ // At least one of resolveFunc or rejectFunc have been called, so ignore
+ // the exception. FIXME(nsm): This should be reported to the error
+ // console though, for debugging.
+ }
+
+ return rv.StealNSResult();
+ }
+
+private:
+ RefPtr<Promise> mPromise;
+ JS::PersistentRooted<JSObject*> mThenable;
+ RefPtr<PromiseInit> mThen;
+ NS_DECL_OWNINGTHREAD;
+};
+
+// A struct implementing
+// <http://www.ecma-international.org/ecma-262/6.0/#sec-promisecapability-records>.
+// While the spec holds on to these in some places, in practice those places
+// don't actually need everything from this struct, so we explicitly grab
+// members from it as needed in those situations. That allows us to make this a
+// stack-only struct and keep the rooting simple.
+//
+// We also add an optimization for the (common) case when we discover that the
+// Promise constructor we're supposed to use is in fact the canonical Promise
+// constructor. In that case we will just set mNativePromise in our
+// PromiseCapability and not set mPromise/mResolve/mReject; the correct
+// callbacks will be the standard Promise ones, and we don't really want to
+// synthesize JSFunctions for them in that situation.
+struct MOZ_STACK_CLASS Promise::PromiseCapability
+{
+ explicit PromiseCapability(JSContext* aCx)
+ : mPromise(aCx)
+ , mResolve(aCx)
+ , mReject(aCx)
+ {}
+
+ // Take an exception on aCx and try to convert it into a promise rejection.
+ // Note that this can result in a new exception being thrown on aCx, or an
+ // exception getting thrown on aRv. On entry to this method, aRv is assumed
+ // to not be a failure. This should only be called if NewPromiseCapability
+ // succeeded on this PromiseCapability.
+ void RejectWithException(JSContext* aCx, ErrorResult& aRv);
+
+ // Return a JS::Value representing the promise. This should only be called if
+ // NewPromiseCapability succeeded on this PromiseCapability. It makes no
+ // guarantees about compartments (e.g. in the mNativePromise case it's in the
+ // compartment of the reflector, but in the mPromise case it might be in the
+ // compartment of some cross-compartment wrapper for a reflector).
+ JS::Value PromiseValue() const;
+
+ // All the JS::Value fields of this struct are actually objects, but for our
+ // purposes it's simpler to store them as JS::Value.
+
+ // [[Promise]].
+ JS::Rooted<JSObject*> mPromise;
+ // [[Resolve]]. Value in the context compartment.
+ JS::Rooted<JS::Value> mResolve;
+ // [[Reject]]. Value in the context compartment.
+ JS::Rooted<JS::Value> mReject;
+ // If mNativePromise is non-null, we should use it, not mPromise.
+ RefPtr<Promise> mNativePromise;
+
+private:
+ // We don't want to allow creation of temporaries of this type, ever.
+ PromiseCapability(const PromiseCapability&) = delete;
+ PromiseCapability(PromiseCapability&&) = delete;
+};
+
+void
+Promise::PromiseCapability::RejectWithException(JSContext* aCx,
+ ErrorResult& aRv)
+{
+ // This method basically implements
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-ifabruptrejectpromise
+ // or at least the parts of it that happen if we have an abrupt completion.
+
+ MOZ_ASSERT(!aRv.Failed());
+ MOZ_ASSERT(mNativePromise || mPromise,
+ "NewPromiseCapability didn't succeed");
+
+ JS::Rooted<JS::Value> exn(aCx);
+ if (!JS_GetPendingException(aCx, &exn)) {
+ // This is an uncatchable exception, so can't be converted into a rejection.
+ // Just rethrow that on aRv.
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ JS_ClearPendingException(aCx);
+
+ // If we have a native promise, just reject it without trying to call out into
+ // JS.
+ if (mNativePromise) {
+ mNativePromise->MaybeRejectInternal(aCx, exn);
+ return;
+ }
+
+ JS::Rooted<JS::Value> ignored(aCx);
+ if (!JS::Call(aCx, JS::UndefinedHandleValue, mReject, JS::HandleValueArray(exn),
+ &ignored)) {
+ aRv.NoteJSContextException(aCx);
+ }
+}
+
+JS::Value
+Promise::PromiseCapability::PromiseValue() const
+{
+ MOZ_ASSERT(mNativePromise || mPromise,
+ "NewPromiseCapability didn't succeed");
+
+ if (mNativePromise) {
+ return JS::ObjectValue(*mNativePromise->GetWrapper());
+ }
+
+ return JS::ObjectValue(*mPromise);
+}
+
+#endif // SPIDERMONKEY_PROMISE
+
+// Promise
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Promise)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
+#ifndef SPIDERMONKEY_PROMISE
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ tmp->MaybeReportRejectedOnce();
+#else
+ tmp->mResult = JS::UndefinedValue();
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+#endif // SPIDERMONKEY_PROMISE
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+#ifndef SPIDERMONKEY_PROMISE
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+#else // SPIDERMONKEY_PROMISE
+ tmp->mPromiseObj = nullptr;
+#endif // SPIDERMONKEY_PROMISE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+#ifndef SPIDERMONKEY_PROMISE
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveCallbacks)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectCallbacks)
+#endif // SPIDERMONKEY_PROMISE
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise)
+#ifndef SPIDERMONKEY_PROMISE
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResult)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAllocationStack)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRejectionStack)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mFullfillmentStack)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+#else // SPIDERMONKEY_PROMISE
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj);
+#endif // SPIDERMONKEY_PROMISE
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+#ifndef SPIDERMONKEY_PROMISE
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Promise)
+ if (tmp->IsBlack()) {
+ tmp->mResult.exposeToActiveJS();
+ tmp->mAllocationStack.exposeToActiveJS();
+ tmp->mRejectionStack.exposeToActiveJS();
+ tmp->mFullfillmentStack.exposeToActiveJS();
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Promise)
+ return tmp->IsBlackAndDoesNotNeedTracing(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Promise)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+#endif // SPIDERMONKEY_PROMISE
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Promise)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Promise)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Promise)
+#ifndef SPIDERMONKEY_PROMISE
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+#endif // SPIDERMONKEY_PROMISE
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(Promise)
+NS_INTERFACE_MAP_END
+
+Promise::Promise(nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal)
+#ifndef SPIDERMONKEY_PROMISE
+ , mResult(JS::UndefinedValue())
+ , mAllocationStack(nullptr)
+ , mRejectionStack(nullptr)
+ , mFullfillmentStack(nullptr)
+ , mState(Pending)
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ , mHadRejectCallback(false)
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ , mTaskPending(false)
+ , mResolvePending(false)
+ , mIsLastInChain(true)
+ , mWasNotifiedAsUncaught(false)
+ , mID(0)
+#else // SPIDERMONKEY_PROMISE
+ , mPromiseObj(nullptr)
+#endif // SPIDERMONKEY_PROMISE
+{
+ MOZ_ASSERT(mGlobal);
+
+ mozilla::HoldJSObjects(this);
+
+#ifndef SPIDERMONKEY_PROMISE
+ mCreationTimestamp = TimeStamp::Now();
+#endif // SPIDERMONKEY_PROMISE
+}
+
+Promise::~Promise()
+{
+#ifndef SPIDERMONKEY_PROMISE
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ MaybeReportRejectedOnce();
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+#endif // SPIDERMONKEY_PROMISE
+ mozilla::DropJSObjects(this);
+}
+
+#ifdef SPIDERMONKEY_PROMISE
+
+bool
+Promise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aWrapper)
+{
+#ifdef DEBUG
+ binding_detail::AssertReflectorHasGivenProto(aCx, mPromiseObj, aGivenProto);
+#endif // DEBUG
+ aWrapper.set(mPromiseObj);
+ return true;
+}
+
+// static
+already_AddRefed<Promise>
+Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv)
+{
+ if (!aGlobal) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ RefPtr<Promise> p = new Promise(aGlobal);
+ p->CreateWrapper(nullptr, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return p.forget();
+}
+
+// static
+already_AddRefed<Promise>
+Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv)
+{
+ JSAutoCompartment ac(aCx, aGlobal->GetGlobalJSObject());
+ JS::Rooted<JSObject*> p(aCx,
+ JS::CallOriginalPromiseResolve(aCx, aValue));
+ if (!p) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+
+ return CreateFromExisting(aGlobal, p);
+}
+
+// static
+already_AddRefed<Promise>
+Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv)
+{
+ JSAutoCompartment ac(aCx, aGlobal->GetGlobalJSObject());
+ JS::Rooted<JSObject*> p(aCx,
+ JS::CallOriginalPromiseReject(aCx, aValue));
+ if (!p) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+
+ return CreateFromExisting(aGlobal, p);
+}
+
+// static
+already_AddRefed<Promise>
+Promise::All(const GlobalObject& aGlobal,
+ const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global;
+ global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ JSContext* cx = aGlobal.Context();
+
+ JS::AutoObjectVector promises(cx);
+ if (!promises.reserve(aPromiseList.Length())) {
+ aRv.NoteJSContextException(cx);
+ return nullptr;
+ }
+
+ for (auto& promise : aPromiseList) {
+ JS::Rooted<JSObject*> promiseObj(cx, promise->PromiseObj());
+ // Just in case, make sure these are all in the context compartment.
+ if (!JS_WrapObject(cx, &promiseObj)) {
+ aRv.NoteJSContextException(cx);
+ return nullptr;
+ }
+ promises.infallibleAppend(promiseObj);
+ }
+
+ JS::Rooted<JSObject*> result(cx, JS::GetWaitForAllPromise(cx, promises));
+ if (!result) {
+ aRv.NoteJSContextException(cx);
+ return nullptr;
+ }
+
+ return CreateFromExisting(global, result);
+}
+
+void
+Promise::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)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ // Let's hope this does the right thing with Xrays... Ensure everything is
+ // just in the caller compartment; that ought to do the trick. In theory we
+ // should consider aCalleeGlobal, but in practice our only caller is
+ // DOMRequest::Then, which is not working with a Promise subclass, so things
+ // should be OK.
+ JS::Rooted<JSObject*> promise(aCx, PromiseObj());
+ if (!JS_WrapObject(aCx, &promise)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ JS::Rooted<JSObject*> resolveCallback(aCx);
+ if (aResolveCallback) {
+ resolveCallback = aResolveCallback->Callback();
+ if (!JS_WrapObject(aCx, &resolveCallback)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ }
+
+ JS::Rooted<JSObject*> rejectCallback(aCx);
+ if (aRejectCallback) {
+ rejectCallback = aRejectCallback->Callback();
+ if (!JS_WrapObject(aCx, &rejectCallback)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ }
+
+ JS::Rooted<JSObject*> retval(aCx);
+ retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback,
+ rejectCallback);
+ if (!retval) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ aRetval.setObject(*retval);
+}
+
+// We need a dummy function to pass to JS::NewPromiseObject.
+static bool
+DoNothingPromiseExecutor(JSContext*, unsigned aArgc, JS::Value* aVp)
+{
+ JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
+ args.rval().setUndefined();
+ return true;
+}
+
+void
+Promise::CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv)
+{
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobal)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JSFunction* doNothingFunc =
+ JS_NewFunction(cx, DoNothingPromiseExecutor, /* nargs = */ 2,
+ /* flags = */ 0, nullptr);
+ if (!doNothingFunc) {
+ JS_ClearPendingException(cx);
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::Rooted<JSObject*> doNothingObj(cx, JS_GetFunctionObject(doNothingFunc));
+ mPromiseObj = JS::NewPromiseObject(cx, doNothingObj, aDesiredProto);
+ if (!mPromiseObj) {
+ JS_ClearPendingException(cx);
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+}
+
+void
+Promise::MaybeResolve(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ JS::Rooted<JSObject*> p(aCx, PromiseObj());
+ if (!JS::ResolvePromise(aCx, p, aValue)) {
+ // Now what? There's nothing sane to do here.
+ JS_ClearPendingException(aCx);
+ }
+}
+
+void
+Promise::MaybeReject(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ JS::Rooted<JSObject*> p(aCx, PromiseObj());
+ if (!JS::RejectPromise(aCx, p, aValue)) {
+ // Now what? There's nothing sane to do here.
+ JS_ClearPendingException(aCx);
+ }
+}
+
+#define SLOT_NATIVEHANDLER 0
+#define SLOT_NATIVEHANDLER_TASK 1
+
+enum class NativeHandlerTask : int32_t {
+ Resolve,
+ Reject
+};
+
+static bool
+NativeHandlerCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+ JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+ JS::Value v = js::GetFunctionNativeReserved(&args.callee(),
+ SLOT_NATIVEHANDLER);
+ MOZ_ASSERT(v.isObject());
+
+ JS::Rooted<JSObject*> obj(aCx, &v.toObject());
+ PromiseNativeHandler* handler = nullptr;
+ if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &obj, handler))) {
+ return Throw(aCx, NS_ERROR_UNEXPECTED);
+ }
+
+ v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
+ NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
+
+ if (task == NativeHandlerTask::Resolve) {
+ handler->ResolvedCallback(aCx, args.get(0));
+ } else {
+ MOZ_ASSERT(task == NativeHandlerTask::Reject);
+ handler->RejectedCallback(aCx, args.get(0));
+ }
+
+ return true;
+}
+
+static JSObject*
+CreateNativeHandlerFunction(JSContext* aCx, JS::Handle<JSObject*> aHolder,
+ NativeHandlerTask aTask)
+{
+ JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
+ /* nargs = */ 1,
+ /* flags = */ 0, nullptr);
+ if (!func) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
+
+ JS::ExposeObjectToActiveJS(aHolder);
+ js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER,
+ JS::ObjectValue(*aHolder));
+ js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER_TASK,
+ JS::Int32Value(static_cast<int32_t>(aTask)));
+
+ return obj;
+}
+
+namespace {
+
+class PromiseNativeHandlerShim final : public PromiseNativeHandler
+{
+ RefPtr<PromiseNativeHandler> mInner;
+
+ ~PromiseNativeHandlerShim()
+ {
+ }
+
+public:
+ explicit PromiseNativeHandlerShim(PromiseNativeHandler* aInner)
+ : mInner(aInner)
+ {
+ MOZ_ASSERT(mInner);
+ }
+
+ void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ mInner->ResolvedCallback(aCx, aValue);
+ mInner = nullptr;
+ }
+
+ void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ mInner->RejectedCallback(aCx, aValue);
+ mInner = nullptr;
+ }
+
+ bool
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aWrapper)
+ {
+ return PromiseNativeHandlerBinding::Wrap(aCx, this, aGivenProto, aWrapper);
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim)
+};
+
+NS_IMPL_CYCLE_COLLECTION(PromiseNativeHandlerShim, mInner)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeHandlerShim)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeHandlerShim)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeHandlerShim)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+} // anonymous namespace
+
+void
+Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
+ // Our API doesn't allow us to return a useful error. Not like this should
+ // happen anyway.
+ return;
+ }
+
+ // The self-hosted promise js may keep the object we pass to it alive
+ // for quite a while depending on when GC runs. Therefore, pass a shim
+ // object instead. The shim will free its inner PromiseNativeHandler
+ // after the promise has settled just like our previous c++ promises did.
+ RefPtr<PromiseNativeHandlerShim> shim =
+ new PromiseNativeHandlerShim(aRunnable);
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> handlerWrapper(cx);
+ // Note: PromiseNativeHandler is NOT wrappercached. So we can't use
+ // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
+ if (NS_WARN_IF(!shim->WrapObject(cx, nullptr, &handlerWrapper))) {
+ // Again, no way to report errors.
+ jsapi.ClearException();
+ return;
+ }
+
+ JS::Rooted<JSObject*> resolveFunc(cx);
+ resolveFunc =
+ CreateNativeHandlerFunction(cx, handlerWrapper, NativeHandlerTask::Resolve);
+ if (NS_WARN_IF(!resolveFunc)) {
+ jsapi.ClearException();
+ return;
+ }
+
+ JS::Rooted<JSObject*> rejectFunc(cx);
+ rejectFunc =
+ CreateNativeHandlerFunction(cx, handlerWrapper, NativeHandlerTask::Reject);
+ if (NS_WARN_IF(!rejectFunc)) {
+ jsapi.ClearException();
+ return;
+ }
+
+ JS::Rooted<JSObject*> promiseObj(cx, PromiseObj());
+ if (NS_WARN_IF(!JS::AddPromiseReactions(cx, promiseObj, resolveFunc,
+ rejectFunc))) {
+ jsapi.ClearException();
+ return;
+ }
+}
+
+void
+Promise::HandleException(JSContext* aCx)
+{
+ JS::Rooted<JS::Value> exn(aCx);
+ if (JS_GetPendingException(aCx, &exn)) {
+ JS_ClearPendingException(aCx);
+ // This is only called from MaybeSomething, so it's OK to MaybeReject here,
+ // unlike in the version that's used when !SPIDERMONKEY_PROMISE.
+ MaybeReject(aCx, exn);
+ }
+}
+
+// static
+already_AddRefed<Promise>
+Promise::CreateFromExisting(nsIGlobalObject* aGlobal,
+ JS::Handle<JSObject*> aPromiseObj)
+{
+ MOZ_ASSERT(js::GetObjectCompartment(aGlobal->GetGlobalJSObject()) ==
+ js::GetObjectCompartment(aPromiseObj));
+ RefPtr<Promise> p = new Promise(aGlobal);
+ p->mPromiseObj = aPromiseObj;
+ return p.forget();
+}
+
+#else // SPIDERMONKEY_PROMISE
+
+JSObject*
+Promise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PromiseBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<Promise>
+Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv,
+ JS::Handle<JSObject*> aDesiredProto)
+{
+ if (!aGlobal) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ RefPtr<Promise> p = new Promise(aGlobal);
+ p->CreateWrapper(aDesiredProto, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return p.forget();
+}
+
+void
+Promise::CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv)
+{
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobal)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JS::Value> wrapper(cx);
+ if (!GetOrCreateDOMReflector(cx, this, &wrapper, aDesiredProto)) {
+ JS_ClearPendingException(cx);
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ dom::PreserveWrapper(this);
+
+ // Now grab our allocation stack
+ if (!CaptureStack(cx, mAllocationStack)) {
+ JS_ClearPendingException(cx);
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::RootedObject obj(cx, &wrapper.toObject());
+ JS::dbg::onNewPromise(cx, obj);
+}
+
+void
+Promise::MaybeResolve(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ MaybeResolveInternal(aCx, aValue);
+}
+
+void
+Promise::MaybeReject(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ MaybeRejectInternal(aCx, aValue);
+}
+
+#endif // SPIDERMONKEY_PROMISE
+
+void
+Promise::MaybeResolveWithUndefined()
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ MaybeResolve(JS::UndefinedHandleValue);
+}
+
+void
+Promise::MaybeReject(const RefPtr<MediaStreamError>& aArg) {
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ MaybeSomething(aArg, &Promise::MaybeReject);
+}
+
+void
+Promise::MaybeRejectWithUndefined()
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ MaybeSomething(JS::UndefinedHandleValue, &Promise::MaybeReject);
+}
+
+#ifdef SPIDERMONKEY_PROMISE
+void
+Promise::ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise)
+{
+ MOZ_ASSERT(!js::IsWrapper(aPromise));
+
+ MOZ_ASSERT(JS::GetPromiseState(aPromise) == JS::PromiseState::Rejected);
+
+ JS::Rooted<JS::Value> result(aCx, JS::GetPromiseResult(aPromise));
+
+ js::ErrorReport report(aCx);
+ if (!report.init(aCx, result, js::ErrorReport::NoSideEffects)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+
+ RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+ bool isMainThread = MOZ_LIKELY(NS_IsMainThread());
+ bool isChrome = isMainThread ? nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(aPromise))
+ : GetCurrentThreadWorkerPrivate()->IsChromeWorker();
+ nsGlobalWindow* win = isMainThread ? xpc::WindowGlobalOrNull(aPromise) : nullptr;
+ xpcReport->Init(report.report(), report.toStringResult().c_str(), isChrome,
+ win ? win->AsInner()->WindowID() : 0);
+
+ // Now post an event to do the real reporting async
+ NS_DispatchToMainThread(new AsyncErrorReporter(xpcReport));
+}
+#endif // defined(SPIDERMONKEY_PROMISE)
+
+bool
+Promise::PerformMicroTaskCheckpoint()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+
+ CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
+
+ // On the main thread, we always use the main promise micro task queue.
+ std::queue<nsCOMPtr<nsIRunnable>>& microtaskQueue =
+ context->GetPromiseMicroTaskQueue();
+
+ if (microtaskQueue.empty()) {
+ return false;
+ }
+
+ AutoSlowOperation aso;
+
+ do {
+ nsCOMPtr<nsIRunnable> runnable = microtaskQueue.front().forget();
+ MOZ_ASSERT(runnable);
+
+ // This function can re-enter, so we remove the element before calling.
+ microtaskQueue.pop();
+ nsresult rv = runnable->Run();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ aso.CheckForInterrupt();
+ context->AfterProcessMicrotask();
+ } while (!microtaskQueue.empty());
+
+ return true;
+}
+
+void
+Promise::PerformWorkerMicroTaskCheckpoint()
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
+
+ CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
+ if (!context) {
+ return;
+ }
+
+ for (;;) {
+ // For a normal microtask checkpoint, we try to use the debugger microtask
+ // queue first. If the debugger queue is empty, we use the normal microtask
+ // queue instead.
+ std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
+ &context->GetDebuggerPromiseMicroTaskQueue();
+
+ if (microtaskQueue->empty()) {
+ microtaskQueue = &context->GetPromiseMicroTaskQueue();
+ if (microtaskQueue->empty()) {
+ break;
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front().forget();
+ MOZ_ASSERT(runnable);
+
+ // This function can re-enter, so we remove the element before calling.
+ microtaskQueue->pop();
+ nsresult rv = runnable->Run();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ context->AfterProcessMicrotask();
+ }
+}
+
+void
+Promise::PerformWorkerDebuggerMicroTaskCheckpoint()
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
+
+ CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
+ if (!context) {
+ return;
+ }
+
+ for (;;) {
+ // For a debugger microtask checkpoint, we always use the debugger microtask
+ // queue.
+ std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
+ &context->GetDebuggerPromiseMicroTaskQueue();
+
+ if (microtaskQueue->empty()) {
+ break;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front().forget();
+ MOZ_ASSERT(runnable);
+
+ // This function can re-enter, so we remove the element before calling.
+ microtaskQueue->pop();
+ nsresult rv = runnable->Run();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ context->AfterProcessMicrotask();
+ }
+}
+
+#ifndef SPIDERMONKEY_PROMISE
+
+/* static */ bool
+Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+ JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+ JS::Rooted<JS::Value> v(aCx,
+ js::GetFunctionNativeReserved(&args.callee(),
+ SLOT_PROMISE));
+ MOZ_ASSERT(v.isObject());
+
+ Promise* promise;
+ if (NS_FAILED(UNWRAP_OBJECT(Promise, &v.toObject(), promise))) {
+ return Throw(aCx, NS_ERROR_UNEXPECTED);
+ }
+
+ v = js::GetFunctionNativeReserved(&args.callee(), SLOT_DATA);
+ PromiseCallback::Task task = static_cast<PromiseCallback::Task>(v.toInt32());
+
+ if (task == PromiseCallback::Resolve) {
+ if (!promise->CaptureStack(aCx, promise->mFullfillmentStack)) {
+ return false;
+ }
+ promise->MaybeResolveInternal(aCx, args.get(0));
+ } else {
+ promise->MaybeRejectInternal(aCx, args.get(0));
+ if (!promise->CaptureStack(aCx, promise->mRejectionStack)) {
+ return false;
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/*
+ * Common bits of (JSCallbackThenableResolver/JSCallbackThenableRejecter).
+ * Resolves/rejects the Promise if it is ok to do so, based on whether either of
+ * the callbacks have been called before or not.
+ */
+/* static */ bool
+Promise::ThenableResolverCommon(JSContext* aCx, uint32_t aTask,
+ unsigned aArgc, JS::Value* aVp)
+{
+ JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
+ JS::Rooted<JSObject*> thisFunc(aCx, &args.callee());
+ if (!MarkAsCalledIfNotCalledBefore(aCx, thisFunc)) {
+ // A function from this pair has been called before.
+ args.rval().setUndefined();
+ return true;
+ }
+
+ Promise* promise = GetPromise(aCx, thisFunc);
+ MOZ_ASSERT(promise);
+
+ if (aTask == PromiseCallback::Resolve) {
+ promise->ResolveInternal(aCx, args.get(0));
+ } else {
+ promise->RejectInternal(aCx, args.get(0));
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/* static */ bool
+Promise::JSCallbackThenableResolver(JSContext* aCx,
+ unsigned aArgc, JS::Value* aVp)
+{
+ return ThenableResolverCommon(aCx, PromiseCallback::Resolve, aArgc, aVp);
+}
+
+/* static */ bool
+Promise::JSCallbackThenableRejecter(JSContext* aCx,
+ unsigned aArgc, JS::Value* aVp)
+{
+ return ThenableResolverCommon(aCx, PromiseCallback::Reject, aArgc, aVp);
+}
+
+/* static */ JSObject*
+Promise::CreateFunction(JSContext* aCx, Promise* aPromise, int32_t aTask)
+{
+ // If this function ever changes, make sure to update
+ // WrapperPromiseCallback::GetDependentPromise.
+ JSFunction* func = js::NewFunctionWithReserved(aCx, JSCallback,
+ 1 /* nargs */, 0 /* flags */,
+ nullptr);
+ if (!func) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
+
+ JS::Rooted<JS::Value> promiseObj(aCx);
+ if (!dom::GetOrCreateDOMReflector(aCx, aPromise, &promiseObj)) {
+ return nullptr;
+ }
+
+ JS::ExposeValueToActiveJS(promiseObj);
+ js::SetFunctionNativeReserved(obj, SLOT_PROMISE, promiseObj);
+ js::SetFunctionNativeReserved(obj, SLOT_DATA, JS::Int32Value(aTask));
+
+ return obj;
+}
+
+/* static */ JSObject*
+Promise::CreateThenableFunction(JSContext* aCx, Promise* aPromise, uint32_t aTask)
+{
+ JSNative whichFunc =
+ aTask == PromiseCallback::Resolve ? JSCallbackThenableResolver :
+ JSCallbackThenableRejecter ;
+
+ JSFunction* func = js::NewFunctionWithReserved(aCx, whichFunc,
+ 1 /* nargs */, 0 /* flags */,
+ nullptr);
+ if (!func) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
+
+ JS::Rooted<JS::Value> promiseObj(aCx);
+ if (!dom::GetOrCreateDOMReflector(aCx, aPromise, &promiseObj)) {
+ return nullptr;
+ }
+
+ JS::ExposeValueToActiveJS(promiseObj);
+ js::SetFunctionNativeReserved(obj, SLOT_PROMISE, promiseObj);
+
+ return obj;
+}
+
+/* static */ already_AddRefed<Promise>
+Promise::Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
+ ErrorResult& aRv, JS::Handle<JSObject*> aDesiredProto)
+{
+ nsCOMPtr<nsIGlobalObject> global;
+ global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Create(global, aRv, aDesiredProto);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ promise->CallInitFunction(aGlobal, aInit, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+void
+Promise::CallInitFunction(const GlobalObject& aGlobal,
+ PromiseInit& aInit, ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+
+ JS::Rooted<JSObject*> resolveFunc(cx,
+ CreateFunction(cx, this,
+ PromiseCallback::Resolve));
+ if (!resolveFunc) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ JS::Rooted<JSObject*> rejectFunc(cx,
+ CreateFunction(cx, this,
+ PromiseCallback::Reject));
+ if (!rejectFunc) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ aInit.Call(resolveFunc, rejectFunc, aRv, "promise initializer",
+ CallbackObject::eRethrowExceptions, Compartment());
+ aRv.WouldReportJSException();
+
+ if (aRv.Failed()) {
+ if (aRv.IsUncatchableException()) {
+ // Just propagate this to the caller.
+ return;
+ }
+
+ // There are two possibilities here. Either we've got a rethrown exception,
+ // or we reported that already and synthesized a generic NS_ERROR_FAILURE on
+ // the ErrorResult. In the former case, it doesn't much matter how we get
+ // the exception JS::Value from the ErrorResult to us, since we'll just end
+ // up wrapping it into the right compartment as needed if we hand it to
+ // someone. But in the latter case we have to ensure that the new exception
+ // object we create is created in our reflector compartment, not in our
+ // current compartment, because in the case when we're a Promise constructor
+ // called over Xrays creating it in the current compartment would mean
+ // rejecting with a value that can't be accessed by code that can call
+ // then() on this Promise.
+ //
+ // Luckily, MaybeReject(aRv) does exactly what we want here: it enters our
+ // reflector compartment before trying to produce a JS::Value from the
+ // ErrorResult.
+ MaybeReject(aRv);
+ }
+}
+
+#define GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT 0
+#define GET_CAPABILITIES_EXECUTOR_REJECT_SLOT 1
+
+namespace {
+bool
+GetCapabilitiesExecutor(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+ // Implements
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-getcapabilitiesexecutor-functions
+ // except we store the [[Resolve]] and [[Reject]] in our own internal slots,
+ // not in a PromiseCapability. The PromiseCapability will then read them from
+ // us.
+ JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+ // Step 1 is an assert.
+
+ // Step 2 doesn't need to be done, because it's just giving a name to the
+ // PromiseCapability record which is supposed to be stored in an internal
+ // slot. But we don't store that at all, per the comment above; we just
+ // directly store its [[Resolve]] and [[Reject]] members.
+
+ // Steps 3 and 4.
+ if (!js::GetFunctionNativeReserved(&args.callee(),
+ GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT).isUndefined() ||
+ !js::GetFunctionNativeReserved(&args.callee(),
+ GET_CAPABILITIES_EXECUTOR_REJECT_SLOT).isUndefined()) {
+ ErrorResult rv;
+ rv.ThrowTypeError<MSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY>();
+ return !rv.MaybeSetPendingException(aCx);
+ }
+
+ // Step 5.
+ js::SetFunctionNativeReserved(&args.callee(),
+ GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT,
+ args.get(0));
+
+ // Step 6.
+ js::SetFunctionNativeReserved(&args.callee(),
+ GET_CAPABILITIES_EXECUTOR_REJECT_SLOT,
+ args.get(1));
+
+ // Step 7.
+ args.rval().setUndefined();
+ return true;
+}
+} // anonymous namespace
+
+/* static */ void
+Promise::NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal,
+ JS::Handle<JS::Value> aConstructor,
+ bool aForceCallbackCreation,
+ PromiseCapability& aCapability,
+ ErrorResult& aRv)
+{
+ // Implements
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-newpromisecapability
+
+ if (!aConstructor.isObject() ||
+ !JS::IsConstructor(&aConstructor.toObject())) {
+ aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
+ return;
+ }
+
+ // Step 2 is a note.
+ // Step 3 is already done because we got the PromiseCapability passed in.
+
+ // Optimization: Check whether constructor is in fact the canonical
+ // Promise constructor for aGlobal.
+ JS::Rooted<JSObject*> global(aCx, aGlobal->GetGlobalJSObject());
+ {
+ // Scope for the JSAutoCompartment, since we need to enter the compartment
+ // of global to get constructors from it. Save the compartment we used to
+ // be in, though; we'll need it later.
+ JS::Rooted<JSObject*> callerGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
+ JSAutoCompartment ac(aCx, global);
+
+ // Now wrap aConstructor into the compartment of aGlobal, so comparing it to
+ // the canonical Promise for that compartment actually makes sense.
+ JS::Rooted<JS::Value> constructorValue(aCx, aConstructor);
+ if (!MaybeWrapObjectValue(aCx, &constructorValue)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ JSObject* defaultCtor = PromiseBinding::GetConstructorObject(aCx);
+ if (!defaultCtor) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ if (defaultCtor == &constructorValue.toObject()) {
+ // This is the canonical Promise constructor.
+ aCapability.mNativePromise = Promise::Create(aGlobal, aRv);
+ if (aForceCallbackCreation) {
+ // We have to be a bit careful here. We want to create these functions
+ // in the compartment in which they would be created if we actually
+ // invoked the constructor via JS::Construct below. That means our
+ // callerGlobal compartment if aConstructor is an Xray and the reflector
+ // compartment of the promise we're creating otherwise. But note that
+ // our callerGlobal compartment is precisely the reflector compartment
+ // unless the call was done over Xrays, because the reflector
+ // compartment comes from xpc::XrayAwareCalleeGlobal. So we really just
+ // want to create these functions in the callerGlobal compartment.
+ MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(&aConstructor.toObject()) ||
+ callerGlobal == global);
+ JSAutoCompartment ac2(aCx, callerGlobal);
+
+ JSObject* resolveFuncObj =
+ CreateFunction(aCx, aCapability.mNativePromise,
+ PromiseCallback::Resolve);
+ if (!resolveFuncObj) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ aCapability.mResolve.setObject(*resolveFuncObj);
+
+ JSObject* rejectFuncObj =
+ CreateFunction(aCx, aCapability.mNativePromise,
+ PromiseCallback::Reject);
+ if (!rejectFuncObj) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ aCapability.mReject.setObject(*rejectFuncObj);
+ }
+ return;
+ }
+ }
+
+ // Step 4.
+ // We can create our get-capabilities function in the calling compartment. It
+ // will work just as if we did |new promiseConstructor(function(a,b){}).
+ // Notably, if we're called over Xrays that's all fine, because we will end up
+ // creating the callbacks in the caller compartment in that case.
+ JSFunction* getCapabilitiesFunc =
+ js::NewFunctionWithReserved(aCx, GetCapabilitiesExecutor,
+ 2 /* nargs */,
+ 0 /* flags */,
+ nullptr);
+ if (!getCapabilitiesFunc) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::Rooted<JSObject*> getCapabilitiesObj(aCx);
+ getCapabilitiesObj = JS_GetFunctionObject(getCapabilitiesFunc);
+
+ // Step 5 doesn't need to be done, since we're not actually storing a
+ // PromiseCapability in the executor; see the comments in
+ // GetCapabilitiesExecutor above.
+
+ // Step 6 and step 7.
+ JS::Rooted<JS::Value> getCapabilities(aCx,
+ JS::ObjectValue(*getCapabilitiesObj));
+ JS::Rooted<JSObject*> promiseObj(aCx);
+ if (!JS::Construct(aCx, aConstructor,
+ JS::HandleValueArray(getCapabilities),
+ &promiseObj)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ // Step 8 plus copying over the value to the PromiseCapability.
+ JS::Rooted<JS::Value> v(aCx);
+ v = js::GetFunctionNativeReserved(getCapabilitiesObj,
+ GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT);
+ if (!v.isObject() || !JS::IsCallable(&v.toObject())) {
+ aRv.ThrowTypeError<MSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE>();
+ return;
+ }
+ aCapability.mResolve = v;
+
+ // Step 9 plus copying over the value to the PromiseCapability.
+ v = js::GetFunctionNativeReserved(getCapabilitiesObj,
+ GET_CAPABILITIES_EXECUTOR_REJECT_SLOT);
+ if (!v.isObject() || !JS::IsCallable(&v.toObject())) {
+ aRv.ThrowTypeError<MSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE>();
+ return;
+ }
+ aCapability.mReject = v;
+
+ // Step 10.
+ aCapability.mPromise = promiseObj;
+
+ // Step 11 doesn't need anything, since the PromiseCapability was passed in.
+}
+
+/* static */ void
+Promise::Resolve(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
+ JS::Handle<JS::Value> aValue,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv)
+{
+ // Implementation of
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-promise.resolve
+
+ JSContext* cx = aGlobal.Context();
+
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Steps 1 and 2.
+ if (!aThisv.isObject()) {
+ aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
+ return;
+ }
+
+ // Step 3. If a Promise was passed and matches our constructor, just return it.
+ if (aValue.isObject()) {
+ JS::Rooted<JSObject*> valueObj(cx, &aValue.toObject());
+ Promise* nextPromise;
+ nsresult rv = UNWRAP_OBJECT(Promise, valueObj, nextPromise);
+
+ if (NS_SUCCEEDED(rv)) {
+ JS::Rooted<JS::Value> constructor(cx);
+ if (!JS_GetProperty(cx, valueObj, "constructor", &constructor)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+
+ // Cheat instead of calling JS_SameValue, since we know one's an object.
+ if (aThisv == constructor) {
+ aRetval.setObject(*valueObj);
+ return;
+ }
+ }
+ }
+
+ // Step 4.
+ PromiseCapability capability(cx);
+ NewPromiseCapability(cx, global, aThisv, false, capability, aRv);
+ // Step 5.
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 6.
+ Promise* p = capability.mNativePromise;
+ if (p) {
+ p->MaybeResolveInternal(cx, aValue);
+ p->mFullfillmentStack = p->mAllocationStack;
+ } else {
+ JS::Rooted<JS::Value> value(cx, aValue);
+ JS::Rooted<JS::Value> ignored(cx);
+ if (!JS::Call(cx, JS::UndefinedHandleValue /* thisVal */,
+ capability.mResolve, JS::HandleValueArray(value),
+ &ignored)) {
+ // Step 7.
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+ }
+
+ // Step 8.
+ aRetval.set(capability.PromiseValue());
+}
+
+/* static */ already_AddRefed<Promise>
+Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv)
+{
+ RefPtr<Promise> promise = Create(aGlobal, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ promise->MaybeResolveInternal(aCx, aValue);
+ return promise.forget();
+}
+
+/* static */ void
+Promise::Reject(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
+ JS::Handle<JS::Value> aValue,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv)
+{
+ // Implementation of
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-promise.reject
+
+ JSContext* cx = aGlobal.Context();
+
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Steps 1 and 2.
+ if (!aThisv.isObject()) {
+ aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
+ return;
+ }
+
+ // Step 3.
+ PromiseCapability capability(cx);
+ NewPromiseCapability(cx, global, aThisv, false, capability, aRv);
+ // Step 4.
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 5.
+ Promise* p = capability.mNativePromise;
+ if (p) {
+ p->MaybeRejectInternal(cx, aValue);
+ p->mRejectionStack = p->mAllocationStack;
+ } else {
+ JS::Rooted<JS::Value> value(cx, aValue);
+ JS::Rooted<JS::Value> ignored(cx);
+ if (!JS::Call(cx, JS::UndefinedHandleValue /* thisVal */,
+ capability.mReject, JS::HandleValueArray(value),
+ &ignored)) {
+ // Step 6.
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+ }
+
+ // Step 7.
+ aRetval.set(capability.PromiseValue());
+}
+
+/* static */ already_AddRefed<Promise>
+Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv)
+{
+ RefPtr<Promise> promise = Create(aGlobal, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ promise->MaybeRejectInternal(aCx, aValue);
+ return promise.forget();
+}
+
+namespace {
+void
+SpeciesConstructor(JSContext* aCx,
+ JS::Handle<JSObject*> promise,
+ JS::Handle<JS::Value> defaultCtor,
+ JS::MutableHandle<JS::Value> ctor,
+ ErrorResult& aRv)
+{
+ // 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(aCx);
+ 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(aCx);
+ 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)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ // 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(aCx);
+ 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<JS::Value> defaultCtorVal(aCx);
+ { // Scope for JSAutoCompartment
+ JSAutoCompartment ac(aCx, aCalleeGlobal);
+ JSObject* defaultCtor = PromiseBinding::GetConstructorObject(aCx);
+ if (!defaultCtor) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ defaultCtorVal.setObject(*defaultCtor);
+ }
+ if (!MaybeWrapObjectValue(aCx, &defaultCtorVal)) {
+ aRv.NoteJSContextException(aCx);
+ 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(aCx);
+ 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> rejectCb =
+ PromiseCallback::Factory(promise, global, aRejectCallback,
+ PromiseCallback::Reject);
+
+ AppendCallbacks(resolveCb, rejectCb);
+ } else {
+ JS::Rooted<JSObject*> resolveObj(aCx, &capability.mResolve.toObject());
+ RefPtr<AnyCallback> resolveFunc =
+ new AnyCallback(aCx, resolveObj, GetIncumbentGlobal());
+
+ JS::Rooted<JSObject*> rejectObj(aCx, &capability.mReject.toObject());
+ RefPtr<AnyCallback> rejectFunc =
+ new AnyCallback(aCx, rejectObj, GetIncumbentGlobal());
+
+ if (!capability.mPromise) {
+ aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
+ return;
+ }
+ JS::Rooted<JSObject*> newPromiseObj(aCx, capability.mPromise);
+ // 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());
+}
+
+void
+Promise::Catch(JSContext* aCx, AnyCallback* aRejectCallback,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ // 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(aCx);
+ 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(aCx);
+ return;
+ }
+ } else {
+ callbacks[1].setNull();
+ }
+ if (!JS_CallFunctionName(aCx, promiseObj, "then", callbacks, aRetval)) {
+ aRv.NoteJSContextException(aCx);
+ }
+}
+
+/**
+ * The CountdownHolder class encapsulates Promise.all countdown functions and
+ * the countdown holder parts of the Promises spec. It maintains the result
+ * array and AllResolveElementFunctions use SetValue() to set the array indices.
+ */
+class CountdownHolder final : public nsISupports
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CountdownHolder)
+
+ CountdownHolder(const GlobalObject& aGlobal, Promise* aPromise,
+ uint32_t aCountdown)
+ : mPromise(aPromise), mCountdown(aCountdown)
+ {
+ MOZ_ASSERT(aCountdown != 0);
+ JSContext* cx = aGlobal.Context();
+
+ // The only time aGlobal.Context() and aGlobal.Get() are not
+ // same-compartment is when we're called via Xrays, and in that situation we
+ // in fact want to create the array in the callee compartment
+
+ JSAutoCompartment ac(cx, aGlobal.Get());
+ mValues = JS_NewArrayObject(cx, aCountdown);
+ mozilla::HoldJSObjects(this);
+ }
+
+private:
+ ~CountdownHolder()
+ {
+ mozilla::DropJSObjects(this);
+ }
+
+public:
+ void SetValue(uint32_t index, const JS::Handle<JS::Value> aValue)
+ {
+ MOZ_ASSERT(mCountdown > 0);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mValues)) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JS::Value> value(cx, aValue);
+ JS::Rooted<JSObject*> values(cx, mValues);
+ if (!JS_WrapValue(cx, &value) ||
+ !JS_DefineElement(cx, values, index, value, JSPROP_ENUMERATE)) {
+ MOZ_ASSERT(JS_IsExceptionPending(cx));
+ JS::Rooted<JS::Value> exn(cx);
+ if (!jsapi.StealException(&exn)) {
+ mPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ } else {
+ mPromise->MaybeReject(cx, exn);
+ }
+ }
+
+ --mCountdown;
+ if (mCountdown == 0) {
+ JS::Rooted<JS::Value> result(cx, JS::ObjectValue(*mValues));
+ mPromise->MaybeResolve(cx, result);
+ }
+ }
+
+private:
+ RefPtr<Promise> mPromise;
+ uint32_t mCountdown;
+ JS::Heap<JSObject*> mValues;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CountdownHolder)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CountdownHolder)
+NS_IMPL_CYCLE_COLLECTION_CLASS(CountdownHolder)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CountdownHolder)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CountdownHolder)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mValues)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CountdownHolder)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CountdownHolder)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
+ tmp->mValues = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+/**
+ * An AllResolveElementFunction is the per-promise
+ * part of the Promise.all() algorithm.
+ * Every Promise in the handler is handed an instance of this as a resolution
+ * handler and it sets the relevant index in the CountdownHolder.
+ */
+class AllResolveElementFunction final : public PromiseNativeHandler
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(AllResolveElementFunction)
+
+ AllResolveElementFunction(CountdownHolder* aHolder, uint32_t aIndex)
+ : mCountdownHolder(aHolder), mIndex(aIndex)
+ {
+ MOZ_ASSERT(aHolder);
+ }
+
+ void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ mCountdownHolder->SetValue(mIndex, aValue);
+ }
+
+ void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ // Should never be attached to Promise as a reject handler.
+ MOZ_CRASH("AllResolveElementFunction should never be attached to a Promise's reject handler!");
+ }
+
+private:
+ ~AllResolveElementFunction()
+ {
+ }
+
+ RefPtr<CountdownHolder> mCountdownHolder;
+ uint32_t mIndex;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AllResolveElementFunction)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AllResolveElementFunction)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AllResolveElementFunction)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(AllResolveElementFunction, mCountdownHolder)
+
+static const JSClass PromiseAllDataHolderClass = {
+ "PromiseAllDataHolder", JSCLASS_HAS_RESERVED_SLOTS(3)
+};
+
+// Slot indices for objects of class PromiseAllDataHolderClass.
+#define DATA_HOLDER_REMAINING_ELEMENTS_SLOT 0
+#define DATA_HOLDER_VALUES_ARRAY_SLOT 1
+#define DATA_HOLDER_RESOLVE_FUNCTION_SLOT 2
+
+// Slot indices for PromiseAllResolveElement.
+// The RESOLVE_ELEMENT_INDEX_SLOT stores our index unless we've already been
+// called. Then it stores INT32_MIN (which is never a valid index value).
+#define RESOLVE_ELEMENT_INDEX_SLOT 0
+// The RESOLVE_ELEMENT_DATA_HOLDER_SLOT slot stores an object of class
+// PromiseAllDataHolderClass.
+#define RESOLVE_ELEMENT_DATA_HOLDER_SLOT 1
+
+static bool
+PromiseAllResolveElement(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+ // Implements
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-promise.all-resolve-element-functions
+ //
+ // See the big comment about compartments in Promise::All "Substep 4" that
+ // explains what compartments the various stuff here lives in.
+ JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+ // Step 1.
+ int32_t index =
+ js::GetFunctionNativeReserved(&args.callee(),
+ RESOLVE_ELEMENT_INDEX_SLOT).toInt32();
+ // Step 2.
+ if (index == INT32_MIN) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 3.
+ js::SetFunctionNativeReserved(&args.callee(),
+ RESOLVE_ELEMENT_INDEX_SLOT,
+ JS::Int32Value(INT32_MIN));
+
+ // Step 4 already done.
+
+ // Step 5.
+ JS::Rooted<JSObject*> dataHolder(aCx,
+ &js::GetFunctionNativeReserved(&args.callee(),
+ RESOLVE_ELEMENT_DATA_HOLDER_SLOT).toObject());
+
+ JS::Rooted<JS::Value> values(aCx,
+ js::GetReservedSlot(dataHolder, DATA_HOLDER_VALUES_ARRAY_SLOT));
+
+ // Step 6, effectively.
+ JS::Rooted<JS::Value> resolveFunc(aCx,
+ js::GetReservedSlot(dataHolder, DATA_HOLDER_RESOLVE_FUNCTION_SLOT));
+
+ // Step 7.
+ int32_t remainingElements =
+ js::GetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT).toInt32();
+
+ // Step 8.
+ JS::Rooted<JSObject*> valuesObj(aCx, &values.toObject());
+ if (!JS_DefineElement(aCx, valuesObj, index, args.get(0), JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ // Step 9.
+ remainingElements -= 1;
+ js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT,
+ JS::Int32Value(remainingElements));
+
+ // Step 10.
+ if (remainingElements == 0) {
+ return JS::Call(aCx, JS::UndefinedHandleValue, resolveFunc,
+ JS::HandleValueArray(values), args.rval());
+ }
+
+ // Step 11.
+ args.rval().setUndefined();
+ return true;
+}
+
+
+/* static */ void
+Promise::All(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
+ JS::Handle<JS::Value> aIterable,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv)
+{
+ // Implements http://www.ecma-international.org/ecma-262/6.0/#sec-promise.all
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ JSContext* cx = aGlobal.Context();
+
+ // Steps 1-5: nothing to do. Note that the @@species bits got removed in
+ // https://github.com/tc39/ecma262/pull/211
+
+ // Step 6.
+ PromiseCapability capability(cx);
+ NewPromiseCapability(cx, global, aThisv, true, capability, aRv);
+ // Step 7.
+ if (aRv.Failed()) {
+ return;
+ }
+
+ MOZ_ASSERT(aThisv.isObject(), "How did NewPromiseCapability succeed?");
+ JS::Rooted<JSObject*> constructorObj(cx, &aThisv.toObject());
+
+ // After this point we have a useful promise value in "capability", so just go
+ // ahead and put it in our retval now. Every single return path below would
+ // want to do that anyway.
+ aRetval.set(capability.PromiseValue());
+ if (!MaybeWrapValue(cx, aRetval)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+
+ // The arguments we're going to be passing to "then" on each loop iteration.
+ // The second one we know already; the first one will be created on each
+ // iteration of the loop.
+ JS::AutoValueArray<2> callbackFunctions(cx);
+ callbackFunctions[1].set(capability.mReject);
+
+ // Steps 8 and 9.
+ JS::ForOfIterator iter(cx);
+ if (!iter.init(aIterable, JS::ForOfIterator::AllowNonIterable)) {
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ if (!iter.valueIsIterable()) {
+ ThrowErrorMessage(cx, MSG_PROMISE_ARG_NOT_ITERABLE,
+ "Argument of Promise.all");
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ // Step 10 doesn't need to be done, because ForOfIterator handles it
+ // for us.
+
+ // Now we jump over to
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-performpromiseall
+ // and do its steps.
+
+ // Substep 4. Create our data holder that holds all the things shared across
+ // every step of the iterator. In particular, this holds the
+ // remainingElementsCount (as an integer reserved slot), the array of values,
+ // and the resolve function from our PromiseCapability.
+ //
+ // We have to be very careful about which compartments we create things in
+ // here. In particular, we have to maintain the invariant that anything
+ // stored in a reserved slot is same-compartment with the object whose
+ // reserved slot it's in. But we want to create the values array in the
+ // Promise reflector compartment, because that array can get exposed to code
+ // that has access to the Promise reflector (in particular code from that
+ // compartment), and that should work, even if the Promise reflector
+ // compartment is less-privileged than our caller compartment.
+ //
+ // So the plan is as follows: Create the values array in the promise reflector
+ // compartment. Create the PromiseAllResolveElement function and the data
+ // holder in our current compartment. Store a cross-compartment wrapper to
+ // the values array in the holder. This should be OK because the only things
+ // we hand the PromiseAllResolveElement function to are the "then" calls we do
+ // and in the case when the reflector compartment is not the current
+ // compartment those are happening over Xrays anyway, which means they get the
+ // canonical "then" function and content can't see our
+ // PromiseAllResolveElement.
+ JS::Rooted<JSObject*> dataHolder(cx);
+ dataHolder = JS_NewObjectWithGivenProto(cx, &PromiseAllDataHolderClass,
+ nullptr);
+ if (!dataHolder) {
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ JS::Rooted<JSObject*> reflectorGlobal(cx, global->GetGlobalJSObject());
+ JS::Rooted<JSObject*> valuesArray(cx);
+ { // Scope for JSAutoCompartment.
+ JSAutoCompartment ac(cx, reflectorGlobal);
+ valuesArray = JS_NewArrayObject(cx, 0);
+ }
+ if (!valuesArray) {
+ // It's important that we've exited the JSAutoCompartment by now, before
+ // calling RejectWithException and possibly invoking capability.mReject.
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ // The values array as a value we can pass to a function in our current
+ // compartment, or store in the holder's reserved slot.
+ JS::Rooted<JS::Value> valuesArrayVal(cx, JS::ObjectValue(*valuesArray));
+ if (!MaybeWrapObjectValue(cx, &valuesArrayVal)) {
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT,
+ JS::Int32Value(1));
+ js::SetReservedSlot(dataHolder, DATA_HOLDER_VALUES_ARRAY_SLOT,
+ valuesArrayVal);
+ js::SetReservedSlot(dataHolder, DATA_HOLDER_RESOLVE_FUNCTION_SLOT,
+ capability.mResolve);
+
+ // Substep 5.
+ CheckedInt32 index = 0;
+
+ // Substep 6.
+ JS::Rooted<JS::Value> nextValue(cx);
+ while (true) {
+ bool done;
+ // Steps a, b, c, e, f, g.
+ if (!iter.next(&nextValue, &done)) {
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ // Step d.
+ if (done) {
+ int32_t remainingCount =
+ js::GetReservedSlot(dataHolder,
+ DATA_HOLDER_REMAINING_ELEMENTS_SLOT).toInt32();
+ remainingCount -= 1;
+ if (remainingCount == 0) {
+ JS::Rooted<JS::Value> ignored(cx);
+ if (!JS::Call(cx, JS::UndefinedHandleValue, capability.mResolve,
+ JS::HandleValueArray(valuesArrayVal), &ignored)) {
+ capability.RejectWithException(cx, aRv);
+ }
+ return;
+ }
+ js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT,
+ JS::Int32Value(remainingCount));
+ // We're all set for now!
+ return;
+ }
+
+ // Step h.
+ { // Scope for the JSAutoCompartment we need to work with valuesArray. We
+ // mostly do this for performance; we could go ahead and do the define via
+ // a cross-compartment proxy instead...
+ JSAutoCompartment ac(cx, valuesArray);
+ if (!JS_DefineElement(cx, valuesArray, index.value(),
+ JS::UndefinedHandleValue, JSPROP_ENUMERATE)) {
+ // Have to go back into the caller compartment before we try to touch
+ // capability.mReject. Luckily, capability.mReject is guaranteed to be
+ // an object in the right compartment here.
+ JSAutoCompartment ac2(cx, &capability.mReject.toObject());
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+ }
+
+ // Step i. Sadly, we can't take a shortcut here even if
+ // capability.mNativePromise exists, because someone could have overridden
+ // "resolve" on the canonical Promise constructor.
+ JS::Rooted<JS::Value> nextPromise(cx);
+ if (!JS_CallFunctionName(cx, constructorObj, "resolve",
+ JS::HandleValueArray(nextValue),
+ &nextPromise)) {
+ // Step j.
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ // Step k.
+ JS::Rooted<JSObject*> resolveElement(cx);
+ JSFunction* resolveFunc =
+ js::NewFunctionWithReserved(cx, PromiseAllResolveElement,
+ 1 /* nargs */, 0 /* flags */, nullptr);
+ if (!resolveFunc) {
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ resolveElement = JS_GetFunctionObject(resolveFunc);
+ // Steps l-p.
+ js::SetFunctionNativeReserved(resolveElement,
+ RESOLVE_ELEMENT_INDEX_SLOT,
+ JS::Int32Value(index.value()));
+ js::SetFunctionNativeReserved(resolveElement,
+ RESOLVE_ELEMENT_DATA_HOLDER_SLOT,
+ JS::ObjectValue(*dataHolder));
+
+ // Step q.
+ int32_t remainingElements =
+ js::GetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT).toInt32();
+ js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT,
+ JS::Int32Value(remainingElements + 1));
+
+ // Step r. And now we don't know whether nextPromise has an overridden
+ // "then" method, so no shortcuts here either.
+ callbackFunctions[0].setObject(*resolveElement);
+ JS::Rooted<JSObject*> nextPromiseObj(cx);
+ JS::Rooted<JS::Value> ignored(cx);
+ if (!JS_ValueToObject(cx, nextPromise, &nextPromiseObj) ||
+ !JS_CallFunctionName(cx, nextPromiseObj, "then", callbackFunctions,
+ &ignored)) {
+ // Step s.
+ capability.RejectWithException(cx, aRv);
+ }
+
+ // Step t.
+ index += 1;
+ if (!index.isValid()) {
+ // Let's just claim OOM.
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ capability.RejectWithException(cx, aRv);
+ }
+ }
+}
+
+/* static */ already_AddRefed<Promise>
+Promise::All(const GlobalObject& aGlobal,
+ const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ JSContext* cx = aGlobal.Context();
+
+ if (aPromiseList.IsEmpty()) {
+ JS::Rooted<JSObject*> empty(cx, JS_NewArrayObject(cx, 0));
+ if (!empty) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ JS::Rooted<JS::Value> value(cx, JS::ObjectValue(*empty));
+ // We know "value" is not a promise, so call the Resolve function
+ // that doesn't have to check for that.
+ return Promise::Resolve(global, cx, value, aRv);
+ }
+
+ RefPtr<Promise> promise = Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ RefPtr<CountdownHolder> holder =
+ new CountdownHolder(aGlobal, promise, aPromiseList.Length());
+
+ JS::Rooted<JSObject*> obj(cx, JS::CurrentGlobalOrNull(cx));
+ if (!obj) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ RefPtr<PromiseCallback> rejectCb = new RejectPromiseCallback(promise, obj);
+
+ for (uint32_t i = 0; i < aPromiseList.Length(); ++i) {
+ RefPtr<PromiseNativeHandler> resolveHandler =
+ new AllResolveElementFunction(holder, i);
+
+ RefPtr<PromiseCallback> resolveCb =
+ new NativePromiseCallback(resolveHandler, Resolved);
+
+ // Every promise gets its own resolve callback, which will set the right
+ // index in the array to the resolution value.
+ aPromiseList[i]->AppendCallbacks(resolveCb, rejectCb);
+ }
+
+ return promise.forget();
+}
+
+/* static */ void
+Promise::Race(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
+ JS::Handle<JS::Value> aIterable, JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv)
+{
+ // Implements http://www.ecma-international.org/ecma-262/6.0/#sec-promise.race
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ JSContext* cx = aGlobal.Context();
+
+ // Steps 1-5: nothing to do. Note that the @@species bits got removed in
+ // https://github.com/tc39/ecma262/pull/211
+ PromiseCapability capability(cx);
+
+ // Step 6.
+ NewPromiseCapability(cx, global, aThisv, true, capability, aRv);
+ // Step 7.
+ if (aRv.Failed()) {
+ return;
+ }
+
+ MOZ_ASSERT(aThisv.isObject(), "How did NewPromiseCapability succeed?");
+ JS::Rooted<JSObject*> constructorObj(cx, &aThisv.toObject());
+
+ // After this point we have a useful promise value in "capability", so just go
+ // ahead and put it in our retval now. Every single return path below would
+ // want to do that anyway.
+ aRetval.set(capability.PromiseValue());
+ if (!MaybeWrapValue(cx, aRetval)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+
+ // The arguments we're going to be passing to "then" on each loop iteration.
+ JS::AutoValueArray<2> callbackFunctions(cx);
+ callbackFunctions[0].set(capability.mResolve);
+ callbackFunctions[1].set(capability.mReject);
+
+ // Steps 8 and 9.
+ JS::ForOfIterator iter(cx);
+ if (!iter.init(aIterable, JS::ForOfIterator::AllowNonIterable)) {
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ if (!iter.valueIsIterable()) {
+ ThrowErrorMessage(cx, MSG_PROMISE_ARG_NOT_ITERABLE,
+ "Argument of Promise.race");
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ // Step 10 doesn't need to be done, because ForOfIterator handles it
+ // for us.
+
+ // Now we jump over to
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-performpromiserace
+ // and do its steps.
+ JS::Rooted<JS::Value> nextValue(cx);
+ while (true) {
+ bool done;
+ // Steps a, b, c, e, f, g.
+ if (!iter.next(&nextValue, &done)) {
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ // Step d.
+ if (done) {
+ // We're all set!
+ return;
+ }
+
+ // Step h. Sadly, we can't take a shortcut here even if
+ // capability.mNativePromise exists, because someone could have overridden
+ // "resolve" on the canonical Promise constructor.
+ JS::Rooted<JS::Value> nextPromise(cx);
+ if (!JS_CallFunctionName(cx, constructorObj, "resolve",
+ JS::HandleValueArray(nextValue), &nextPromise)) {
+ // Step i.
+ capability.RejectWithException(cx, aRv);
+ return;
+ }
+
+ // Step j. And now we don't know whether nextPromise has an overridden
+ // "then" method, so no shortcuts here either.
+ JS::Rooted<JSObject*> nextPromiseObj(cx);
+ JS::Rooted<JS::Value> ignored(cx);
+ if (!JS_ValueToObject(cx, nextPromise, &nextPromiseObj) ||
+ !JS_CallFunctionName(cx, nextPromiseObj, "then", callbackFunctions,
+ &ignored)) {
+ // Step k.
+ capability.RejectWithException(cx, aRv);
+ }
+ }
+}
+
+/* static */
+bool
+Promise::PromiseSpecies(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+ JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
+ args.rval().set(args.thisv());
+ return true;
+}
+
+void
+Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ RefPtr<PromiseCallback> resolveCb =
+ new NativePromiseCallback(aRunnable, Resolved);
+
+ RefPtr<PromiseCallback> rejectCb =
+ new NativePromiseCallback(aRunnable, Rejected);
+
+ AppendCallbacks(resolveCb, rejectCb);
+}
+
+#endif // SPIDERMONKEY_PROMISE
+
+JSObject*
+Promise::GlobalJSObject() const
+{
+ return mGlobal->GetGlobalJSObject();
+}
+
+JSCompartment*
+Promise::Compartment() const
+{
+ return js::GetObjectCompartment(GlobalJSObject());
+}
+
+#ifndef SPIDERMONKEY_PROMISE
+void
+Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
+ PromiseCallback* aRejectCallback)
+{
+ if (!mGlobal || mGlobal->IsDying()) {
+ return;
+ }
+
+ MOZ_ASSERT(aResolveCallback);
+ MOZ_ASSERT(aRejectCallback);
+
+ if (mIsLastInChain && mState == PromiseState::Rejected) {
+ // This rejection is now consumed.
+ PromiseDebugging::AddConsumedRejection(*this);
+ // Note that we may not have had the opportunity to call
+ // RunResolveTask() yet, so we may never have called
+ // `PromiseDebugging:AddUncaughtRejection`.
+ }
+ mIsLastInChain = false;
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ // Now that there is a callback, we don't need to report anymore.
+ mHadRejectCallback = true;
+ RemoveWorkerHolder();
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+ mResolveCallbacks.AppendElement(aResolveCallback);
+ mRejectCallbacks.AppendElement(aRejectCallback);
+
+ // If promise's state is fulfilled, queue a task to process our fulfill
+ // 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) {
+ TriggerPromiseReactions();
+ }
+}
+#endif // SPIDERMONKEY_PROMISE
+
+#ifndef SPIDERMONKEY_PROMISE
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+void
+Promise::MaybeReportRejected()
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ if (mState != Rejected || mHadRejectCallback || mResult.isUndefined()) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ // We may not have a usable global by now (if it got unlinked
+ // already), so don't init with it.
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> obj(cx, GetWrapper());
+ MOZ_ASSERT(obj); // We preserve our wrapper, so should always have one here.
+ JS::Rooted<JS::Value> val(cx, mResult);
+
+ JSAutoCompartment ac(cx, obj);
+ if (!JS_WrapValue(cx, &val)) {
+ JS_ClearPendingException(cx);
+ return;
+ }
+
+ js::ErrorReport report(cx);
+ RefPtr<Exception> exp;
+ bool isObject = val.isObject();
+ if (!isObject || NS_FAILED(UNWRAP_OBJECT(Exception, &val.toObject(), exp))) {
+ if (!isObject ||
+ NS_FAILED(UNWRAP_OBJECT(DOMException, &val.toObject(), exp))) {
+ if (!report.init(cx, val, js::ErrorReport::NoSideEffects)) {
+ NS_WARNING("Couldn't convert the unhandled rejected value to an exception.");
+ JS_ClearPendingException(cx);
+ return;
+ }
+ }
+ }
+
+ RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+ bool isMainThread = MOZ_LIKELY(NS_IsMainThread());
+ bool isChrome = isMainThread ? nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(obj))
+ : GetCurrentThreadWorkerPrivate()->IsChromeWorker();
+ nsGlobalWindow* win = isMainThread ? xpc::WindowGlobalOrNull(obj) : nullptr;
+ uint64_t windowID = win ? win->AsInner()->WindowID() : 0;
+ if (exp) {
+ xpcReport->Init(cx, exp, isChrome, windowID);
+ } else {
+ xpcReport->Init(report.report(), report.toStringResult(),
+ isChrome, windowID);
+ }
+
+ // Now post an event to do the real reporting async
+ // Since Promises preserve their wrapper, it is essential to RefPtr<> the
+ // AsyncErrorReporter, otherwise if the call to DispatchToMainThread fails, it
+ // will leak. See Bug 958684. So... don't use DispatchToMainThread()
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ if (NS_WARN_IF(!mainThread)) {
+ // Would prefer NS_ASSERTION, but that causes failure in xpcshell tests
+ NS_WARNING("!!! Trying to report rejected Promise after MainThread shutdown");
+ }
+ if (mainThread) {
+ RefPtr<AsyncErrorReporter> r = new AsyncErrorReporter(xpcReport);
+ mainThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ }
+}
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+void
+Promise::MaybeResolveInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ if (mResolvePending) {
+ return;
+ }
+
+ ResolveInternal(aCx, aValue);
+}
+
+void
+Promise::MaybeRejectInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ if (mResolvePending) {
+ return;
+ }
+
+ RejectInternal(aCx, aValue);
+}
+
+void
+Promise::HandleException(JSContext* aCx)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ JS::Rooted<JS::Value> exn(aCx);
+ if (JS_GetPendingException(aCx, &exn)) {
+ JS_ClearPendingException(aCx);
+ RejectInternal(aCx, exn);
+ }
+}
+
+void
+Promise::ResolveInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
+
+ mResolvePending = true;
+
+ if (aValue.isObject()) {
+ JS::Rooted<JSObject*> valueObj(aCx, &aValue.toObject());
+
+ // Thenables.
+ JS::Rooted<JS::Value> then(aCx);
+ if (!JS_GetProperty(aCx, valueObj, "then", &then)) {
+ HandleException(aCx);
+ return;
+ }
+
+ if (then.isObject() && JS::IsCallable(&then.toObject())) {
+ // This is the then() function of the thenable aValueObj.
+ JS::Rooted<JSObject*> thenObj(aCx, &then.toObject());
+
+ // We used to have a fast path here for the case when the following
+ // requirements held:
+ //
+ // 1) valueObj is a Promise.
+ // 2) thenObj is a JSFunction backed by our actual Promise::Then
+ // implementation.
+ //
+ // But now that we're doing subclassing in Promise.prototype.then we would
+ // also need the following requirements:
+ //
+ // 3) Getting valueObj.constructor has no side-effects.
+ // 4) Getting valueObj.constructor[@@species] has no side-effects.
+ // 5) valueObj.constructor[@@species] is a function and calling it has no
+ // side-effects (e.g. it's the canonical Promise constructor) and it
+ // provides some callback functions to call as arguments to its
+ // argument.
+ //
+ // Ensuring that stuff while not inside SpiderMonkey is painful, so let's
+ // drop the fast path for now.
+
+ RefPtr<PromiseInit> thenCallback =
+ new PromiseInit(nullptr, thenObj, mozilla::dom::GetIncumbentGlobal());
+ RefPtr<PromiseResolveThenableJob> task =
+ new PromiseResolveThenableJob(this, valueObj, thenCallback);
+ context->DispatchToMicroTask(task.forget());
+ return;
+ }
+ }
+
+ MaybeSettle(aValue, Resolved);
+}
+
+void
+Promise::RejectInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ mResolvePending = true;
+
+ MaybeSettle(aValue, Rejected);
+}
+
+void
+Promise::Settle(JS::Handle<JS::Value> aValue, PromiseState aState)
+{
+ MOZ_ASSERT(mGlobal,
+ "We really should have a global here. Except we sometimes don't "
+ "in the wild for some odd reason");
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ if (!mGlobal || mGlobal->IsDying()) {
+ return;
+ }
+
+ mSettlementTimestamp = TimeStamp::Now();
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::RootedObject wrapper(cx, GetWrapper());
+ MOZ_ASSERT(wrapper); // We preserved it
+ JSAutoCompartment ac(cx, wrapper);
+
+ JS::Rooted<JS::Value> value(cx, aValue);
+
+ if (!JS_WrapValue(cx, &value)) {
+ JS_ClearPendingException(cx);
+ value = JS::UndefinedValue();
+ }
+ SetResult(value);
+ SetState(aState);
+
+ JS::dbg::onPromiseSettled(cx, wrapper);
+
+ if (aState == PromiseState::Rejected &&
+ mIsLastInChain) {
+ // The Promise has just been rejected, and it is last in chain.
+ // We need to inform PromiseDebugging.
+ // If the Promise is eventually not the last in chain anymore,
+ // we will need to inform PromiseDebugging again.
+ PromiseDebugging::AddUncaughtRejection(*this);
+ }
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ // If the Promise was rejected, and there is no reject handler already setup,
+ // watch for thread shutdown.
+ if (aState == PromiseState::Rejected &&
+ !mHadRejectCallback &&
+ !NS_IsMainThread()) {
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+
+ mWorkerHolder = new PromiseReportRejectWorkerHolder(this);
+ if (NS_WARN_IF(!mWorkerHolder->HoldWorker(worker, Closing))) {
+ mWorkerHolder = nullptr;
+ // Worker is shutting down, report rejection immediately since it is
+ // unlikely that reject callbacks will be added after this point.
+ MaybeReportRejectedOnce();
+ }
+ }
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+ TriggerPromiseReactions();
+}
+
+void
+Promise::MaybeSettle(JS::Handle<JS::Value> aValue,
+ PromiseState aState)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ // Promise.all() or Promise.race() implementations will repeatedly call
+ // Resolve/RejectInternal rather than using the Maybe... forms. Stop SetState
+ // from asserting.
+ if (mState != Pending) {
+ return;
+ }
+
+ Settle(aValue, aState);
+}
+
+void
+Promise::TriggerPromiseReactions()
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ CycleCollectedJSContext* runtime = CycleCollectedJSContext::Get();
+
+ nsTArray<RefPtr<PromiseCallback>> callbacks;
+ callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
+ : mRejectCallbacks);
+ mResolveCallbacks.Clear();
+ mRejectCallbacks.Clear();
+
+ for (uint32_t i = 0; i < callbacks.Length(); ++i) {
+ RefPtr<PromiseReactionJob> task =
+ new PromiseReactionJob(this, callbacks[i], mResult);
+ runtime->DispatchToMicroTask(task.forget());
+ }
+}
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+void
+Promise::RemoveWorkerHolder()
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ // The DTOR of this WorkerHolder will release the worker for us.
+ mWorkerHolder = nullptr;
+}
+
+bool
+PromiseReportRejectWorkerHolder::Notify(Status aStatus)
+{
+ MOZ_ASSERT(aStatus > Running);
+ mPromise->MaybeReportRejectedOnce();
+ // After this point, `this` has been deleted by RemoveWorkerHolder!
+ return true;
+}
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+bool
+Promise::CaptureStack(JSContext* aCx, JS::Heap<JSObject*>& aTarget)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ JS::Rooted<JSObject*> stack(aCx);
+ if (!JS::CaptureCurrentStack(aCx, &stack)) {
+ return false;
+ }
+ aTarget = stack;
+ return true;
+}
+
+void
+Promise::GetDependentPromises(nsTArray<RefPtr<Promise>>& aPromises)
+{
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ // We want to return promises that correspond to then() calls, Promise.all()
+ // calls, and Promise.race() calls.
+ //
+ // For the then() case, we have both resolve and reject callbacks that know
+ // what the next promise is.
+ //
+ // For the race() case, likewise.
+ //
+ // For the all() case, our reject callback knows what the next promise is, but
+ // our resolve callback just knows it needs to notify some
+ // PromiseNativeHandler, which itself only has an indirect relationship to the
+ // next promise.
+ //
+ // So we walk over our _reject_ callbacks and ask each of them what promise
+ // its dependent promise is.
+ for (size_t i = 0; i < mRejectCallbacks.Length(); ++i) {
+ Promise* p = mRejectCallbacks[i]->GetDependentPromise();
+ if (p) {
+ aPromises.AppendElement(p);
+ }
+ }
+}
+
+#endif // SPIDERMONKEY_PROMISE
+
+// A WorkerRunnable to resolve/reject the Promise on the worker thread.
+// Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
+class PromiseWorkerProxyRunnable : public WorkerRunnable
+{
+public:
+ PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
+ PromiseWorkerProxy::RunCallbackFunc aFunc)
+ : WorkerRunnable(aPromiseWorkerProxy->GetWorkerPrivate(),
+ WorkerThreadUnchangedBusyCount)
+ , mPromiseWorkerProxy(aPromiseWorkerProxy)
+ , mFunc(aFunc)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPromiseWorkerProxy);
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
+
+ MOZ_ASSERT(mPromiseWorkerProxy);
+ RefPtr<Promise> workerPromise = mPromiseWorkerProxy->WorkerPromise();
+
+ // Here we convert the buffer to a JS::Value.
+ JS::Rooted<JS::Value> value(aCx);
+ if (!mPromiseWorkerProxy->Read(aCx, &value)) {
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+
+ (workerPromise->*mFunc)(aCx, value);
+
+ // Release the Promise because it has been resolved/rejected for sure.
+ mPromiseWorkerProxy->CleanUp();
+ return true;
+ }
+
+protected:
+ ~PromiseWorkerProxyRunnable() {}
+
+private:
+ RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
+
+ // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
+ PromiseWorkerProxy::RunCallbackFunc mFunc;
+};
+
+class PromiseWorkerHolder final : public WorkerHolder
+{
+ // RawPointer because this proxy keeps alive the holder.
+ PromiseWorkerProxy* mProxy;
+
+public:
+ explicit PromiseWorkerHolder(PromiseWorkerProxy* aProxy)
+ : mProxy(aProxy)
+ {
+ MOZ_ASSERT(aProxy);
+ }
+
+ bool
+ Notify(Status aStatus) override
+ {
+ if (aStatus >= Canceling) {
+ mProxy->CleanUp();
+ }
+
+ return true;
+ }
+};
+
+/* static */
+already_AddRefed<PromiseWorkerProxy>
+PromiseWorkerProxy::Create(WorkerPrivate* aWorkerPrivate,
+ Promise* aWorkerPromise,
+ const PromiseWorkerProxyStructuredCloneCallbacks* aCb)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(aWorkerPromise);
+ MOZ_ASSERT_IF(aCb, !!aCb->Write && !!aCb->Read);
+
+ RefPtr<PromiseWorkerProxy> proxy =
+ new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise, aCb);
+
+ // We do this to make sure the worker thread won't shut down before the
+ // promise is resolved/rejected on the worker thread.
+ if (!proxy->AddRefObject()) {
+ // Probably the worker is terminating. We cannot complete the operation
+ // and we have to release all the resources.
+ proxy->CleanProperties();
+ return nullptr;
+ }
+
+ return proxy.forget();
+}
+
+NS_IMPL_ISUPPORTS0(PromiseWorkerProxy)
+
+PromiseWorkerProxy::PromiseWorkerProxy(WorkerPrivate* aWorkerPrivate,
+ Promise* aWorkerPromise,
+ const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks)
+ : mWorkerPrivate(aWorkerPrivate)
+ , mWorkerPromise(aWorkerPromise)
+ , mCleanedUp(false)
+ , mCallbacks(aCallbacks)
+ , mCleanUpLock("cleanUpLock")
+{
+}
+
+PromiseWorkerProxy::~PromiseWorkerProxy()
+{
+ MOZ_ASSERT(mCleanedUp);
+ MOZ_ASSERT(!mWorkerHolder);
+ MOZ_ASSERT(!mWorkerPromise);
+ MOZ_ASSERT(!mWorkerPrivate);
+}
+
+void
+PromiseWorkerProxy::CleanProperties()
+{
+#ifdef DEBUG
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+#endif
+ // Ok to do this unprotected from Create().
+ // CleanUp() holds the lock before calling this.
+ mCleanedUp = true;
+ mWorkerPromise = nullptr;
+ mWorkerPrivate = nullptr;
+
+ // Clear the StructuredCloneHolderBase class.
+ Clear();
+}
+
+bool
+PromiseWorkerProxy::AddRefObject()
+{
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(!mWorkerHolder);
+ mWorkerHolder.reset(new PromiseWorkerHolder(this));
+ if (NS_WARN_IF(!mWorkerHolder->HoldWorker(mWorkerPrivate, Canceling))) {
+ mWorkerHolder = nullptr;
+ return false;
+ }
+
+ // Maintain a reference so that we have a valid object to clean up when
+ // removing the feature.
+ AddRef();
+ return true;
+}
+
+WorkerPrivate*
+PromiseWorkerProxy::GetWorkerPrivate() const
+{
+#ifdef DEBUG
+ if (NS_IsMainThread()) {
+ mCleanUpLock.AssertCurrentThreadOwns();
+ }
+#endif
+ // Safe to check this without a lock since we assert lock ownership on the
+ // main thread above.
+ MOZ_ASSERT(!mCleanedUp);
+ MOZ_ASSERT(mWorkerHolder);
+
+ return mWorkerPrivate;
+}
+
+Promise*
+PromiseWorkerProxy::WorkerPromise() const
+{
+#ifdef DEBUG
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+#endif
+ MOZ_ASSERT(mWorkerPromise);
+ return mWorkerPromise;
+}
+
+void
+PromiseWorkerProxy::RunCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ RunCallbackFunc aFunc)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MutexAutoLock lock(Lock());
+ // If the worker thread's been cancelled we don't need to resolve the Promise.
+ if (CleanedUp()) {
+ return;
+ }
+
+ // The |aValue| is written into the StructuredCloneHolderBase.
+ if (!Write(aCx, aValue)) {
+ JS_ClearPendingException(aCx);
+ MOZ_ASSERT(false, "cannot serialize the value with the StructuredCloneAlgorithm!");
+ }
+
+ RefPtr<PromiseWorkerProxyRunnable> runnable =
+ new PromiseWorkerProxyRunnable(this, aFunc);
+
+ runnable->Dispatch();
+}
+
+void
+PromiseWorkerProxy::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ RunCallback(aCx, aValue, &Promise::MaybeResolve);
+}
+
+void
+PromiseWorkerProxy::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ RunCallback(aCx, aValue, &Promise::MaybeReject);
+}
+
+void
+PromiseWorkerProxy::CleanUp()
+{
+ // Can't release Mutex while it is still locked, so scope the lock.
+ {
+ MutexAutoLock lock(Lock());
+
+ // |mWorkerPrivate| is not safe to use anymore if we have already
+ // cleaned up and RemoveWorkerHolder(), so we need to check |mCleanedUp|
+ // first.
+ if (CleanedUp()) {
+ return;
+ }
+
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ // Release the Promise and remove the PromiseWorkerProxy from the holders of
+ // the worker thread since the Promise has been resolved/rejected or the
+ // worker thread has been cancelled.
+ MOZ_ASSERT(mWorkerHolder);
+ mWorkerHolder = nullptr;
+
+ CleanProperties();
+ }
+ Release();
+}
+
+JSObject*
+PromiseWorkerProxy::CustomReadHandler(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag,
+ uint32_t aIndex)
+{
+ if (NS_WARN_IF(!mCallbacks)) {
+ return nullptr;
+ }
+
+ return mCallbacks->Read(aCx, aReader, this, aTag, aIndex);
+}
+
+bool
+PromiseWorkerProxy::CustomWriteHandler(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj)
+{
+ if (NS_WARN_IF(!mCallbacks)) {
+ return false;
+ }
+
+ return mCallbacks->Write(aCx, aWriter, this, aObj);
+}
+
+// Specializations of MaybeRejectBrokenly we actually support.
+template<>
+void Promise::MaybeRejectBrokenly(const RefPtr<DOMError>& aArg) {
+ MaybeSomething(aArg, &Promise::MaybeReject);
+}
+template<>
+void Promise::MaybeRejectBrokenly(const nsAString& aArg) {
+ MaybeSomething(aArg, &Promise::MaybeReject);
+}
+
+#ifndef SPIDERMONKEY_PROMISE
+uint64_t
+Promise::GetID() {
+ if (mID != 0) {
+ return mID;
+ }
+ return mID = ++gIDGenerator;
+}
+#endif // SPIDERMONKEY_PROMISE
+
+#ifndef SPIDERMONKEY_PROMISE
+Promise::PromiseState
+Promise::State() const
+{
+ return mState;
+}
+#else // SPIDERMONKEY_PROMISE
+Promise::PromiseState
+Promise::State() const
+{
+ JS::Rooted<JSObject*> p(RootingCx(), PromiseObj());
+ const JS::PromiseState state = JS::GetPromiseState(p);
+
+ if (state == JS::PromiseState::Fulfilled) {
+ return PromiseState::Resolved;
+ }
+
+ if (state == JS::PromiseState::Rejected) {
+ return PromiseState::Rejected;
+ }
+
+ return PromiseState::Pending;
+}
+#endif // SPIDERMONKEY_PROMISE
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/promise/Promise.h b/dom/promise/Promise.h
new file mode 100644
index 0000000000..f2ad3bd6c1
--- /dev/null
+++ b/dom/promise/Promise.h
@@ -0,0 +1,537 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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_Promise_h
+#define mozilla_dom_Promise_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/WeakPtr.h"
+#include "nsWrapperCache.h"
+#include "nsAutoPtr.h"
+#include "js/TypeDecls.h"
+#include "jspubtd.h"
+
+// Bug 1083361 introduces a new mechanism for tracking uncaught
+// rejections. This #define serves to track down the parts of code
+// that need to be removed once clients have been put together
+// to take advantage of the new mechanism. New code should not
+// depend on code #ifdefed to this #define.
+#define DOM_PROMISE_DEPRECATED_REPORTING !SPIDERMONKEY_PROMISE
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+#include "mozilla/dom/workers/bindings/WorkerHolder.h"
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class AnyCallback;
+class DOMError;
+class MediaStreamError;
+class PromiseCallback;
+class PromiseInit;
+class PromiseNativeHandler;
+class PromiseDebugging;
+
+class Promise;
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+class PromiseReportRejectWorkerHolder : public workers::WorkerHolder
+{
+ // PromiseReportRejectWorkerHolder is held by an nsAutoPtr on the Promise
+ // which means that this object will be destroyed before the Promise is
+ // destroyed.
+ Promise* MOZ_NON_OWNING_REF mPromise;
+
+public:
+ explicit PromiseReportRejectWorkerHolder(Promise* aPromise)
+ : mPromise(aPromise)
+ {
+ MOZ_ASSERT(mPromise);
+ }
+
+ virtual bool
+ Notify(workers::Status aStatus) override;
+};
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+#define NS_PROMISE_IID \
+ { 0x1b8d6215, 0x3e67, 0x43ba, \
+ { 0x8a, 0xf9, 0x31, 0x5e, 0x8f, 0xce, 0x75, 0x65 } }
+
+class Promise : public nsISupports,
+#ifndef SPIDERMONKEY_PROMISE
+ // Only wrappercached when we're not using SpiderMonkey
+ // promises, because those don't have a useful object moved
+ // hook, which wrappercache needs.
+ public nsWrapperCache,
+#endif // SPIDERMONKEY_PROMISE
+ public SupportsWeakPtr<Promise>
+{
+ friend class NativePromiseCallback;
+ friend class PromiseReactionJob;
+ friend class PromiseResolverTask;
+ friend class PromiseTask;
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ friend class PromiseReportRejectWorkerHolder;
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ friend class PromiseWorkerProxy;
+ friend class PromiseWorkerProxyRunnable;
+ friend class RejectPromiseCallback;
+ friend class ResolvePromiseCallback;
+ friend class PromiseResolveThenableJob;
+ friend class FastPromiseResolveThenableJob;
+ friend class WrapperPromiseCallback;
+
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROMISE_IID)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+#ifdef SPIDERMONKEY_PROMISE
+ // We're not skippable, since we're not owned from JS to start with.
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Promise)
+#else // SPIDERMONKEY_PROMISE
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(Promise)
+#endif // SPIDERMONKEY_PROMISE
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(Promise)
+
+ // Promise creation tries to create a JS reflector for the Promise, so is
+ // fallible. Furthermore, we don't want to do JS-wrapping on a 0-refcount
+ // object, so we addref before doing that and return the addrefed pointer
+ // here.
+#ifdef SPIDERMONKEY_PROMISE
+ static already_AddRefed<Promise>
+ Create(nsIGlobalObject* aGlobal, ErrorResult& aRv);
+
+ // Reports a rejected Promise by sending an error report.
+ static void ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise);
+#else
+ static already_AddRefed<Promise>
+ Create(nsIGlobalObject* aGlobal, ErrorResult& aRv,
+ // Passing null for aDesiredProto will use Promise.prototype.
+ JS::Handle<JSObject*> aDesiredProto = nullptr);
+#endif // SPIDERMONKEY_PROMISE
+
+ typedef void (Promise::*MaybeFunc)(JSContext* aCx,
+ JS::Handle<JS::Value> aValue);
+
+ void MaybeResolve(JSContext* aCx,
+ JS::Handle<JS::Value> aValue);
+ void MaybeReject(JSContext* aCx,
+ JS::Handle<JS::Value> aValue);
+
+ // Helpers for using Promise from C++.
+ // Most DOM objects are handled already. To add a new type T, add a
+ // ToJSValue overload in ToJSValue.h.
+ // aArg is a const reference so we can pass rvalues like integer constants
+ template <typename T>
+ void MaybeResolve(const T& aArg) {
+ MaybeSomething(aArg, &Promise::MaybeResolve);
+ }
+
+ void MaybeResolveWithUndefined();
+
+ inline void MaybeReject(nsresult aArg) {
+ MOZ_ASSERT(NS_FAILED(aArg));
+ MaybeSomething(aArg, &Promise::MaybeReject);
+ }
+
+ inline void MaybeReject(ErrorResult& aArg) {
+ MOZ_ASSERT(aArg.Failed());
+ MaybeSomething(aArg, &Promise::MaybeReject);
+ }
+
+ void MaybeReject(const RefPtr<MediaStreamError>& aArg);
+
+ void MaybeRejectWithUndefined();
+
+ // DO NOT USE MaybeRejectBrokenly with in new code. Promises should be
+ // rejected with Error instances.
+ // Note: MaybeRejectBrokenly is a template so we can use it with DOMError
+ // without instantiating the DOMError specialization of MaybeSomething in
+ // every translation unit that includes this header, because that would
+ // require use to include DOMError.h either here or in all those translation
+ // units.
+ template<typename T>
+ void MaybeRejectBrokenly(const T& aArg); // Not implemented by default; see
+ // specializations in the .cpp for
+ // the T values we support.
+
+ // Called by DOM to let us execute our callbacks. May be called recursively.
+ // Returns true if at least one microtask was processed.
+ static bool PerformMicroTaskCheckpoint();
+
+ static void PerformWorkerMicroTaskCheckpoint();
+
+ static void PerformWorkerDebuggerMicroTaskCheckpoint();
+
+ // WebIDL
+
+ nsIGlobalObject* GetParentObject() const
+ {
+ return mGlobal;
+ }
+
+#ifdef SPIDERMONKEY_PROMISE
+ bool
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aWrapper);
+
+ // Do the equivalent of Promise.resolve in the current compartment of aCx.
+ // Errorrs are reported on the ErrorResult; if aRv comes back !Failed(), this
+ // function MUST return a non-null value.
+ static already_AddRefed<Promise>
+ Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv);
+
+ // Do the equivalent of Promise.reject in the current compartment of aCx.
+ // Errorrs are reported on the ErrorResult; if aRv comes back !Failed(), this
+ // function MUST return a non-null value.
+ static already_AddRefed<Promise>
+ Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv);
+
+ static already_AddRefed<Promise>
+ All(const GlobalObject& aGlobal,
+ const nsTArray<RefPtr<Promise>>& aPromiseList, 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);
+
+ JSObject* PromiseObj() const
+ {
+ return mPromiseObj;
+ }
+
+#else // SPIDERMONKEY_PROMISE
+ JSObject* PromiseObj()
+ {
+ return GetWrapper();
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<Promise>
+ Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
+ ErrorResult& aRv, JS::Handle<JSObject*> aDesiredProto);
+
+ static void
+ Resolve(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
+ JS::Handle<JS::Value> aValue,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv);
+
+ static already_AddRefed<Promise>
+ Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv);
+
+ static void
+ Reject(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
+ JS::Handle<JS::Value> aValue,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv);
+
+ static already_AddRefed<Promise>
+ Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::Handle<JS::Value> aValue, 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);
+
+ void
+ Catch(JSContext* aCx,
+ AnyCallback* aRejectCallback,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv);
+
+ static void
+ All(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
+ JS::Handle<JS::Value> aIterable, JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Promise>
+ All(const GlobalObject& aGlobal,
+ const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv);
+
+ static void
+ Race(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
+ JS::Handle<JS::Value> aIterable, JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv);
+
+ static bool
+ PromiseSpecies(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
+#endif // SPIDERMONKEY_PROMISE
+
+ void AppendNativeHandler(PromiseNativeHandler* aRunnable);
+
+ JSObject* GlobalJSObject() const;
+
+ JSCompartment* Compartment() const;
+
+#ifndef SPIDERMONKEY_PROMISE
+ // Return a unique-to-the-process identifier for this Promise.
+ uint64_t GetID();
+#endif // SPIDERMONKEY_PROMISE
+
+#ifndef SPIDERMONKEY_PROMISE
+ enum JSCallbackSlots {
+ SLOT_PROMISE = 0,
+ SLOT_DATA
+ };
+#endif // SPIDERMONKEY_PROMISE
+
+#ifdef SPIDERMONKEY_PROMISE
+ // Create a dom::Promise from a given SpiderMonkey Promise object.
+ // aPromiseObj MUST be in the compartment of aGlobal's global JS object.
+ static already_AddRefed<Promise>
+ CreateFromExisting(nsIGlobalObject* aGlobal,
+ JS::Handle<JSObject*> aPromiseObj);
+#endif // SPIDERMONKEY_PROMISE
+
+ enum class PromiseState {
+ Pending,
+ Resolved,
+ Rejected
+ };
+
+ PromiseState State() const;
+
+protected:
+ struct PromiseCapability;
+
+ // Do NOT call this unless you're Promise::Create or
+ // Promise::CreateFromExisting. I wish we could enforce that from inside this
+ // class too, somehow.
+ explicit Promise(nsIGlobalObject* aGlobal);
+
+ virtual ~Promise();
+
+ // Do JS-wrapping after Promise creation. Passing null for aDesiredProto will
+ // use the default prototype for the sort of Promise we have.
+ void CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv);
+
+#ifndef SPIDERMONKEY_PROMISE
+ // Create the JS resolving functions of resolve() and reject(). And provide
+ // references to the two functions by calling PromiseInit passed from Promise
+ // constructor.
+ void CallInitFunction(const GlobalObject& aGlobal, PromiseInit& aInit,
+ ErrorResult& aRv);
+
+ // The NewPromiseCapability function from
+ // <http://www.ecma-international.org/ecma-262/6.0/#sec-newpromisecapability>.
+ // Errors are communicated via aRv. If aForceCallbackCreation is
+ // true, then this function will ensure that aCapability has a
+ // useful mResolve/mReject even if mNativePromise is non-null.
+ static void NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal,
+ JS::Handle<JS::Value> aConstructor,
+ bool aForceCallbackCreation,
+ PromiseCapability& aCapability,
+ ErrorResult& aRv);
+
+ bool IsPending()
+ {
+ return mResolvePending;
+ }
+
+ void GetDependentPromises(nsTArray<RefPtr<Promise>>& aPromises);
+
+ bool IsLastInChain() const
+ {
+ return mIsLastInChain;
+ }
+
+ void SetNotifiedAsUncaught()
+ {
+ mWasNotifiedAsUncaught = true;
+ }
+
+ bool WasNotifiedAsUncaught() const
+ {
+ return mWasNotifiedAsUncaught;
+ }
+#endif // SPIDERMONKEY_PROMISE
+
+private:
+#ifndef SPIDERMONKEY_PROMISE
+ friend class PromiseDebugging;
+
+ void SetState(PromiseState aState)
+ {
+ MOZ_ASSERT(mState == Pending);
+ MOZ_ASSERT(aState != Pending);
+ mState = aState;
+ }
+
+ void SetResult(JS::Handle<JS::Value> aValue)
+ {
+ mResult = aValue;
+ }
+
+ // This method enqueues promise's resolve/reject callbacks with promise's
+ // result. It's executed when the resolver.resolve() or resolver.reject() is
+ // called or when the promise already has a result and new callbacks are
+ // appended by then() or catch().
+ void TriggerPromiseReactions();
+
+ void Settle(JS::Handle<JS::Value> aValue, Promise::PromiseState aState);
+ void MaybeSettle(JS::Handle<JS::Value> aValue, Promise::PromiseState aState);
+
+ void AppendCallbacks(PromiseCallback* aResolveCallback,
+ PromiseCallback* aRejectCallback);
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ // If we have been rejected and our mResult is a JS exception,
+ // report it to the error console.
+ // Use MaybeReportRejectedOnce() for actual calls.
+ void MaybeReportRejected();
+
+ void MaybeReportRejectedOnce() {
+ MaybeReportRejected();
+ RemoveWorkerHolder();
+ mResult.setUndefined();
+ }
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+ void MaybeResolveInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aValue);
+ void MaybeRejectInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aValue);
+
+ void ResolveInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aValue);
+ void RejectInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aValue);
+#endif // SPIDERMONKEY_PROMISE
+
+ template <typename T>
+ void MaybeSomething(T& aArgument, MaybeFunc aFunc) {
+ MOZ_ASSERT(PromiseObj()); // It was preserved!
+
+ AutoEntryScript aes(mGlobal, "Promise resolution or rejection");
+ JSContext* cx = aes.cx();
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, aArgument, &val)) {
+ HandleException(cx);
+ return;
+ }
+
+ (this->*aFunc)(cx, val);
+ }
+
+#ifndef SPIDERMONKEY_PROMISE
+ // Static methods for the PromiseInit functions.
+ static bool
+ JSCallback(JSContext *aCx, unsigned aArgc, JS::Value *aVp);
+
+ static bool
+ ThenableResolverCommon(JSContext* aCx, uint32_t /* PromiseCallback::Task */ aTask,
+ unsigned aArgc, JS::Value* aVp);
+ static bool
+ JSCallbackThenableResolver(JSContext *aCx, unsigned aArgc, JS::Value *aVp);
+ static bool
+ JSCallbackThenableRejecter(JSContext *aCx, unsigned aArgc, JS::Value *aVp);
+
+ static JSObject*
+ CreateFunction(JSContext* aCx, Promise* aPromise, int32_t aTask);
+
+ static JSObject*
+ CreateThenableFunction(JSContext* aCx, Promise* aPromise, uint32_t aTask);
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ void RemoveWorkerHolder();
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+ // Capture the current stack and store it in aTarget. If false is
+ // returned, an exception is presumably pending on aCx.
+ bool CaptureStack(JSContext* aCx, JS::Heap<JSObject*>& aTarget);
+#endif // SPIDERMONKEY_PROMISE
+
+ void HandleException(JSContext* aCx);
+
+ RefPtr<nsIGlobalObject> mGlobal;
+
+#ifndef SPIDERMONKEY_PROMISE
+ nsTArray<RefPtr<PromiseCallback> > mResolveCallbacks;
+ nsTArray<RefPtr<PromiseCallback> > mRejectCallbacks;
+
+ JS::Heap<JS::Value> mResult;
+ // A stack that shows where this promise was allocated, if there was
+ // JS running at the time. Otherwise null.
+ JS::Heap<JSObject*> mAllocationStack;
+ // mRejectionStack is only set when the promise is rejected directly from
+ // script, by calling Promise.reject() or the rejection callback we pass to
+ // the PromiseInit function. Promises that are rejected internally do not
+ // have a rejection stack.
+ JS::Heap<JSObject*> mRejectionStack;
+ // mFullfillmentStack is only set when the promise is fulfilled directly from
+ // script, by calling Promise.resolve() or the fulfillment callback we pass to
+ // the PromiseInit function. Promises that are fulfilled internally do not
+ // have a fulfillment stack.
+ JS::Heap<JSObject*> mFullfillmentStack;
+ PromiseState mState;
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ bool mHadRejectCallback;
+
+ // If a rejected promise on a worker has no reject callbacks attached, it
+ // needs to know when the worker is shutting down, to report the error on the
+ // console before the worker's context is deleted. This feature is used for
+ // that purpose.
+ nsAutoPtr<PromiseReportRejectWorkerHolder> mWorkerHolder;
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+ bool mTaskPending;
+ bool mResolvePending;
+
+ // `true` if this Promise is the last in the chain, or `false` if
+ // another Promise has been created from this one by a call to
+ // `then`, `all`, `race`, etc.
+ bool mIsLastInChain;
+
+ // `true` if PromiseDebugging has already notified at least one observer that
+ // this promise was left uncaught, `false` otherwise.
+ bool mWasNotifiedAsUncaught;
+
+ // The time when this promise was created.
+ TimeStamp mCreationTimestamp;
+
+ // The time when this promise transitioned out of the pending state.
+ TimeStamp mSettlementTimestamp;
+
+ // Once `GetID()` has been called, a unique-to-the-process identifier for this
+ // promise. Until then, `0`.
+ uint64_t mID;
+#else // SPIDERMONKEY_PROMISE
+ JS::Heap<JSObject*> mPromiseObj;
+#endif // SPIDERMONKEY_PROMISE
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Promise, NS_PROMISE_IID)
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Promise_h
diff --git a/dom/promise/PromiseCallback.cpp b/dom/promise/PromiseCallback.cpp
new file mode 100644
index 0000000000..3f4a689ae1
--- /dev/null
+++ b/dom/promise/PromiseCallback.cpp
@@ -0,0 +1,575 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "PromiseCallback.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "jswrapper.h"
+
+namespace mozilla {
+namespace dom {
+
+#ifndef SPIDERMONKEY_PROMISE
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseCallback)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_0(PromiseCallback)
+
+PromiseCallback::PromiseCallback()
+{
+}
+
+PromiseCallback::~PromiseCallback()
+{
+}
+
+// ResolvePromiseCallback
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ResolvePromiseCallback)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ResolvePromiseCallback,
+ PromiseCallback)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
+ tmp->mGlobal = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ResolvePromiseCallback,
+ PromiseCallback)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ResolvePromiseCallback)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ResolvePromiseCallback)
+NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)
+
+NS_IMPL_ADDREF_INHERITED(ResolvePromiseCallback, PromiseCallback)
+NS_IMPL_RELEASE_INHERITED(ResolvePromiseCallback, PromiseCallback)
+
+ResolvePromiseCallback::ResolvePromiseCallback(Promise* aPromise,
+ JS::Handle<JSObject*> aGlobal)
+ : mPromise(aPromise)
+ , mGlobal(aGlobal)
+{
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(aGlobal);
+ HoldJSObjects(this);
+}
+
+ResolvePromiseCallback::~ResolvePromiseCallback()
+{
+ DropJSObjects(this);
+}
+
+nsresult
+ResolvePromiseCallback::Call(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ // Run resolver's algorithm with value and the synchronous flag set.
+
+ 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;
+ }
+
+ mPromise->ResolveInternal(aCx, value);
+ return NS_OK;
+}
+
+// RejectPromiseCallback
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(RejectPromiseCallback)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(RejectPromiseCallback,
+ PromiseCallback)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
+ tmp->mGlobal = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(RejectPromiseCallback,
+ PromiseCallback)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(RejectPromiseCallback)
+NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(RejectPromiseCallback,
+ PromiseCallback)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(RejectPromiseCallback, PromiseCallback)
+NS_IMPL_RELEASE_INHERITED(RejectPromiseCallback, PromiseCallback)
+
+RejectPromiseCallback::RejectPromiseCallback(Promise* aPromise,
+ JS::Handle<JSObject*> aGlobal)
+ : mPromise(aPromise)
+ , mGlobal(aGlobal)
+{
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(mGlobal);
+ HoldJSObjects(this);
+}
+
+RejectPromiseCallback::~RejectPromiseCallback()
+{
+ DropJSObjects(this);
+}
+
+nsresult
+RejectPromiseCallback::Call(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ // Run resolver's algorithm with value and the synchronous flag set.
+
+ 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;
+ }
+
+
+ mPromise->RejectInternal(aCx, value);
+ 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::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)
+NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)
+
+NS_IMPL_ADDREF_INHERITED(WrapperPromiseCallback, PromiseCallback)
+NS_IMPL_RELEASE_INHERITED(WrapperPromiseCallback, PromiseCallback)
+
+WrapperPromiseCallback::WrapperPromiseCallback(Promise* aNextPromise,
+ JS::Handle<JSObject*> aGlobal,
+ AnyCallback* aCallback)
+ : mNextPromise(aNextPromise)
+ , mGlobal(aGlobal)
+ , mCallback(aCallback)
+{
+ MOZ_ASSERT(aNextPromise);
+ MOZ_ASSERT(aGlobal);
+ 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);
+}
+
+nsresult
+WrapperPromiseCallback::Call(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ 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;
+
+ // 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,
+ compartment);
+
+ rv.WouldReportJSException();
+
+ // PromiseReactionTask step 7
+ if (rv.Failed()) {
+ if (rv.IsUncatchableException()) {
+ // We have nothing to resolve/reject the promise with.
+ return rv.StealNSResult();
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ { // Scope for JSAutoCompartment
+ // 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. 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);
+ }
+
+ 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());
+ 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;
+
+ // Try to get some information about the callback to report a sane error,
+ // but don't try too hard (only deals with scripted functions).
+ JS::Rooted<JSObject*> unwrapped(aCx,
+ js::CheckedUnwrap(mCallback->Callback()));
+
+ if (unwrapped) {
+ JSAutoCompartment ac(aCx, unwrapped);
+ if (JS_ObjectIsFunction(aCx, unwrapped)) {
+ JS::Rooted<JS::Value> asValue(aCx, JS::ObjectValue(*unwrapped));
+ JS::Rooted<JSFunction*> func(aCx, JS_ValueToFunction(aCx, asValue));
+
+ MOZ_ASSERT(func);
+ JSScript* script = JS_GetFunctionScript(aCx, func);
+ if (script) {
+ fileName = JS_GetScriptFilename(script);
+ lineNumber = JS_GetScriptBaseLineNumber(aCx, script);
+ }
+ }
+ }
+
+ // We're back in aValue's compartment here.
+ JS::Rooted<JSString*> fn(aCx, JS_NewStringCopyZ(aCx, fileName));
+ if (!fn) {
+ // Out of memory. Promise will stay unresolved.
+ JS_ClearPendingException(aCx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ JS::Rooted<JSString*> message(aCx,
+ JS_NewStringCopyZ(aCx,
+ "then() cannot return same Promise that it resolves."));
+ if (!message) {
+ // Out of memory. Promise will stay unresolved.
+ JS_ClearPendingException(aCx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ JS::Rooted<JS::Value> typeError(aCx);
+ if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, fn, lineNumber, 0,
+ nullptr, message, &typeError)) {
+ // Out of memory. Promise will stay unresolved.
+ JS_ClearPendingException(aCx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ 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;
+ }
+ }
+
+ // Otherwise, run resolver's resolve with value.
+ if (!JS_WrapValue(aCx, &retValue)) {
+ NS_WARNING("Failed to wrap value into the right compartment.");
+ return NS_ERROR_FAILURE;
+ }
+
+ 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;
+}
+
+Promise*
+WrapperPromiseCallback::GetDependentPromise()
+{
+ // Per spec, various algorithms like all() and race() are actually implemented
+ // in terms of calling then() but passing it the resolve/reject functions that
+ // are passed as arguments to function passed to the Promise constructor.
+ // That will cause the promise in question to hold on to a
+ // WrapperPromiseCallback, but the dependent promise should really be the one
+ // whose constructor those functions came from, not the about-to-be-ignored
+ // return value of "then". So try to determine whether we're in that case and
+ // if so go ahead and dig the dependent promise out of the function we have.
+ JSObject* callable = mCallback->Callable();
+ // Unwrap it, in case it's a cross-compartment wrapper. Our caller here is
+ // system, so it's really ok to just go and unwrap.
+ callable = js::UncheckedUnwrap(callable);
+ if (JS_IsNativeFunction(callable, Promise::JSCallback)) {
+ JS::Value promiseVal =
+ js::GetFunctionNativeReserved(callable, Promise::SLOT_PROMISE);
+ Promise* promise;
+ UNWRAP_OBJECT(Promise, &promiseVal.toObject(), promise);
+ return promise;
+ }
+
+ if (mNextPromise) {
+ return mNextPromise;
+ }
+
+ Promise* promise;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(Promise, mNextPromiseObj, promise))) {
+ return promise;
+ }
+
+ // Oh, well.
+ return nullptr;
+}
+
+// NativePromiseCallback
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(NativePromiseCallback,
+ PromiseCallback, mHandler)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(NativePromiseCallback)
+NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)
+
+NS_IMPL_ADDREF_INHERITED(NativePromiseCallback, PromiseCallback)
+NS_IMPL_RELEASE_INHERITED(NativePromiseCallback, PromiseCallback)
+
+NativePromiseCallback::NativePromiseCallback(PromiseNativeHandler* aHandler,
+ Promise::PromiseState aState)
+ : mHandler(aHandler)
+ , mState(aState)
+{
+ MOZ_ASSERT(aHandler);
+}
+
+NativePromiseCallback::~NativePromiseCallback()
+{
+}
+
+nsresult
+NativePromiseCallback::Call(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ JS::ExposeValueToActiveJS(aValue);
+
+ if (mState == Promise::Resolved) {
+ mHandler->ResolvedCallback(aCx, aValue);
+ return NS_OK;
+ }
+
+ if (mState == Promise::Rejected) {
+ mHandler->RejectedCallback(aCx, aValue);
+ return NS_OK;
+ }
+
+ NS_NOTREACHED("huh?");
+ return NS_ERROR_FAILURE;
+}
+
+/* static */ PromiseCallback*
+PromiseCallback::Factory(Promise* aNextPromise, JS::Handle<JSObject*> aGlobal,
+ AnyCallback* aCallback, Task aTask)
+{
+ 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(aNextPromise, aGlobal, aCallback);
+ }
+
+ if (aTask == Resolve) {
+ return new ResolvePromiseCallback(aNextPromise, aGlobal);
+ }
+
+ if (aTask == Reject) {
+ return new RejectPromiseCallback(aNextPromise, aGlobal);
+ }
+
+ MOZ_ASSERT(false, "This should not happen");
+ return nullptr;
+}
+
+#endif // SPIDERMONKEY_PROMISE
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/promise/PromiseCallback.h b/dom/promise/PromiseCallback.h
new file mode 100644
index 0000000000..9f55e03d01
--- /dev/null
+++ b/dom/promise/PromiseCallback.h
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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_PromiseCallback_h
+#define mozilla_dom_PromiseCallback_h
+
+#include "mozilla/dom/Promise.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+namespace dom {
+
+#ifndef SPIDERMONKEY_PROMISE
+// This is the base class for any PromiseCallback.
+// It's a logical step in the promise chain of callbacks.
+class PromiseCallback : public nsISupports
+{
+protected:
+ virtual ~PromiseCallback();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(PromiseCallback)
+
+ PromiseCallback();
+
+ virtual nsresult Call(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) = 0;
+
+ // Return the Promise that this callback will end up resolving or
+ // rejecting, if any.
+ virtual Promise* GetDependentPromise() = 0;
+
+ enum Task {
+ Resolve,
+ Reject
+ };
+
+ // This factory returns a PromiseCallback object with refcount of 0.
+ static PromiseCallback*
+ Factory(Promise* aNextPromise, JS::Handle<JSObject*> aObject,
+ AnyCallback* aCallback, Task aTask);
+};
+
+// WrapperPromiseCallback execs a JS Callback with a value, and then the return
+// 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:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WrapperPromiseCallback,
+ PromiseCallback)
+
+ nsresult Call(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) override;
+
+ 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;
+};
+
+// ResolvePromiseCallback calls aPromise->ResolveFunction() with the value
+// received by Call().
+class ResolvePromiseCallback final : public PromiseCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ResolvePromiseCallback,
+ PromiseCallback)
+
+ nsresult Call(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) override;
+
+ Promise* GetDependentPromise() override
+ {
+ return mPromise;
+ }
+
+ ResolvePromiseCallback(Promise* aPromise, JS::Handle<JSObject*> aGlobal);
+
+private:
+ ~ResolvePromiseCallback();
+
+ RefPtr<Promise> mPromise;
+ JS::Heap<JSObject*> mGlobal;
+};
+
+// RejectPromiseCallback calls aPromise->RejectFunction() with the value
+// received by Call().
+class RejectPromiseCallback final : public PromiseCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(RejectPromiseCallback,
+ PromiseCallback)
+
+ nsresult Call(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) override;
+
+ Promise* GetDependentPromise() override
+ {
+ return mPromise;
+ }
+
+ RejectPromiseCallback(Promise* aPromise, JS::Handle<JSObject*> aGlobal);
+
+private:
+ ~RejectPromiseCallback();
+
+ RefPtr<Promise> mPromise;
+ 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
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NativePromiseCallback,
+ PromiseCallback)
+
+ nsresult Call(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) override;
+
+ Promise* GetDependentPromise() override
+ {
+ return nullptr;
+ }
+
+ NativePromiseCallback(PromiseNativeHandler* aHandler,
+ Promise::PromiseState aState);
+
+private:
+ ~NativePromiseCallback();
+
+ RefPtr<PromiseNativeHandler> mHandler;
+ Promise::PromiseState mState;
+};
+
+#endif // SPIDERMONKEY_PROMISE
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PromiseCallback_h
diff --git a/dom/promise/PromiseDebugging.cpp b/dom/promise/PromiseDebugging.cpp
new file mode 100644
index 0000000000..fc0942ee41
--- /dev/null
+++ b/dom/promise/PromiseDebugging.cpp
@@ -0,0 +1,521 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "js/Value.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/PromiseDebugging.h"
+#include "mozilla/dom/PromiseDebuggingBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class FlushRejections: public CancelableRunnable
+{
+public:
+ static void Init() {
+ if (!sDispatched.init()) {
+ MOZ_CRASH("Could not initialize FlushRejections::sDispatched");
+ }
+ sDispatched.set(false);
+ }
+
+ static void DispatchNeeded() {
+ if (sDispatched.get()) {
+ // An instance of `FlushRejections` has already been dispatched
+ // and not run yet. No need to dispatch another one.
+ return;
+ }
+ sDispatched.set(true);
+ NS_DispatchToCurrentThread(new FlushRejections());
+ }
+
+ static void FlushSync() {
+ sDispatched.set(false);
+
+ // Call the callbacks if necessary.
+ // Note that these callbacks may in turn cause Promise to turn
+ // uncaught or consumed. Since `sDispatched` is `false`,
+ // `FlushRejections` will be called once again, on an ulterior
+ // tick.
+ PromiseDebugging::FlushUncaughtRejectionsInternal();
+ }
+
+ NS_IMETHOD Run() override {
+ FlushSync();
+ return NS_OK;
+ }
+
+private:
+ // `true` if an instance of `FlushRejections` is currently dispatched
+ // and has not been executed yet.
+ static MOZ_THREAD_LOCAL(bool) sDispatched;
+};
+
+/* static */ MOZ_THREAD_LOCAL(bool)
+FlushRejections::sDispatched;
+
+#ifndef SPIDERMONKEY_PROMISE
+static Promise*
+UnwrapPromise(JS::Handle<JSObject*> aPromise, ErrorResult& aRv)
+{
+ Promise* promise;
+ if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Promise, aPromise, promise)))) {
+ aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING("Argument"));
+ return nullptr;
+ }
+ return promise;
+}
+#endif // SPIDERMONKEY_PROMISE
+
+#ifdef SPIDERMONKEY_PROMISE
+/* static */ void
+PromiseDebugging::GetState(GlobalObject& aGlobal, JS::Handle<JSObject*> aPromise,
+ PromiseDebuggingStateHolder& aState,
+ ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(aPromise));
+ if (!obj || !JS::IsPromiseObject(obj)) {
+ aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING(
+ "Argument of PromiseDebugging.getState"));
+ return;
+ }
+ switch (JS::GetPromiseState(obj)) {
+ case JS::PromiseState::Pending:
+ aState.mState = PromiseDebuggingState::Pending;
+ break;
+ case JS::PromiseState::Fulfilled:
+ aState.mState = PromiseDebuggingState::Fulfilled;
+ aState.mValue = JS::GetPromiseResult(obj);
+ break;
+ case JS::PromiseState::Rejected:
+ aState.mState = PromiseDebuggingState::Rejected;
+ aState.mReason = JS::GetPromiseResult(obj);
+ break;
+ }
+}
+
+/* static */ void
+PromiseDebugging::GetPromiseID(GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aPromise,
+ nsString& aID,
+ ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(aPromise));
+ if (!obj || !JS::IsPromiseObject(obj)) {
+ aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING(
+ "Argument of PromiseDebugging.getState"));
+ return;
+ }
+ uint64_t promiseID = JS::GetPromiseID(obj);
+ aID = sIDPrefix;
+ aID.AppendInt(promiseID);
+}
+
+/* static */ void
+PromiseDebugging::GetAllocationStack(GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aPromise,
+ JS::MutableHandle<JSObject*> aStack,
+ ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(aPromise));
+ if (!obj || !JS::IsPromiseObject(obj)) {
+ aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING(
+ "Argument of PromiseDebugging.getAllocationStack"));
+ return;
+ }
+ aStack.set(JS::GetPromiseAllocationSite(obj));
+}
+
+/* static */ void
+PromiseDebugging::GetRejectionStack(GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aPromise,
+ JS::MutableHandle<JSObject*> aStack,
+ ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(aPromise));
+ if (!obj || !JS::IsPromiseObject(obj)) {
+ aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING(
+ "Argument of PromiseDebugging.getRejectionStack"));
+ return;
+ }
+ aStack.set(JS::GetPromiseResolutionSite(obj));
+}
+
+/* static */ void
+PromiseDebugging::GetFullfillmentStack(GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aPromise,
+ JS::MutableHandle<JSObject*> aStack,
+ ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(aPromise));
+ if (!obj || !JS::IsPromiseObject(obj)) {
+ aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING(
+ "Argument of PromiseDebugging.getFulfillmentStack"));
+ return;
+ }
+ aStack.set(JS::GetPromiseResolutionSite(obj));
+}
+
+#else
+
+/* static */ void
+PromiseDebugging::GetState(GlobalObject&, JS::Handle<JSObject*> aPromise,
+ PromiseDebuggingStateHolder& aState,
+ ErrorResult& aRv)
+{
+ Promise* promise = UnwrapPromise(aPromise, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ switch (promise->mState) {
+ case Promise::Pending:
+ aState.mState = PromiseDebuggingState::Pending;
+ break;
+ case Promise::Resolved:
+ aState.mState = PromiseDebuggingState::Fulfilled;
+ aState.mValue = promise->mResult;
+ break;
+ case Promise::Rejected:
+ aState.mState = PromiseDebuggingState::Rejected;
+ aState.mReason = promise->mResult;
+ break;
+ }
+}
+
+#endif // SPIDERMONKEY_PROMISE
+
+/*static */ nsString
+PromiseDebugging::sIDPrefix;
+
+/* static */ void
+PromiseDebugging::Init()
+{
+ FlushRejections::Init();
+
+ // Generate a prefix for identifiers: "PromiseDebugging.$processid."
+ sIDPrefix = NS_LITERAL_STRING("PromiseDebugging.");
+ if (XRE_IsContentProcess()) {
+ sIDPrefix.AppendInt(ContentChild::GetSingleton()->GetID());
+ sIDPrefix.Append('.');
+ } else {
+ sIDPrefix.AppendLiteral("0.");
+ }
+}
+
+/* static */ void
+PromiseDebugging::Shutdown()
+{
+ sIDPrefix.SetIsVoid(true);
+}
+
+/* static */ void
+PromiseDebugging::FlushUncaughtRejections()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ FlushRejections::FlushSync();
+}
+
+#ifndef SPIDERMONKEY_PROMISE
+
+/* static */ void
+PromiseDebugging::GetAllocationStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
+ JS::MutableHandle<JSObject*> aStack,
+ ErrorResult& aRv)
+{
+ Promise* promise = UnwrapPromise(aPromise, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ aStack.set(promise->mAllocationStack);
+}
+
+/* static */ void
+PromiseDebugging::GetRejectionStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
+ JS::MutableHandle<JSObject*> aStack,
+ ErrorResult& aRv)
+{
+ Promise* promise = UnwrapPromise(aPromise, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ aStack.set(promise->mRejectionStack);
+}
+
+/* static */ void
+PromiseDebugging::GetFullfillmentStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
+ JS::MutableHandle<JSObject*> aStack,
+ ErrorResult& aRv)
+{
+ Promise* promise = UnwrapPromise(aPromise, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ aStack.set(promise->mFullfillmentStack);
+}
+
+/* static */ void
+PromiseDebugging::GetDependentPromises(GlobalObject&, JS::Handle<JSObject*> aPromise,
+ nsTArray<RefPtr<Promise>>& aPromises,
+ ErrorResult& aRv)
+{
+ Promise* promise = UnwrapPromise(aPromise, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ promise->GetDependentPromises(aPromises);
+}
+
+/* static */ double
+PromiseDebugging::GetPromiseLifetime(GlobalObject&,
+ JS::Handle<JSObject*> aPromise,
+ ErrorResult& aRv)
+{
+ Promise* promise = UnwrapPromise(aPromise, aRv);
+ if (aRv.Failed()) {
+ return 0;
+ }
+ return (TimeStamp::Now() - promise->mCreationTimestamp).ToMilliseconds();
+}
+
+/* static */ double
+PromiseDebugging::GetTimeToSettle(GlobalObject&, JS::Handle<JSObject*> aPromise,
+ ErrorResult& aRv)
+{
+ Promise* promise = UnwrapPromise(aPromise, aRv);
+ if (aRv.Failed()) {
+ return 0;
+ }
+ if (promise->mState == Promise::Pending) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return 0;
+ }
+ return (promise->mSettlementTimestamp -
+ promise->mCreationTimestamp).ToMilliseconds();
+}
+
+#endif // SPIDERMONKEY_PROMISE
+
+/* static */ void
+PromiseDebugging::AddUncaughtRejectionObserver(GlobalObject&,
+ UncaughtRejectionObserver& aObserver)
+{
+ CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
+ nsTArray<nsCOMPtr<nsISupports>>& observers = storage->mUncaughtRejectionObservers;
+ observers.AppendElement(&aObserver);
+}
+
+/* static */ bool
+PromiseDebugging::RemoveUncaughtRejectionObserver(GlobalObject&,
+ UncaughtRejectionObserver& aObserver)
+{
+ CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
+ nsTArray<nsCOMPtr<nsISupports>>& observers = storage->mUncaughtRejectionObservers;
+ for (size_t i = 0; i < observers.Length(); ++i) {
+ UncaughtRejectionObserver* observer = static_cast<UncaughtRejectionObserver*>(observers[i].get());
+ if (*observer == aObserver) {
+ observers.RemoveElementAt(i);
+ return true;
+ }
+ }
+ return false;
+}
+
+#ifdef SPIDERMONKEY_PROMISE
+
+/* static */ void
+PromiseDebugging::AddUncaughtRejection(JS::HandleObject aPromise)
+{
+ // This might OOM, but won't set a pending exception, so we'll just ignore it.
+ if (CycleCollectedJSContext::Get()->mUncaughtRejections.append(aPromise)) {
+ FlushRejections::DispatchNeeded();
+ }
+}
+
+/* void */ void
+PromiseDebugging::AddConsumedRejection(JS::HandleObject aPromise)
+{
+ // If the promise is in our list of uncaught rejections, we haven't yet
+ // reported it as unhandled. In that case, just remove it from the list
+ // and don't add it to the list of consumed rejections.
+ auto& uncaughtRejections = CycleCollectedJSContext::Get()->mUncaughtRejections;
+ for (size_t i = 0; i < uncaughtRejections.length(); i++) {
+ if (uncaughtRejections[i] == aPromise) {
+ // To avoid large amounts of memmoves, we don't shrink the vector here.
+ // Instead, we filter out nullptrs when iterating over the vector later.
+ uncaughtRejections[i].set(nullptr);
+ return;
+ }
+ }
+ // This might OOM, but won't set a pending exception, so we'll just ignore it.
+ if (CycleCollectedJSContext::Get()->mConsumedRejections.append(aPromise)) {
+ FlushRejections::DispatchNeeded();
+ }
+}
+
+/* static */ void
+PromiseDebugging::FlushUncaughtRejectionsInternal()
+{
+ CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
+
+ auto& uncaught = storage->mUncaughtRejections;
+ auto& consumed = storage->mConsumedRejections;
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ // Notify observers of uncaught Promise.
+ auto& observers = storage->mUncaughtRejectionObservers;
+
+ for (size_t i = 0; i < uncaught.length(); i++) {
+ JS::RootedObject promise(cx, uncaught[i]);
+ // Filter out nullptrs which might've been added by
+ // PromiseDebugging::AddConsumedRejection.
+ if (!promise) {
+ continue;
+ }
+
+ for (size_t j = 0; j < observers.Length(); ++j) {
+ RefPtr<UncaughtRejectionObserver> obs =
+ static_cast<UncaughtRejectionObserver*>(observers[j].get());
+
+ IgnoredErrorResult err;
+ obs->OnLeftUncaught(promise, err);
+ }
+ JSAutoCompartment ac(cx, promise);
+ Promise::ReportRejectedPromise(cx, promise);
+ }
+ storage->mUncaughtRejections.clear();
+
+ // Notify observers of consumed Promise.
+
+ for (size_t i = 0; i < consumed.length(); i++) {
+ JS::RootedObject promise(cx, consumed[i]);
+
+ for (size_t j = 0; j < observers.Length(); ++j) {
+ RefPtr<UncaughtRejectionObserver> obs =
+ static_cast<UncaughtRejectionObserver*>(observers[j].get());
+
+ IgnoredErrorResult err;
+ obs->OnConsumed(promise, err);
+ }
+ }
+ storage->mConsumedRejections.clear();
+}
+
+#else
+
+/* static */ void
+PromiseDebugging::AddUncaughtRejection(Promise& aPromise)
+{
+ CycleCollectedJSContext::Get()->mUncaughtRejections.AppendElement(&aPromise);
+ FlushRejections::DispatchNeeded();
+}
+
+/* void */ void
+PromiseDebugging::AddConsumedRejection(Promise& aPromise)
+{
+ CycleCollectedJSContext::Get()->mConsumedRejections.AppendElement(&aPromise);
+ FlushRejections::DispatchNeeded();
+}
+
+/* static */ void
+PromiseDebugging::GetPromiseID(GlobalObject&,
+ JS::Handle<JSObject*> aPromise,
+ nsString& aID,
+ ErrorResult& aRv)
+{
+ Promise* promise = UnwrapPromise(aPromise, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ uint64_t promiseID = promise->GetID();
+ aID = sIDPrefix;
+ aID.AppendInt(promiseID);
+}
+
+/* static */ void
+PromiseDebugging::FlushUncaughtRejectionsInternal()
+{
+ CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
+
+ // The Promise that have been left uncaught (rejected and last in
+ // their chain) since the last call to this function.
+ nsTArray<nsCOMPtr<nsISupports>> uncaught;
+ storage->mUncaughtRejections.SwapElements(uncaught);
+
+ // The Promise that have been left uncaught at some point, but that
+ // have eventually had their `then` method called.
+ nsTArray<nsCOMPtr<nsISupports>> consumed;
+ storage->mConsumedRejections.SwapElements(consumed);
+
+ nsTArray<nsCOMPtr<nsISupports>>& observers = storage->mUncaughtRejectionObservers;
+
+ nsresult rv;
+ // Notify observers of uncaught Promise.
+
+ for (size_t i = 0; i < uncaught.Length(); ++i) {
+ nsCOMPtr<Promise> promise = do_QueryInterface(uncaught[i], &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!promise->IsLastInChain()) {
+ // This promise is not the last in the chain anymore,
+ // so the error has been caught at some point.
+ continue;
+ }
+
+ // For the moment, the Promise is still at the end of the
+ // chain. Let's inform observers, so that they may decide whether
+ // to report it.
+ for (size_t j = 0; j < observers.Length(); ++j) {
+ ErrorResult err;
+ RefPtr<UncaughtRejectionObserver> obs =
+ static_cast<UncaughtRejectionObserver*>(observers[j].get());
+
+ obs->OnLeftUncaught(*promise, err); // Ignore errors
+ }
+
+ promise->SetNotifiedAsUncaught();
+ }
+
+ // Notify observers of consumed Promise.
+
+ for (size_t i = 0; i < consumed.Length(); ++i) {
+ nsCOMPtr<Promise> promise = do_QueryInterface(consumed[i], &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!promise->WasNotifiedAsUncaught()) {
+ continue;
+ }
+
+ MOZ_ASSERT(!promise->IsLastInChain());
+ for (size_t j = 0; j < observers.Length(); ++j) {
+ ErrorResult err;
+ RefPtr<UncaughtRejectionObserver> obs =
+ static_cast<UncaughtRejectionObserver*>(observers[j].get());
+
+ obs->OnConsumed(*promise, err); // Ignore errors
+ }
+ }
+}
+#endif // SPIDERMONKEY_PROMISE
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/promise/PromiseDebugging.h b/dom/promise/PromiseDebugging.h
new file mode 100644
index 0000000000..218a64c2eb
--- /dev/null
+++ b/dom/promise/PromiseDebugging.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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_PromiseDebugging_h
+#define mozilla_dom_PromiseDebugging_h
+
+#include "js/TypeDecls.h"
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+namespace workers {
+class WorkerPrivate;
+} // namespace workers
+
+class Promise;
+struct PromiseDebuggingStateHolder;
+class GlobalObject;
+class UncaughtRejectionObserver;
+class FlushRejections;
+
+void TriggerFlushRejections();
+
+class PromiseDebugging
+{
+public:
+ static void Init();
+ static void Shutdown();
+
+ static void GetState(GlobalObject&, JS::Handle<JSObject*> aPromise,
+ PromiseDebuggingStateHolder& aState,
+ ErrorResult& aRv);
+
+ static void GetPromiseID(GlobalObject&, JS::Handle<JSObject*>, nsString&,
+ ErrorResult&);
+
+ static void GetAllocationStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
+ JS::MutableHandle<JSObject*> aStack,
+ ErrorResult& aRv);
+ static void GetRejectionStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
+ JS::MutableHandle<JSObject*> aStack,
+ ErrorResult& aRv);
+ static void GetFullfillmentStack(GlobalObject&,
+ JS::Handle<JSObject*> aPromise,
+ JS::MutableHandle<JSObject*> aStack,
+ ErrorResult& aRv);
+
+#ifndef SPIDERMONKEY_PROMISE
+ static void GetDependentPromises(GlobalObject&,
+ JS::Handle<JSObject*> aPromise,
+ nsTArray<RefPtr<Promise>>& aPromises,
+ ErrorResult& aRv);
+ static double GetPromiseLifetime(GlobalObject&,
+ JS::Handle<JSObject*> aPromise,
+ ErrorResult& aRv);
+ static double GetTimeToSettle(GlobalObject&, JS::Handle<JSObject*> aPromise,
+ ErrorResult& aRv);
+#endif // SPIDERMONKEY_PROMISE
+
+ // Mechanism for watching uncaught instances of Promise.
+ static void AddUncaughtRejectionObserver(GlobalObject&,
+ UncaughtRejectionObserver& aObserver);
+ static bool RemoveUncaughtRejectionObserver(GlobalObject&,
+ UncaughtRejectionObserver& aObserver);
+
+#ifdef SPIDERMONKEY_PROMISE
+ // Mark a Promise as having been left uncaught at script completion.
+ static void AddUncaughtRejection(JS::HandleObject);
+ // Mark a Promise previously added with `AddUncaughtRejection` as
+ // eventually consumed.
+ static void AddConsumedRejection(JS::HandleObject);
+#else
+ // Mark a Promise as having been left uncaught at script completion.
+ static void AddUncaughtRejection(Promise&);
+ // Mark a Promise previously added with `AddUncaughtRejection` as
+ // eventually consumed.
+ static void AddConsumedRejection(Promise&);
+#endif // SPIDERMONKEY_PROMISE
+ // Propagate the informations from AddUncaughtRejection
+ // and AddConsumedRejection to observers.
+ static void FlushUncaughtRejections();
+
+protected:
+ static void FlushUncaughtRejectionsInternal();
+ friend class FlushRejections;
+ friend class mozilla::dom::workers::WorkerPrivate;
+private:
+ // Identity of the process.
+ // This property is:
+ // - set during initialization of the layout module,
+ // prior to any Worker using it;
+ // - read by both the main thread and the Workers;
+ // - unset during shutdown of the layout module,
+ // after any Worker has been shutdown.
+ static nsString sIDPrefix;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PromiseDebugging_h
diff --git a/dom/promise/PromiseNativeHandler.h b/dom/promise/PromiseNativeHandler.h
new file mode 100644
index 0000000000..6ba7142aa1
--- /dev/null
+++ b/dom/promise/PromiseNativeHandler.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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_PromiseNativeHandler_h
+#define mozilla_dom_PromiseNativeHandler_h
+
+#include "nsISupports.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * PromiseNativeHandler allows C++ to react to a Promise being rejected/resolved.
+ * A PromiseNativeHandler can be appended to a Promise using
+ * Promise::AppendNativeHandler().
+ */
+class PromiseNativeHandler : public nsISupports
+{
+protected:
+ virtual ~PromiseNativeHandler()
+ { }
+
+public:
+ virtual void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) = 0;
+
+ virtual void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) = 0;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PromiseNativeHandler_h
diff --git a/dom/promise/PromiseWorkerProxy.h b/dom/promise/PromiseWorkerProxy.h
new file mode 100644
index 0000000000..bcb44d38f3
--- /dev/null
+++ b/dom/promise/PromiseWorkerProxy.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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_PromiseWorkerProxy_h
+#define mozilla_dom_PromiseWorkerProxy_h
+
+// Required for Promise::PromiseTaskSync.
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/workers/bindings/WorkerHolder.h"
+#include "nsProxyRelease.h"
+
+#include "WorkerRunnable.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+
+namespace workers {
+class WorkerPrivate;
+} // namespace workers
+
+// A proxy to (eventually) mirror a resolved/rejected Promise's result from the
+// main thread to a Promise on the worker thread.
+//
+// How to use:
+//
+// 1. Create a Promise on the worker thread and return it to the content
+// script:
+//
+// RefPtr<Promise> promise = Promise::Create(workerPrivate->GlobalScope(), aRv);
+// if (aRv.Failed()) {
+// return nullptr;
+// }
+//
+// 2. Create a PromiseWorkerProxy wrapping the Promise. If this fails, the
+// worker is shutting down and you should fail the original call. This is
+// only likely to happen in (Gecko-specific) worker onclose handlers.
+//
+// RefPtr<PromiseWorkerProxy> proxy =
+// PromiseWorkerProxy::Create(workerPrivate, promise);
+// if (!proxy) {
+// // You may also reject the Promise with an AbortError or similar.
+// return nullptr;
+// }
+//
+// 3. Dispatch a runnable to the main thread, with a reference to the proxy to
+// perform the main thread operation. PromiseWorkerProxy is thread-safe
+// refcounted.
+//
+// 4. Return the worker thread promise to the JS caller:
+//
+// return promise.forget();
+//
+// 5. In your main thread runnable Run(), obtain a Promise on
+// the main thread and call its AppendNativeHandler(PromiseNativeHandler*)
+// to bind the PromiseWorkerProxy created at #2.
+//
+// 4. Then the Promise results returned by ResolvedCallback/RejectedCallback
+// will be dispatched as a WorkerRunnable to the worker thread to
+// resolve/reject the Promise created at #1.
+//
+// PromiseWorkerProxy can also be used in situations where there is no main
+// thread Promise, or where special handling is required on the worker thread
+// for promise resolution. Create a PromiseWorkerProxy as in steps 1 to 3
+// above. When the main thread is ready to resolve the worker thread promise:
+//
+// 1. Acquire the mutex before attempting to access the worker private.
+//
+// AssertIsOnMainThread();
+// MutexAutoLock lock(proxy->Lock());
+// if (proxy->CleanedUp()) {
+// // Worker has already shut down, can't access worker private.
+// return;
+// }
+//
+// 2. Dispatch a runnable to the worker. Use GetWorkerPrivate() to acquire the
+// worker.
+//
+// RefPtr<FinishTaskWorkerRunnable> runnable =
+// new FinishTaskWorkerRunnable(proxy->GetWorkerPrivate(), proxy, result);
+// if (!r->Dispatch()) {
+// // Worker is alive but not Running any more, so the Promise can't
+// // be resolved, give up. The proxy will get Release()d at some
+// // point.
+//
+// // Usually do nothing, but you may want to log the fact.
+// }
+//
+// 3. In the WorkerRunnable's WorkerRun() use WorkerPromise() to access the
+// Promise and resolve/reject it. Then call CleanUp().
+//
+// bool
+// WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+// {
+// aWorkerPrivate->AssertIsOnWorkerThread();
+// RefPtr<Promise> promise = mProxy->WorkerPromise();
+// promise->MaybeResolve(mResult);
+// mProxy->CleanUp();
+// }
+//
+// Note: If a PromiseWorkerProxy is not cleaned up by a WorkerRunnable - this
+// can happen if the main thread Promise is never fulfilled - it will
+// stay alive till the worker reaches a Canceling state, even if all external
+// references to it are dropped.
+
+class PromiseWorkerProxy : public PromiseNativeHandler
+ , public StructuredCloneHolderBase
+{
+ friend class PromiseWorkerProxyRunnable;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+public:
+ typedef JSObject* (*ReadCallbackOp)(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ const PromiseWorkerProxy* aProxy,
+ uint32_t aTag,
+ uint32_t aData);
+ typedef bool (*WriteCallbackOp)(JSContext* aCx,
+ JSStructuredCloneWriter* aWorker,
+ PromiseWorkerProxy* aProxy,
+ JS::HandleObject aObj);
+
+ struct PromiseWorkerProxyStructuredCloneCallbacks
+ {
+ ReadCallbackOp Read;
+ WriteCallbackOp Write;
+ };
+
+ static already_AddRefed<PromiseWorkerProxy>
+ Create(workers::WorkerPrivate* aWorkerPrivate,
+ Promise* aWorkerPromise,
+ const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks = nullptr);
+
+ // Main thread callers must hold Lock() and check CleanUp() before calling this.
+ // Worker thread callers, this will assert that the proxy has not been cleaned
+ // up.
+ workers::WorkerPrivate* GetWorkerPrivate() const;
+
+ // This should only be used within WorkerRunnable::WorkerRun() running on the
+ // worker thread! Do not call this after calling CleanUp().
+ Promise* WorkerPromise() const;
+
+ // Worker thread only. Calling this invalidates several assumptions, so be
+ // sure this is the last thing you do.
+ // 1. WorkerPrivate() will no longer return a valid worker.
+ // 2. WorkerPromise() will crash!
+ void CleanUp();
+
+ Mutex& Lock()
+ {
+ return mCleanUpLock;
+ }
+
+ bool CleanedUp() const
+ {
+ mCleanUpLock.AssertCurrentThreadOwns();
+ return mCleanedUp;
+ }
+
+ // StructuredCloneHolderBase
+
+ JSObject* CustomReadHandler(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag,
+ uint32_t aIndex) override;
+
+ bool CustomWriteHandler(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj) override;
+
+protected:
+ virtual void ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) override;
+
+ virtual void RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) override;
+
+private:
+ PromiseWorkerProxy(workers::WorkerPrivate* aWorkerPrivate,
+ Promise* aWorkerPromise,
+ const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks = nullptr);
+
+ virtual ~PromiseWorkerProxy();
+
+ bool AddRefObject();
+
+ // If not called from Create(), be sure to hold Lock().
+ void CleanProperties();
+
+ // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
+ typedef void (Promise::*RunCallbackFunc)(JSContext*,
+ JS::Handle<JS::Value>);
+
+ void RunCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ RunCallbackFunc aFunc);
+
+ // Any thread with appropriate checks.
+ workers::WorkerPrivate* mWorkerPrivate;
+
+ // Worker thread only.
+ RefPtr<Promise> mWorkerPromise;
+
+ // Modified on the worker thread.
+ // It is ok to *read* this without a lock on the worker.
+ // Main thread must always acquire a lock.
+ bool mCleanedUp; // To specify if the cleanUp() has been done.
+
+ const PromiseWorkerProxyStructuredCloneCallbacks* mCallbacks;
+
+ // Ensure the worker and the main thread won't race to access |mCleanedUp|.
+ Mutex mCleanUpLock;
+
+ UniquePtr<workers::WorkerHolder> mWorkerHolder;
+};
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PromiseWorkerProxy_h
diff --git a/dom/promise/moz.build b/dom/promise/moz.build
new file mode 100644
index 0000000000..11d2a74962
--- /dev/null
+++ b/dom/promise/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ 'Promise.h',
+ 'PromiseDebugging.h',
+ 'PromiseNativeHandler.h',
+ 'PromiseWorkerProxy.h',
+]
+
+UNIFIED_SOURCES += [
+ 'Promise.cpp',
+ 'PromiseCallback.cpp',
+ 'PromiseDebugging.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '../base',
+ '../ipc',
+ '../workers',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+
+MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/promise/tests/chrome.ini b/dom/promise/tests/chrome.ini
new file mode 100644
index 0000000000..c6dc855c44
--- /dev/null
+++ b/dom/promise/tests/chrome.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+
+[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
diff --git a/dom/promise/tests/file_promise_and_timeout_ordering.js b/dom/promise/tests/file_promise_and_timeout_ordering.js
new file mode 100644
index 0000000000..c83b5a6ac0
--- /dev/null
+++ b/dom/promise/tests/file_promise_and_timeout_ordering.js
@@ -0,0 +1,18 @@
+var log = [];
+var resolvedPromise = Promise.resolve(null);
+function schedulePromiseTask(f) {
+ resolvedPromise.then(f);
+}
+
+setTimeout(function() {
+ log.push('t1start');
+ schedulePromiseTask(function() {
+ log.push('promise');
+ });
+ log.push('t1end');
+}, 10);
+
+setTimeout(function() {
+ log.push('t2');
+ postMessage(log.join(', '));
+}, 10);
diff --git a/dom/promise/tests/file_promise_xrays.html b/dom/promise/tests/file_promise_xrays.html
new file mode 100644
index 0000000000..73f9bf7d73
--- /dev/null
+++ b/dom/promise/tests/file_promise_xrays.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <script>
+ function vendGetter(name) {
+ return function() { throw "Getting " + String(name) };
+ }
+ function vendSetter(name) {
+ return function() { throw "Setting " + String(name) };
+ }
+ var setupThrew = false;
+ try {
+ // Neuter everything we can think of on Promise.
+ for (var obj of [Promise, Promise.prototype]) {
+ propNames = Object.getOwnPropertyNames(obj);
+ propNames = propNames.concat(Object.getOwnPropertySymbols(obj));
+ for (var propName of propNames) {
+ if ((propName == "prototype" ||
+ propName == Symbol.hasInstance) &&
+ obj == Promise) {
+ // They're not configurable.
+ continue;
+ }
+ Object.defineProperty(obj, propName,
+ { get: vendGetter(propName), set: vendSetter(propName) });
+ }
+ }
+ } catch (e) {
+ // Something went wrong. Save that info so the test can check for it.
+ setupThrew = e;
+ }
+ </script>
+</html>
diff --git a/dom/promise/tests/mochitest.ini b/dom/promise/tests/mochitest.ini
new file mode 100644
index 0000000000..0e6466d160
--- /dev/null
+++ b/dom/promise/tests/mochitest.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+support-files =
+ promise_uncatchable_exception.js
+
+[test_bug883683.html]
+[test_promise.html]
+[test_promise_uncatchable_exception.html]
+skip-if = debug == false
+[test_promise_utils.html]
+[test_resolve.html]
+[test_resolver_return_value.html]
+[test_thenable_vs_promise_ordering.html]
+[test_promise_and_timeout_ordering.html]
+support-files = file_promise_and_timeout_ordering.js
+[test_promise_and_timeout_ordering_workers.html]
+support-files = file_promise_and_timeout_ordering.js
+[test_species_getter.html]
+[test_webassembly_compile.html]
diff --git a/dom/promise/tests/promise_uncatchable_exception.js b/dom/promise/tests/promise_uncatchable_exception.js
new file mode 100644
index 0000000000..d062e21af3
--- /dev/null
+++ b/dom/promise/tests/promise_uncatchable_exception.js
@@ -0,0 +1,9 @@
+postMessage("Done", "*");
+
+var p = new Promise(function(resolve, reject) {
+ TestFunctions.throwUncatchableException();
+ ok(false, "Shouldn't get here!");
+}).catch(function(exception) {
+ ok(false, "Shouldn't get here!");
+});
+ok(false, "Shouldn't get here!");
diff --git a/dom/promise/tests/test_bug883683.html b/dom/promise/tests/test_bug883683.html
new file mode 100644
index 0000000000..ab23ca10d4
--- /dev/null
+++ b/dom/promise/tests/test_bug883683.html
@@ -0,0 +1,41 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Promise - bug 883683</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+function runTest() {
+ [{}, {}, {}, {}, {}].reduce(Promise.reject.bind(Promise));
+ ok(true, "No leaks with reject?");
+
+ [{}, {}, {}, {}, {}].reduce(Promise.resolve.bind(Promise));
+ ok(true, "No leaks with resolve?");
+
+ [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { throw a; }); });
+ ok(true, "No leaks with exception?");
+
+ [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { }); });
+ ok(true, "No leaks with empty promise?");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+// -->
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_on_new_promise.html b/dom/promise/tests/test_on_new_promise.html
new file mode 100644
index 0000000000..634dd7dda8
--- /dev/null
+++ b/dom/promise/tests/test_on_new_promise.html
@@ -0,0 +1,45 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<!--
+Bug 1083210 - Sanity test for interaction between DOM promises and
+Debugger.prototype.onNewPromise.
+-->
+
+<html>
+<head>
+ <title>Test for interaction with SpiderMonkey's Debugger.prototype.onNewPromise</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ is(Object.prototype.toString.call(new Promise(function () {})),
+ "[object Promise]",
+ "We should have the native DOM promise implementation.");
+
+ var Cu = Components.utils;
+ Cu.import("resource://gre/modules/jsdebugger.jsm");
+ var dbgGlobal = new Cu.Sandbox(document.nodePrincipal);
+ addDebuggerToGlobal(dbgGlobal);
+ var dbg = new dbgGlobal.Debugger(this);
+
+ var wrappedPromise;
+ dbg.onNewPromise = function (wp) { wrappedPromise = wp; };
+
+ var promise = new Promise(function () {});
+ debugger;
+ ok(wrappedPromise);
+ is(wrappedPromise.unsafeDereference(), promise);
+ </script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/promise/tests/test_on_promise_settled.html b/dom/promise/tests/test_on_promise_settled.html
new file mode 100644
index 0000000000..4061d39975
--- /dev/null
+++ b/dom/promise/tests/test_on_promise_settled.html
@@ -0,0 +1,54 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<!--
+Bug 1084065 - Sanity test for interaction between DOM promises and
+Debugger.prototype.onPromiseResolved.
+-->
+
+<html>
+<head>
+ <title>Test for interaction with SpiderMonkey's Debugger.prototype.onNewPromise</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ is(Object.prototype.toString.call(new Promise(function () {})),
+ "[object Promise]",
+ "We should have the native DOM promise implementation.");
+
+ var Cu = Components.utils;
+ Cu.import("resource://gre/modules/jsdebugger.jsm");
+ var dbgGlobal = new Cu.Sandbox(document.nodePrincipal);
+ addDebuggerToGlobal(dbgGlobal);
+ var dbg = new dbgGlobal.Debugger(this);
+
+ var wrappedPromise;
+ dbg.onPromiseSettled = function (wp) { wrappedPromise = wp; };
+
+ var promise = Promise.resolve();
+ promise
+ .then(function () {
+ ok(wrappedPromise);
+ is(wrappedPromise.unsafeDereference(), promise);
+ dbg.onPromiseSettled = undefined;
+ })
+ .then(null, function (e) {
+ ok(false, "Got an unexpected error: " + e);
+ })
+ .then(SimpleTest.finish);
+ </script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/promise/tests/test_on_promise_settled_duplicates.html b/dom/promise/tests/test_on_promise_settled_duplicates.html
new file mode 100644
index 0000000000..5a1eddb1e5
--- /dev/null
+++ b/dom/promise/tests/test_on_promise_settled_duplicates.html
@@ -0,0 +1,59 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<!--
+Bug 1084065 - Test that Debugger.prototype.onPromiseResolved doesn't get dupes.
+-->
+
+<html>
+<head>
+ <title>Test for interaction with SpiderMonkey's Debugger.prototype.onNewPromise</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ is(Object.prototype.toString.call(new Promise(function () {})),
+ "[object Promise]",
+ "We should have the native DOM promise implementation.");
+
+ var Cu = Components.utils;
+ Cu.import("resource://gre/modules/jsdebugger.jsm");
+ var dbgGlobal = new Cu.Sandbox(document.nodePrincipal);
+ addDebuggerToGlobal(dbgGlobal);
+ var dbg = new dbgGlobal.Debugger(this);
+
+ var seen = new Set();
+ dbg.onPromiseSettled = function (wp) {
+ is(seen.has(wp), false);
+ seen.add(wp);
+ };
+
+ var promise = new Promise(function (fulfill, reject) {
+ fulfill(1);
+ fulfill(2);
+ fulfill(3);
+ });
+
+ promise
+ .then(function () {
+ dbg.onPromiseSettled = undefined;
+ })
+ .then(null, function (e) {
+ ok(false, "Got an unexpected error: " + e);
+ })
+ .then(SimpleTest.finish);
+ </script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/promise/tests/test_promise.html b/dom/promise/tests/test_promise.html
new file mode 100644
index 0000000000..af185efcda
--- /dev/null
+++ b/dom/promise/tests/test_promise.html
@@ -0,0 +1,831 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Basic Promise Test</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+function promiseResolve() {
+ ok(Promise, "Promise object should exist");
+
+ var promise = new Promise(function(resolve, reject) {
+ ok(resolve, "Promise.resolve exists");
+ ok(reject, "Promise.reject exists");
+
+ resolve(42);
+ }).then(function(what) {
+ ok(true, "Then - resolveCb has been called");
+ is(what, 42, "ResolveCb received 42");
+ runTest();
+ }, function() {
+ ok(false, "Then - rejectCb has been called");
+ runTest();
+ });
+}
+
+function promiseResolveNoArg() {
+ var promise = new Promise(function(resolve, reject) {
+ ok(resolve, "Promise.resolve exists");
+ ok(reject, "Promise.reject exists");
+
+ resolve();
+ }).then(function(what) {
+ ok(true, "Then - resolveCb has been called");
+ is(what, undefined, "ResolveCb received undefined");
+ runTest();
+ }, function() {
+ ok(false, "Then - rejectCb has been called");
+ runTest();
+ });
+}
+
+function promiseReject() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ }).then(function(what) {
+ ok(false, "Then - resolveCb has been called");
+ runTest();
+ }, function(what) {
+ ok(true, "Then - rejectCb has been called");
+ is(what, 42, "RejectCb received 42");
+ runTest();
+ });
+}
+
+function promiseRejectNoHandler() {
+ // This test only checks that the code that reports unhandled errors in the
+ // Promises implementation does not crash or leak.
+ var promise = new Promise(function(res, rej) {
+ noSuchMethod();
+ });
+ runTest();
+}
+
+function promiseRejectNoArg() {
+ var promise = new Promise(function(resolve, reject) {
+ reject();
+ }).then(function(what) {
+ ok(false, "Then - resolveCb has been called");
+ runTest();
+ }, function(what) {
+ ok(true, "Then - rejectCb has been called");
+ is(what, undefined, "RejectCb received undefined");
+ runTest();
+ });
+}
+
+function promiseException() {
+ var promise = new Promise(function(resolve, reject) {
+ throw 42;
+ }).then(function(what) {
+ ok(false, "Then - resolveCb has been called");
+ runTest();
+ }, function(what) {
+ ok(true, "Then - rejectCb has been called");
+ is(what, 42, "RejectCb received 42");
+ runTest();
+ });
+}
+
+function promiseGC() {
+ var resolve;
+ var promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ }).then(function(what) {
+ ok(true, "Then - promise is still alive");
+ runTest();
+ });
+
+ promise = null;
+
+ SpecialPowers.gc();
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+
+ resolve(42);
+}
+
+function promiseAsync_TimeoutResolveThen() {
+ var handlerExecuted = false;
+
+ setTimeout(function() {
+ ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ }, 0);
+
+ Promise.resolve().then(function() {
+ handlerExecuted = true;
+ });
+
+ ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_ResolveTimeoutThen() {
+ var handlerExecuted = false;
+
+ var promise = Promise.resolve();
+
+ setTimeout(function() {
+ ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ }, 0);
+
+ promise.then(function() {
+ handlerExecuted = true;
+ });
+
+ ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_ResolveThenTimeout() {
+ var handlerExecuted = false;
+
+ Promise.resolve().then(function() {
+ handlerExecuted = true;
+ });
+
+ setTimeout(function() {
+ ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ }, 0);
+
+ ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_SyncXHR()
+{
+ var handlerExecuted = false;
+
+ Promise.resolve().then(function() {
+ handlerExecuted = true;
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ });
+
+ ok(!handlerExecuted, "Handlers are not called until the next microtask.");
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "testXHR.txt", false);
+ xhr.send(null);
+
+ todo(!handlerExecuted, "Sync XHR should not trigger microtask execution.");
+}
+
+function promiseDoubleThen() {
+ var steps = 0;
+ var promise = new Promise(function(r1, r2) {
+ r1(42);
+ });
+
+ promise.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 42, "Value == 42");
+ steps++;
+ }, function(what) {
+ ok(false, "Then.reject has been called");
+ });
+
+ promise.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(steps, 1, "Then.resolve - step == 1");
+ is(what, 42, "Value == 42");
+ runTest();
+ }, function(what) {
+ ok(false, "Then.reject has been called");
+ });
+}
+
+function promiseThenException() {
+ var promise = new Promise(function(resolve, reject) {
+ resolve(42);
+ });
+
+ promise.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ throw "booh";
+ }).catch(function(e) {
+ ok(true, "window.onerror has been called!");
+ runTest();
+ });
+}
+
+function promiseThenCatchThen() {
+ var promise = new Promise(function(resolve, reject) {
+ resolve(42);
+ });
+
+ var promise2 = promise.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 42, "Value == 42");
+ return what + 1;
+ }, function(what) {
+ ok(false, "Then.reject has been called");
+ });
+
+ isnot(promise, promise2, "These 2 promise objs are different");
+
+ promise2.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 43, "Value == 43");
+ return what + 1;
+ }, function(what) {
+ ok(false, "Then.reject has been called");
+ }).catch(function() {
+ ok(false, "Catch has been called");
+ }).then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 44, "Value == 44");
+ runTest();
+ }, function(what) {
+ ok(false, "Then.reject has been called");
+ });
+}
+
+function promiseThenNoArg() {
+ var promise = new Promise(function(resolve, reject) {
+ resolve(42);
+ });
+
+ var clone = promise.then();
+ isnot(promise, clone, "These 2 promise objs are different");
+ promise.then(function(v) {
+ clone.then(function(cv) {
+ is(v, cv, "Both resolve to the same value");
+ runTest();
+ });
+ });
+}
+
+function promiseThenUndefinedResolveFunction() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ try {
+ promise.then(undefined, function(v) {
+ is(v, 42, "Promise rejected with 42");
+ runTest();
+ });
+ } catch (e) {
+ ok(false, "then should not throw on undefined resolve function");
+ }
+}
+
+function promiseThenNullResolveFunction() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ try {
+ promise.then(null, function(v) {
+ is(v, 42, "Promise rejected with 42");
+ runTest();
+ });
+ } catch (e) {
+ ok(false, "then should not throw on null resolve function");
+ }
+}
+
+function promiseRejectThenCatchThen() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ var promise2 = promise.then(function(what) {
+ ok(false, "Then.resolve has been called");
+ }, function(what) {
+ ok(true, "Then.reject has been called");
+ is(what, 42, "Value == 42");
+ return what + 1;
+ });
+
+ isnot(promise, promise2, "These 2 promise objs are different");
+
+ promise2.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 43, "Value == 43");
+ return what+1;
+ }).catch(function(what) {
+ ok(false, "Catch has been called");
+ }).then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 44, "Value == 44");
+ runTest();
+ });
+}
+
+function promiseRejectThenCatchThen2() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ promise.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 42, "Value == 42");
+ return what+1;
+ }).catch(function(what) {
+ is(what, 42, "Value == 42");
+ ok(true, "Catch has been called");
+ return what+1;
+ }).then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 43, "Value == 43");
+ runTest();
+ });
+}
+
+function promiseRejectThenCatchExceptionThen() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ promise.then(function(what) {
+ ok(false, "Then.resolve has been called");
+ }, function(what) {
+ ok(true, "Then.reject has been called");
+ is(what, 42, "Value == 42");
+ throw(what + 1);
+ }).catch(function(what) {
+ ok(true, "Catch has been called");
+ is(what, 43, "Value == 43");
+ return what + 1;
+ }).then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 44, "Value == 44");
+ runTest();
+ });
+}
+
+function promiseThenCatchOrderingResolve() {
+ var global = 0;
+ var f = new Promise(function(r1, r2) {
+ r1(42);
+ });
+
+ f.then(function() {
+ f.then(function() {
+ global++;
+ });
+ f.catch(function() {
+ global++;
+ });
+ f.then(function() {
+ global++;
+ });
+ setTimeout(function() {
+ is(global, 2, "Many steps... should return 2");
+ runTest();
+ }, 0);
+ });
+}
+
+function promiseThenCatchOrderingReject() {
+ var global = 0;
+ var f = new Promise(function(r1, r2) {
+ r2(42);
+ })
+
+ f.then(function() {}, function() {
+ f.then(function() {
+ global++;
+ });
+ f.catch(function() {
+ global++;
+ });
+ f.then(function() {}, function() {
+ global++;
+ });
+ setTimeout(function() {
+ is(global, 2, "Many steps... should return 2");
+ runTest();
+ }, 0);
+ });
+}
+
+function promiseCatchNoArg() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ var clone = promise.catch();
+ isnot(promise, clone, "These 2 promise objs are different");
+ promise.catch(function(v) {
+ clone.catch(function(cv) {
+ is(v, cv, "Both reject to the same value");
+ runTest();
+ });
+ });
+}
+
+function promiseNestedPromise() {
+ new Promise(function(resolve, reject) {
+ resolve(new Promise(function(resolve, reject) {
+ ok(true, "Nested promise is executed");
+ resolve(42);
+ }));
+ }).then(function(value) {
+ is(value, 42, "Nested promise is executed and then == 42");
+ runTest();
+ });
+}
+
+function promiseNestedNestedPromise() {
+ new Promise(function(resolve, reject) {
+ resolve(new Promise(function(resolve, reject) {
+ ok(true, "Nested promise is executed");
+ resolve(42);
+ }).then(function(what) { return what+1; }));
+ }).then(function(value) {
+ is(value, 43, "Nested promise is executed and then == 43");
+ runTest();
+ });
+}
+
+function promiseWrongNestedPromise() {
+ new Promise(function(resolve, reject) {
+ resolve(new Promise(function(r, r2) {
+ ok(true, "Nested promise is executed");
+ r(42);
+ }));
+ reject(42);
+ }).then(function(value) {
+ is(value, 42, "Nested promise is executed and then == 42");
+ runTest();
+ }, function(value) {
+ ok(false, "This is wrong");
+ });
+}
+
+function promiseLoop() {
+ new Promise(function(resolve, reject) {
+ resolve(new Promise(function(r1, r2) {
+ ok(true, "Nested promise is executed");
+ r1(new Promise(function(r1, r2) {
+ ok(true, "Nested nested promise is executed");
+ r1(42);
+ }));
+ }));
+ }).then(function(value) {
+ is(value, 42, "Nested nested promise is executed and then == 42");
+ runTest();
+ }, function(value) {
+ ok(false, "This is wrong");
+ });
+}
+
+function promiseStaticReject() {
+ var promise = Promise.reject(42).then(function(what) {
+ ok(false, "This should not be called");
+ }, function(what) {
+ is(what, 42, "Value == 42");
+ runTest();
+ });
+}
+
+function promiseStaticResolve() {
+ var promise = Promise.resolve(42).then(function(what) {
+ is(what, 42, "Value == 42");
+ runTest();
+ }, function() {
+ ok(false, "This should not be called");
+ });
+}
+
+function promiseResolveNestedPromise() {
+ var promise = Promise.resolve(new Promise(function(r, r2) {
+ ok(true, "Nested promise is executed");
+ r(42);
+ }, function() {
+ ok(false, "This should not be called");
+ })).then(function(what) {
+ is(what, 42, "Value == 42");
+ runTest();
+ }, function() {
+ ok(false, "This should not be called");
+ });
+}
+
+function promiseSimpleThenableResolve() {
+ var thenable = { then: function(resolve) { resolve(5); } };
+ var promise = new Promise(function(resolve, reject) {
+ resolve(thenable);
+ });
+
+ promise.then(function(v) {
+ ok(v === 5, "promiseSimpleThenableResolve");
+ runTest();
+ }, function(e) {
+ ok(false, "promiseSimpleThenableResolve: Should not reject");
+ });
+}
+
+function promiseSimpleThenableReject() {
+ var thenable = { then: function(resolve, reject) { reject(5); } };
+ var promise = new Promise(function(resolve, reject) {
+ resolve(thenable);
+ });
+
+ promise.then(function() {
+ ok(false, "promiseSimpleThenableReject: Should not resolve");
+ runTest();
+ }, function(e) {
+ ok(e === 5, "promiseSimpleThenableReject");
+ runTest();
+ });
+}
+
+function promiseThenableThrowsBeforeCallback() {
+ var thenable = { then: function(resolve) {
+ throw new TypeError("Hi there");
+ resolve(5);
+ }};
+
+ var promise = Promise.resolve(thenable);
+ promise.then(function(v) {
+ ok(false, "promiseThenableThrowsBeforeCallback: Should've rejected");
+ runTest();
+ }, function(e) {
+ ok(e instanceof TypeError, "promiseThenableThrowsBeforeCallback");
+ runTest();
+ });
+}
+
+function promiseThenableThrowsAfterCallback() {
+ var thenable = { then: function(resolve) {
+ resolve(5);
+ throw new TypeError("Hi there");
+ }};
+
+ var promise = Promise.resolve(thenable);
+ promise.then(function(v) {
+ ok(v === 5, "promiseThenableThrowsAfterCallback");
+ runTest();
+ }, function(e) {
+ ok(false, "promiseThenableThrowsAfterCallback: Should've resolved");
+ runTest();
+ });
+}
+
+function promiseThenableRejectThenResolve() {
+ var thenable = { then: function(resolve, reject) {
+ reject(new TypeError("Hi there"));
+ resolve(5);
+ }};
+
+ var promise = Promise.resolve(thenable);
+ promise.then(function(v) {
+ ok(false, "promiseThenableRejectThenResolve should have rejected");
+ runTest();
+ }, function(e) {
+ ok(e instanceof TypeError, "promiseThenableRejectThenResolve");
+ runTest();
+ });
+}
+
+function promiseWithThenReplaced() {
+ // Ensure that we call the 'then' on the promise and not the internal then.
+ var promise = new Promise(function(resolve, reject) {
+ resolve(5);
+ });
+
+ // Rogue `then` always rejects.
+ promise.then = function(onFulfill, onReject) {
+ onReject(new TypeError("Foo"));
+ }
+
+ var promise2 = Promise.resolve(promise);
+ promise2.then(function(v) {
+ ok(false, "promiseWithThenReplaced: Should've rejected");
+ runTest();
+ }, function(e) {
+ ok(e instanceof TypeError, "promiseWithThenReplaced");
+ runTest();
+ });
+}
+
+function promiseStrictHandlers() {
+ var promise = Promise.resolve(5);
+ promise.then(function() {
+ "use strict";
+ ok(this === undefined, "Strict mode callback should have this === undefined.");
+ runTest();
+ });
+}
+
+function promiseStrictExecutorThisArg() {
+ var promise = new Promise(function(resolve, reject) {
+ "use strict";
+ ok(this === undefined, "thisArg should be undefined.");
+ runTest();
+ });
+}
+
+function promiseResolveArray() {
+ var p = Promise.resolve([1,2,3]);
+ ok(p instanceof Promise, "Should return a Promise.");
+ p.then(function(v) {
+ ok(Array.isArray(v), "Resolved value should be an Array");
+ is(v.length, 3, "Length should match");
+ is(v[0], 1, "Resolved value should match original");
+ is(v[1], 2, "Resolved value should match original");
+ is(v[2], 3, "Resolved value should match original");
+ runTest();
+ });
+}
+
+function promiseResolveThenable() {
+ var p = Promise.resolve({ then: function(onFulfill, onReject) { onFulfill(2); } });
+ ok(p instanceof Promise, "Should cast to a Promise.");
+ p.then(function(v) {
+ is(v, 2, "Should resolve to 2.");
+ runTest();
+ }, function(e) {
+ ok(false, "promiseResolveThenable should've resolved");
+ runTest();
+ });
+}
+
+function promiseResolvePromise() {
+ var original = Promise.resolve(true);
+ var cast = Promise.resolve(original);
+
+ ok(cast instanceof Promise, "Should cast to a Promise.");
+ is(cast, original, "Should return original Promise.");
+ cast.then(function(v) {
+ is(v, true, "Should resolve to true.");
+ runTest();
+ });
+}
+
+// Bug 1009569.
+// Ensure that thenables are run on a clean stack asynchronously.
+// Test case adopted from
+// https://gist.github.com/getify/d64bb01751b50ed6b281#file-bug1-js.
+function promiseResolveThenableCleanStack() {
+ function immed(s) { x++; s(); }
+ function incX(){ x++; }
+
+ var x = 0;
+ var thenable = { then: immed };
+ var results = [];
+
+ var p = Promise.resolve(thenable).then(incX);
+ results.push(x);
+
+ // check what happens after all "next cycle" steps
+ // have had a chance to complete
+ setTimeout(function(){
+ // Result should be [0, 2] since `thenable` will be called async.
+ is(results[0], 0, "Expected thenable to be called asynchronously");
+ // See Bug 1023547 comment 13 for why this check has to be gated on p.
+ p.then(function() {
+ results.push(x);
+ is(results[1], 2, "Expected thenable to be called asynchronously");
+ runTest();
+ });
+ },1000);
+}
+
+// Bug 1008467 - Promise fails with "too much recursion".
+// The bug was that the callbacks passed to a thenable would resolve the
+// promise synchronously when the fulfill handler returned a non-thenable.
+//
+// For example:
+// var p = new Promise(function(resolve) {
+// resolve(5);
+// });
+// var m = Promise.resolve(p);
+//
+// At this point `m` is a Promise that is resolved with a thenable `p`, so it
+// calls `p.then()` with two callbacks, both of which would synchronously resolve
+// `m` when `p` invoked them (on account of itself being resolved, possibly
+// synchronously. A chain of these 'Promise resolved by a Promise' would lead to
+// stack overflow.
+function promiseTestAsyncThenableResolution()
+{
+ var k = 3000;
+ Promise.resolve().then(function next() {
+ k--;
+ if (k > 0) return Promise.resolve().then(next);
+ }).then(function () {
+ ok(true, "Resolution of a chain of thenables should not be synchronous.");
+ runTest();
+ });
+}
+
+// Bug 1062323
+function promiseWrapperAsyncResolution()
+{
+ var p = new Promise(function(resolve, reject){
+ resolve();
+ });
+
+ var results = [];
+ var q = p.then(function () {
+ results.push("1-1");
+ }).then(function () {
+ results.push("1-2");
+ }).then(function () {
+ results.push("1-3");
+ });
+
+ var r = p.then(function () {
+ results.push("2-1");
+ }).then(function () {
+ results.push("2-2");
+ }).then(function () {
+ results.push("2-3");
+ });
+
+ Promise.all([q, r]).then(function() {
+ var match = results[0] == "1-1" &&
+ results[1] == "2-1" &&
+ results[2] == "1-2" &&
+ results[3] == "2-2" &&
+ results[4] == "1-3" &&
+ results[5] == "2-3";
+ info(results);
+ ok(match, "Chained promises should resolve asynchronously.");
+ runTest();
+ }, function() {
+ ok(false, "promiseWrapperAsyncResolution: One of the promises failed.");
+ runTest();
+ });
+}
+
+var tests = [ promiseResolve, promiseReject,
+ promiseException, promiseGC,
+ promiseAsync_TimeoutResolveThen,
+ promiseAsync_ResolveTimeoutThen,
+ promiseAsync_ResolveThenTimeout,
+ promiseAsync_SyncXHR,
+ promiseDoubleThen, promiseThenException,
+ promiseThenCatchThen, promiseRejectThenCatchThen,
+ promiseRejectThenCatchThen2,
+ promiseRejectThenCatchExceptionThen,
+ promiseThenCatchOrderingResolve,
+ promiseThenCatchOrderingReject,
+ promiseNestedPromise, promiseNestedNestedPromise,
+ promiseWrongNestedPromise, promiseLoop,
+ promiseStaticReject, promiseStaticResolve,
+ promiseResolveNestedPromise,
+ promiseResolveNoArg,
+ promiseRejectNoArg,
+ promiseThenNoArg,
+ promiseThenUndefinedResolveFunction,
+ promiseThenNullResolveFunction,
+ promiseCatchNoArg,
+ promiseRejectNoHandler,
+ promiseSimpleThenableResolve,
+ promiseSimpleThenableReject,
+ promiseThenableThrowsBeforeCallback,
+ promiseThenableThrowsAfterCallback,
+ promiseThenableRejectThenResolve,
+ promiseWithThenReplaced,
+ promiseStrictHandlers,
+ promiseStrictExecutorThisArg,
+ promiseResolveArray,
+ promiseResolveThenable,
+ promiseResolvePromise,
+ promiseResolveThenableCleanStack,
+ promiseTestAsyncThenableResolution,
+ promiseWrapperAsyncResolution,
+ ];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+runTest();
+// -->
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/promise/tests/test_promise_and_timeout_ordering.html b/dom/promise/tests/test_promise_and_timeout_ordering.html
new file mode 100644
index 0000000000..1dfa13bbba
--- /dev/null
+++ b/dom/promise/tests/test_promise_and_timeout_ordering.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for promise and timeout ordering</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test("Promise callbacks should run immediately after the setTimeout handler that enqueues them");
+var origPostMessage = window.postMessage;
+window.postMessage = function(msg) { origPostMessage.call(window, msg, "*"); }
+window.onmessage = t.step_func_done(function(e) {
+ assert_equals(e.data, "t1start, t1end, promise, t2");
+});
+</script>
+<script src="file_promise_and_timeout_ordering.js"></script>
diff --git a/dom/promise/tests/test_promise_and_timeout_ordering_workers.html b/dom/promise/tests/test_promise_and_timeout_ordering_workers.html
new file mode 100644
index 0000000000..122a794db6
--- /dev/null
+++ b/dom/promise/tests/test_promise_and_timeout_ordering_workers.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for promise and timeout ordering in workers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test("Promise callbacks in workers should run immediately after the setTimeout handler that enqueues them");
+var w = new Worker("file_promise_and_timeout_ordering.js");
+w.onmessage = t.step_func_done(function(e) {
+ assert_equals(e.data, "t1start, t1end, promise, t2");
+});
+</script>
diff --git a/dom/promise/tests/test_promise_uncatchable_exception.html b/dom/promise/tests/test_promise_uncatchable_exception.html
new file mode 100644
index 0000000000..8f7167d566
--- /dev/null
+++ b/dom/promise/tests/test_promise_uncatchable_exception.html
@@ -0,0 +1,35 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Promise - uncatchable exceptions</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+onmessage = function(evt) {
+ ok(true, "finished");
+ SimpleTest.finish();
+}
+
+function go() {
+ var script = document.createElement("script");
+ script.src = "promise_uncatchable_exception.js";
+ document.body.appendChild(script);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, go);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise_utils.html b/dom/promise/tests/test_promise_utils.html
new file mode 100644
index 0000000000..316ea02a54
--- /dev/null
+++ b/dom/promise/tests/test_promise_utils.html
@@ -0,0 +1,320 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Test for Promise.all, Promise.race</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+function promiseUtilitiesDefined() {
+ ok(Promise.all, "Promise.all must be defined when Promise is enabled.");
+ ok(Promise.race, "Promise.race must be defined when Promise is enabled.");
+ runTest();
+}
+
+function promiseAllEmptyArray() {
+ var p = Promise.all([]);
+ ok(p instanceof Promise, "Return value of Promise.all should be a Promise.");
+ p.then(function(values) {
+ ok(Array.isArray(values), "Resolved value should be an array.");
+ is(values.length, 0, "Resolved array length should match iterable's length.");
+ runTest();
+ }, function() {
+ ok(false, "Promise.all shouldn't fail when iterable has no rejected Promises.");
+ runTest();
+ });
+}
+
+function promiseAllArray() {
+ var p = Promise.all([1, new Date(), Promise.resolve("firefox")]);
+ ok(p instanceof Promise, "Return value of Promise.all should be a Promise.");
+ p.then(function(values) {
+ ok(Array.isArray(values), "Resolved value should be an array.");
+ is(values.length, 3, "Resolved array length should match iterable's length.");
+ is(values[0], 1, "Array values should match.");
+ ok(values[1] instanceof Date, "Array values should match.");
+ is(values[2], "firefox", "Array values should match.");
+ runTest();
+ }, function() {
+ ok(false, "Promise.all shouldn't fail when iterable has no rejected Promises.");
+ runTest();
+ });
+}
+
+function promiseAllIterable() {
+ function* promiseGen() {
+ var i = 3;
+ while (--i) {
+ yield Promise.resolve(i);
+ }
+
+ yield new Promise(function(resolve) {
+ setTimeout(resolve, 10);
+ });
+ }
+
+ Promise.all(promiseGen()).then(function(values) {
+ is(values.length, 3, "Resolved array length should match iterable's length.");
+ is(values[0], 2, "Array values should match.");
+ is(values[1], 1, "Array values should match.");
+ is(values[2], undefined, "Array values should match.");
+ runTest();
+ }, function(e) {
+ ok(false, "Promise.all shouldn't fail when an iterable is passed.");
+ runTest();
+ });
+}
+
+function promiseAllWaitsForAllPromises() {
+ var arr = [
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 1), 50);
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 2), 10);
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, new Promise(function(resolve2) {
+ resolve2(3);
+ })), 10);
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 4), 20);
+ })
+ ];
+
+ var p = Promise.all(arr);
+ p.then(function(values) {
+ ok(Array.isArray(values), "Resolved value should be an array.");
+ is(values.length, 4, "Resolved array length should match iterable's length.");
+ is(values[0], 1, "Array values should match.");
+ is(values[1], 2, "Array values should match.");
+ is(values[2], 3, "Array values should match.");
+ is(values[3], 4, "Array values should match.");
+ runTest();
+ }, function() {
+ ok(false, "Promise.all shouldn't fail when iterable has no rejected Promises.");
+ runTest();
+ });
+}
+
+function promiseAllRejectFails() {
+ var arr = [
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 1), 50);
+ }),
+ new Promise(function(resolve, reject) {
+ setTimeout(reject.bind(undefined, 2), 10);
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 3), 10);
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 4), 20);
+ })
+ ];
+
+ var p = Promise.all(arr);
+ p.then(function(values) {
+ ok(false, "Promise.all shouldn't resolve when iterable has rejected Promises.");
+ runTest();
+ }, function(e) {
+ ok(true, "Promise.all should reject when iterable has rejected Promises.");
+ is(e, 2, "Rejection value should match.");
+ runTest();
+ });
+}
+
+function promiseAllCastError() {
+ var p = Promise.all([Promise.resolve(2), { then: function() { foo(); } }]);
+ ok(p instanceof Promise, "Should cast to a Promise.");
+ p.then(function(v) {
+ ok(false, "promiseAllCastError: should've rejected.");
+ runTest();
+ }, function(e) {
+ ok(e instanceof ReferenceError, "promiseCastThenableError");
+ runTest();
+ });
+}
+
+// Check that the resolved array is enumerable.
+function promiseAllEnumerable() {
+ var p = Promise.all([1, new Date(), Promise.resolve("firefox")]);
+ p.then(function(v) {
+ var count = 0;
+ for (key in v) {
+ ++count;
+ ok(v[key] === 1 || v[key] instanceof Date || v[key] === "firefox",
+ "Enumerated properties don't match.");
+ }
+ is(count, 3, "Resolved array from Promise.all should be enumerable");
+ runTest();
+ }, function(e) {
+ ok(false, "promiseAllEnumerable: should've resolved.");
+ runTest();
+ });
+}
+
+function promiseRaceEmpty() {
+ var p = Promise.race([]);
+ ok(p instanceof Promise, "Should return a Promise.");
+ p.then(function() {
+ ok(false, "Should not resolve");
+ }, function() {
+ ok(false, "Should not reject");
+ });
+ // Per spec, An empty race never resolves or rejects.
+ setTimeout(function() {
+ ok(true);
+ runTest();
+ }, 50);
+}
+
+function promiseRaceValuesArray() {
+ var p = Promise.race([true, new Date(), 3]);
+ ok(p instanceof Promise, "Should return a Promise.");
+ p.then(function(winner) {
+ is(winner, true, "First value should win.");
+ runTest();
+ }, function(err) {
+ ok(false, "Should not fail " + err + ".");
+ runTest();
+ });
+}
+
+function promiseRacePromiseArray() {
+ function timeoutPromise(n) {
+ return new Promise(function(resolve) {
+ setTimeout(function() {
+ resolve(n);
+ }, n);
+ });
+ }
+
+ var arr = [
+ new Promise(function(resolve) {
+ resolve("first");
+ }),
+ Promise.resolve("second"),
+ new Promise(function() {}),
+ new Promise(function(resolve) {
+ setTimeout(function() {
+ setTimeout(function() {
+ resolve("fourth");
+ }, 0);
+ }, 0);
+ }),
+ ];
+
+ var p = Promise.race(arr);
+ p.then(function(winner) {
+ is(winner, "first", "First queued resolution should win the race.");
+ runTest();
+ });
+}
+
+function promiseRaceIterable() {
+ function* participants() {
+ yield new Promise(function(resolve) {
+ setTimeout(resolve, 10, 10);
+ });
+ yield new Promise(function(resolve) {
+ setTimeout(resolve, 20, 20);
+ });
+ }
+
+ Promise.race(participants()).then(function(winner) {
+ is(winner, 10, "Winner should be the one that finished earlier.");
+ runTest();
+ }, function(e) {
+ ok(false, "Promise.race shouldn't throw when an iterable is passed!");
+ runTest();
+ });
+}
+
+function promiseRaceReject() {
+ var p = Promise.race([
+ Promise.reject(new Error("Fail bad!")),
+ new Promise(function(resolve) {
+ setTimeout(resolve, 0);
+ })
+ ]);
+
+ p.then(function() {
+ ok(false, "Should not resolve when winning Promise rejected.");
+ runTest();
+ }, function(e) {
+ ok(true, "Should be rejected");
+ ok(e instanceof Error, "Should reject with Error.");
+ ok(e.message == "Fail bad!", "Message should match.");
+ runTest();
+ });
+}
+
+function promiseRaceThrow() {
+ var p = Promise.race([
+ new Promise(function(resolve) {
+ nonExistent();
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve, 0);
+ })
+ ]);
+
+ p.then(function() {
+ ok(false, "Should not resolve when winning Promise had an error.");
+ runTest();
+ }, function(e) {
+ ok(true, "Should be rejected");
+ ok(e instanceof ReferenceError, "Should reject with ReferenceError for function nonExistent().");
+ runTest();
+ });
+}
+
+var tests = [
+ promiseUtilitiesDefined,
+ promiseAllEmptyArray,
+ promiseAllArray,
+ promiseAllIterable,
+ promiseAllWaitsForAllPromises,
+ promiseAllRejectFails,
+ promiseAllCastError,
+ promiseAllEnumerable,
+
+ promiseRaceEmpty,
+ promiseRaceValuesArray,
+ promiseRacePromiseArray,
+ promiseRaceIterable,
+ promiseRaceReject,
+ promiseRaceThrow,
+ ];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+runTest();
+// -->
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/promise/tests/test_promise_xrays.html b/dom/promise/tests/test_promise_xrays.html
new file mode 100644
index 0000000000..55398167ec
--- /dev/null
+++ b/dom/promise/tests/test_promise_xrays.html
@@ -0,0 +1,365 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1170760
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1170760</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=1170760">Mozilla Bug 1170760</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 type="application/javascript">
+
+var win = $("t").contentWindow;
+
+/** Test for Bug 1170760 **/
+SimpleTest.waitForExplicitFinish();
+
+function testLoadComplete() {
+ is(win.location.href, $("t").src, "Should have loaded the right thing");
+ nextTest();
+}
+
+function testHaveXray() {
+ is(typeof win.Promise.race, "function", "Should see a race() function");
+ var exception;
+ try {
+ win.Promise.wrappedJSObject.race;
+ } catch (e) {
+ exception = e;
+ }
+ is(exception, "Getting race", "Should have thrown the right exception");
+ is(win.wrappedJSObject.setupThrew, false, "Setup should not have thrown");
+ nextTest();
+}
+
+function testConstructor1() {
+ var p = new win.Promise(function(resolve, reject) { resolve(win.Promise.resolve(5)); });
+ p.then(
+ function(arg) {
+ is(arg, 5, "Content Promise constructor resolved with content promise should work");
+ },
+ function(e) {
+ ok(false, "Content Promise constructor resolved with content promise should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testConstructor2() {
+ var p = new win.Promise(function(resolve, reject) { resolve(Promise.resolve(5)); });
+ p.then(
+ function(arg) {
+ is(arg, 5, "Content Promise constructor resolved with chrome promise should work");
+ },
+ function(e) {
+ ok(false, "Content Promise constructor resolved with chrome promise should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testRace1() {
+ var p = win.Promise.race(new win.Array(1, 2));
+ p.then(
+ function(arg) {
+ ok(arg == 1 || arg == 2,
+ "Should get the right value when racing content-side array");
+ },
+ function(e) {
+ ok(false, "testRace1 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testRace2() {
+ var p = win.Promise.race(
+ new Array(win.Promise.resolve(1), win.Promise.resolve(2)));
+ p.then(
+ function(arg) {
+ ok(arg == 1 || arg == 2,
+ "Should get the right value when racing content-side array of explicit Promises");
+ },
+ function(e) {
+ ok(false, "testRace2 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testRace3() {
+ // This works with a chrome-side array because we do the iteration
+ // while still in the Xray compartment.
+ var p = win.Promise.race([1, 2]);
+ p.then(
+ function(arg) {
+ ok(arg == 1 || arg == 2,
+ "Should get the right value when racing chrome-side array");
+ },
+ function(e) {
+ ok(false, "testRace3 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testRace4() {
+ // This works with both content-side and chrome-side Promises because we want
+ // it to and go to some lengths to make it work.
+ var p = win.Promise.race([Promise.resolve(1), win.Promise.resolve(2)]);
+ p.then(
+ function(arg) {
+ ok(arg == 1 || arg == 2,
+ "Should get the right value when racing chrome-side promises");
+ },
+ function(e) {
+ ok(false, "testRace4 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testAll1() {
+ var p = win.Promise.all(new win.Array(1, 2));
+ p.then(
+ function(arg) {
+ ok(arg instanceof win.Array, "Should get an Array from Promise.all (1)");
+ is(arg[0], 1, "First entry of Promise.all return value should be correct (1)");
+ is(arg[1], 2, "Second entry of Promise.all return value should be correct (1)");
+ },
+ function(e) {
+ ok(false, "testAll1 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testAll2() {
+ var p = win.Promise.all(
+ new Array(win.Promise.resolve(1), win.Promise.resolve(2)));
+ p.then(
+ function(arg) {
+ ok(arg instanceof win.Array, "Should get an Array from Promise.all (2)");
+ is(arg[0], 1, "First entry of Promise.all return value should be correct (2)");
+ is(arg[1], 2, "Second entry of Promise.all return value should be correct (2)");
+ },
+ function(e) {
+ ok(false, "testAll2 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testAll3() {
+ // This works with a chrome-side array because we do the iteration
+ // while still in the Xray compartment.
+ var p = win.Promise.all([1, 2]);
+ p.then(
+ function(arg) {
+ ok(arg instanceof win.Array, "Should get an Array from Promise.all (3)");
+ is(arg[0], 1, "First entry of Promise.all return value should be correct (3)");
+ is(arg[1], 2, "Second entry of Promise.all return value should be correct (3)");
+ },
+ function(e) {
+ ok(false, "testAll3 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testAll4() {
+ // This works with both content-side and chrome-side Promises because we want
+ // it to and go to some lengths to make it work.
+ var p = win.Promise.all([Promise.resolve(1), win.Promise.resolve(2)]);
+ p.then(
+ function(arg) {
+ ok(arg instanceof win.Array, "Should get an Array from Promise.all (4)");
+ is(arg[0], 1, "First entry of Promise.all return value should be correct (4)");
+ is(arg[1], 2, "Second entry of Promise.all return value should be correct (4)");
+ },
+ function(e) {
+ ok(false, "testAll4 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testAll5() {
+ var p = win.Promise.all(new win.Array());
+ p.then(
+ function(arg) {
+ ok(arg instanceof win.Array, "Should get an Array from Promise.all (5)");
+ },
+ function(e) {
+ ok(false, "testAll5 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testResolve1() {
+ var p = win.Promise.resolve(5);
+ ok(p instanceof win.Promise, "Promise.resolve should return a promise");
+ p.then(
+ function(arg) {
+ is(arg, 5, "Should get correct Promise.resolve value");
+ },
+ function(e) {
+ ok(false, "testAll5 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testResolve2() {
+ var p = win.Promise.resolve(5);
+ var q = win.Promise.resolve(p);
+ is(q, p, "Promise.resolve should just pass through Promise values");
+ nextTest();
+}
+
+function testResolve3() {
+ var p = win.Promise.resolve(Promise.resolve(5));
+ p.then(
+ function(arg) {
+ is(arg, 5, "Promise.resolve with chrome Promise should work");
+ },
+ function(e) {
+ ok(false, "Promise.resolve with chrome Promise should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testResolve4() {
+ var p = new win.Promise((res, rej) => {});
+ Components.utils.getJSTestingFunctions().resolvePromise(p, 42);
+ p.then(
+ function(arg) {
+ is(arg, 42, "Resolving an Xray to a promise with TestingFunctions resolvePromise should work");
+ },
+ function(e) {
+ ok(false, "Resolving an Xray to a promise with TestingFunctions resolvePromise should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testReject1() {
+ var p = win.Promise.reject(5);
+ ok(p instanceof win.Promise, "Promise.reject should return a promise");
+ p.then(
+ function(arg) {
+ ok(false, "Promise should be rejected");
+ },
+ function(e) {
+ is(e, 5, "Should get correct Promise.reject value");
+ }
+ ).then(nextTest);
+}
+
+function testReject2() {
+ var p = new win.Promise((res, rej) => {});
+ Components.utils.getJSTestingFunctions().rejectPromise(p, 42);
+ p.then(
+ function(arg) {
+ ok(false, "Rejecting an Xray to a promise with TestingFunctions rejectPromise should trigger catch handler");
+ },
+ function(e) {
+ is(e, 42, "Rejecting an Xray to a promise with TestingFunctions rejectPromise should work");
+ }
+ ).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.reject 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);
+}
+
+function testToStringTag1() {
+ is(win.Promise.prototype[Symbol.toStringTag], "Promise", "@@toStringTag was incorrect");
+ var p = win.Promise.resolve();
+ is(String(p), "[object Promise]", "String() result was incorrect");
+ is(p.toString(), "[object Promise]", "toString result was incorrect");
+ is(Object.prototype.toString.call(p), "[object Promise]", "second toString result was incorrect");
+ nextTest();
+}
+
+var tests = [
+ testLoadComplete,
+ testHaveXray,
+ testConstructor1,
+ testConstructor2,
+ testRace1,
+ testRace2,
+ testRace3,
+ testRace4,
+ testAll1,
+ testAll2,
+ testAll3,
+ testAll4,
+ testAll5,
+ testResolve1,
+ testResolve2,
+ testResolve3,
+ testResolve4,
+ testReject1,
+ testReject2,
+ testThen1,
+ testThen2,
+ testCatch1,
+ testToStringTag1,
+];
+
+function nextTest() {
+ if (tests.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ tests.shift()();
+}
+
+addLoadEvent(nextTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_resolve.html b/dom/promise/tests/test_resolve.html
new file mode 100644
index 0000000000..780eae6c6c
--- /dev/null
+++ b/dom/promise/tests/test_resolve.html
@@ -0,0 +1,67 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Promise.resolve(anything) Test</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+var tests = [
+ null,
+ 42,
+ "hello world",
+ true,
+ false,
+ {},
+ { a: 42 },
+ [ 1, 2, 3, 4, null, true, "hello world" ],
+ function() {},
+ window,
+ undefined,
+ document.createElement('input'),
+ new Date(),
+];
+
+function cbError() {
+ ok(false, "Nothing should arrive here!");
+}
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.pop();
+
+ new Promise(function(resolve, reject) {
+ resolve(test);
+ }).then(function(what) {
+ ok(test === what, "What is: " + what);
+ }, cbError).then(function() {
+ new Promise(function(resolve, reject) {
+ reject(test)
+ }).then(cbError, function(what) {
+ ok(test === what, "What is: " + what);
+ }).then(runTest, cbError);
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+// -->
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/promise/tests/test_resolver_return_value.html b/dom/promise/tests/test_resolver_return_value.html
new file mode 100644
index 0000000000..1fb9652ace
--- /dev/null
+++ b/dom/promise/tests/test_resolver_return_value.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1120235
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1120235</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1120235 **/
+ var res, rej;
+ var p = new Promise(function(resolve, reject) { res = resolve; rej = reject; });
+ is(res(1), undefined, "Resolve function should return undefined");
+ is(rej(2), undefined, "Reject function should return undefined");
+
+ var thenable = {
+ then: function(resolve, reject) {
+ is(resolve(3), undefined, "Thenable resolve argument should return undefined");
+ is(reject(4), undefined, "Thenable reject argument should return undefined");
+ SimpleTest.finish();
+ }
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ p = Promise.resolve(thenable);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1120235">Mozilla Bug 1120235</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_species_getter.html b/dom/promise/tests/test_species_getter.html
new file mode 100644
index 0000000000..04e590a131
--- /dev/null
+++ b/dom/promise/tests/test_species_getter.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for ...</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var desc = Object.getOwnPropertyDescriptor(Promise, Symbol.species);
+ assert_not_equals(desc, undefined, "Should have a property");
+
+ assert_equals(desc.configurable, true, "Property should be configurable");
+ assert_equals(desc.enumerable, false, "Property should not be enumerable");
+ assert_equals(desc.set, undefined, "Should not have a setter");
+ var getter = desc.get;
+
+ var things = [undefined, null, 5, "xyz", Promise, Object];
+ for (var thing of things) {
+ assert_equals(getter.call(thing), thing,
+ "Getter should return its this value");
+ }
+
+}, "Promise should have an @@species getter that works per spec");
+</script>
diff --git a/dom/promise/tests/test_thenable_vs_promise_ordering.html b/dom/promise/tests/test_thenable_vs_promise_ordering.html
new file mode 100644
index 0000000000..161e95d75b
--- /dev/null
+++ b/dom/promise/tests/test_thenable_vs_promise_ordering.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for promise resolution ordering with thenables and promises</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test("A promise resolved first (with a thenable) should trigger its callbacks before a promise resolved second (with a promise).");
+t.step(function() {
+ var customThenCalled = false;
+ var p0 = Promise.resolve();
+ p0.then = function(resolved, rejected) {
+ customThenCalled = true;
+ Promise.prototype.then.call(this, resolved, rejected);
+ }
+ var p1 = new Promise(function(r) { r(p0); });
+ delete p0.then;
+ var p2 = new Promise(function(r) { r(p0); });
+ var resolutionOrder = "";
+ Promise.all([ p1.then(function() { resolutionOrder += "1"; }),
+ p2.then(function() { resolutionOrder += "2"; }) ])
+ .then(t.step_func_done(function() {
+ assert_true(customThenCalled, "Should have called custom then");
+ assert_equals(resolutionOrder, "12");
+ }));
+});
+</script>
diff --git a/dom/promise/tests/test_webassembly_compile.html b/dom/promise/tests/test_webassembly_compile.html
new file mode 100644
index 0000000000..0243df49ca
--- /dev/null
+++ b/dom/promise/tests/test_webassembly_compile.html
@@ -0,0 +1,174 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>WebAssembly.compile Test</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+const wasmTextToBinary = SpecialPowers.unwrap(SpecialPowers.Cu.getJSTestingFunctions().wasmTextToBinary);
+const wasmIsSupported = SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupported
+const fooModuleCode = wasmTextToBinary(`(module
+ (func $foo (result i32) (i32.const 42))
+ (export "foo" $foo)
+)`, 'new-format');
+
+function checkFooModule(m) {
+ ok(m instanceof WebAssembly.Module, "got a module");
+ var i = new WebAssembly.Instance(m);
+ ok(i instanceof WebAssembly.Instance, "got an instance");
+ ok(i.exports.foo() === 42, "got 42");
+}
+
+function checkFooInstance(i) {
+ ok(i instanceof WebAssembly.Instance, "got a module");
+ ok(i.exports.foo() === 42, "got 42");
+}
+
+function propertiesExist() {
+ if (!wasmIsSupported()) {
+ ok(!this["WebAssembly"], "If the device doesn't support, there will be no WebAssembly object");
+ SimpleTest.finish();
+ return;
+ }
+
+ ok(WebAssembly, "WebAssembly object should exist");
+ ok(WebAssembly.compile, "WebAssembly.compile function should exist");
+ runTest();
+}
+
+function compileFail() {
+ WebAssembly.compile().then(
+ () => { ok(false, "should have failed"); runTest() }
+ ).catch(
+ err => { ok(err instanceof TypeError, "empty compile failed"); runTest() }
+ );
+}
+
+function compileSuccess() {
+ WebAssembly.compile(fooModuleCode).then(
+ m => { checkFooModule(m); runTest() }
+ ).catch(
+ err => { ok(false, String(err)); runTest() }
+ );
+}
+
+function compileManySuccess() {
+ const N = 100;
+
+ var arr = [];
+ for (var i = 0; i < N; i++)
+ arr.push(WebAssembly.compile(fooModuleCode));
+
+ SpecialPowers.gc();
+
+ Promise.all(arr).then (ms => {
+ ok(ms.length === N, "got the right number");
+ for (var i = 0; i < N; i++)
+ checkFooModule(ms[i]);
+ runTest();
+ }).catch(
+ err => { ok(false, String(err)); runTest() }
+ );
+}
+
+function compileInWorker() {
+ var w = new Worker(`data:text/plain,
+ onmessage = e => {
+ WebAssembly.compile(e.data).then(m => {
+ var i = new WebAssembly.Instance(m);
+ if (i.exports.foo() !== 42)
+ throw "bad i.exports.foo() result";
+ postMessage("ok");
+ close();
+ }).catch(err => { throw err });
+ }
+ `);
+ w.postMessage(fooModuleCode);
+ w.onmessage = e => {
+ ok(e.data === "ok", "worker test");
+ runTest();
+ }
+}
+
+function terminateCompileInWorker() {
+ var w = new Worker(`data:text/plain,
+ var fooModuleCode;
+ function spawnWork() {
+ const N = 100;
+ var arr = [];
+ for (var i = 0; i < N; i++)
+ arr.push(WebAssembly.compile(fooModuleCode));
+ Promise.all(arr).then(spawnWork);
+ }
+ onmessage = e => {
+ fooModuleCode = e.data;
+ spawnWork();
+ postMessage("ok");
+ }
+ `);
+ w.postMessage(fooModuleCode);
+ w.onmessage = e => {
+ ok(e.data === "ok", "worker finished first step");
+ w.terminate();
+ runTest();
+ }
+}
+
+function instantiateFail() {
+ WebAssembly.instantiate().then(
+ () => { ok(false, "should have failed"); runTest() }
+ ).catch(
+ err => { ok(err instanceof TypeError, "empty compile failed"); runTest() }
+ );
+}
+
+function instantiateSuccess() {
+ WebAssembly.instantiate(fooModuleCode).then(
+ r => { checkFooModule(r.module); checkFooInstance(r.instance); runTest() }
+ ).catch(
+ err => { ok(false, String(err)); runTest() }
+ );
+}
+
+function chainSuccess() {
+ WebAssembly.compile(fooModuleCode).then(
+ m => WebAssembly.instantiate(m)
+ ).then(
+ i => { checkFooInstance(i); runTest() }
+ ).catch(
+ err => { ok(false, String(err)); runTest() }
+ );
+}
+
+var tests = [ propertiesExist,
+ compileFail,
+ compileSuccess,
+ compileManySuccess,
+ compileInWorker,
+ terminateCompileInWorker,
+ instantiateFail,
+ instantiateSuccess,
+ chainSuccess
+ ];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["javascript.options.wasm", true]]}, runTest);
+</script>
+</body>
+</html>
+
diff --git a/dom/promise/tests/unit/test_monitor_uncaught.js b/dom/promise/tests/unit/test_monitor_uncaught.js
new file mode 100644
index 0000000000..7dd80d212c
--- /dev/null
+++ b/dom/promise/tests/unit/test_monitor_uncaught.js
@@ -0,0 +1,274 @@
+/* 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/. */
+
+"use strict";
+
+var { utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Timer.jsm", this);
+Cu.import("resource://testing-common/PromiseTestUtils.jsm", this);
+
+// Prevent test failures due to the unhandled rejections in this test file.
+PromiseTestUtils.disableUncaughtRejectionObserverForSelfTest();
+
+add_task(function* test_globals() {
+ Assert.equal(Promise.defer || undefined, undefined, "We are testing DOM Promise.");
+ Assert.notEqual(PromiseDebugging, undefined, "PromiseDebugging is available.");
+});
+
+add_task(function* test_promiseID() {
+ let p1 = new Promise(resolve => {});
+ let p2 = new Promise(resolve => {});
+ let p3 = p2.then(null, null);
+ let promise = [p1, p2, p3];
+
+ let identifiers = promise.map(PromiseDebugging.getPromiseID);
+ do_print("Identifiers: " + JSON.stringify(identifiers));
+ let idSet = new Set(identifiers);
+ Assert.equal(idSet.size, identifiers.length,
+ "PromiseDebugging.getPromiseID returns a distinct id per promise");
+
+ let identifiers2 = promise.map(PromiseDebugging.getPromiseID);
+ Assert.equal(JSON.stringify(identifiers),
+ JSON.stringify(identifiers2),
+ "Successive calls to PromiseDebugging.getPromiseID return the same id for the same promise");
+});
+
+add_task(function* test_observe_uncaught() {
+ // The names of Promise instances
+ let names = new Map();
+
+ // The results for UncaughtPromiseObserver callbacks.
+ let CallbackResults = function(name) {
+ this.name = name;
+ this.expected = new Set();
+ this.observed = new Set();
+ this.blocker = new Promise(resolve => this.resolve = resolve);
+ };
+ CallbackResults.prototype = {
+ observe: function(promise) {
+ do_print(this.name + " observing Promise " + names.get(promise));
+ Assert.equal(PromiseDebugging.getState(promise).state, "rejected",
+ this.name + " observed a rejected Promise");
+ if (!this.expected.has(promise)) {
+ Assert.ok(false,
+ this.name + " observed a Promise that it expected to observe, " +
+ names.get(promise) +
+ " (" + PromiseDebugging.getPromiseID(promise) +
+ ", " + PromiseDebugging.getAllocationStack(promise) + ")");
+
+ }
+ Assert.ok(this.expected.delete(promise),
+ this.name + " observed a Promise that it expected to observe, " +
+ names.get(promise) + " (" + PromiseDebugging.getPromiseID(promise) + ")");
+ Assert.ok(!this.observed.has(promise),
+ this.name + " observed a Promise that it has not observed yet");
+ this.observed.add(promise);
+ if (this.expected.size == 0) {
+ this.resolve();
+ } else {
+ do_print(this.name + " is still waiting for " + this.expected.size + " observations:");
+ do_print(JSON.stringify(Array.from(this.expected.values(), (x) => names.get(x))));
+ }
+ },
+ };
+
+ let onLeftUncaught = new CallbackResults("onLeftUncaught");
+ let onConsumed = new CallbackResults("onConsumed");
+
+ let observer = {
+ onLeftUncaught: function(promise, data) {
+ onLeftUncaught.observe(promise);
+ },
+ onConsumed: function(promise) {
+ onConsumed.observe(promise);
+ },
+ };
+
+ let resolveLater = function(delay = 20) {
+ return new Promise((resolve, reject) => setTimeout(resolve, delay));
+ };
+ let rejectLater = function(delay = 20) {
+ return new Promise((resolve, reject) => setTimeout(reject, delay));
+ };
+ let makeSamples = function*() {
+ yield {
+ promise: Promise.resolve(0),
+ name: "Promise.resolve",
+ };
+ yield {
+ promise: Promise.resolve(resolve => resolve(0)),
+ name: "Resolution callback",
+ };
+ yield {
+ promise: Promise.resolve(0).then(null, null),
+ name: "`then(null, null)`"
+ };
+ yield {
+ promise: Promise.reject(0).then(null, () => {}),
+ name: "Reject and catch immediately",
+ };
+ yield {
+ promise: resolveLater(),
+ name: "Resolve later",
+ };
+ yield {
+ promise: Promise.reject("Simple rejection"),
+ leftUncaught: true,
+ consumed: false,
+ name: "Promise.reject",
+ };
+
+ // Reject a promise now, consume it later.
+ let p = Promise.reject("Reject now, consume later");
+ setTimeout(() => p.then(null, () => {
+ do_print("Consumed promise");
+ }), 200);
+ yield {
+ promise: p,
+ leftUncaught: true,
+ consumed: true,
+ name: "Reject now, consume later",
+ };
+
+ yield {
+ promise: Promise.all([
+ Promise.resolve("Promise.all"),
+ rejectLater()
+ ]),
+ leftUncaught: true,
+ name: "Rejecting through Promise.all"
+ };
+ yield {
+ promise: Promise.race([
+ resolveLater(500),
+ Promise.reject(),
+ ]),
+ leftUncaught: true, // The rejection wins the race.
+ name: "Rejecting through Promise.race",
+ };
+ yield {
+ promise: Promise.race([
+ Promise.resolve(),
+ rejectLater(500)
+ ]),
+ leftUncaught: false, // The resolution wins the race.
+ name: "Resolving through Promise.race",
+ };
+
+ let boom = new Error("`throw` in the constructor");
+ yield {
+ promise: new Promise(() => { throw boom; }),
+ leftUncaught: true,
+ name: "Throwing in the constructor",
+ };
+
+ let rejection = Promise.reject("`reject` during resolution");
+ yield {
+ promise: rejection,
+ leftUncaught: false,
+ consumed: false, // `rejection` is consumed immediately (see below)
+ name: "Promise.reject, again",
+ };
+
+ yield {
+ promise: new Promise(resolve => resolve(rejection)),
+ leftUncaught: true,
+ consumed: false,
+ name: "Resolving with a rejected promise",
+ };
+
+ yield {
+ promise: Promise.resolve(0).then(() => rejection),
+ leftUncaught: true,
+ consumed: false,
+ name: "Returning a rejected promise from success handler",
+ };
+
+ yield {
+ promise: Promise.resolve(0).then(() => { throw new Error(); }),
+ leftUncaught: true,
+ consumed: false,
+ name: "Throwing during the call to the success callback",
+ };
+ };
+ let samples = [];
+ for (let s of makeSamples()) {
+ samples.push(s);
+ do_print("Promise '" + s.name + "' has id " + PromiseDebugging.getPromiseID(s.promise));
+ }
+
+ PromiseDebugging.addUncaughtRejectionObserver(observer);
+
+ for (let s of samples) {
+ names.set(s.promise, s.name);
+ if (s.leftUncaught || false) {
+ onLeftUncaught.expected.add(s.promise);
+ }
+ if (s.consumed || false) {
+ onConsumed.expected.add(s.promise);
+ }
+ }
+
+ do_print("Test setup, waiting for callbacks.");
+ yield onLeftUncaught.blocker;
+
+ do_print("All calls to onLeftUncaught are complete.");
+ if (onConsumed.expected.size != 0) {
+ do_print("onConsumed is still waiting for the following Promise:");
+ do_print(JSON.stringify(Array.from(onConsumed.expected.values(), (x) => names.get(x))));
+ yield onConsumed.blocker;
+ }
+
+ do_print("All calls to onConsumed are complete.");
+ let removed = PromiseDebugging.removeUncaughtRejectionObserver(observer);
+ Assert.ok(removed, "removeUncaughtRejectionObserver succeeded");
+ removed = PromiseDebugging.removeUncaughtRejectionObserver(observer);
+ Assert.ok(!removed, "second call to removeUncaughtRejectionObserver didn't remove anything");
+});
+
+
+add_task(function* test_uninstall_observer() {
+ let Observer = function() {
+ this.blocker = new Promise(resolve => this.resolve = resolve);
+ this.active = true;
+ };
+ Observer.prototype = {
+ set active(x) {
+ this._active = x;
+ if (x) {
+ PromiseDebugging.addUncaughtRejectionObserver(this);
+ } else {
+ PromiseDebugging.removeUncaughtRejectionObserver(this);
+ }
+ },
+ onLeftUncaught: function() {
+ Assert.ok(this._active, "This observer is active.");
+ this.resolve();
+ },
+ onConsumed: function() {
+ Assert.ok(false, "We should not consume any Promise.");
+ },
+ };
+
+ do_print("Adding an observer.");
+ let deactivate = new Observer();
+ Promise.reject("I am an uncaught rejection.");
+ yield deactivate.blocker;
+ Assert.ok(true, "The observer has observed an uncaught Promise.");
+ deactivate.active = false;
+ do_print("Removing the observer, it should not observe any further uncaught Promise.");
+
+ do_print("Rejecting a Promise and waiting a little to give a chance to observers.");
+ let wait = new Observer();
+ Promise.reject("I am another uncaught rejection.");
+ yield wait.blocker;
+ yield new Promise(resolve => setTimeout(resolve, 100));
+ // Normally, `deactivate` should not be notified of the uncaught rejection.
+ wait.active = false;
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/dom/promise/tests/unit/xpcshell.ini b/dom/promise/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..73df2380bf
--- /dev/null
+++ b/dom/promise/tests/unit/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head =
+tail =
+
+[test_monitor_uncaught.js]