Bug 466254 - "Workers: Implement close() and onclose handlers". r+sr=jst.
authorBen Turner <bent.mozilla@gmail.com>
Sun, 15 Mar 2009 15:30:31 -0700
changeset 26209 388c17a510e8870b39f045bfe1d75ff7b470c525
parent 26208 237db3258d19208e37cc171e77bbeeee4ab23c8c
child 26210 7208e221c1097d8609f9369f4c7271397547738f
push id5949
push userbturner@mozilla.com
push dateSun, 15 Mar 2009 22:31:12 +0000
treeherdermozilla-central@388c17a510e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs466254
milestone1.9.2a1pre
Bug 466254 - "Workers: Implement close() and onclose handlers". r+sr=jst.
dom/interfaces/threads/nsIDOMWorkers.idl
dom/src/threads/nsDOMThreadService.cpp
dom/src/threads/nsDOMThreadService.h
dom/src/threads/nsDOMWorker.cpp
dom/src/threads/nsDOMWorker.h
dom/src/threads/nsDOMWorkerPool.cpp
dom/src/threads/nsDOMWorkerScriptLoader.cpp
dom/src/threads/nsDOMWorkerTimeout.cpp
dom/src/threads/nsDOMWorkerXHR.cpp
dom/src/threads/nsDOMWorkerXHRProxy.cpp
dom/src/threads/test/Makefile.in
dom/src/threads/test/closeOnGC_server.sjs
dom/src/threads/test/closeOnGC_worker.js
dom/src/threads/test/close_worker.js
dom/src/threads/test/terminate_worker.js
dom/src/threads/test/test_close.html
dom/src/threads/test/test_closeOnGC.html
dom/src/threads/test/test_terminate.html
--- a/dom/interfaces/threads/nsIDOMWorkers.idl
+++ b/dom/interfaces/threads/nsIDOMWorkers.idl
@@ -96,22 +96,25 @@ interface nsIWorkerNavigator : nsISuppor
 interface nsIWorkerGlobalScope : nsISupports
 {
   readonly attribute nsIWorkerGlobalScope self;
   readonly attribute nsIWorkerNavigator navigator;
 
   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)]
 interface nsIAbstractWorker : nsIDOMEventTarget
 {
   attribute nsIDOMEventListener onerror;
 };
 
--- a/dom/src/threads/nsDOMThreadService.cpp
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -114,16 +114,23 @@ static nsDOMThreadService* gDOMThreadSer
 // These pointers actually carry references and must be released.
 static nsIObserverService* gObserverService = nsnull;
 static nsIJSRuntimeService* gJSRuntimeService = nsnull;
 static nsIThreadJSContextStack* gThreadJSContextStack = nsnull;
 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.
  */
 class JSAutoContextDestroyer
 {
 public:
   JSAutoContextDestroyer(JSContext* aCx)
@@ -186,17 +193,17 @@ public:
       }
     }
 #endif
 
     NS_NAMED_LITERAL_STRING(errorStr, "error");
 
     nsresult rv;
 
-    if (mWorker->mOuterHandler->HasListeners(errorStr)) {
+    if (mWorker->HasListeners(errorStr)) {
       // Construct the error event.
       nsString message;
       rv = mScriptError->GetErrorMessage(message);
       NS_ENSURE_SUCCESS(rv, rv);
 
       nsString filename;
       rv = mScriptError->GetSourceName(filename);
       NS_ENSURE_SUCCESS(rv, rv);
@@ -207,17 +214,17 @@ public:
 
       nsRefPtr<nsDOMWorkerErrorEvent> event(new nsDOMWorkerErrorEvent());
       NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
 
       rv = event->InitErrorEvent(errorStr, PR_FALSE, PR_TRUE, message,
                                  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),
                                   &stopPropagation);
       if (NS_SUCCEEDED(rv) && stopPropagation) {
         return NS_OK;
       }
     }
@@ -253,61 +260,112 @@ private:
   nsCOMPtr<nsIScriptError> mScriptError;
 };
 
 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) { }
 
   NS_IMETHOD Run() {
     return mTimeout->Run();
   }
 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
  * as the thread limit has not been reached. Since our DOM workers can only be
  * accessed by one thread at a time we could end up spawning a new thread that
  * does nothing but wait initially. There is no way to control this behavior
  * 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:
+  NS_DECL_ISUPPORTS
+
   nsDOMWorkerRunnable(nsDOMWorker* aWorker)
-  : mWorker(aWorker) { }
+  : mWorker(aWorker), mCloseTimeoutInterval(0), mKillWorkerWhenDone(PR_FALSE) {
+  }
 
   virtual ~nsDOMWorkerRunnable() {
-    nsCOMPtr<nsIRunnable> runnable;
-    while ((runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront()))) {
-      // Loop until all the runnables are dead.
+    ClearQueue();
+  }
+
+  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.
+
+    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 PutRunnable(nsIRunnable* aRunnable) {
-    NS_ASSERTION(aRunnable, "Null pointer!");
-
-    NS_ADDREF(aRunnable);
+  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.
 
-    mRunnables.Push(aRunnable);
+    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() {
     NS_ASSERTION(!NS_IsMainThread(),
                  "This should *never* run on the main thread!");
 
     // This must have been set up by the thread service
     NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!");
@@ -315,57 +373,98 @@ public:
     // Make sure we have a JSContext to run everything on.
     JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
     NS_ASSERTION(cx, "nsDOMThreadService didn't give us a context!");
 
     NS_ASSERTION(!JS_GetGlobalObject(cx), "Shouldn't have a global!");
 
     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.
       JS_SetGlobalObject(cx, NULL);
       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) {
       nsCOMPtr<nsIRunnable> runnable;
       {
         nsAutoMonitor mon(gDOMThreadService->mMonitor);
 
         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()) {
             LOG(("Bailing out of run loop for canceled worker[0x%p]",
                  static_cast<void*>(mWorker.get())));
           }
 #endif
+          *aCloseRunnableSet = mKillWorkerWhenDone;
           gDOMThreadService->WorkerComplete(this);
           mon.NotifyAll();
           return;
         }
       }
 
       if (!operationCallbackTriggered) {
         // Make sure that our operation callback is set to run before starting.
@@ -376,38 +475,40 @@ protected:
         operationCallbackTriggered = PR_TRUE;
       }
 
       // Clear out any old cruft hanging around in the regexp statics.
       JS_ClearRegExpStatics(aCx);
 
       runnable->Run();
     }
+    NS_NOTREACHED("Shouldn't ever get here!");
   }
 
   // Set at construction
   nsRefPtr<nsDOMWorker> mWorker;
 
   // Protected by mMonitor
   nsDeque mRunnables;
+  nsCOMPtr<nsIRunnable> mCloseRunnable;
+  PRIntervalTime mCloseTimeoutInterval;
+  PRBool mKillWorkerWhenDone;
 };
 
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerRunnable, nsIRunnable)
+
 /*******************************************************************************
  * JS environment function and callbacks
  */
 
 JSBool
 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;
 
   for (;;) {
     // Kill execution if we're canceled.
     if (worker->IsCanceled()) {
       LOG(("Forcefully killing JS for worker [0x%p]",
@@ -432,41 +533,38 @@ DOMWorkerOperationCallback(JSContext* aC
           gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
         }
         JS_ResumeRequest(aCx, suspendDepth);
       }
       return JS_TRUE;
     }
 
     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);
 
       // Since we're going to block this thread we should open up a new thread
       // in the thread pool for other workers. Must check the return value to
       // make sure we don't decrement when we failed.
       extraThreadAllowed =
         NS_SUCCEEDED(gDOMThreadService->ChangeThreadPoolMaxThreads(1));
 
       // Only do all this setup once.
       wasSuspended = PR_TRUE;
     }
 
-    nsAutoMonitor mon(pool->Monitor());
+    nsAutoMonitor mon(worker->Pool()->Monitor());
     mon.Wait();
   }
 }
 
 void
 DOMWorkerErrorReporter(JSContext* aCx,
                        const char* aMessage,
                        JSErrorReport* aReport)
@@ -483,19 +581,25 @@ DOMWorkerErrorReporter(JSContext* aCx,
   }
 
   if (worker->mErrorHandlerRecursionCount == 2) {
     // We've somehow ended up in a recursive onerror loop. Bail out.
     return;
   }
 
   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);
 
   nsAutoString filename;
   filename.AssignWithConversion(aReport->filename);
 
   const PRUnichar* line =
@@ -506,38 +610,40 @@ DOMWorkerErrorReporter(JSContext* aCx,
   rv = scriptError->Init(message, filename.get(), line, aReport->lineno,
                          column, aReport->flags, "DOM Worker javascript");
   NS_ENSURE_SUCCESS(rv,);
 
   // Don't call the error handler if we're out of stack space.
   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;
           }
         }
       }
     }
   }
 
   // Still unhandled, fire at the onerror handler on the worker.
@@ -600,16 +706,18 @@ nsDOMThreadService::Init()
     do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   obs.forget(&gObserverService);
 
+  RegisterPrefCallbacks();
+
   mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mThreadPool->SetListener(this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mThreadPool->SetThreadLimit(THREADPOOL_MAX_THREADS);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -711,16 +819,18 @@ nsDOMThreadService::Cleanup()
 
   // This will either be called at 'xpcom-shutdown' or earlier if the call to
   // Init fails somehow. We can therefore assume that all services will still
   // be available here.
 
   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
   // listener. We must shut down the thread pool manually to break this cycle.
   if (mThreadPool) {
     mThreadPool->Shutdown();
     mThreadPool = nsnull;
   }
@@ -741,42 +851,47 @@ nsDOMThreadService::Cleanup()
   nsAutoMonitor mon(mMonitor);
   NS_ASSERTION(!mPools.Count(), "Live workers left!");
 
   mPools.Clear();
 }
 
 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;
   }
 
   nsRefPtr<nsDOMWorkerRunnable> workerRunnable;
   {
     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);
   }
 
   nsresult rv = mThreadPool->Dispatch(workerRunnable, NS_DISPATCH_NORMAL);
 
   // XXX This is a mess and it could probably be removed once we have an
@@ -799,16 +914,33 @@ nsDOMThreadService::Dispatch(nsDOMWorker
 
     return rv;
   }
 
   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)
 {
 
   // No need to be in the monitor here because we should already be in it.
 
 #ifdef DEBUG
   nsRefPtr<nsDOMWorker>& debugWorker = aRunnable->mWorker;
 
@@ -1258,8 +1390,58 @@ nsDOMThreadService::GetPlatform(nsAStrin
 
 void
 nsDOMThreadService::GetUserAgent(nsAString& aUserAgent)
 {
   NS_ASSERTION(mNavigatorStringsLoaded,
                "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.
+    PRUint32 timeoutMS =
+      nsContentUtils::GetIntPref(aPrefName, gWorkerCloseHandlerTimeoutMS);
+
+    // We must have a timeout value, 0 is not ok. If the pref is set to 0 then
+    // fall back to our default.
+    if (timeoutMS) {
+      gWorkerCloseHandlerTimeoutMS = timeoutMS;
+    }
+  }
+  return 0;
+}
+
+// static
+PRUint32
+nsDOMThreadService::GetWorkerCloseHandlerTimeoutMS()
+{
+  return gWorkerCloseHandlerTimeoutMS;
+}
--- a/dom/src/threads/nsDOMThreadService.h
+++ b/dom/src/threads/nsDOMThreadService.h
@@ -119,17 +119,22 @@ private:
   ~nsDOMThreadService();
 
   nsresult Init();
   void Cleanup();
 
   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);
 
   static JSContext* CreateJSContext();
 
   already_AddRefed<nsDOMWorkerPool>
     GetPoolForGlobal(nsIScriptGlobalObject* aGlobalObject,
                      PRBool aRemove);
@@ -143,16 +148,24 @@ private:
   nsresult RegisterWorker(nsDOMWorker* aWorker,
                           nsIScriptGlobalObject* aGlobalObject);
 
   void GetAppName(nsAString& aAppName);
   void GetAppVersion(nsAString& aAppVersion);
   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;
 
   // Maps nsIScriptGlobalObject* to nsDOMWorkerPool.
   nsRefPtrHashtable<nsISupportsHashKey, nsDOMWorkerPool> mPools;
 
   // mMonitor protects all access to mWorkersInProgress and
   // mCreationsInProgress.
--- a/dom/src/threads/nsDOMWorker.cpp
+++ b/dom/src/threads/nsDOMWorker.cpp
@@ -134,16 +134,22 @@ nsDOMWorkerFunctions::MakeTimeout(JSCont
   NS_ASSERTION(worker, "This should be set by the DOM thread service!");
 
   if (worker->IsCanceled()) {
     return JS_FALSE;
   }
 
   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);
     return JS_FALSE;
   }
 
   nsresult rv = timeout->Init(aCx, aArgc, aArgv, aIsInterval);
   if (NS_FAILED(rv)) {
@@ -338,25 +344,19 @@ nsDOMWorkerFunctions::NewWorker(JSContex
     return JS_FALSE;
   }
 
   if (!aArgc) {
     JS_ReportError(aCx, "Worker constructor must have an argument!");
     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;
   }
 
   nsCOMPtr<nsIXPConnectWrappedNative> wrappedWorker =
     worker->GetWrappedNative();
   if (!wrappedWorker) {
@@ -519,26 +519,26 @@ GetStringForArgument(nsAString& aString,
   aString.Assign(writer.mOutputString);
   *aIsJSON = PR_TRUE;
 
   return NS_OK;
 }
 
 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,
                                                nsIDOMEventTarget,
                                                nsIXPCScriptable)
 
 NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerScope)
 NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(nsDOMWorkerScope)
@@ -558,31 +558,73 @@ nsDOMWorkerScope::GetHelperForLanguage(P
   return NS_OK;
 }
 
 // Use the xpc_map_end.h macros to generate the nsIXPCScriptable methods we want
 // for the scope.
 
 #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           | \
   nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY           | \
   nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY           | \
   nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE            | \
   nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY            | \
   nsIXPCScriptable::DONT_REFLECT_INTERFACE_NAMES         | \
   nsIXPCScriptable::WANT_ADDPROPERTY
 
 #define XPC_MAP_WANT_ADDPROPERTY
 
 #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,
                               JSObject* aObj,
                               jsval aId,
                               jsval* aVp,
                               PRBool* _retval)
 {
   // We're not going to be setting any exceptions manually so set _retval to
@@ -680,35 +722,34 @@ nsDOMWorkerScope::GetOnerror(nsIDOMEvent
     rv = cc->GetRetValPtr(&retval);
     NS_ENSURE_SUCCESS(rv, rv);
 
     *retval = JSVAL_VOID;
     return cc->SetReturnValueWasSet(PR_TRUE);
   }
 
   nsCOMPtr<nsIDOMEventListener> listener =
-    mWorker->mInnerHandler->GetOnXListener(NS_LITERAL_STRING("error"));
+    GetOnXListener(NS_LITERAL_STRING("error"));
   listener.forget(aOnerror);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWorkerScope::SetOnerror(nsIDOMEventListener* aOnerror)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (mWorker->IsCanceled()) {
     return NS_ERROR_ABORT;
   }
 
   mHasOnerror = PR_TRUE;
 
-  return mWorker->mInnerHandler->SetOnXListener(NS_LITERAL_STRING("error"),
-                                                aOnerror);
+  return SetOnXListener(NS_LITERAL_STRING("error"), aOnerror);
 }
 
 NS_IMETHODIMP
 nsDOMWorkerScope::PostMessage(/* JSObject aMessage */)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (mWorker->IsCanceled()) {
@@ -720,100 +761,143 @@ nsDOMWorkerScope::PostMessage(/* JSObjec
 
   nsresult rv = GetStringForArgument(message, &isJSON, &isPrimitive);
   NS_ENSURE_SUCCESS(rv, rv);
 
   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)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ENSURE_ARG_POINTER(aOnmessage);
 
   if (mWorker->IsCanceled()) {
     return NS_ERROR_ABORT;
   }
 
   nsCOMPtr<nsIDOMEventListener> listener =
-    mWorker->mInnerHandler->GetOnXListener(NS_LITERAL_STRING("message"));
+    GetOnXListener(NS_LITERAL_STRING("message"));
   listener.forget(aOnmessage);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWorkerScope::SetOnmessage(nsIDOMEventListener* aOnmessage)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (mWorker->IsCanceled()) {
     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
 nsDOMWorkerScope::AddEventListener(const nsAString& aType,
                                    nsIDOMEventListener* aListener,
                                    PRBool aUseCapture)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (mWorker->IsCanceled()) {
     return NS_ERROR_ABORT;
   }
 
-  return mWorker->mInnerHandler->AddEventListener(aType, aListener,
-                                                  aUseCapture);
+  return nsDOMWorkerMessageHandler::AddEventListener(aType, aListener,
+                                                     aUseCapture);
 }
 
 NS_IMETHODIMP
 nsDOMWorkerScope::RemoveEventListener(const nsAString& aType,
                                       nsIDOMEventListener* aListener,
                                       PRBool aUseCapture)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (mWorker->IsCanceled()) {
     return NS_ERROR_ABORT;
   }
 
-  return mWorker->mInnerHandler->RemoveEventListener(aType, aListener,
-                                                     aUseCapture);
+  return nsDOMWorkerMessageHandler::RemoveEventListener(aType, aListener,
+                                                        aUseCapture);
 }
 
 NS_IMETHODIMP
 nsDOMWorkerScope::DispatchEvent(nsIDOMEvent* aEvent,
                                 PRBool* _retval)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (mWorker->IsCanceled()) {
     return NS_ERROR_ABORT;
   }
 
-  return mWorker->mInnerHandler->DispatchEvent(aEvent, _retval);
+  return nsDOMWorkerMessageHandler::DispatchEvent(aEvent, _retval);
 }
 
 class nsWorkerHoldingRunnable : public nsIRunnable
 {
 public:
   NS_DECL_ISUPPORTS
 
   nsWorkerHoldingRunnable(nsDOMWorker* aWorker)
   : mWorker(aWorker), mWorkerWN(aWorker->GetWrappedNative()) { }
 
   NS_IMETHOD Run() {
     return NS_OK;
   }
 
+  void ReplaceWrappedNative(nsIXPConnectWrappedNative* aWrappedNative) {
+    mWorkerWN = aWrappedNative;
+  }
+
 protected:
   virtual ~nsWorkerHoldingRunnable() { }
 
   nsRefPtr<nsDOMWorker> mWorker;
 
 private:
   nsCOMPtr<nsIXPConnectWrappedNative> mWorkerWN;
 };
@@ -848,51 +932,33 @@ public:
       NS_ASSERTION(currentWorker == targetWorker, "Wrong worker!");
     }
 #endif
     if (mWorker->IsCanceled()) {
       return NS_ERROR_ABORT;
     }
 
     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);
 
     mEvent->SetTarget(target);
     return target->DispatchEvent(mEvent, nsnull);
   }
 
 protected:
   nsRefPtr<nsDOMWorkerEvent> mEvent;
   PRBool mToInner;
 };
 
 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)
 nsDOMWorkerFeature::AddRef()
 {
   NS_ASSERTION(mRefCnt >= 0, "Illegal refcnt!");
   return PR_AtomicIncrement((PRInt32*)&mRefCnt);
 }
@@ -953,20 +1019,20 @@ nsDOMWorker::nsDOMWorker(nsDOMWorker* aP
   mParentWN(aParentWN),
   mLock(nsnull),
   mInnerScope(nsnull),
   mGlobal(NULL),
   mNextTimeoutId(0),
   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();
   NS_ASSERTION(aParent ? !mainThread : mainThread, "Wrong thread!");
 #endif
 }
 
 nsDOMWorker::~nsDOMWorker()
@@ -1005,26 +1071,27 @@ nsDOMWorker::NewWorker(nsISupports** aNe
   nsCOMPtr<nsISupports> newWorker =
     NS_ISUPPORTS_CAST(nsIWorker*, new nsDOMWorker(nsnull, nsnull));
   NS_ENSURE_TRUE(newWorker, NS_ERROR_OUT_OF_MEMORY);
 
   newWorker.forget(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
 NS_INTERFACE_MAP_END
 
 // Use the xpc_map_end.h macros to generate the nsIXPCScriptable methods we want
 // for the worker.
 
@@ -1044,63 +1111,60 @@ NS_INTERFACE_MAP_END
 
 #include "xpc_map_end.h"
 
 NS_IMETHODIMP
 nsDOMWorker::PostCreate(nsIXPConnectWrappedNative* aWrapper,
                         JSContext* /* aCx */,
                         JSObject* /* aObj */)
 {
+  nsAutoLock lock(mLock);
   mWrappedNative = aWrapper;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWorker::Trace(nsIXPConnectWrappedNative* /* aWrapper */,
                    JSTracer* aTracer,
                    JSObject* /*aObj */)
 {
   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;
 }
 
 NS_IMETHODIMP
 nsDOMWorker::Finalize(nsIXPConnectWrappedNative* /* aWrapper */,
                       JSContext* aCx,
                       JSObject* /* aObj */)
 {
   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;
-
-  // And we can let our parent die now too.
-  mParent = nsnull;
-  mParentWN = nsnull;
+  // Do this *after* we null out mWrappedNative so that we don't hand out a
+  // freed pointer.
+  TerminateInternal(PR_TRUE);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWorker::Initialize(nsISupports* aOwner,
                         JSContext* aCx,
                         JSObject* aObj,
@@ -1130,34 +1194,38 @@ nsDOMWorker::InitializeInternal(nsIScrip
   NS_ENSURE_TRUE(str, NS_ERROR_XPC_BAD_CONVERT_JS);
 
   mScriptURL.Assign(nsDependentJSString(str));
   NS_ENSURE_FALSE(mScriptURL.IsEmpty(), NS_ERROR_INVALID_ARG);
 
   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();
 
   nsCOMPtr<nsIXPConnectJSObjectHolder> thisWrapped;
   nsresult rv = xpc->WrapNative(aCx, aObj, static_cast<nsIWorker*>(this),
                                 NS_GET_IID(nsISupports),
                                 getter_AddRefs(thisWrapped));
   NS_ENSURE_SUCCESS(rv, rv);
 
   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.
   nsCOMPtr<nsIRunnable> runnable(new nsWorkerHoldingRunnable(this));
   NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
 
   nsRefPtr<nsDOMThreadService> threadService =
     nsDOMThreadService::GetOrInitService();
@@ -1172,40 +1240,210 @@ nsDOMWorker::InitializeInternal(nsIScrip
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 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;
+
+  // 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;
+    }
+
+    DOMWorkerStatus oldStatus = mStatus;
+    mStatus = eCanceled;
+    if (oldStatus != eRunning) {
+      enforceTimeout = PR_TRUE;
+    }
+  }
+
+  PRUint32 timeoutMS = nsDOMThreadService::GetWorkerCloseHandlerTimeoutMS();
+  NS_ASSERTION(timeoutMS, "This must not be 0!");
+
+#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!");
+}
 
-  CancelFeatures();
+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;
+
+  // And we can let our parent die now too.
+  mParent = nsnull;
+  mParentWN = nsnull;
 }
 
 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;
+
+  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);
 
-  ResumeFeatures();
+  // 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
 nsDOMWorker::PostMessageInternal(const nsAString& aMessage,
                                  PRBool aIsJSON,
                                  PRBool aIsPrimitive,
                                  PRBool aToInner)
 {
@@ -1311,44 +1549,51 @@ nsDOMWorker::CompileGlobalObject(JSConte
     }
   }
 #endif
 
   // Set up worker thread functions
   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);
   NS_ASSERTION(loader, "Out of memory!");
   if (!loader) {
     mGlobal = NULL;
     mInnerScope = nsnull;
+    mScopeWN = nsnull;
     return PR_FALSE;
   }
 
   rv = AddFeature(loader, aCx);
   if (NS_FAILED(rv)) {
     mGlobal = NULL;
     mInnerScope = nsnull;
+    mScopeWN = nsnull;
     return PR_FALSE;
   }
 
   rv = loader->LoadScript(aCx, mScriptURL, PR_TRUE);
 
   JS_ReportPendingException(aCx);
 
   if (NS_FAILED(rv)) {
     mGlobal = NULL;
     mInnerScope = nsnull;
+    mScopeWN = nsnull;
     return PR_FALSE;
   }
 
   NS_ASSERTION(mPrincipal && mURI, "Script loader didn't set our principal!");
 
   return PR_TRUE;
 }
 
@@ -1357,34 +1602,42 @@ nsDOMWorker::SetPool(nsDOMWorkerPool* aP
 {
   NS_ASSERTION(!mPool, "Shouldn't ever set pool more than once!");
   mPool = 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();
 }
 
 nsresult
 nsDOMWorker::AddFeature(nsDOMWorkerFeature* aFeature,
                         JSContext* aCx)
 {
   NS_ASSERTION(aFeature, "Null pointer!");
 
   PRBool shouldSuspend;
   {
     // aCx may be null.
     JSAutoSuspendRequest asr(aCx);
 
     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);
 
     aFeature->FreeToDie(PR_FALSE);
     shouldSuspend = mFeatureSuspendDepth > 0;
   }
 
   if (shouldSuspend) {
@@ -1486,64 +1739,203 @@ nsDOMWorker::ResumeFeatures()
   }
 
   PRUint32 count = features.Length();
   for (PRUint32 i = 0; i < count; i++) {
     features[i]->Resume();
   }
 }
 
-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];
+    if ((wakeUp = mSuspended)) {
+      NS_ASSERTION(mStatus == eCanceled ||
+                   (mStatus == eTerminated && aFromFinalize),
+                   "How can this happen otherwise?!");
+      mSuspended = PR_FALSE;
+    }
+  }
+
+  if (wakeUp) {
+    nsAutoMonitor mon(mPool->Monitor());
+    mon.NotifyAll();
+  }
 
-#ifdef DEBUG
-      nsRefPtr<nsDOMWorkerFeature>* newFeature =
-#endif
-      features.AppendElement(feature);
-      NS_ASSERTION(newFeature, "Out of memory!");
+  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);
 
-      feature->FreeToDie(PR_TRUE);
-    }
+  nsRefPtr<nsDOMFireEventRunnable> runnable =
+    new nsDOMFireEventRunnable(this, event, PR_TRUE);
+  NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
 
-    mFeatures.Clear();
+  // 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);
   }
 
-  count = features.Length();
-  for (index = 0; index < count; index++) {
-    features[index]->Cancel();
+  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>
 nsDOMWorker::GetParent()
 {
   nsRefPtr<nsDOMWorker> parent(mParent);
   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;
   PRBool isJSON, isPrimitive;
 
   nsresult rv = GetStringForArgument(message, &isJSON, &isPrimitive);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1553,63 +1945,81 @@ nsDOMWorker::PostMessage(/* JSObject aMe
 /**
  * See nsIWorker
  */
 NS_IMETHODIMP
 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;
 }
 
 /**
  * See nsIWorker
  */
 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);
 }
 
 /**
  * See nsIWorker
  */
 NS_IMETHODIMP
 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;
 }
 
 /**
  * See nsIWorker
  */
 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;
+  return TerminateInternal(PR_FALSE);
+}
 
-  nsCOMPtr<nsIRunnable> runnable = new nsCancelDOMWorkerRunnable(this);
-  NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
-
-  return NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
+NS_IMETHODIMP
+nsDOMWorker::Notify(nsITimer* aTimer)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  Kill();
+  return NS_OK;
 }
--- a/dom/src/threads/nsDOMWorker.h
+++ b/dom/src/threads/nsDOMWorker.h
@@ -38,16 +38,17 @@
 
 #ifndef __NSDOMWORKER_H__
 #define __NSDOMWORKER_H__
 
 #include "nsIDOMEventTarget.h"
 #include "nsIDOMWorkers.h"
 #include "nsIJSNativeInitializer.h"
 #include "nsIPrincipal.h"
+#include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsIXPCScriptable.h"
 
 #include "jsapi.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsTPtrArray.h"
 #include "prlock.h"
@@ -61,49 +62,55 @@ class nsDOMWorkerNavigator;
 class nsDOMWorkerPool;
 class nsDOMWorkerTimeout;
 class nsICancelable;
 class nsIDOMEventListener;
 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;
   friend class nsDOMWorkerXHR;
   friend class nsDOMWorkerXHRProxy;
   friend class nsReportErrorRunnable;
 
   friend JSBool DOMWorkerOperationCallback(JSContext* aCx);
@@ -112,20 +119,21 @@ class nsDOMWorker : public nsIWorker,
                                      JSErrorReport* aReport);
 
 #ifdef DEBUG
   // For fun assertions.
   friend class nsDOMFireEventRunnable;
 #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);
 
   nsDOMWorker(nsDOMWorker* aParent,
               nsIXPConnectWrappedNative* aParentWN);
 
   NS_IMETHOD Initialize(nsISupports* aOwner,
@@ -136,26 +144,23 @@ public:
 
   nsresult InitializeInternal(nsIScriptGlobalObject* aOwner,
                               JSContext* aCx,
                               JSObject* aObj,
                               PRUint32 aArgc,
                               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);
 
   void SetPool(nsDOMWorkerPool* aPool);
 
   nsDOMWorkerPool* Pool() {
     return mPool;
   }
@@ -166,16 +171,72 @@ public:
 
   already_AddRefed<nsIXPConnectWrappedNative> GetWrappedNative();
   already_AddRefed<nsDOMWorker> GetParent();
 
   nsDOMWorkerScope* GetInnerScope() {
     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 DOMWorkerStatus {
+    // 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();
 
   nsresult PostMessageInternal(const nsAString& aMessage,
                                PRBool aIsJSON,
                                PRBool aIsPrimitive,
                                PRBool aToInner);
 
@@ -187,17 +248,16 @@ private:
 
   nsresult AddFeature(nsDOMWorkerFeature* aFeature,
                       JSContext* aCx);
   void RemoveFeature(nsDOMWorkerFeature* aFeature,
                      JSContext* aCx);
   void CancelTimeoutWithId(PRUint32 aId);
   void SuspendFeatures();
   void ResumeFeatures();
-  void CancelFeatures();
 
   nsIPrincipal* GetPrincipal() {
     return mPrincipal;
   }
 
   void SetPrincipal(nsIPrincipal* aPrincipal) {
     mPrincipal = aPrincipal;
   }
@@ -205,51 +265,62 @@ private:
   nsIURI* GetURI() {
     return mURI;
   }
 
   void SetURI(nsIURI* aURI) {
     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
   // reflection alive, so we only hold one strong reference to mParentWN.
   nsDOMWorker* mParent;
   nsCOMPtr<nsIXPConnectWrappedNative> mParentWN;
 
   PRLock* mLock;
 
-  nsRefPtr<nsDOMWorkerMessageHandler> mInnerHandler;
-  nsRefPtr<nsDOMWorkerMessageHandler> mOuterHandler;
-
   nsRefPtr<nsDOMWorkerPool> mPool;
 
   nsDOMWorkerScope* mInnerScope;
+  nsCOMPtr<nsIXPConnectWrappedNative> mScopeWN;
   JSObject* mGlobal;
 
   PRUint32 mNextTimeoutId;
 
   nsTArray<nsDOMWorkerFeature*> mFeatures;
   PRUint32 mFeatureSuspendDepth;
 
   nsString mScriptURL;
 
   nsIXPConnectWrappedNative* mWrappedNative;
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIURI> mURI;
 
   PRInt32 mErrorHandlerRecursionCount;
 
-  PRPackedBool mCanceled;
+  // Always protected by mLock
+  DOMWorkerStatus mStatus;
+
+  // Always protected by mLock
+  PRIntervalTime mExpirationTime;
+
+  nsCOMPtr<nsITimer> mKillTimer;
+
   PRPackedBool mSuspended;
   PRPackedBool mCompileAttempted;
-  PRPackedBool mTerminated;
 };
 
 /**
  * A worker "feature" holds the worker alive yet can be canceled, paused, and
  * resumed by the worker. It is up to each derived class to implement these
  * methods. This class uses a custom implementation of Release in order to
  * ensure no races between Cancel and object destruction can occur, so derived
  * classes must use the ISUPPORTS_INHERITED macros.
--- a/dom/src/threads/nsDOMWorkerPool.cpp
+++ b/dom/src/threads/nsDOMWorkerPool.cpp
@@ -48,16 +48,17 @@
 #include "nsIThreadManager.h"
 #include "nsIXPConnect.h"
 #include "nsPIDOMWindow.h"
 
 // Other includes
 #include "nsAutoLock.h"
 #include "nsContentUtils.h"
 #include "nsDOMJSUtils.h"
+#include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 
 // DOMWorker includes
 #include "nsDOMThreadService.h"
 #include "nsDOMWorker.h"
 
 #define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
 
@@ -70,16 +71,31 @@ nsDOMWorkerPool::nsDOMWorkerPool(nsIScri
   mSuspended(PR_FALSE)
 {
   NS_ASSERTION(aGlobalObject, "Must have a global object!");
   NS_ASSERTION(aDocument, "Must have a document!");
 }
 
 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);
   }
 }
 
 NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerPool)
 NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerPool)
 
@@ -176,19 +192,16 @@ nsDOMWorkerPool::Cancel()
   PRUint32 count = workers.Length();
   if (count) {
     for (PRUint32 index = 0; index < count; index++) {
       workers[index]->Cancel();
     }
     nsAutoMonitor mon(mMonitor);
     mon.NotifyAll();
   }
-
-  mParentGlobal = nsnull;
-  mParentDocument = nsnull;
 }
 
 void
 nsDOMWorkerPool::Suspend()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   nsAutoTArray<nsDOMWorker*, 10> workers;
--- a/dom/src/threads/nsDOMWorkerScriptLoader.cpp
+++ b/dom/src/threads/nsDOMWorkerScriptLoader.cpp
@@ -171,30 +171,19 @@ nsDOMWorkerScriptLoader::DoRunLoop(JSCon
 
   volatile PRBool done = PR_FALSE;
   mDoneRunnable = new ScriptLoaderDone(this, &done);
   NS_ENSURE_TRUE(mDoneRunnable, NS_ERROR_OUT_OF_MEMORY);
 
   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;
 }
 
 nsresult
 nsDOMWorkerScriptLoader::VerifyScripts(JSContext* aCx)
 {
@@ -614,17 +603,21 @@ nsDOMWorkerScriptLoader::OnStreamComplet
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!requestSucceeded) {
       return rv = NS_ERROR_NOT_AVAILABLE;
     }
   }
 
   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.
   rv = nsScriptLoader::ConvertToUTF16(loadInfo.channel, aString, aStringLen,
                                       EmptyString(), parentDoc,
                                       loadInfo.scriptText);
   if (NS_FAILED(rv)) {
     return rv;
--- a/dom/src/threads/nsDOMWorkerTimeout.cpp
+++ b/dom/src/threads/nsDOMWorkerTimeout.cpp
@@ -368,22 +368,16 @@ nsDOMWorkerTimeout::Cancel()
 
   // This call to Cancel should kill us.
   mTimer->Cancel();
 }
 
 void
 nsDOMWorkerTimeout::Suspend()
 {
-#ifdef DEBUG
-  if (mStarted) {
-    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  }
-#endif
-
   AutoSpinlock lock(this);
 
   NS_ASSERTION(!IsSuspendedNoLock(), "Bad state!");
 
   mIsSuspended = PR_TRUE;
   mSuspendedRef = this;
 
   if (!mStarted) {
@@ -399,22 +393,16 @@ nsDOMWorkerTimeout::Suspend()
   LOG(("Worker [0x%p] suspending timeout [0x%p] with id %u (interval = %u)",
        static_cast<void*>(mWorker.get()), static_cast<void*>(this), mId,
        mSuspendInterval));
 }
 
 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",
        static_cast<void*>(mWorker.get()), static_cast<void*>(this), mId));
 
   AutoSpinlock lock(this);
 
   NS_ASSERTION(IsSuspendedNoLock(), "Should be suspended!");
--- a/dom/src/threads/nsDOMWorkerXHR.cpp
+++ b/dom/src/threads/nsDOMWorkerXHR.cpp
@@ -334,17 +334,28 @@ nsDOMWorkerXHR::nsDOMWorkerXHR(nsDOMWork
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aWorker, "Must have a worker!");
 }
 
 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();
+    }
   }
 }
 
 // Tricky! We use the AddRef/Release method of nsDOMWorkerFeature (to make sure
 // we properly remove ourselves from the worker array) but inherit the QI of
 // nsDOMWorkerXHREventTarget.
 NS_IMPL_ADDREF_INHERITED(nsDOMWorkerXHR, nsDOMWorkerFeature)
 NS_IMPL_RELEASE_INHERITED(nsDOMWorkerXHR, nsDOMWorkerFeature)
@@ -683,31 +694,43 @@ NS_IMETHODIMP
 nsDOMWorkerXHR::Send(nsIVariant* aBody)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (mCanceled) {
     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);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWorkerXHR::SendAsBinary(const nsAString& aBody)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (mCanceled) {
     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);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWorkerXHR::SetRequestHeader(const nsACString& aHeader,
--- a/dom/src/threads/nsDOMWorkerXHRProxy.cpp
+++ b/dom/src/threads/nsDOMWorkerXHRProxy.cpp
@@ -399,21 +399,24 @@ nsDOMWorkerXHRProxy::InitInternal()
   nsDOMWorker* worker = mWorkerXHR->mWorker;
   nsRefPtr<nsDOMWorkerPool> pool = worker->Pool();
 
   if (worker->IsCanceled()) {
     return NS_ERROR_ABORT;
   }
 
   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);
 
   // Call QI manually here to avoid keeping up with the cast madness of
   // nsXMLHttpRequest.
   nsCOMPtr<nsIXMLHttpRequest> xhr =
     do_QueryInterface(static_cast<nsIXMLHttpRequest*>(xhrConcrete));
   NS_ENSURE_TRUE(xhr, NS_ERROR_NO_INTERFACE);
--- a/dom/src/threads/test/Makefile.in
+++ b/dom/src/threads/test/Makefile.in
@@ -43,16 +43,21 @@ VPATH            = @srcdir@
 
 relativesrcdir   = dom/src/threads/test
 
 include $(DEPTH)/config/autoconf.mk
 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 \
   test_functionHandlers.html \
   functionHandlers_worker.js \
   test_importScripts.html \
   importScripts_worker.js \
   importScripts_worker_imported1.js \
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/closeOnGC_server.sjs
@@ -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");
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/closeOnGC_worker.js
@@ -0,0 +1,5 @@
+onclose = function() {
+  var xhr = new XMLHttpRequest();
+  xhr.open("POST", "closeOnGC_server.sjs", false);
+  xhr.send();
+};
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/close_worker.js
@@ -0,0 +1,5 @@
+onclose = function() {
+  postMessage("closed");
+};
+
+setTimeout(function() { close(); }, 1000);
--- a/dom/src/threads/test/terminate_worker.js
+++ b/dom/src/threads/test/terminate_worker.js
@@ -1,5 +1,9 @@
+onclose = function() {
+  postMessage("Closed!");
+}
+
 onmessage = function(event) {
   throw "No messages should reach me!";
 }
 
 setInterval(function() { postMessage("Still alive!"); }, 100);
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_close.html
@@ -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>
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_closeOnGC.html
@@ -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>
--- a/dom/src/threads/test/test_terminate.html
+++ b/dom/src/threads/test/test_terminate.html
@@ -15,34 +15,38 @@ Tests of DOM Worker terminate feature
 
 </div>
 <pre id="test">
 <script class="testbody" language="javascript">
 
   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);
     }
   };
 
   worker.onerror = function(event) {
     ok(false, "Worker had an error: " + event.data);
     SimpleTest.finish();
   }