mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 19:04:45 +00:00
Bug 466254 - "Workers: Implement close() and onclose handlers". r+sr=jst.
This commit is contained in:
parent
d962874347
commit
24307fec9b
@ -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)]
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 \
|
||||
|
19
dom/src/threads/test/closeOnGC_server.sjs
Normal file
19
dom/src/threads/test/closeOnGC_server.sjs
Normal 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");
|
||||
}
|
5
dom/src/threads/test/closeOnGC_worker.js
Normal file
5
dom/src/threads/test/closeOnGC_worker.js
Normal file
@ -0,0 +1,5 @@
|
||||
onclose = function() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "closeOnGC_server.sjs", false);
|
||||
xhr.send();
|
||||
};
|
5
dom/src/threads/test/close_worker.js
Normal file
5
dom/src/threads/test/close_worker.js
Normal file
@ -0,0 +1,5 @@
|
||||
onclose = function() {
|
||||
postMessage("closed");
|
||||
};
|
||||
|
||||
setTimeout(function() { close(); }, 1000);
|
@ -1,3 +1,7 @@
|
||||
onclose = function() {
|
||||
postMessage("Closed!");
|
||||
}
|
||||
|
||||
onmessage = function(event) {
|
||||
throw "No messages should reach me!";
|
||||
}
|
||||
|
26
dom/src/threads/test/test_close.html
Normal file
26
dom/src/threads/test/test_close.html
Normal 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>
|
44
dom/src/threads/test/test_closeOnGC.html
Normal file
44
dom/src/threads/test/test_closeOnGC.html
Normal 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>
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user