Bug 466254 - "Workers: Implement close() and onclose handlers". r+sr=jst.

This commit is contained in:
Ben Turner 2009-03-14 20:42:54 -07:00
parent d962874347
commit 24307fec9b
18 changed files with 1038 additions and 234 deletions

View File

@ -101,12 +101,15 @@ interface nsIWorkerGlobalScope : nsISupports
attribute nsIDOMEventListener onerror;
};
[scriptable, uuid(d30a2f61-86e2-434e-837f-4f1985efa865)]
[scriptable, uuid(5c55ea4b-e4ac-4ceb-bfeb-46bd5e521b8a)]
interface nsIWorkerScope : nsIWorkerGlobalScope
{
void postMessage(/* in JSObject aMessage */);
void close();
attribute nsIDOMEventListener onmessage;
attribute nsIDOMEventListener onclose;
};
[scriptable, uuid(b90b7561-b5e2-4545-84b0-280dbaaa94ea)]

View File

@ -119,6 +119,13 @@ static nsIXPCSecurityManager* gWorkerSecurityManager = nsnull;
PRUintn gJSContextIndex = BAD_TLS_INDEX;
static const char* sPrefsToWatch[] = {
"dom.max_script_run_time"
};
// The length of time the close handler is allowed to run in milliseconds.
static PRUint32 gWorkerCloseHandlerTimeoutMS = 10000;
/**
* Simple class to automatically destroy a JSContext to make error handling
* easier.
@ -191,7 +198,7 @@ public:
nsresult rv;
if (mWorker->mOuterHandler->HasListeners(errorStr)) {
if (mWorker->HasListeners(errorStr)) {
// Construct the error event.
nsString message;
rv = mScriptError->GetErrorMessage(message);
@ -212,7 +219,7 @@ public:
filename, lineno);
NS_ENSURE_SUCCESS(rv, rv);
event->SetTarget(mWorker);
event->SetTarget(static_cast<nsDOMWorkerMessageHandler*>(mWorker));
PRBool stopPropagation = PR_FALSE;
rv = mWorker->DispatchEvent(static_cast<nsDOMWorkerEvent*>(event),
@ -258,9 +265,11 @@ NS_IMPL_THREADSAFE_ISUPPORTS1(nsReportErrorRunnable, nsIRunnable)
/**
* Used to post an expired timeout to the correct worker.
*/
class nsDOMWorkerTimeoutRunnable : public nsRunnable
class nsDOMWorkerTimeoutRunnable : public nsIRunnable
{
public:
NS_DECL_ISUPPORTS
nsDOMWorkerTimeoutRunnable(nsDOMWorkerTimeout* aTimeout)
: mTimeout(aTimeout) { }
@ -271,6 +280,28 @@ protected:
nsRefPtr<nsDOMWorkerTimeout> mTimeout;
};
NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerTimeoutRunnable, nsIRunnable)
class nsDOMWorkerKillRunnable : public nsIRunnable
{
public:
NS_DECL_ISUPPORTS
nsDOMWorkerKillRunnable(nsDOMWorker* aWorker)
: mWorker(aWorker) { }
NS_IMETHOD Run() {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mWorker->Kill();
return NS_OK;
}
private:
nsRefPtr<nsDOMWorker> mWorker;
};
NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerKillRunnable, nsIRunnable)
/**
* This class exists to solve a particular problem: Calling Dispatch on a
* thread pool will always create a new thread to service the runnable as long
@ -280,29 +311,56 @@ protected:
* currently so we cheat by using a runnable that emulates a thread. The
* nsDOMThreadService's monitor protects the queue of events.
*/
class nsDOMWorkerRunnable : public nsRunnable
class nsDOMWorkerRunnable : public nsIRunnable
{
friend class nsDOMThreadService;
public:
nsDOMWorkerRunnable(nsDOMWorker* aWorker)
: mWorker(aWorker) { }
NS_DECL_ISUPPORTS
virtual ~nsDOMWorkerRunnable() {
nsCOMPtr<nsIRunnable> runnable;
while ((runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront()))) {
// Loop until all the runnables are dead.
}
nsDOMWorkerRunnable(nsDOMWorker* aWorker)
: mWorker(aWorker), mCloseTimeoutInterval(0), mKillWorkerWhenDone(PR_FALSE) {
}
void PutRunnable(nsIRunnable* aRunnable) {
NS_ASSERTION(aRunnable, "Null pointer!");
virtual ~nsDOMWorkerRunnable() {
ClearQueue();
}
NS_ADDREF(aRunnable);
void PutRunnable(nsIRunnable* aRunnable,
PRIntervalTime aTimeoutInterval,
PRBool aClearQueue) {
NS_ASSERTION(aRunnable, "Null pointer!");
// No need to enter the monitor because we should already be in it.
mRunnables.Push(aRunnable);
if (NS_LIKELY(!aTimeoutInterval)) {
NS_ADDREF(aRunnable);
mRunnables.Push(aRunnable);
}
else {
NS_ASSERTION(!mCloseRunnable, "More than one close runnable?!");
if (aClearQueue) {
ClearQueue();
}
mCloseRunnable = aRunnable;
mCloseTimeoutInterval = aTimeoutInterval;
mKillWorkerWhenDone = PR_TRUE;
}
}
void SetCloseRunnableTimeout(PRIntervalTime aTimeoutInterval) {
NS_ASSERTION(aTimeoutInterval, "No timeout specified!");
NS_ASSERTION(aTimeoutInterval!= PR_INTERVAL_NO_TIMEOUT, "Bad timeout!");
// No need to enter the monitor because we should already be in it.
NS_ASSERTION(mWorker->GetExpirationTime() == PR_INTERVAL_NO_TIMEOUT,
"Asked to set timeout on a runnable with no close handler!");
// This may actually overflow but we don't care - the worst that could
// happen is that the close handler could run for a slightly different
// amount of time and the spec leaves the time up to us anyway.
mWorker->SetExpirationTime(PR_IntervalNow() + aTimeoutInterval);
}
NS_IMETHOD Run() {
@ -320,9 +378,15 @@ public:
JS_SetContextPrivate(cx, mWorker);
PRBool killWorkerWhenDone;
// Tell the worker which context it will be using
if (mWorker->SetGlobalForContext(cx)) {
RunQueue(cx);
RunQueue(cx, &killWorkerWhenDone);
// Code in XPConnect assumes that the context's global object won't be
// replaced outside of a request.
JSAutoRequest ar(cx);
// Remove the global object from the context so that it might be garbage
// collected.
@ -330,21 +394,42 @@ public:
JS_SetContextPrivate(cx, NULL);
}
else {
// This is usually due to a parse error in the worker script...
JS_SetGlobalObject(cx, NULL);
JS_SetContextPrivate(cx, NULL);
{
// Code in XPConnect assumes that the context's global object won't be
// replaced outside of a request.
JSAutoRequest ar(cx);
// This is usually due to a parse error in the worker script...
JS_SetGlobalObject(cx, NULL);
JS_SetContextPrivate(cx, NULL);
}
nsAutoMonitor mon(gDOMThreadService->mMonitor);
killWorkerWhenDone = mKillWorkerWhenDone;
gDOMThreadService->WorkerComplete(this);
mon.NotifyAll();
}
if (killWorkerWhenDone) {
nsCOMPtr<nsIRunnable> runnable = new nsDOMWorkerKillRunnable(mWorker);
NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
protected:
void ClearQueue() {
nsCOMPtr<nsIRunnable> runnable;
while ((runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront()))) {
// Loop until all the runnables are dead.
}
}
void RunQueue(JSContext* aCx) {
void RunQueue(JSContext* aCx, PRBool* aCloseRunnableSet) {
PRBool operationCallbackTriggered = PR_FALSE;
while (1) {
@ -354,6 +439,19 @@ protected:
runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront());
if (!runnable && mCloseRunnable) {
PRIntervalTime expirationTime;
if (mCloseTimeoutInterval == PR_INTERVAL_NO_TIMEOUT) {
expirationTime = mCloseTimeoutInterval;
}
else {
expirationTime = PR_IntervalNow() + mCloseTimeoutInterval;
}
mWorker->SetExpirationTime(expirationTime);
runnable.swap(mCloseRunnable);
}
if (!runnable || mWorker->IsCanceled()) {
#ifdef PR_LOGGING
if (mWorker->IsCanceled()) {
@ -361,6 +459,7 @@ protected:
static_cast<void*>(mWorker.get())));
}
#endif
*aCloseRunnableSet = mKillWorkerWhenDone;
gDOMThreadService->WorkerComplete(this);
mon.NotifyAll();
return;
@ -381,6 +480,7 @@ protected:
runnable->Run();
}
NS_NOTREACHED("Shouldn't ever get here!");
}
// Set at construction
@ -388,8 +488,13 @@ protected:
// Protected by mMonitor
nsDeque mRunnables;
nsCOMPtr<nsIRunnable> mCloseRunnable;
PRIntervalTime mCloseTimeoutInterval;
PRBool mKillWorkerWhenDone;
};
NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerRunnable, nsIRunnable)
/*******************************************************************************
* JS environment function and callbacks
*/
@ -399,10 +504,6 @@ DOMWorkerOperationCallback(JSContext* aCx)
{
nsDOMWorker* worker = (nsDOMWorker*)JS_GetContextPrivate(aCx);
// Want a strong ref here to make sure that the monitor we wait on won't go
// away.
nsRefPtr<nsDOMWorkerPool> pool;
PRBool wasSuspended = PR_FALSE;
PRBool extraThreadAllowed = PR_FALSE;
jsrefcount suspendDepth = 0;
@ -437,16 +538,13 @@ DOMWorkerOperationCallback(JSContext* aCx)
}
if (!wasSuspended) {
// Make sure we can get the monitor we need to wait on. It's possible that
// the worker was canceled since we checked above.
// It's possible that the worker was canceled since we checked above.
if (worker->IsCanceled()) {
NS_WARNING("Tried to suspend on a pool that has gone away");
JS_ClearPendingException(aCx);
return JS_FALSE;
}
pool = worker->Pool();
// Make sure to suspend our request while we block like this, otherwise we
// prevent GC for everyone.
suspendDepth = JS_SuspendRequest(aCx);
@ -461,7 +559,7 @@ DOMWorkerOperationCallback(JSContext* aCx)
wasSuspended = PR_TRUE;
}
nsAutoMonitor mon(pool->Monitor());
nsAutoMonitor mon(worker->Pool()->Monitor());
mon.Wait();
}
}
@ -488,9 +586,15 @@ DOMWorkerErrorReporter(JSContext* aCx,
}
nsresult rv;
nsCOMPtr<nsIScriptError> scriptError =
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv,);
nsCOMPtr<nsIScriptError> scriptError;
{
// CreateInstance will lock, make sure we suspend our request!
JSAutoSuspendRequest ar(aCx);
scriptError = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv,);
}
const PRUnichar* message =
reinterpret_cast<const PRUnichar*>(aReport->ucmessage);
@ -511,28 +615,30 @@ DOMWorkerErrorReporter(JSContext* aCx,
if (aReport->errorNumber != JSMSG_SCRIPT_STACK_QUOTA &&
aReport->errorNumber != JSMSG_OVER_RECURSED) {
// Try the onerror handler for the worker's scope.
nsCOMPtr<nsIDOMEventListener> handler =
worker->mInnerHandler->GetOnXListener(NS_LITERAL_STRING("error"));
nsRefPtr<nsDOMWorkerScope> scope = worker->GetInnerScope();
NS_ASSERTION(scope, "Null scope!");
if (handler) {
PRBool hasListeners = scope->HasListeners(NS_LITERAL_STRING("error"));
if (hasListeners) {
nsRefPtr<nsDOMWorkerErrorEvent> event(new nsDOMWorkerErrorEvent());
if (event) {
rv = event->InitErrorEvent(NS_LITERAL_STRING("error"), PR_FALSE, PR_TRUE,
nsDependentString(message), filename,
aReport->lineno);
if (NS_SUCCEEDED(rv)) {
NS_ASSERTION(worker->GetInnerScope(), "Null scope!");
event->SetTarget(worker->GetInnerScope());
event->SetTarget(scope);
NS_ASSERTION(worker->mErrorHandlerRecursionCount >= 0,
"Bad recursion count logic!");
worker->mErrorHandlerRecursionCount++;
handler->HandleEvent(static_cast<nsDOMWorkerEvent*>(event));
PRBool preventDefaultCalled = PR_FALSE;
scope->DispatchEvent(static_cast<nsDOMWorkerEvent*>(event),
&preventDefaultCalled);
worker->mErrorHandlerRecursionCount--;
if (event->PreventDefaultCalled()) {
if (preventDefaultCalled) {
return;
}
}
@ -605,6 +711,8 @@ nsDOMThreadService::Init()
obs.forget(&gObserverService);
RegisterPrefCallbacks();
mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
@ -716,6 +824,8 @@ nsDOMThreadService::Cleanup()
if (gObserverService) {
gObserverService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
NS_RELEASE(gObserverService);
UnregisterPrefCallbacks();
}
// The thread pool holds a circular reference to this service through its
@ -746,15 +856,20 @@ nsDOMThreadService::Cleanup()
nsresult
nsDOMThreadService::Dispatch(nsDOMWorker* aWorker,
nsIRunnable* aRunnable)
nsIRunnable* aRunnable,
PRIntervalTime aTimeoutInterval,
PRBool aClearQueue)
{
NS_ASSERTION(aWorker, "Null pointer!");
NS_ASSERTION(aRunnable, "Null pointer!");
NS_ASSERTION(mThreadPool, "Dispatch called after 'xpcom-shutdown'!");
if (aWorker->IsCanceled()) {
LOG(("Will not dispatch runnable [0x%p] for canceled worker [0x%p]",
// Don't accept the runnable if the worker's close handler has been triggered
// (unless, of course, this is the close runnable as indicated by the non-0
// timeout value).
if (aWorker->IsClosing() && !aTimeoutInterval) {
LOG(("Will not dispatch runnable [0x%p] for closing worker [0x%p]",
static_cast<void*>(aRunnable), static_cast<void*>(aWorker)));
return NS_ERROR_NOT_AVAILABLE;
}
@ -764,14 +879,14 @@ nsDOMThreadService::Dispatch(nsDOMWorker* aWorker,
nsAutoMonitor mon(mMonitor);
if (mWorkersInProgress.Get(aWorker, getter_AddRefs(workerRunnable))) {
workerRunnable->PutRunnable(aRunnable);
workerRunnable->PutRunnable(aRunnable, aTimeoutInterval, aClearQueue);
return NS_OK;
}
workerRunnable = new nsDOMWorkerRunnable(aWorker);
NS_ENSURE_TRUE(workerRunnable, NS_ERROR_OUT_OF_MEMORY);
workerRunnable->PutRunnable(aRunnable);
workerRunnable->PutRunnable(aRunnable, aTimeoutInterval, PR_FALSE);
PRBool success = mWorkersInProgress.Put(aWorker, workerRunnable);
NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
@ -803,6 +918,23 @@ nsDOMThreadService::Dispatch(nsDOMWorker* aWorker,
return NS_OK;
}
void
nsDOMThreadService::SetWorkerTimeout(nsDOMWorker* aWorker,
PRIntervalTime aTimeoutInterval)
{
NS_ASSERTION(aWorker, "Null pointer!");
NS_ASSERTION(aTimeoutInterval, "No timeout specified!");
NS_ASSERTION(mThreadPool, "Dispatch called after 'xpcom-shutdown'!");
nsAutoMonitor mon(mMonitor);
nsRefPtr<nsDOMWorkerRunnable> workerRunnable;
if (mWorkersInProgress.Get(aWorker, getter_AddRefs(workerRunnable))) {
workerRunnable->SetCloseRunnableTimeout(aTimeoutInterval);
}
}
void
nsDOMThreadService::WorkerComplete(nsDOMWorkerRunnable* aRunnable)
{
@ -1263,3 +1395,47 @@ nsDOMThreadService::GetUserAgent(nsAString& aUserAgent)
"Shouldn't call this before we have loaded strings!");
aUserAgent.Assign(mUserAgent);
}
void
nsDOMThreadService::RegisterPrefCallbacks()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
for (PRUint32 index = 0; index < NS_ARRAY_LENGTH(sPrefsToWatch); index++) {
nsContentUtils::RegisterPrefCallback(sPrefsToWatch[index], PrefCallback,
nsnull);
PrefCallback(sPrefsToWatch[index], nsnull);
}
}
void
nsDOMThreadService::UnregisterPrefCallbacks()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
for (PRUint32 index = 0; index < NS_ARRAY_LENGTH(sPrefsToWatch); index++) {
nsContentUtils::UnregisterPrefCallback(sPrefsToWatch[index], PrefCallback,
nsnull);
}
}
// static
int
nsDOMThreadService::PrefCallback(const char* aPrefName,
void* aClosure)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
if(!strcmp(aPrefName, "dom.max_script_run_time")) {
// We assume atomic 32bit reads/writes. If this assumption doesn't hold on
// some wacky platform then the worst that could happen is that the close
// handler will run for a slightly different amount of time.
gWorkerCloseHandlerTimeoutMS =
nsContentUtils::GetIntPref(aPrefName, gWorkerCloseHandlerTimeoutMS);
}
return 0;
}
// static
PRUint32
nsDOMThreadService::GetWorkerCloseHandlerTimeoutMS()
{
return gWorkerCloseHandlerTimeoutMS;
}

View File

@ -124,7 +124,12 @@ private:
static void Shutdown();
nsresult Dispatch(nsDOMWorker* aWorker,
nsIRunnable* aRunnable);
nsIRunnable* aRunnable,
PRIntervalTime aTimeoutInterval = 0,
PRBool aClearQueue = PR_FALSE);
void SetWorkerTimeout(nsDOMWorker* aWorker,
PRIntervalTime aTimeoutInterval);
void WorkerComplete(nsDOMWorkerRunnable* aRunnable);
@ -148,6 +153,14 @@ private:
void GetPlatform(nsAString& aPlatform);
void GetUserAgent(nsAString& aUserAgent);
void RegisterPrefCallbacks();
void UnregisterPrefCallbacks();
static int PrefCallback(const char* aPrefName,
void* aClosure);
static PRUint32 GetWorkerCloseHandlerTimeoutMS();
// Our internal thread pool.
nsCOMPtr<nsIThreadPool> mThreadPool;

View File

@ -139,6 +139,12 @@ nsDOMWorkerFunctions::MakeTimeout(JSContext* aCx,
PRUint32 id = worker->NextTimeoutId();
if (worker->IsClosing()) {
// Timeouts won't run in the close handler, fake success and bail.
*aRval = INT_TO_JSVAL(id);
return JS_TRUE;
}
nsRefPtr<nsDOMWorkerTimeout> timeout = new nsDOMWorkerTimeout(worker, id);
if (!timeout) {
JS_ReportOutOfMemory(aCx);
@ -343,15 +349,9 @@ nsDOMWorkerFunctions::NewWorker(JSContext* aCx,
return JS_FALSE;
}
nsRefPtr<nsDOMWorkerPool> pool = worker->Pool();
if (!pool) {
JS_ReportError(aCx, "Couldn't get pool from worker!");
return JS_FALSE;
}
// This pointer is protected by our pool, but it is *not* threadsafe and must
// not be used in any way other than to pass it along to the Initialize call.
nsIScriptGlobalObject* owner = pool->ScriptGlobalObject();
nsIScriptGlobalObject* owner = worker->Pool()->ScriptGlobalObject();
if (!owner) {
JS_ReportError(aCx, "Couldn't get owner from pool!");
return JS_FALSE;
@ -524,16 +524,16 @@ GetStringForArgument(nsAString& aString,
nsDOMWorkerScope::nsDOMWorkerScope(nsDOMWorker* aWorker)
: mWorker(aWorker),
mWrappedNative(nsnull),
mHasOnerror(PR_FALSE)
{
NS_ASSERTION(aWorker, "Null pointer!");
}
NS_IMPL_THREADSAFE_ISUPPORTS5(nsDOMWorkerScope, nsIWorkerScope,
nsIWorkerGlobalScope,
nsIDOMEventTarget,
nsIXPCScriptable,
nsIClassInfo)
NS_IMPL_ISUPPORTS_INHERITED3(nsDOMWorkerScope, nsDOMWorkerMessageHandler,
nsIWorkerScope,
nsIWorkerGlobalScope,
nsIXPCScriptable)
NS_IMPL_CI_INTERFACE_GETTER4(nsDOMWorkerScope, nsIWorkerScope,
nsIWorkerGlobalScope,
@ -563,6 +563,9 @@ nsDOMWorkerScope::GetHelperForLanguage(PRUint32 aLanguage,
#define XPC_MAP_CLASSNAME nsDOMWorkerScope
#define XPC_MAP_QUOTED_CLASSNAME "DedicatedWorkerGlobalScope"
#define XPC_MAP_WANT_POSTCREATE
#define XPC_MAP_WANT_TRACE
#define XPC_MAP_WANT_FINALIZE
#define XPC_MAP_FLAGS \
nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY | \
@ -577,6 +580,45 @@ nsDOMWorkerScope::GetHelperForLanguage(PRUint32 aLanguage,
#include "xpc_map_end.h"
NS_IMETHODIMP
nsDOMWorkerScope::PostCreate(nsIXPConnectWrappedNative* aWrapper,
JSContext* /* aCx */,
JSObject* /* aObj */)
{
NS_ASSERTION(!mWrappedNative, "Already got a wrapper?!");
mWrappedNative = aWrapper;
return NS_OK;
}
NS_IMETHODIMP
nsDOMWorkerScope::Trace(nsIXPConnectWrappedNative* /* aWrapper */,
JSTracer* aTracer,
JSObject* /*aObj */)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
nsDOMWorkerMessageHandler::Trace(aTracer);
return NS_OK;
}
NS_IMETHODIMP
nsDOMWorkerScope::Finalize(nsIXPConnectWrappedNative* /* aWrapper */,
JSContext* /* aCx */,
JSObject* /* aObj */)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
ClearAllListeners();
mWrappedNative = nsnull;
return NS_OK;
}
already_AddRefed<nsIXPConnectWrappedNative>
nsDOMWorkerScope::GetWrappedNative()
{
nsCOMPtr<nsIXPConnectWrappedNative> wrappedNative = mWrappedNative;
NS_ASSERTION(wrappedNative, "Null wrapped native!");
return wrappedNative.forget();
}
NS_IMETHODIMP
nsDOMWorkerScope::AddProperty(nsIXPConnectWrappedNative* aWrapper,
JSContext* aCx,
@ -685,7 +727,7 @@ nsDOMWorkerScope::GetOnerror(nsIDOMEventListener** aOnerror)
}
nsCOMPtr<nsIDOMEventListener> listener =
mWorker->mInnerHandler->GetOnXListener(NS_LITERAL_STRING("error"));
GetOnXListener(NS_LITERAL_STRING("error"));
listener.forget(aOnerror);
return NS_OK;
@ -702,8 +744,7 @@ nsDOMWorkerScope::SetOnerror(nsIDOMEventListener* aOnerror)
mHasOnerror = PR_TRUE;
return mWorker->mInnerHandler->SetOnXListener(NS_LITERAL_STRING("error"),
aOnerror);
return SetOnXListener(NS_LITERAL_STRING("error"), aOnerror);
}
NS_IMETHODIMP
@ -724,6 +765,14 @@ nsDOMWorkerScope::PostMessage(/* JSObject aMessage */)
return mWorker->PostMessageInternal(message, isJSON, isPrimitive, PR_FALSE);
}
NS_IMETHODIMP
nsDOMWorkerScope::Close()
{
NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
return mWorker->Close();
}
NS_IMETHODIMP
nsDOMWorkerScope::GetOnmessage(nsIDOMEventListener** aOnmessage)
{
@ -735,7 +784,7 @@ nsDOMWorkerScope::GetOnmessage(nsIDOMEventListener** aOnmessage)
}
nsCOMPtr<nsIDOMEventListener> listener =
mWorker->mInnerHandler->GetOnXListener(NS_LITERAL_STRING("message"));
GetOnXListener(NS_LITERAL_STRING("message"));
listener.forget(aOnmessage);
return NS_OK;
@ -750,8 +799,39 @@ nsDOMWorkerScope::SetOnmessage(nsIDOMEventListener* aOnmessage)
return NS_ERROR_ABORT;
}
return mWorker->mInnerHandler->SetOnXListener(NS_LITERAL_STRING("message"),
aOnmessage);
return SetOnXListener(NS_LITERAL_STRING("message"), aOnmessage);
}
NS_IMETHODIMP
nsDOMWorkerScope::GetOnclose(nsIDOMEventListener** aOnclose)
{
NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_ARG_POINTER(aOnclose);
if (mWorker->IsCanceled()) {
return NS_ERROR_ABORT;
}
nsCOMPtr<nsIDOMEventListener> listener =
GetOnXListener(NS_LITERAL_STRING("close"));
listener.forget(aOnclose);
return NS_OK;
}
NS_IMETHODIMP
nsDOMWorkerScope::SetOnclose(nsIDOMEventListener* aOnclose)
{
NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
if (mWorker->IsCanceled()) {
return NS_ERROR_ABORT;
}
nsresult rv = SetOnXListener(NS_LITERAL_STRING("close"), aOnclose);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
@ -765,8 +845,8 @@ nsDOMWorkerScope::AddEventListener(const nsAString& aType,
return NS_ERROR_ABORT;
}
return mWorker->mInnerHandler->AddEventListener(aType, aListener,
aUseCapture);
return nsDOMWorkerMessageHandler::AddEventListener(aType, aListener,
aUseCapture);
}
NS_IMETHODIMP
@ -780,8 +860,8 @@ nsDOMWorkerScope::RemoveEventListener(const nsAString& aType,
return NS_ERROR_ABORT;
}
return mWorker->mInnerHandler->RemoveEventListener(aType, aListener,
aUseCapture);
return nsDOMWorkerMessageHandler::RemoveEventListener(aType, aListener,
aUseCapture);
}
NS_IMETHODIMP
@ -794,7 +874,7 @@ nsDOMWorkerScope::DispatchEvent(nsIDOMEvent* aEvent,
return NS_ERROR_ABORT;
}
return mWorker->mInnerHandler->DispatchEvent(aEvent, _retval);
return nsDOMWorkerMessageHandler::DispatchEvent(aEvent, _retval);
}
class nsWorkerHoldingRunnable : public nsIRunnable
@ -809,6 +889,10 @@ public:
return NS_OK;
}
void ReplaceWrappedNative(nsIXPConnectWrappedNative* aWrappedNative) {
mWorkerWN = aWrappedNative;
}
protected:
virtual ~nsWorkerHoldingRunnable() { }
@ -853,8 +937,8 @@ public:
}
nsCOMPtr<nsIDOMEventTarget> target = mToInner ?
static_cast<nsIDOMEventTarget*>(mWorker->GetInnerScope()) :
static_cast<nsIDOMEventTarget*>(mWorker);
static_cast<nsDOMWorkerMessageHandler*>(mWorker->GetInnerScope()) :
static_cast<nsDOMWorkerMessageHandler*>(mWorker);
NS_ASSERTION(target, "Null target!");
NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
@ -870,24 +954,6 @@ protected:
NS_IMPL_ISUPPORTS_INHERITED0(nsDOMFireEventRunnable, nsWorkerHoldingRunnable)
class nsCancelDOMWorkerRunnable : public nsWorkerHoldingRunnable
{
NS_DECL_ISUPPORTS_INHERITED
nsCancelDOMWorkerRunnable(nsDOMWorker* aWorker)
: nsWorkerHoldingRunnable(aWorker) { }
NS_IMETHOD Run() {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
if (!mWorker->IsCanceled()) {
mWorker->Cancel();
}
return NS_OK;
}
};
NS_IMPL_ISUPPORTS_INHERITED0(nsCancelDOMWorkerRunnable, nsWorkerHoldingRunnable)
// Standard NS_IMPL_THREADSAFE_ADDREF without the logging stuff (since this
// class is made to be inherited anyway).
NS_IMETHODIMP_(nsrefcnt)
@ -958,10 +1024,10 @@ nsDOMWorker::nsDOMWorker(nsDOMWorker* aParent,
mFeatureSuspendDepth(0),
mWrappedNative(nsnull),
mErrorHandlerRecursionCount(0),
mCanceled(PR_FALSE),
mStatus(eRunning),
mExpirationTime(0),
mSuspended(PR_FALSE),
mCompileAttempted(PR_FALSE),
mTerminated(PR_FALSE)
mCompileAttempted(PR_FALSE)
{
#ifdef DEBUG
PRBool mainThread = NS_IsMainThread();
@ -1010,16 +1076,17 @@ nsDOMWorker::NewWorker(nsISupports** aNewObject)
return NS_OK;
}
NS_IMPL_THREADSAFE_ADDREF(nsDOMWorker)
NS_IMPL_THREADSAFE_RELEASE(nsDOMWorker)
NS_IMPL_ADDREF_INHERITED(nsDOMWorker, nsDOMWorkerMessageHandler)
NS_IMPL_RELEASE_INHERITED(nsDOMWorker, nsDOMWorkerMessageHandler)
NS_INTERFACE_MAP_BEGIN(nsDOMWorker)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWorker)
NS_INTERFACE_MAP_ENTRY(nsIWorker)
NS_INTERFACE_MAP_ENTRY(nsIAbstractWorker)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventTarget, nsDOMWorkerMessageHandler)
NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
NS_INTERFACE_MAP_ENTRY(nsIJSNativeInitializer)
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
foundInterface = static_cast<nsIClassInfo*>(&sDOMWorkerClassInfo);
} else
@ -1049,6 +1116,7 @@ nsDOMWorker::PostCreate(nsIXPConnectWrappedNative* aWrapper,
JSContext* /* aCx */,
JSObject* /* aObj */)
{
nsAutoLock lock(mLock);
mWrappedNative = aWrapper;
return NS_OK;
}
@ -1060,16 +1128,14 @@ nsDOMWorker::Trace(nsIXPConnectWrappedNative* /* aWrapper */,
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
if (!IsCanceled()) {
if (mGlobal) {
JS_SET_TRACING_DETAILS(aTracer, nsnull, this, 0);
JS_CallTracer(aTracer, mGlobal, JSTRACE_OBJECT);
}
// We should never get null handlers here if our call to Initialize succeeded.
NS_ASSERTION(mInnerHandler && mOuterHandler, "Shouldn't be possible!");
PRBool canceled = PR_FALSE;
{
nsAutoLock lock(mLock);
canceled = mStatus == eKilled;
}
mInnerHandler->Trace(aTracer);
mOuterHandler->Trace(aTracer);
if (!canceled) {
nsDOMWorkerMessageHandler::Trace(aTracer);
}
return NS_OK;
@ -1083,15 +1149,17 @@ nsDOMWorker::Finalize(nsIXPConnectWrappedNative* /* aWrapper */,
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
// Don't leave dangling JSObject pointers in our handlers!
mInnerHandler->ClearAllListeners();
mOuterHandler->ClearAllListeners();
ClearAllListeners();
// Clear our wrapped native now that it has died.
mWrappedNative = nsnull;
{
nsAutoLock lock(mLock);
mWrappedNative = nsnull;
}
// We no longer need to keep our inner scope.
mGlobal = NULL;
mInnerScope = nsnull;
// Do this *after* we null out mWrappedNative so that we don't hand out a
// freed pointer.
TerminateInternal(PR_TRUE);
// And we can let our parent die now too.
mParent = nsnull;
@ -1135,12 +1203,6 @@ nsDOMWorker::InitializeInternal(nsIScriptGlobalObject* aOwner,
mLock = nsAutoLock::NewLock("nsDOMWorker::mLock");
NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
mInnerHandler = new nsDOMWorkerMessageHandler();
NS_ENSURE_TRUE(mInnerHandler, NS_ERROR_OUT_OF_MEMORY);
mOuterHandler = new nsDOMWorkerMessageHandler();
NS_ENSURE_TRUE(mOuterHandler, NS_ERROR_OUT_OF_MEMORY);
NS_ASSERTION(!mGlobal, "Already got a global?!");
nsIXPConnect* xpc = nsContentUtils::XPConnect();
@ -1153,6 +1215,16 @@ nsDOMWorker::InitializeInternal(nsIScriptGlobalObject* aOwner,
NS_ASSERTION(mWrappedNative, "Post-create hook should have set this!");
mKillTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIThread> mainThread;
rv = NS_GetMainThread(getter_AddRefs(mainThread));
NS_ENSURE_SUCCESS(rv, rv);
rv = mKillTimer->SetTarget(mainThread);
NS_ENSURE_SUCCESS(rv, rv);
// This is pretty cool - all we have to do to get our script executed is to
// pass a no-op runnable to the thread service and it will make sure we have
// a context and global object.
@ -1177,30 +1249,195 @@ nsDOMWorker::InitializeInternal(nsIScriptGlobalObject* aOwner,
void
nsDOMWorker::Cancel()
{
// Called by the pool when the window that created us is being torn down. Must
// always be on the main thread.
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mCanceled = PR_TRUE;
CancelFeatures();
// We set the eCanceled status to indicate this. It behaves just like the
// eTerminated status (canceled while close runnable is unscheduled, not
// canceled while close runnable is running) except that it always reports
// that it is canceled when running on the main thread. This status trumps all
// others (except eKilled). Have to do this because the window that created
// us has gone away and had its scope cleared so XPConnect will assert all
// over the place if we try to run anything.
PRBool enforceTimeout = PR_FALSE;
{
nsAutoLock lock(mLock);
NS_ASSERTION(mStatus != eCanceled, "Canceled more than once?!");
if (mStatus == eKilled) {
return;
}
Status oldStatus = mStatus;
mStatus = eCanceled;
if (oldStatus != eRunning) {
enforceTimeout = PR_TRUE;
}
}
PRUint32 timeoutMS = nsDOMThreadService::GetWorkerCloseHandlerTimeoutMS();
#ifdef DEBUG
nsresult rv;
#endif
if (enforceTimeout) {
// Tell the thread service to enforce a timeout on the close handler that
// is already scheduled.
nsDOMThreadService::get()->
SetWorkerTimeout(this, PR_MillisecondsToInterval(timeoutMS));
#ifdef DEBUG
rv =
#endif
mKillTimer->InitWithCallback(this, timeoutMS, nsITimer::TYPE_ONE_SHOT);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to init kill timer!");
return;
}
#ifdef DEBUG
rv =
#endif
FireCloseRunnable(PR_MillisecondsToInterval(timeoutMS), PR_TRUE, PR_FALSE);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to fire close runnable!");
}
void
nsDOMWorker::Kill()
{
// Cancel all features and set our status to eKilled. This should only be
// called on the main thread by the thread service or our kill timer to
// indicate that the worker's close handler has run (or timed out).
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(IsClosing(), "Close handler should have run by now!");
// If the close handler finished before our kill timer then we don't need it
// any longer.
if (mKillTimer) {
mKillTimer->Cancel();
mKillTimer = nsnull;
}
PRUint32 count, index;
nsAutoTArray<nsRefPtr<nsDOMWorkerFeature>, 20> features;
{
nsAutoLock lock(mLock);
if (mStatus == eKilled) {
NS_ASSERTION(mFeatures.Length() == 0, "Features added after killed!");
return;
}
mStatus = eKilled;
count = mFeatures.Length();
for (index = 0; index < count; index++) {
nsDOMWorkerFeature*& feature = mFeatures[index];
#ifdef DEBUG
nsRefPtr<nsDOMWorkerFeature>* newFeature =
#endif
features.AppendElement(feature);
NS_ASSERTION(newFeature, "Out of memory!");
feature->FreeToDie(PR_TRUE);
}
mFeatures.Clear();
}
count = features.Length();
for (index = 0; index < count; index++) {
features[index]->Cancel();
}
// We no longer need to keep our inner scope.
mInnerScope = nsnull;
mScopeWN = nsnull;
mGlobal = NULL;
}
void
nsDOMWorker::Suspend()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!mSuspended, "Suspended more than once!");
mSuspended = PR_TRUE;
SuspendFeatures();
PRBool shouldSuspendFeatures;
{
nsAutoLock lock(mLock);
NS_ASSERTION(!mSuspended, "Suspended more than once!");
shouldSuspendFeatures = !mSuspended;
mSuspended = PR_TRUE;
}
if (shouldSuspendFeatures) {
SuspendFeatures();
}
}
void
nsDOMWorker::Resume()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(mSuspended, "Not suspended!");
mSuspended = PR_FALSE;
ResumeFeatures();
PRBool shouldResumeFeatures;
{
nsAutoLock lock(mLock);
#ifdef DEBUG
// Should only have a mismatch if GC or Cancel happened while suspended.
if (!mSuspended) {
NS_ASSERTION(mStatus == eCanceled ||
(mStatus == eTerminated && !mWrappedNative),
"Not suspended!");
}
#endif
shouldResumeFeatures = mSuspended;
mSuspended = PR_FALSE;
}
if (shouldResumeFeatures) {
ResumeFeatures();
}
}
PRBool
nsDOMWorker::IsCanceled()
{
nsAutoLock lock(mLock);
// There are several conditions under which we want JS code to abort and all
// other functions to bail:
// 1. If we've already run our close handler then we are canceled forevermore.
// 2. If we've been terminated then we want to pretend to be canceled until
// our close handler is scheduled and running.
// 3. If we've been canceled then we pretend to be canceled until the close
// handler has been scheduled.
// 4. If the close handler has run for longer than the allotted time then we
// should be canceled as well.
// 5. If we're on the main thread then we'll pretend to be canceled if the
// user has navigated away from the page.
return mStatus == eKilled ||
(mStatus == eTerminated && !mExpirationTime) ||
(mStatus == eCanceled && !mExpirationTime) ||
(mExpirationTime && mExpirationTime != PR_INTERVAL_NO_TIMEOUT &&
mExpirationTime <= PR_IntervalNow()) ||
(mStatus == eCanceled && NS_IsMainThread());
}
PRBool
nsDOMWorker::IsClosing()
{
nsAutoLock lock(mLock);
return mStatus != eRunning;
}
PRBool
nsDOMWorker::IsSuspended()
{
nsAutoLock lock(mLock);
return mSuspended;
}
nsresult
@ -1316,10 +1553,14 @@ nsDOMWorker::CompileGlobalObject(JSContext* aCx)
PRBool success = JS_DefineFunctions(aCx, global, gDOMWorkerFunctions);
NS_ENSURE_TRUE(success, PR_FALSE);
// From here on out we have to remember to null mGlobal and mInnerScope if
// something fails!
// From here on out we have to remember to null mGlobal, mInnerScope, and
// mScopeWN if something fails! We really don't need to hang on to mGlobal
// as long as we have mScopeWN, but it saves us a virtual call every time the
// worker is scheduled. Meh.
mGlobal = global;
mInnerScope = scope;
mScopeWN = scope->GetWrappedNative();
NS_ASSERTION(mScopeWN, "Should have a wrapped native here!");
nsRefPtr<nsDOMWorkerScriptLoader> loader =
new nsDOMWorkerScriptLoader(this);
@ -1327,6 +1568,7 @@ nsDOMWorker::CompileGlobalObject(JSContext* aCx)
if (!loader) {
mGlobal = NULL;
mInnerScope = nsnull;
mScopeWN = nsnull;
return PR_FALSE;
}
@ -1334,6 +1576,7 @@ nsDOMWorker::CompileGlobalObject(JSContext* aCx)
if (NS_FAILED(rv)) {
mGlobal = NULL;
mInnerScope = nsnull;
mScopeWN = nsnull;
return PR_FALSE;
}
@ -1344,6 +1587,7 @@ nsDOMWorker::CompileGlobalObject(JSContext* aCx)
if (NS_FAILED(rv)) {
mGlobal = NULL;
mInnerScope = nsnull;
mScopeWN = nsnull;
return PR_FALSE;
}
@ -1362,8 +1606,11 @@ nsDOMWorker::SetPool(nsDOMWorkerPool* aPool)
already_AddRefed<nsIXPConnectWrappedNative>
nsDOMWorker::GetWrappedNative()
{
nsCOMPtr<nsIXPConnectWrappedNative> wrappedNative = mWrappedNative;
NS_ASSERTION(wrappedNative, "Null wrapped native!");
nsCOMPtr<nsIXPConnectWrappedNative> wrappedNative;
{
nsAutoLock lock(mLock);
wrappedNative = mWrappedNative;
}
return wrappedNative.forget();
}
@ -1380,6 +1627,11 @@ nsDOMWorker::AddFeature(nsDOMWorkerFeature* aFeature,
nsAutoLock lock(mLock);
if (mStatus == eKilled) {
// No features may be added after we've been canceled. Sorry.
return NS_ERROR_FAILURE;
}
nsDOMWorkerFeature** newFeature = mFeatures.AppendElement(aFeature);
NS_ENSURE_TRUE(newFeature, NS_ERROR_OUT_OF_MEMORY);
@ -1491,37 +1743,110 @@ nsDOMWorker::ResumeFeatures()
}
}
void
nsDOMWorker::CancelFeatures()
nsresult
nsDOMWorker::FireCloseRunnable(PRIntervalTime aTimeoutInterval,
PRBool aClearQueue,
PRBool aFromFinalize)
{
NS_ASSERTION(IsCanceled(), "More items can still be added!");
PRUint32 count, index;
nsAutoTArray<nsRefPtr<nsDOMWorkerFeature>, 20> features;
// Resume the worker (but not its features) if we're currently suspended. This
// should only ever happen if we are being called from Cancel (page falling
// out of bfcache or quitting) or Finalize, in which case all we really want
// to do is unblock the waiting thread.
PRBool wakeUp;
{
nsAutoLock lock(mLock);
NS_ASSERTION(mExpirationTime == 0,
"Close runnable should not be scheduled already!");
count = mFeatures.Length();
for (index = 0; index < count; index++) {
nsDOMWorkerFeature*& feature = mFeatures[index];
#ifdef DEBUG
nsRefPtr<nsDOMWorkerFeature>* newFeature =
#endif
features.AppendElement(feature);
NS_ASSERTION(newFeature, "Out of memory!");
feature->FreeToDie(PR_TRUE);
if ((wakeUp = mSuspended)) {
NS_ASSERTION(mStatus == eCanceled ||
(mStatus == eTerminated && aFromFinalize),
"How can this happen otherwise?!");
mSuspended = PR_FALSE;
}
mFeatures.Clear();
}
count = features.Length();
for (index = 0; index < count; index++) {
features[index]->Cancel();
if (wakeUp) {
nsAutoMonitor mon(mPool->Monitor());
mon.NotifyAll();
}
nsRefPtr<nsDOMWorkerEvent> event = new nsDOMWorkerEvent();
NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
nsresult rv =
event->InitEvent(NS_LITERAL_STRING("close"), PR_FALSE, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<nsDOMFireEventRunnable> runnable =
new nsDOMFireEventRunnable(this, event, PR_TRUE);
NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
// Our worker has been collected and we want to keep the inner scope alive,
// so pass that along in the runnable.
if (aFromFinalize) {
NS_ASSERTION(mScopeWN, "This shouldn't be null!");
runnable->ReplaceWrappedNative(mScopeWN);
}
rv = nsDOMThreadService::get()->Dispatch(this, runnable, aTimeoutInterval,
aClearQueue);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsDOMWorker::Close()
{
{
nsAutoLock lock(mLock);
NS_ASSERTION(mStatus != eKilled, "This should be impossible!");
if (mStatus != eRunning) {
return NS_OK;
}
mStatus = eClosed;
}
nsresult rv = FireCloseRunnable(PR_INTERVAL_NO_TIMEOUT, PR_FALSE, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsDOMWorker::TerminateInternal(PRBool aFromFinalize)
{
{
nsAutoLock lock(mLock);
#ifdef DEBUG
if (!aFromFinalize) {
NS_ASSERTION(mStatus != eCanceled, "Shouldn't be able to get here!");
}
#endif
if (mStatus == eRunning) {
// This is the beginning of the close process, fire an event and prevent
// any other close events from being generated.
mStatus = eTerminated;
}
else {
if (mStatus == eClosed) {
// The worker was previously closed which means that an expiration time
// might not be set. Setting the status to eTerminated will force the
// worker to jump to its close handler.
mStatus = eTerminated;
}
// No need to fire another close handler, it has already been done.
return NS_OK;
}
}
nsresult rv = FireCloseRunnable(PR_INTERVAL_NO_TIMEOUT, PR_TRUE,
aFromFinalize);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
already_AddRefed<nsDOMWorker>
@ -1531,14 +1856,80 @@ nsDOMWorker::GetParent()
return parent.forget();
}
void
nsDOMWorker::SetExpirationTime(PRIntervalTime aExpirationTime)
{
{
nsAutoLock lock(mLock);
NS_ASSERTION(mStatus != eRunning && mStatus != eKilled, "Bad status!");
NS_ASSERTION(!mExpirationTime || mExpirationTime == PR_INTERVAL_NO_TIMEOUT,
"Overwriting a timeout that was previously set!");
mExpirationTime = aExpirationTime;
}
}
#ifdef DEBUG
PRIntervalTime
nsDOMWorker::GetExpirationTime()
{
nsAutoLock lock(mLock);
return mExpirationTime;
}
#endif
NS_IMETHODIMP
nsDOMWorker::AddEventListener(const nsAString& aType,
nsIDOMEventListener* aListener,
PRBool aUseCapture)
{
NS_ASSERTION(mWrappedNative, "Called after Finalize!");
if (IsCanceled()) {
return NS_OK;
}
return nsDOMWorkerMessageHandler::AddEventListener(aType, aListener,
aUseCapture);
}
NS_IMETHODIMP
nsDOMWorker::RemoveEventListener(const nsAString& aType,
nsIDOMEventListener* aListener,
PRBool aUseCapture)
{
if (IsCanceled()) {
return NS_OK;
}
return nsDOMWorkerMessageHandler::RemoveEventListener(aType, aListener,
aUseCapture);
}
NS_IMETHODIMP
nsDOMWorker::DispatchEvent(nsIDOMEvent* aEvent,
PRBool* _retval)
{
if (IsCanceled()) {
return NS_OK;
}
return nsDOMWorkerMessageHandler::DispatchEvent(aEvent, _retval);
}
/**
* See nsIWorker
*/
NS_IMETHODIMP
nsDOMWorker::PostMessage(/* JSObject aMessage */)
{
if (mTerminated) {
return NS_OK;
{
nsAutoLock lock(mLock);
// There's no reason to dispatch this message after the close handler has
// been triggered since it will never be allowed to run.
if (mStatus != eRunning) {
return NS_OK;
}
}
nsString message;
@ -1558,8 +1949,13 @@ nsDOMWorker::GetOnerror(nsIDOMEventListener** aOnerror)
{
NS_ENSURE_ARG_POINTER(aOnerror);
if (IsCanceled()) {
*aOnerror = nsnull;
return NS_OK;
}
nsCOMPtr<nsIDOMEventListener> listener =
mOuterHandler->GetOnXListener(NS_LITERAL_STRING("error"));
GetOnXListener(NS_LITERAL_STRING("error"));
listener.forget(aOnerror);
return NS_OK;
@ -1571,7 +1967,12 @@ nsDOMWorker::GetOnerror(nsIDOMEventListener** aOnerror)
NS_IMETHODIMP
nsDOMWorker::SetOnerror(nsIDOMEventListener* aOnerror)
{
return mOuterHandler->SetOnXListener(NS_LITERAL_STRING("error"), aOnerror);
NS_ASSERTION(mWrappedNative, "Called after Finalize!");
if (IsCanceled()) {
return NS_OK;
}
return SetOnXListener(NS_LITERAL_STRING("error"), aOnerror);
}
/**
@ -1582,8 +1983,13 @@ nsDOMWorker::GetOnmessage(nsIDOMEventListener** aOnmessage)
{
NS_ENSURE_ARG_POINTER(aOnmessage);
if (IsCanceled()) {
*aOnmessage = nsnull;
return NS_OK;
}
nsCOMPtr<nsIDOMEventListener> listener =
mOuterHandler->GetOnXListener(NS_LITERAL_STRING("message"));
GetOnXListener(NS_LITERAL_STRING("message"));
listener.forget(aOnmessage);
return NS_OK;
@ -1595,21 +2001,24 @@ nsDOMWorker::GetOnmessage(nsIDOMEventListener** aOnmessage)
NS_IMETHODIMP
nsDOMWorker::SetOnmessage(nsIDOMEventListener* aOnmessage)
{
return mOuterHandler->SetOnXListener(NS_LITERAL_STRING("message"),
aOnmessage);
NS_ASSERTION(mWrappedNative, "Called after Finalize!");
if (IsCanceled()) {
return NS_OK;
}
return SetOnXListener(NS_LITERAL_STRING("message"), aOnmessage);
}
NS_IMETHODIMP
nsDOMWorker::Terminate()
{
if (mCanceled || mTerminated) {
return NS_OK;
}
mTerminated = PR_TRUE;
nsCOMPtr<nsIRunnable> runnable = new nsCancelDOMWorkerRunnable(this);
NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
return NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
return TerminateInternal(PR_FALSE);
}
NS_IMETHODIMP
nsDOMWorker::Notify(nsITimer* aTimer)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
Kill();
return NS_OK;
}

View File

@ -43,6 +43,7 @@
#include "nsIDOMWorkers.h"
#include "nsIJSNativeInitializer.h"
#include "nsIPrincipal.h"
#include "nsITimer.h"
#include "nsIURI.h"
#include "nsIXPCScriptable.h"
@ -66,39 +67,45 @@ class nsIEventTarget;
class nsIScriptGlobalObject;
class nsIXPConnectWrappedNative;
class nsDOMWorkerScope : public nsIWorkerScope,
public nsIDOMEventTarget,
public nsIXPCScriptable,
public nsIClassInfo
class nsDOMWorkerScope : public nsDOMWorkerMessageHandler,
public nsIWorkerScope,
public nsIXPCScriptable
{
friend class nsDOMWorker;
typedef nsresult (NS_STDCALL nsDOMWorkerScope::*SetListenerFunc)
(nsIDOMEventListener*);
public:
NS_DECL_ISUPPORTS
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIDOMEVENTTARGET
NS_DECL_NSIWORKERGLOBALSCOPE
NS_DECL_NSIWORKERSCOPE
NS_DECL_NSIDOMEVENTTARGET
NS_DECL_NSIXPCSCRIPTABLE
NS_DECL_NSICLASSINFO
nsDOMWorkerScope(nsDOMWorker* aWorker);
protected:
already_AddRefed<nsIXPConnectWrappedNative> GetWrappedNative();
private:
nsDOMWorker* mWorker;
nsIXPConnectWrappedNative* mWrappedNative;
nsRefPtr<nsDOMWorkerNavigator> mNavigator;
PRPackedBool mHasOnerror;
};
class nsDOMWorker : public nsIWorker,
class nsDOMWorker : public nsDOMWorkerMessageHandler,
public nsIWorker,
public nsITimerCallback,
public nsIJSNativeInitializer,
public nsIXPCScriptable
{
friend class nsDOMWorkerFeature;
friend class nsDOMWorkerFunctions;
friend class nsDOMWorkerRefPtr;
friend class nsDOMWorkerScope;
friend class nsDOMWorkerScriptLoader;
friend class nsDOMWorkerTimeout;
@ -117,10 +124,11 @@ class nsDOMWorker : public nsIWorker,
#endif
public:
NS_DECL_ISUPPORTS
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIDOMEVENTTARGET
NS_DECL_NSIABSTRACTWORKER
NS_DECL_NSIWORKER
NS_FORWARD_SAFE_NSIDOMEVENTTARGET(mOuterHandler)
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSIXPCSCRIPTABLE
static nsresult NewWorker(nsISupports** aNewObject);
@ -141,16 +149,13 @@ public:
jsval* aArgv);
void Cancel();
void Kill();
void Suspend();
void Resume();
PRBool IsCanceled() {
return mCanceled;
}
PRBool IsSuspended() {
return mSuspended;
}
PRBool IsCanceled();
PRBool IsClosing();
PRBool IsSuspended();
PRBool SetGlobalForContext(JSContext* aCx);
@ -171,6 +176,62 @@ public:
return mInnerScope;
}
void SetExpirationTime(PRIntervalTime aExpirationTime);
#ifdef DEBUG
PRIntervalTime GetExpirationTime();
#endif
/**
* Use this chart to help figure out behavior during each of the closing
* statuses. Details below.
*
* +=============+=============+=================+=======================+
* | status | clear queue | abort execution | close handler timeout |
* +=============+=============+=================+=======================+
* | eClosed | yes | no | no |
* +-------------+-------------+-----------------+-----------------------+
* | eTerminated | yes | yes | no |
* +-------------+-------------+-----------------+-----------------------+
* | eCanceled | yes | yes | yes |
* +-------------+-------------+-----------------+-----------------------+
*
*/
enum Status {
// This status means that the close handler has not yet been scheduled.
eRunning = 0,
// Inner script called Close() on the worker global scope. Setting this
// status causes the worker to clear its queue of events but does not abort
// the currently running script. The close handler is also scheduled with
// no expiration time. This status may be superseded by 'eTerminated' in
// which case the currently running script will be aborted as detailed
// below. It may also be superseded by 'eCanceled' at which point the close
// handler will be assigned an expiration time. Once the close handler has
// completed or timed out the status will be changed to 'eKilled'.
eClosed,
// Outer script called Terminate() on the worker or the worker object was
// garbage collected in its outer script. Setting this status causes the
// worker to abort immediately, clear its queue of events, and schedules the
// close handler with no expiration time. This status may be superseded by
// 'eCanceled' at which point the close handler will have an expiration time
// assigned. Once the close handler has completed or timed out the status
// will be changed to 'eKilled'.
eTerminated,
// Either the user navigated away from the owning page, the owning page fell
// out of bfcache, or the user quit the application. Setting this status
// causes the worker to abort immediately and schedules the close handler
// with an expiration time. Since the page has gone away the worker may not
// post any messages. Once the close handler has completed or timed out the
// status will be changed to 'eKilled'.
eCanceled,
// The close handler has run and the worker is effectively dead.
eKilled
};
private:
~nsDOMWorker();
@ -192,7 +253,6 @@ private:
void CancelTimeoutWithId(PRUint32 aId);
void SuspendFeatures();
void ResumeFeatures();
void CancelFeatures();
nsIPrincipal* GetPrincipal() {
return mPrincipal;
@ -210,6 +270,13 @@ private:
mURI = aURI;
}
nsresult FireCloseRunnable(PRIntervalTime aTimeoutInterval,
PRBool aClearQueue,
PRBool aFromFinalize);
nsresult Close();
nsresult TerminateInternal(PRBool aFromFinalize);
private:
// mParent will live as long as mParentWN but only mParentWN will keep the JS
@ -219,12 +286,10 @@ private:
PRLock* mLock;
nsRefPtr<nsDOMWorkerMessageHandler> mInnerHandler;
nsRefPtr<nsDOMWorkerMessageHandler> mOuterHandler;
nsRefPtr<nsDOMWorkerPool> mPool;
nsDOMWorkerScope* mInnerScope;
nsCOMPtr<nsIXPConnectWrappedNative> mScopeWN;
JSObject* mGlobal;
PRUint32 mNextTimeoutId;
@ -241,10 +306,16 @@ private:
PRInt32 mErrorHandlerRecursionCount;
PRPackedBool mCanceled;
// Always protected by mLock
Status mStatus;
// Always protected by mLock
PRIntervalTime mExpirationTime;
nsCOMPtr<nsITimer> mKillTimer;
PRPackedBool mSuspended;
PRPackedBool mCompileAttempted;
PRPackedBool mTerminated;
};
/**

View File

@ -53,6 +53,7 @@
#include "nsAutoLock.h"
#include "nsContentUtils.h"
#include "nsDOMJSUtils.h"
#include "nsProxyRelease.h"
#include "nsThreadUtils.h"
// DOMWorker includes
@ -75,6 +76,21 @@ nsDOMWorkerPool::nsDOMWorkerPool(nsIScriptGlobalObject* aGlobalObject,
nsDOMWorkerPool::~nsDOMWorkerPool()
{
nsCOMPtr<nsIThread> mainThread;
NS_GetMainThread(getter_AddRefs(mainThread));
nsIScriptGlobalObject* global;
mParentGlobal.forget(&global);
if (global) {
NS_ProxyRelease(mainThread, global, PR_FALSE);
}
nsIDocument* document;
mParentDocument.forget(&document);
if (document) {
NS_ProxyRelease(mainThread, document, PR_FALSE);
}
if (mMonitor) {
nsAutoMonitor::DestroyMonitor(mMonitor);
}
@ -181,9 +197,6 @@ nsDOMWorkerPool::Cancel()
nsAutoMonitor mon(mMonitor);
mon.NotifyAll();
}
mParentGlobal = nsnull;
mParentDocument = nsnull;
}
void

View File

@ -176,20 +176,9 @@ nsDOMWorkerScriptLoader::DoRunLoop(JSContext* aCx)
nsresult rv = NS_DispatchToMainThread(this);
NS_ENSURE_SUCCESS(rv, rv);
if (!(done || mCanceled)) {
// Since we're going to lock up this thread we might as well allow the
// thread service to schedule another worker on a new thread.
nsDOMThreadService* threadService = nsDOMThreadService::get();
PRBool changed = NS_SUCCEEDED(threadService->ChangeThreadPoolMaxThreads(1));
while (!(done || mCanceled)) {
JSAutoSuspendRequest asr(aCx);
NS_ProcessNextEvent(mTarget);
}
if (changed) {
threadService->ChangeThreadPoolMaxThreads(-1);
}
while (!(done || mCanceled)) {
JSAutoSuspendRequest asr(aCx);
NS_ProcessNextEvent(mTarget);
}
return mCanceled ? NS_ERROR_ABORT : NS_OK;
@ -619,7 +608,11 @@ nsDOMWorkerScriptLoader::OnStreamCompleteInternal(nsIStreamLoader* aLoader,
}
nsIDocument* parentDoc = mWorker->Pool()->ParentDocument();
NS_ASSERTION(parentDoc, "Null parent document?!");
if (!parentDoc) {
NS_ASSERTION(mWorker->IsCanceled(),
"Null parent document when we're not canceled?!");
return rv = NS_ERROR_FAILURE;
}
// Use the regular nsScriptLoader for this grunt work! Should be just fine
// because we're running on the main thread.

View File

@ -373,12 +373,6 @@ nsDOMWorkerTimeout::Cancel()
void
nsDOMWorkerTimeout::Suspend()
{
#ifdef DEBUG
if (mStarted) {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
}
#endif
AutoSpinlock lock(this);
NS_ASSERTION(!IsSuspendedNoLock(), "Bad state!");
@ -404,12 +398,6 @@ nsDOMWorkerTimeout::Suspend()
void
nsDOMWorkerTimeout::Resume()
{
#ifdef DEBUG
if (!mSuspendedBeforeStart) {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
}
#endif
NS_ASSERTION(mTimer, "Impossible to get here without a timer!");
LOG(("Worker [0x%p] resuming timeout [0x%p] with id %u",

View File

@ -339,7 +339,18 @@ nsDOMWorkerXHR::nsDOMWorkerXHR(nsDOMWorker* aWorker)
nsDOMWorkerXHR::~nsDOMWorkerXHR()
{
if (mXHRProxy) {
mXHRProxy->Destroy();
if (!NS_IsMainThread()) {
nsCOMPtr<nsIRunnable> runnable =
NS_NEW_RUNNABLE_METHOD(nsDOMWorkerXHRProxy, mXHRProxy.get(), Destroy);
if (runnable) {
mXHRProxy = nsnull;
NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
}
}
else {
mXHRProxy->Destroy();
}
}
}
@ -688,6 +699,12 @@ nsDOMWorkerXHR::Send(nsIVariant* aBody)
return NS_ERROR_ABORT;
}
if (mWorker->IsClosing() && !mXHRProxy->mSyncRequest) {
// Cheat and don't start this request since we know we'll never be able to
// use the data.
return NS_OK;
}
nsresult rv = mXHRProxy->Send(aBody);
NS_ENSURE_SUCCESS(rv, rv);
@ -703,6 +720,12 @@ nsDOMWorkerXHR::SendAsBinary(const nsAString& aBody)
return NS_ERROR_ABORT;
}
if (mWorker->IsClosing() && !mXHRProxy->mSyncRequest) {
// Cheat and don't start this request since we know we'll never be able to
// use the data.
return NS_OK;
}
nsresult rv = mXHRProxy->SendAsBinary(aBody);
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -404,11 +404,14 @@ nsDOMWorkerXHRProxy::InitInternal()
}
nsIPrincipal* nodePrincipal = pool->ParentDocument()->NodePrincipal();
nsIScriptContext* scriptContext = pool->ScriptGlobalObject()->GetContext();
nsCOMPtr<nsPIDOMWindow> ownerWindow =
do_QueryInterface( pool->ScriptGlobalObject());
nsRefPtr<nsXMLHttpRequest> xhrConcrete = new nsXMLHttpRequest();
NS_ENSURE_TRUE(xhrConcrete, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = xhrConcrete->Init(nodePrincipal, nsnull, nsnull,
nsresult rv = xhrConcrete->Init(nodePrincipal, scriptContext, ownerWindow,
worker->GetURI());
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -48,6 +48,11 @@ include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
test_404.html \
test_close.html \
close_worker.js \
test_closeOnGC.html \
closeOnGC_worker.js \
closeOnGC_server.sjs \
test_errorPropagation.html \
errorPropagation_worker1.js \
errorPropagation_worker2.js \

View File

@ -0,0 +1,19 @@
function handleRequest(request, response)
{
response.setHeader("Content-Type", "text/plain", false);
response.setHeader("Cache-Control", "no-cache", false);
if (request.method == "POST") {
setState("seenPost", "1");
return;
}
if (request.method == "GET") {
if (getState("seenPost") == "1") {
response.write("closed");
}
return;
}
response.setStatusLine(request.httpVersion, 404, "Not found");
}

View File

@ -0,0 +1,5 @@
onclose = function() {
var xhr = new XMLHttpRequest();
xhr.open("POST", "closeOnGC_server.sjs", false);
xhr.send();
};

View File

@ -0,0 +1,5 @@
onclose = function() {
postMessage("closed");
};
setTimeout(function() { close(); }, 1000);

View File

@ -1,3 +1,7 @@
onclose = function() {
postMessage("Closed!");
}
onmessage = function(event) {
throw "No messages should reach me!";
}

View File

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for DOM Worker Threads</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<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">
<script class="testbody" type="text/javascript">
var worker = new Worker("close_worker.js");
worker.onmessage = function(event) {
is(event.data, "closed");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for DOM Worker Threads</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<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">
<script class="testbody" type="text/javascript">
function CC() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.garbageCollect();
}
var worker = new Worker("closeOnGC_worker.js");
worker = null;
CC();
var interval = setInterval(function() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "closeOnGC_server.sjs", false);
xhr.send();
if (xhr.responseText != "closed") {
CC();
return;
}
clearInterval(interval);
SimpleTest.finish();
}, 500);
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

View File

@ -20,24 +20,28 @@ Tests of DOM Worker terminate feature
var worker = new Worker("terminate_worker.js");
var count = 0;
var id;
var interval;
function maybeFinish() {
if (!count) {
clearInterval(id);
SimpleTest.finish();
if (count) {
count = 0;
return;
}
worker.postMessage("You're terminated!");
count = 0;
clearInterval(interval);
SimpleTest.finish();
}
worker.onmessage = function(event) {
count++;
if (!id && count == 20) {
worker.terminate();
id = setInterval(maybeFinish, 2000);
maybeFinish();
if (event.data == "Still alive!") {
count++;
if (!interval && count == 20) {
worker.terminate();
}
}
else if (event.data == "Closed!") {
count = 0;
interval = setInterval(maybeFinish, 500);
}
};