diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index 49879e478ff5..375e261fe840 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -10,6 +10,7 @@ #include "mozilla/dom/OwningNonNull.h" #include "mozilla/dom/PromiseBinding.h" #include "mozilla/Preferences.h" +#include "mozilla/SyncRunnable.h" #include "PromiseCallback.h" #include "nsContentUtils.h" #include "nsPIDOMWindow.h" @@ -22,6 +23,8 @@ namespace mozilla { namespace dom { +using namespace workers; + // PromiseTask // This class processes the promise's callbacks with promise's result. @@ -51,36 +54,65 @@ private: nsRefPtr mPromise; }; -// This class processes the promise's callbacks with promise's result. -class PromiseResolverTask MOZ_FINAL : public nsRunnable +class WorkerPromiseTask MOZ_FINAL : public WorkerRunnable { public: - PromiseResolverTask(Promise* aPromise, - JS::Handle aValue, - Promise::PromiseState aState) + WorkerPromiseTask(WorkerPrivate* aWorkerPrivate, Promise* aPromise) + : WorkerRunnable(aWorkerPrivate, WorkerThread, + UnchangedBusyCount, SkipWhenClearing) + , mPromise(aPromise) + { + MOZ_ASSERT(aPromise); + MOZ_COUNT_CTOR(WorkerPromiseTask); + } + + ~WorkerPromiseTask() + { + MOZ_COUNT_DTOR(WorkerPromiseTask); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + { + mPromise->mTaskPending = false; + mPromise->RunTask(); + return true; + } + +private: + nsRefPtr mPromise; +}; + +class PromiseResolverMixin +{ +public: + PromiseResolverMixin(Promise* aPromise, + JS::Handle aValue, + Promise::PromiseState aState) : mPromise(aPromise) , mValue(aValue) , mState(aState) { MOZ_ASSERT(aPromise); MOZ_ASSERT(mState != Promise::Pending); - MOZ_COUNT_CTOR(PromiseResolverTask); + MOZ_COUNT_CTOR(PromiseResolverMixin); - JSContext* cx = nsContentUtils::GetSafeJSContext(); + JSContext* cx = nsContentUtils::GetDefaultJSContextForThread(); /* It's safe to use unsafeGet() here: the unsafeness comes from the * possibility of updating the value of mJSObject without triggering the * barriers. However if the value will always be marked, post barriers * unnecessary. */ JS_AddNamedValueRootRT(JS_GetRuntime(cx), mValue.unsafeGet(), - "PromiseResolverTask.mValue"); + "PromiseResolverMixin.mValue"); } - ~PromiseResolverTask() + virtual ~PromiseResolverMixin() { - MOZ_COUNT_DTOR(PromiseResolverTask); + NS_ASSERT_OWNINGTHREAD(PromiseResolverMixin); + MOZ_COUNT_DTOR(PromiseResolverMixin); - JSContext* cx = nsContentUtils::GetSafeJSContext(); + JSContext* cx = nsContentUtils::GetDefaultJSContextForThread(); /* It's safe to use unsafeGet() here: the unsafeness comes from the * possibility of updating the value of mJSObject without triggering the @@ -89,18 +121,66 @@ public: JS_RemoveValueRootRT(JS_GetRuntime(cx), mValue.unsafeGet()); } - NS_IMETHOD Run() +protected: + void + RunInternal() { + NS_ASSERT_OWNINGTHREAD(PromiseResolverMixin); mPromise->RunResolveTask( JS::Handle::fromMarkedLocation(mValue.address()), mState, Promise::SyncTask); - return NS_OK; } private: nsRefPtr mPromise; JS::Heap mValue; Promise::PromiseState mState; + NS_DECL_OWNINGTHREAD; +}; + +// This class processes the promise's callbacks with promise's result. +class PromiseResolverTask MOZ_FINAL : public nsRunnable, + public PromiseResolverMixin +{ +public: + PromiseResolverTask(Promise* aPromise, + JS::Handle aValue, + Promise::PromiseState aState) + : PromiseResolverMixin(aPromise, aValue, aState) + {} + + ~PromiseResolverTask() + {} + + NS_IMETHOD Run() + { + RunInternal(); + return NS_OK; + } +}; + +class WorkerPromiseResolverTask MOZ_FINAL : public WorkerRunnable, + public PromiseResolverMixin +{ +public: + WorkerPromiseResolverTask(WorkerPrivate* aWorkerPrivate, + Promise* aPromise, + JS::Handle aValue, + Promise::PromiseState aState) + : WorkerRunnable(aWorkerPrivate, WorkerThread, + UnchangedBusyCount, SkipWhenClearing), + PromiseResolverMixin(aPromise, aValue, aState) + {} + + ~WorkerPromiseResolverTask() + {} + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + { + RunInternal(); + return true; + } }; // Promise @@ -163,25 +243,55 @@ Promise::WrapObject(JSContext* aCx, JS::Handle aScope) return PromiseBinding::Wrap(aCx, aScope, this); } -/* static */ bool -Promise::PrefEnabled() +class PromisePrefEnabledRunnable : public nsRunnable { - return Preferences::GetBool("dom.promise.enabled", false); -} +public: + PromisePrefEnabledRunnable() : mEnabled(false) {} + + bool + Enabled() + { + return mEnabled; + } + + NS_IMETHODIMP + Run() + { + MOZ_ASSERT(NS_IsMainThread()); + mEnabled = Preferences::GetBool("dom.promise.enabled", false); + return NS_OK; + } + +private: + bool mEnabled; +}; /* static */ bool Promise::EnabledForScope(JSContext* aCx, JSObject* /* unused */) { // Enable if the pref is enabled or if we're chrome or if we're a // certified app. - if (PrefEnabled()) { + nsCOMPtr mainThread; + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + NS_ENSURE_SUCCESS(rv, false); + + nsRefPtr r = new PromisePrefEnabledRunnable(); + + // When used from the main thread, SyncRunnable will internally directly call + // the function rather than dispatch a Runnable. So this is usable on any + // thread. + // Although this pause is expensive, it is performed only once per worker when + // the worker in initialized. + SyncRunnable::DispatchToThread(mainThread, r); + if (r->Enabled()) { return true; } + // FIXME(nsm): Remove these checks once promises are enabled by default. // Note that we have no concept of a certified app in workers. // XXXbz well, why not? if (!NS_IsMainThread()) { - return workers::GetWorkerPrivateFromContext(aCx)->IsChromeWorker(); + return GetWorkerPrivateFromContext(aCx)->IsChromeWorker(); } nsIPrincipal* prin = nsContentUtils::GetSubjectPrincipal(); @@ -280,10 +390,15 @@ Promise::Constructor(const GlobalObject& aGlobal, PromiseInit& aInit, ErrorResult& aRv) { JSContext* cx = aGlobal.GetContext(); - nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); - if (!window) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return nullptr; + nsCOMPtr window; + + // On workers, let the window be null. + if (MOZ_LIKELY(NS_IsMainThread())) { + window = do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } } nsRefPtr promise = new Promise(window); @@ -324,10 +439,13 @@ Promise::Constructor(const GlobalObject& aGlobal, Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { - nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); - if (!window) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return nullptr; + nsCOMPtr window; + if (MOZ_LIKELY(NS_IsMainThread())) { + window = do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } } nsRefPtr promise = new Promise(window); @@ -341,10 +459,13 @@ Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx, Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { - nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); - if (!window) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return nullptr; + nsCOMPtr window; + if (MOZ_LIKELY(NS_IsMainThread())) { + window = do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } } nsRefPtr promise = new Promise(window); @@ -403,8 +524,15 @@ Promise::AppendCallbacks(PromiseCallback* aResolveCallback, // callbacks with promise's result. If promise's state is rejected, queue a // task to process our reject callbacks with promise's result. if (mState != Pending && !mTaskPending) { - nsRefPtr task = new PromiseTask(this); - NS_DispatchToCurrentThread(task); + if (MOZ_LIKELY(NS_IsMainThread())) { + nsRefPtr task = new PromiseTask(this); + NS_DispatchToCurrentThread(task); + } else { + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + nsRefPtr task = new WorkerPromiseTask(worker, this); + worker->Dispatch(task); + } mTaskPending = true; } } @@ -420,8 +548,9 @@ Promise::RunTask() mResolveCallbacks.Clear(); mRejectCallbacks.Clear(); - JSAutoRequest ar(nsContentUtils::GetSafeJSContext()); - Optional > value(nsContentUtils::GetSafeJSContext(), mResult); + JSContext* cx = nsContentUtils::GetDefaultJSContextForThread(); + JSAutoRequest ar(cx); + Optional > value(cx, mResult); for (uint32_t i = 0; i < callbacks.Length(); ++i) { callbacks[i]->Call(value); @@ -442,16 +571,27 @@ Promise::MaybeReportRejected() MOZ_ASSERT(mResult.isObject(), "How did we get a JSErrorReport?"); - nsCOMPtr win = - do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(&mResult.toObject())); + // Remains null in case of worker. + nsCOMPtr win; + bool isChromeError = false; + + if (MOZ_LIKELY(NS_IsMainThread())) { + win = + do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(&mResult.toObject())); + nsIPrincipal* principal = nsContentUtils::GetObjectPrincipal(&mResult.toObject()); + isChromeError = nsContentUtils::IsSystemPrincipal(principal); + } else { + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + isChromeError = worker->IsChromeWorker(); + } - nsIPrincipal* principal = nsContentUtils::GetObjectPrincipal(&mResult.toObject()); // Now post an event to do the real reporting async - NS_DispatchToCurrentThread( + NS_DispatchToMainThread( new AsyncErrorReporter(JS_GetObjectRuntime(&mResult.toObject()), report, nullptr, - nsContentUtils::IsSystemPrincipal(principal), + isChromeError, win)); } @@ -530,9 +670,17 @@ Promise::RunResolveTask(JS::Handle aValue, // If the synchronous flag is unset, queue a task to process our // accept callbacks with value. if (aAsynchronous == AsyncTask) { - nsRefPtr task = - new PromiseResolverTask(this, aValue, aState); - NS_DispatchToCurrentThread(task); + if (MOZ_LIKELY(NS_IsMainThread())) { + nsRefPtr task = + new PromiseResolverTask(this, aValue, aState); + NS_DispatchToCurrentThread(task); + } else { + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + nsRefPtr task = + new WorkerPromiseResolverTask(worker, this, aValue, aState); + worker->Dispatch(task); + } return; } diff --git a/dom/promise/Promise.h b/dom/promise/Promise.h index de2c06f11525..6a3f1ca7e889 100644 --- a/dom/promise/Promise.h +++ b/dom/promise/Promise.h @@ -27,10 +27,13 @@ class AnyCallback; class Promise MOZ_FINAL : public nsISupports, public nsWrapperCache { - friend class PromiseTask; + friend class PromiseResolverMixin; friend class PromiseResolverTask; - friend class ResolvePromiseCallback; + friend class PromiseTask; friend class RejectPromiseCallback; + friend class ResolvePromiseCallback; + friend class WorkerPromiseResolverTask; + friend class WorkerPromiseTask; friend class WrapperPromiseCallback; public: @@ -40,7 +43,6 @@ public: Promise(nsPIDOMWindow* aWindow); ~Promise(); - static bool PrefEnabled(); static bool EnabledForScope(JSContext* aCx, JSObject* /* unused */); void MaybeResolve(JSContext* aCx, diff --git a/dom/promise/PromiseCallback.cpp b/dom/promise/PromiseCallback.cpp index f8cb9b9c9a1a..c828b2171355 100644 --- a/dom/promise/PromiseCallback.cpp +++ b/dom/promise/PromiseCallback.cpp @@ -74,7 +74,13 @@ void ResolvePromiseCallback::Call(const Optional >& aValue) { // Run resolver's algorithm with value and the synchronous flag set. - AutoJSContext cx; + JSContext *cx = nsContentUtils::GetDefaultJSContextForThread(); + + Maybe pusher; + if (NS_IsMainThread()) { + pusher.construct(cx); + } + Maybe ac; EnterCompartment(ac, cx, aValue); @@ -109,7 +115,13 @@ void RejectPromiseCallback::Call(const Optional >& aValue) { // Run resolver's algorithm with value and the synchronous flag set. - AutoJSContext cx; + JSContext *cx = nsContentUtils::GetDefaultJSContextForThread(); + + Maybe pusher; + if (NS_IsMainThread()) { + pusher.construct(cx); + } + Maybe ac; EnterCompartment(ac, cx, aValue); @@ -145,7 +157,17 @@ WrapperPromiseCallback::~WrapperPromiseCallback() void WrapperPromiseCallback::Call(const Optional >& aValue) { - AutoJSContext cx; + // AutoCxPusher and co. interact with xpconnect, which crashes on + // workers. On workers we'll get the right context from + // GetDefaultJSContextForThread(), and since there is only one context, we + // don't need to push or pop it from the stack. Is that correct? + JSContext* cx = nsContentUtils::GetDefaultJSContextForThread(); + + Maybe pusher; + if (NS_IsMainThread()) { + pusher.construct(cx); + } + Maybe ac; EnterCompartment(ac, cx, aValue); diff --git a/dom/promise/PromiseCallback.h b/dom/promise/PromiseCallback.h index 38089466eed6..07f156100820 100644 --- a/dom/promise/PromiseCallback.h +++ b/dom/promise/PromiseCallback.h @@ -37,7 +37,7 @@ public: }; // WrapperPromiseCallback execs a JS Callback with a value, and then the return -// value is sent to the aNextPromise->resolveFunction() or to +// value is sent to the aNextPromise->ResolveFunction() or to // aNextPromise->RejectFunction() if the JS Callback throws. class WrapperPromiseCallback MOZ_FINAL : public PromiseCallback { diff --git a/dom/promise/tests/test_promise.html b/dom/promise/tests/test_promise.html index 3e5b1f3cad6a..0cc6a5822b7c 100644 --- a/dom/promise/tests/test_promise.html +++ b/dom/promise/tests/test_promise.html @@ -368,6 +368,15 @@ function promiseResolveNestedPromise() { }); } +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(); +} + var tests = [ promiseResolve, promiseReject, promiseException, promiseGC, promiseAsync, promiseDoubleThen, promiseThenException, @@ -380,6 +389,7 @@ var tests = [ promiseResolve, promiseReject, promiseWrongNestedPromise, promiseLoop, promiseStaticReject, promiseStaticResolve, promiseResolveNestedPromise, + promiseRejectNoHandler, ]; function runTest() { diff --git a/dom/workers/RegisterBindings.cpp b/dom/workers/RegisterBindings.cpp index 505e304d3aab..4dfae63199ab 100644 --- a/dom/workers/RegisterBindings.cpp +++ b/dom/workers/RegisterBindings.cpp @@ -19,6 +19,7 @@ #include "mozilla/dom/ImageDataBinding.h" #include "mozilla/dom/MessageEventBinding.h" #include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/PromiseBinding.h" #include "mozilla/dom/TextDecoderBinding.h" #include "mozilla/dom/TextEncoderBinding.h" #include "mozilla/dom/XMLHttpRequestBinding.h" @@ -61,6 +62,8 @@ WorkerPrivate::RegisterBindings(JSContext* aCx, JS::Handle aGlobal) !ImageDataBinding::GetConstructorObject(aCx, aGlobal) || !MessageEventBinding::GetConstructorObject(aCx, aGlobal) || !MessagePortBinding::GetConstructorObject(aCx, aGlobal) || + (PromiseBinding::ConstructorEnabled(aCx, aGlobal) && + !PromiseBinding::GetConstructorObject(aCx, aGlobal)) || !TextDecoderBinding::GetConstructorObject(aCx, aGlobal) || !TextEncoderBinding::GetConstructorObject(aCx, aGlobal) || !XMLHttpRequestBinding_workers::GetConstructorObject(aCx, aGlobal) || @@ -77,4 +80,4 @@ WorkerPrivate::RegisterBindings(JSContext* aCx, JS::Handle aGlobal) } return true; -} \ No newline at end of file +} diff --git a/dom/workers/test/mochitest.ini b/dom/workers/test/mochitest.ini index ce42a9cb05d8..d13b71239381 100644 --- a/dom/workers/test/mochitest.ini +++ b/dom/workers/test/mochitest.ini @@ -27,6 +27,7 @@ support-files = multi_sharedWorker_sharedWorker.js navigator_worker.js newError_worker.js + promise_worker.js recursion_worker.js recursiveOnerror_worker.js relativeLoad_import.js @@ -83,6 +84,7 @@ support-files = [test_multi_sharedWorker_lifetimes.html] [test_navigator.html] [test_newError.html] +[test_promise.html] [test_recursion.html] [test_recursiveOnerror.html] [test_relativeLoad.html] diff --git a/dom/workers/test/promise_worker.js b/dom/workers/test/promise_worker.js new file mode 100644 index 000000000000..d89b41d24b12 --- /dev/null +++ b/dom/workers/test/promise_worker.js @@ -0,0 +1,395 @@ +function ok(a, msg) { + dump("OK: " + !!a + " => " + a + " " + msg + "\n"); + postMessage({type: 'status', status: !!a, msg: a + ": " + msg }); +} + +function is(a, b, msg) { + dump("IS: " + (a===b) + " => " + a + " | " + b + " " + msg + "\n"); + postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg }); +} + +function isnot(a, b, msg) { + dump("ISNOT: " + (a!==b) + " => " + a + " | " + b + " " + msg + "\n"); + postMessage({type: 'status', status: a !== b, msg: a + " !== " + b + ": " + msg }); +} + +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 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 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 promiseAsync() { + var global = "foo"; + var f = new Promise(function(r1, r2) { + is(global, "foo", "Global should be foo"); + r1(42); + is(global, "foo", "Global should still be foo"); + setTimeout(function() { + is(global, "bar", "Global should still be bar!"); + runTest(); + }, 0); + }).then(function() { + global = "bar"; + }); + is(global, "foo", "Global should still be foo (2)"); +} + +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, "Catch 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 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 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 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(); +} + +var tests = [ + promiseResolve, + promiseReject, + promiseException, + promiseAsync, + promiseDoubleThen, + promiseThenException, + promiseThenCatchThen, + promiseRejectThenCatchThen, + promiseRejectThenCatchThen2, + promiseRejectThenCatchExceptionThen, + promiseThenCatchOrderingResolve, + promiseThenCatchOrderingReject, + promiseNestedPromise, + promiseNestedNestedPromise, + promiseWrongNestedPromise, + promiseLoop, + promiseStaticReject, + promiseStaticResolve, + promiseResolveNestedPromise, + promiseRejectNoHandler, +]; + +function runTest() { + if (!tests.length) { + postMessage({ type: 'finish' }); + return; + } + + var test = tests.shift(); + test(); +} + +onmessage = function() { + runTest(); +} diff --git a/dom/workers/test/test_promise.html b/dom/workers/test/test_promise.html new file mode 100644 index 000000000000..4548072c7080 --- /dev/null +++ b/dom/workers/test/test_promise.html @@ -0,0 +1,44 @@ + + + + + Test for Promise object in workers + + + + +

+ +

+
+
+
+
+