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

This commit is contained in:
Nikhil Marathe 2013-11-24 11:26:07 -08:00
parent 6e076708d5
commit 2b1b97f534
12 changed files with 696 additions and 55 deletions

View File

@ -20,9 +20,13 @@
#include "nsPIDOMWindow.h"
#include "nsJSEnvironment.h"
#include "mozilla/dom/RuntimeService.h"
namespace mozilla {
namespace dom {
using namespace workers;
NS_IMPL_ISUPPORTS0(PromiseNativeHandler)
// PromiseTask
@ -54,36 +58,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
@ -92,18 +125,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
@ -166,23 +247,28 @@ Promise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
return PromiseBinding::Wrap(aCx, aScope, this);
}
/* static */ bool
Promise::PrefEnabled()
{
return Preferences::GetBool("dom.promise.enabled", false);
}
/* static */ bool
Promise::EnabledForScope(JSContext* aCx, JSObject* /* unused */)
{
if (NS_IsMainThread()) {
// No direct return so the chrome/certified app checks happen below.
if (Preferences::GetBool("dom.promise.enabled", false)) {
return true;
}
} else {
RuntimeService* service = RuntimeService::GetService();
MOZ_ASSERT(service);
// Can't just do return ... since the chrome worker/certified app checks
// below should also run.
if (service->PromiseEnabled()) {
return true;
}
}
// Enable if the pref is enabled or if we're chrome or if we're a
// certified app.
if (PrefEnabled()) {
return true;
}
// Note that we have no concept of a certified app in workers.
// XXXbz well, why not?
// FIXME(nsm): Remove these checks once promises are enabled by default.
if (!NS_IsMainThread()) {
return workers::GetWorkerPrivateFromContext(aCx)->UsesSystemPrincipal();
}
@ -278,10 +364,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);
@ -322,10 +413,13 @@ Promise::Constructor(const GlobalObject& aGlobal,
Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx,
const Optional<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);
@ -339,10 +433,13 @@ Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx,
Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx,
const Optional<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);
@ -413,8 +510,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;
}
}
@ -430,7 +534,7 @@ Promise::RunTask()
mResolveCallbacks.Clear();
mRejectCallbacks.Clear();
JSContext* cx = nsContentUtils::GetSafeJSContext();
JSContext* cx = nsContentUtils::GetDefaultJSContextForThread();
JSAutoRequest ar(cx);
JS::Rooted<JS::Value> value(cx, mResult);
@ -453,16 +557,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));
}
@ -539,9 +654,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

@ -29,10 +29,13 @@ class Promise MOZ_FINAL : public nsISupports,
public nsWrapperCache
{
friend class NativePromiseCallback;
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:
@ -42,7 +45,6 @@ public:
Promise(nsPIDOMWindow* aWindow);
~Promise();
static bool PrefEnabled();
static bool EnabledForScope(JSContext* aCx, JSObject* /* unused */);
void MaybeResolve(JSContext* aCx,

View File

@ -75,7 +75,13 @@ void
ResolvePromiseCallback::Call(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);
@ -110,7 +116,13 @@ void
RejectPromiseCallback::Call(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);
@ -146,7 +158,17 @@ WrapperPromiseCallback::~WrapperPromiseCallback()
void
WrapperPromiseCallback::Call(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.
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

@ -63,6 +63,15 @@ function promiseReject() {
});
}
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();
@ -474,7 +483,8 @@ var tests = [ promiseResolve, promiseReject,
promiseThenNoArg,
promiseThenUndefinedResolveFunction,
promiseThenNullResolveFunction,
promiseCatchNoArg
promiseCatchNoArg,
promiseRejectNoHandler,
];
function runTest() {

View File

@ -6,6 +6,7 @@
#include "WorkerPrivate.h"
#include "ChromeWorkerScope.h"
#include "File.h"
#include "RuntimeService.h"
#include "jsapi.h"
#include "js/OldDebugAPI.h"
@ -19,6 +20,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"
@ -54,6 +56,11 @@ WorkerPrivate::RegisterBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
return false;
}
bool promiseEnabled = false;
RuntimeService* service = RuntimeService::GetService();
MOZ_ASSERT(service);
promiseEnabled = service->PromiseEnabled();
// Init other paris-bindings.
if (!DOMExceptionBinding::GetConstructorObject(aCx, aGlobal) ||
!EventBinding::GetConstructorObject(aCx, aGlobal) ||
@ -61,6 +68,7 @@ WorkerPrivate::RegisterBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
!ImageDataBinding::GetConstructorObject(aCx, aGlobal) ||
!MessageEventBinding::GetConstructorObject(aCx, aGlobal) ||
!MessagePortBinding::GetConstructorObject(aCx, aGlobal) ||
(promiseEnabled && !PromiseBinding::GetConstructorObject(aCx, aGlobal)) ||
!TextDecoderBinding::GetConstructorObject(aCx, aGlobal) ||
!TextEncoderBinding::GetConstructorObject(aCx, aGlobal) ||
!XMLHttpRequestBinding_workers::GetConstructorObject(aCx, aGlobal) ||
@ -77,4 +85,4 @@ WorkerPrivate::RegisterBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
}
return true;
}
}

View File

@ -195,6 +195,24 @@ DumpPrefChanged(const char* aPrefName, void* aClosure)
}
#endif
#define PREF_PROMISE_ENABLED "dom.promise.enabled"
// Protected by RuntimeService::mMutex.
bool gPromiseEnabled;
static int
PromiseEnableChanged(const char* aPrefName, void* aClosure)
{
MOZ_ASSERT(NS_IsMainThread());
bool enabled = Preferences::GetBool(PREF_PROMISE_ENABLED, false);
Mutex* mutex = static_cast<Mutex*>(aClosure);
MutexAutoLock lock(*mutex);
gPromiseEnabled = enabled;
return 0;
}
class LiteralRebindingCString : public nsDependentCString
{
public:
@ -1628,6 +1646,10 @@ RuntimeService::Init()
PREF_DOM_WINDOW_DUMP_ENABLED,
&mMutex)) ||
#endif
NS_FAILED(Preferences::RegisterCallbackAndCall(
PromiseEnableChanged,
PREF_PROMISE_ENABLED,
&mMutex)) ||
NS_FAILED(Preferences::RegisterCallback(LoadJSContextOptions,
PREF_JS_OPTIONS_PREFIX,
nullptr)) ||
@ -1790,6 +1812,9 @@ RuntimeService::Cleanup()
NS_FAILED(Preferences::UnregisterCallback(LoadJSContextOptions,
PREF_WORKERS_OPTIONS_PREFIX,
nullptr)) ||
NS_FAILED(Preferences::UnregisterCallback(PromiseEnableChanged,
PREF_PROMISE_ENABLED,
&mMutex)) ||
#if DUMP_CONTROLLED_BY_PREF
NS_FAILED(Preferences::UnregisterCallback(DumpPrefChanged,
PREF_DOM_WINDOW_DUMP_ENABLED,
@ -2243,3 +2268,9 @@ RuntimeService::WorkersDumpEnabled()
return true;
#endif
}
bool
RuntimeService::PromiseEnabled()
{
return gPromiseEnabled;
}

View File

@ -244,6 +244,9 @@ public:
bool
WorkersDumpEnabled();
bool
PromiseEnabled();
private:
RuntimeService();
~RuntimeService();

View File

@ -8,6 +8,7 @@ TEST_DIRS += ['test']
# Public stuff.
EXPORTS.mozilla.dom += [
'RuntimeService.h',
'WorkerPrivate.h',
'WorkerScope.h',
]

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
@ -84,6 +85,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>