Bug 915233 - DOM Promises on Workers. r=baku,smaug,bz sr=sicking

This commit is contained in:
Nikhil Marathe 2013-11-05 20:05:21 -08:00
parent fc8d60de77
commit d6887b15cb
9 changed files with 677 additions and 51 deletions

View File

@ -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<Promise> 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<JS::Value> 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<Promise> mPromise;
};
class PromiseResolverMixin
{
public:
PromiseResolverMixin(Promise* aPromise,
JS::Handle<JS::Value> 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<JS::Value>::fromMarkedLocation(mValue.address()),
mState, Promise::SyncTask);
return NS_OK;
}
private:
nsRefPtr<Promise> mPromise;
JS::Heap<JS::Value> 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<JS::Value> 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<JS::Value> 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<JSObject*> aScope)
return PromiseBinding::Wrap(aCx, aScope, this);
}
/* static */ bool
Promise::PrefEnabled()
class PrefEnabledRunnable : public nsRunnable
{
return Preferences::GetBool("dom.promise.enabled", false);
}
public:
PrefEnabledRunnable() : mEnabled(false) {}
bool
Enabled()
{
return mEnabled;
}
nsresult
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<nsIThread> mainThread;
nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
NS_ENSURE_SUCCESS(rv, false);
nsRefPtr<PrefEnabledRunnable> r = new PrefEnabledRunnable();
// 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<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
nsCOMPtr<nsPIDOMWindow> 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> promise = new Promise(window);
@ -324,10 +439,13 @@ Promise::Constructor(const GlobalObject& aGlobal,
Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx,
JS::Handle<JS::Value> aValue, ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
nsCOMPtr<nsPIDOMWindow> window;
if (MOZ_LIKELY(NS_IsMainThread())) {
window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
}
nsRefPtr<Promise> promise = new Promise(window);
@ -341,10 +459,13 @@ Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx,
Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx,
JS::Handle<JS::Value> aValue, ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
nsCOMPtr<nsPIDOMWindow> window;
if (MOZ_LIKELY(NS_IsMainThread())) {
window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
}
nsRefPtr<Promise> 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<PromiseTask> task = new PromiseTask(this);
NS_DispatchToCurrentThread(task);
if (MOZ_LIKELY(NS_IsMainThread())) {
nsRefPtr<PromiseTask> task = new PromiseTask(this);
NS_DispatchToCurrentThread(task);
} else {
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
nsRefPtr<WorkerPromiseTask> 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<JS::Handle<JS::Value> > value(nsContentUtils::GetSafeJSContext(), mResult);
JSContext* cx = nsContentUtils::GetDefaultJSContextForThread();
JSAutoRequest ar(cx);
Optional<JS::Handle<JS::Value> > 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<nsPIDOMWindow> win =
do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(&mResult.toObject()));
// Remains null in case of worker.
nsCOMPtr<nsPIDOMWindow> 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<JS::Value> aValue,
// If the synchronous flag is unset, queue a task to process our
// accept callbacks with value.
if (aAsynchronous == AsyncTask) {
nsRefPtr<PromiseResolverTask> task =
new PromiseResolverTask(this, aValue, aState);
NS_DispatchToCurrentThread(task);
if (MOZ_LIKELY(NS_IsMainThread())) {
nsRefPtr<PromiseResolverTask> task =
new PromiseResolverTask(this, aValue, aState);
NS_DispatchToCurrentThread(task);
} else {
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
nsRefPtr<WorkerPromiseResolverTask> task =
new WorkerPromiseResolverTask(worker, this, aValue, aState);
worker->Dispatch(task);
}
return;
}

View File

@ -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,

View File

@ -74,7 +74,13 @@ void
ResolvePromiseCallback::Call(const Optional<JS::Handle<JS::Value> >& aValue)
{
// Run resolver's algorithm with value and the synchronous flag set.
AutoJSContext cx;
JSContext *cx = nsContentUtils::GetDefaultJSContextForThread();
Maybe<AutoCxPusher> pusher;
if (NS_IsMainThread()) {
pusher.construct(cx);
}
Maybe<JSAutoCompartment> ac;
EnterCompartment(ac, cx, aValue);
@ -109,7 +115,13 @@ void
RejectPromiseCallback::Call(const Optional<JS::Handle<JS::Value> >& aValue)
{
// Run resolver's algorithm with value and the synchronous flag set.
AutoJSContext cx;
JSContext *cx = nsContentUtils::GetDefaultJSContextForThread();
Maybe<AutoCxPusher> pusher;
if (NS_IsMainThread()) {
pusher.construct(cx);
}
Maybe<JSAutoCompartment> ac;
EnterCompartment(ac, cx, aValue);
@ -145,7 +157,17 @@ WrapperPromiseCallback::~WrapperPromiseCallback()
void
WrapperPromiseCallback::Call(const Optional<JS::Handle<JS::Value> >& 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<AutoCxPusher> pusher;
if (NS_IsMainThread()) {
pusher.construct(cx);
}
Maybe<JSAutoCompartment> ac;
EnterCompartment(ac, cx, aValue);

View File

@ -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
{

View File

@ -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() {

View File

@ -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<JSObject*> 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<JSObject*> aGlobal)
}
return true;
}
}

View File

@ -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]

View File

@ -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();
}

View File

@ -0,0 +1,44 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Promise object in workers</title>
<script type="text/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"></pre>
<script class="testbody" type="text/javascript">
function runTest() {
var worker = new Worker("promise_worker.js");
worker.onmessage = function(event) {
if (event.data.type == 'finish') {
SimpleTest.finish();
} else if (event.data.type == 'status') {
ok(event.data.status, event.data.msg);
}
}
worker.onerror = function(event) {
ok(false, "Worker had an error: " + event.data);
SimpleTest.finish();
};
worker.postMessage(true);
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
</script>
</pre>
</body>
</html>