Bug 437152 - "implement worker threads". r+sr=jst.
authorBen Turner <bent.mozilla@gmail.com>
Sat, 16 Aug 2008 18:25:01 -0700
changeset 16753 1ebacbd09ad97451e63bc7a06cfbcb782e1af3cc
parent 16752 1997aa804c6c40846e2508ada0f800de6c99a905
child 16754 07e5b301501a727bb959c2b885f225545ebbdc71
push id1294
push userbturner@mozilla.com
push dateSun, 17 Aug 2008 01:25:10 +0000
treeherdermozilla-central@1ebacbd09ad9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs437152
milestone1.9.1a2pre
Bug 437152 - "implement worker threads". r+sr=jst.
dom/public/idl/Makefile.in
dom/public/idl/base/nsIDOMNavigator.idl
dom/public/idl/threads/Makefile.in
dom/public/idl/threads/nsIDOMThreads.idl
dom/src/Makefile.in
dom/src/base/Makefile.in
dom/src/base/nsGlobalWindow.cpp
dom/src/threads/Makefile.in
dom/src/threads/nsDOMThreadService.cpp
dom/src/threads/nsDOMThreadService.h
dom/src/threads/nsDOMWorkerBase.cpp
dom/src/threads/nsDOMWorkerBase.h
dom/src/threads/nsDOMWorkerPool.cpp
dom/src/threads/nsDOMWorkerPool.h
dom/src/threads/nsDOMWorkerSecurityManager.cpp
dom/src/threads/nsDOMWorkerSecurityManager.h
dom/src/threads/nsDOMWorkerThread.cpp
dom/src/threads/nsDOMWorkerThread.h
dom/src/threads/nsDOMWorkerTimeout.cpp
dom/src/threads/nsDOMWorkerTimeout.h
dom/src/threads/test/Makefile.in
dom/src/threads/test/test_longThread.html
dom/src/threads/test/test_simpleThread.html
dom/src/threads/test/test_threadErrors.html
dom/src/threads/test/test_threadTimeouts.html
layout/build/Makefile.in
layout/build/nsLayoutStatics.cpp
--- a/dom/public/idl/Makefile.in
+++ b/dom/public/idl/Makefile.in
@@ -55,16 +55,17 @@ DIRS =						\
 	range					\
 	xbl					\
 	xpath					\
 	ls					\
 	xul                                     \
 	storage                                 \
 	json                                    \
 	offline                                 \
-	geolocation
+	geolocation                             \
+	threads
 
 ifdef MOZ_SVG
 DIRS += svg
 endif
 
 include $(topsrcdir)/config/rules.mk
 
--- a/dom/public/idl/base/nsIDOMNavigator.idl
+++ b/dom/public/idl/base/nsIDOMNavigator.idl
@@ -34,16 +34,18 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "domstubs.idl"
 
+interface nsIDOMWorkerPool;
+
 [scriptable, uuid(8e150a70-3e51-45df-bee3-77505fbe016c)]
 interface nsIDOMNavigator : nsISupports
 {
   readonly attribute DOMString           appCodeName;
   readonly attribute DOMString           appName;
   readonly attribute DOMString           appVersion;
   readonly attribute DOMString           language;
   readonly attribute nsIDOMMimeTypeArray mimeTypes;
@@ -58,16 +60,18 @@ interface nsIDOMNavigator : nsISupports
   readonly attribute DOMString           userAgent;
   readonly attribute boolean             cookieEnabled;
   readonly attribute boolean             onLine;
   readonly attribute DOMString           buildID;
 
   boolean                   javaEnabled();
   boolean                   taintEnabled();
 
+  nsIDOMWorkerPool newWorkerPool();
+
   // XXX This one's tough, would nsISupports preference(in DOMString
   // pref /*, ... */); work?
 
   // jsval      preference(/* ... */);
 };
 
 
 [scriptable, uuid(4b4f8316-1dd2-11b2-b265-9a857376d159)]
new file mode 100644
--- /dev/null
+++ b/dom/public/idl/threads/Makefile.in
@@ -0,0 +1,52 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2007
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
+#   Ben Turner <bent.mozilla@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH        = ../../../..
+topsrcdir    = @top_srcdir@
+srcdir       = @srcdir@
+VPATH        = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE       = dom
+XPIDL_MODULE = dom_threads
+GRE_MODULE   = 1
+
+XPIDLSRCS    =  nsIDOMThreads.idl
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/public/idl/threads/nsIDOMThreads.idl
@@ -0,0 +1,140 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
+ *   Ben Turner <bent.mozilla@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface nsIScriptError;
+
+[scriptable, function, uuid(e50ca05d-1381-4abb-a021-02eb720cfc38)]
+interface nsIDOMWorkerMessageListener : nsISupports
+{
+  /**
+   * An nsIDOMWorkerThread receives the onMessage callback when another
+   * worker posts a message to it.
+   *
+   * @param aMessage (in DOMString)
+   *        The message sent from another worker.
+   * @param aSource (in nsISupports)
+   *        The worker that sent the message. Useful for a quick response.
+   */
+  void onMessage(in DOMString aMessage,
+                 in nsISupports aSource);
+};
+
+[scriptable, function, uuid(9df8422e-25dd-43f4-b9b9-709f9e074647)]
+interface nsIDOMWorkerErrorListener : nsISupports
+{
+  /**
+   * An nsIDOMWorkerPool receives the onError callback when one of its child
+   * workers has a parse error or an unhandled exception.
+   * 
+   * @param aMessage (in nsIScriptError)
+   *        Details about the error that occurred. See nsIScriptError.
+   * @param aSource (in nsISupports)
+   *        The worker that sent the message. Depending on the specific error in
+   *        question it may not be possible to use this object (in the case of a
+   *        parse error, for instance, aSource will be unusable).
+   */
+  void onError(in nsIScriptError aError,
+               in nsISupports aSource);
+};
+
+[scriptable, uuid(6f19f3ff-2aaa-4504-9b71-dca3c191efed)]
+interface nsIDOMWorkerThread : nsISupports
+{
+  /**
+   * Sends a message to the worker.
+   *
+   * @param aMessage (in DOMString)
+   *        The message to send.
+   */
+  void postMessage(in DOMString aMessage);
+};
+
+[scriptable, uuid(45312e93-8a3e-4493-9bd9-272a6c23a16c)]
+interface nsIDOMWorkerPool : nsISupports
+{
+  /**
+   * Sends a message to the pool.
+   *
+   * @param aMessage (in DOMString)
+   *        The message to send..
+   */
+  void postMessage(in DOMString aMessage);
+
+  /**
+   * The nsIDOMWorkerMessageListener which handles messages for this worker.
+   * 
+   * Developers should set this attribute to a proper object before another
+   * worker begins sending messages to ensure that all messages are received.
+   */
+  attribute nsIDOMWorkerMessageListener messageListener;
+
+  /**
+   * The nsIDOMWorkerErrorListener which handles errors in child threads.
+   *
+   * Developers should set this attribute to a proper object before calling
+   * createWorker in order to catch parse errors as well as runtime exceptions.
+   */
+  attribute nsIDOMWorkerErrorListener errorListener;
+
+  /**
+   * Create a new worker object by evaluating the given script.
+   *
+   * @param aSourceScript (in DOMString)
+   *        The script to compile. See below for details on the scope in which
+   *        the script will run.
+   */
+  nsIDOMWorkerThread createWorker(in DOMString aSourceScript);
+};
+
+[scriptable, uuid(0f2a52ea-afc9-49e6-86dd-2d0cb65b5dd5)]
+interface nsIDOMThreadService : nsISupports
+{
+  /**
+   * Creates a new DOM worker pool.
+   */
+  nsIDOMWorkerPool createPool();
+};
+
+[scriptable, uuid(fcf387be-a7e3-4283-8bc5-06bfe13c5e8c)]
+interface nsIDOMWorkerThreadContext : nsISupports
+{
+  readonly attribute nsIDOMWorkerThread thisThread;
+};
--- a/dom/src/Makefile.in
+++ b/dom/src/Makefile.in
@@ -37,17 +37,17 @@
 
 DEPTH		= ../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-DIRS		= base jsurl events storage offline json geolocation
+DIRS		= base jsurl events storage offline json geolocation threads
 
 include $(topsrcdir)/config/rules.mk
 
 _FILES = \
 	$(srcdir)/res/hiddenWindow.html \
 	$(NULL)
 
 libs::
--- a/dom/src/base/Makefile.in
+++ b/dom/src/base/Makefile.in
@@ -120,16 +120,17 @@ CPPSRCS =			\
 # static lib.
 FORCE_STATIC_LIB = 1
 
 LOCAL_INCLUDES = \
 		-I$(srcdir)/../events \
 		-I$(srcdir)/../storage \
 		-I$(srcdir)/../offline \
 		-I$(srcdir)/../geolocation \
+		-I$(srcdir)/../threads \
 		-I$(topsrcdir)/content/xbl/src \
 		-I$(topsrcdir)/content/xul/document/src \
 		-I$(topsrcdir)/content/events/src \
 		-I$(topsrcdir)/content/base/src \
 		-I$(topsrcdir)/content/html/document/src \
 		-I$(topsrcdir)/layout/style \
 		$(NULL)
 
--- a/dom/src/base/nsGlobalWindow.cpp
+++ b/dom/src/base/nsGlobalWindow.cpp
@@ -77,16 +77,17 @@
 #ifdef OJI
 #include "nsIJVMManager.h"
 #include "nsILiveConnectManager.h"
 #endif
 #include "nsContentCID.h"
 #include "nsLayoutStatics.h"
 #include "nsCycleCollector.h"
 #include "nsCCUncollectableMarker.h"
+#include "nsDOMThreadService.h"
 
 // Interfaces Needed
 #include "nsIWidget.h"
 #include "nsIBaseWindow.h"
 #include "nsICharsetConverterManager.h"
 #include "nsIContent.h"
 #include "nsIContentViewerEdit.h"
 #include "nsIDocShell.h"
@@ -108,16 +109,17 @@
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsIDOMMessageEvent.h"
 #include "nsIDOMPopupBlockedEvent.h"
 #include "nsIDOMPkcs11.h"
 #include "nsIDOMOfflineResourceList.h"
 #include "nsIDOMGeoGeolocation.h"
+#include "nsIDOMThreads.h"
 #include "nsDOMString.h"
 #include "nsIEmbeddingSiteWindow2.h"
 #include "nsThreadUtils.h"
 #include "nsIEventStateManager.h"
 #include "nsIHttpProtocolHandler.h"
 #include "nsIJSContextStack.h"
 #include "nsIJSRuntimeService.h"
 #include "nsIMarkupDocumentViewer.h"
@@ -857,16 +859,22 @@ nsGlobalWindow::ClearControllers()
   }
 }
 
 void
 nsGlobalWindow::FreeInnerObjects(PRBool aClearScope)
 {
   NS_ASSERTION(IsInnerWindow(), "Don't free inner objects on an outer window");
 
+  // Kill all of the workers for this window.
+  nsDOMThreadService* dts = nsDOMThreadService::get();
+  if (dts) {
+    dts->CancelWorkersForGlobal(static_cast<nsIScriptGlobalObject*>(this));
+  }
+
   ClearAllTimeouts();
 
   mChromeEventHandler = nsnull;
 
   if (mListenerManager) {
     mListenerManager->Disconnect();
     mListenerManager = nsnull;
   }
@@ -8561,16 +8569,21 @@ nsGlobalWindow::RestoreWindowState(nsISu
   return NS_OK;
 }
 
 void
 nsGlobalWindow::SuspendTimeouts()
 {
   FORWARD_TO_INNER_VOID(SuspendTimeouts, ());
 
+  nsDOMThreadService* dts = nsDOMThreadService::get();
+  if (dts) {
+    dts->SuspendWorkersForGlobal(static_cast<nsIScriptGlobalObject*>(this));
+  }
+
   PRTime now = PR_Now();
   for (nsTimeout *t = FirstTimeout(); IsTimeout(t); t = t->Next()) {
     // Change mWhen to be the time remaining for this timer.    
     if (t->mWhen > now)
       t->mWhen -= now;
     else
       t->mWhen = 0;
 
@@ -8616,16 +8629,21 @@ nsGlobalWindow::SuspendTimeouts()
   }
 }
 
 nsresult
 nsGlobalWindow::ResumeTimeouts()
 {
   FORWARD_TO_INNER(ResumeTimeouts, (), NS_ERROR_NOT_INITIALIZED);
 
+  nsDOMThreadService* dts = nsDOMThreadService::get();
+  if (dts) {
+    dts->ResumeWorkersForGlobal(static_cast<nsIScriptGlobalObject*>(this));
+  }
+
   // Restore all of the timeouts, using the stored time remaining
   // (stored in timeout->mWhen).
 
   PRTime now = PR_Now();
   nsresult rv;
 
   for (nsTimeout *t = FirstTimeout(); IsTimeout(t); t = t->Next()) {
     // Make sure to cast the unsigned PR_USEC_PER_MSEC to signed
@@ -9720,8 +9738,23 @@ NS_IMETHODIMP nsNavigator::GetGeolocatio
     nsCOMPtr<nsIDOMWindow> contentDOMWindow(do_GetInterface(mDocShell));
     mGeolocation = new nsGeolocation(contentDOMWindow);
   }
 
   NS_IF_ADDREF(*_retval = mGeolocation);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsNavigator::NewWorkerPool(nsIDOMWorkerPool** _retval)
+{
+  nsresult rv;
+  nsCOMPtr<nsIDOMThreadService> threadService =
+    nsDOMThreadService::GetOrInitService();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIDOMWorkerPool> newPool;
+  rv = threadService->CreatePool(getter_AddRefs(newPool));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  newPool.forget(_retval);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/Makefile.in
@@ -0,0 +1,80 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is worker threads.
+#
+# The Initial Developer of the Original Code is
+#   Mozilla Corporation
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
+#   Ben Turner <bent.mozilla@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH            = ../../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE           = dom
+LIBRARY_NAME     = domthreads_s
+LIBXUL_LIBRARY   = 1
+FORCE_STATIC_LIB = 1
+
+REQUIRES = \
+  caps \
+  content \
+  js \
+  layout \
+  pref \
+  string \
+  widget \
+  xpcom \
+  xpconnect \
+  $(NULL)
+
+CPPSRCS = \
+  nsDOMThreadService.cpp \
+  nsDOMWorkerBase.cpp \
+  nsDOMWorkerPool.cpp \
+  nsDOMWorkerSecurityManager.cpp \
+  nsDOMWorkerThread.cpp \
+  nsDOMWorkerTimeout.cpp \
+  $(NULL)
+
+LOCAL_INCLUDES = \
+  -I$(topsrcdir)/dom/src/base \
+  $(NULL)
+
+ifdef MOZ_MOCHITEST
+DIRS += test
+endif
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -0,0 +1,992 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
+ *   Ben Turner <bent.mozilla@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDOMThreadService.h"
+
+// Interfaces
+#include "nsIComponentManager.h"
+#include "nsIConsoleService.h"
+#include "nsIEventTarget.h"
+#include "nsIGenericFactory.h"
+#include "nsIJSContextStack.h"
+#include "nsIJSRuntimeService.h"
+#include "nsIObserverService.h"
+#include "nsIScriptError.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIServiceManager.h"
+#include "nsISupportsPriority.h"
+#include "nsIThreadPool.h"
+#include "nsIXPConnect.h"
+
+// Other includes
+#include "nsAutoLock.h"
+#include "nsAutoPtr.h"
+#include "nsContentUtils.h"
+#include "nsDeque.h"
+#include "nsIClassInfoImpl.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "nsXPCOMCID.h"
+#include "nsXPCOMCIDInternal.h"
+#include "pratom.h"
+#include "prthread.h"
+
+// DOMWorker includes
+#include "nsDOMWorkerPool.h"
+#include "nsDOMWorkerSecurityManager.h"
+#include "nsDOMWorkerTimeout.h"
+
+#ifdef PR_LOGGING
+PRLogModuleInfo *gDOMThreadsLog = nsnull;
+#endif
+#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
+
+// The maximum number of threads in the internal thread pool
+#define THREADPOOL_MAX_THREADS 3
+
+PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS >= 1);
+
+// The maximum number of idle threads in the internal thread pool
+#define THREADPOOL_IDLE_THREADS 3
+
+PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS >= THREADPOOL_IDLE_THREADS);
+
+// The number of times our JS operation callback will be called before yielding
+// the thread
+#define CALLBACK_YIELD_THRESHOLD 100
+
+// A "bad" value for the NSPR TLS functions.
+#define BAD_TLS_INDEX (PRUintn)-1
+
+// Don't know why nsISupports.idl defines this out...
+#define NS_FORWARD_NSISUPPORTS(_to) \
+  NS_IMETHOD QueryInterface(const nsIID& uuid, void** result) { \
+    return _to QueryInterface(uuid, result); \
+  } \
+  NS_IMETHOD_(nsrefcnt) AddRef(void) { return _to AddRef(); } \
+  NS_IMETHOD_(nsrefcnt) Release(void) { return _to Release(); } 
+
+// Easy access for static functions. No reference here.
+static nsDOMThreadService* gDOMThreadService = nsnull;
+
+// 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;
+
+/**
+ * Simple class to automatically destroy a JSContext to make error handling
+ * easier.
+ */
+class JSAutoContextDestroyer
+{
+public:
+  JSAutoContextDestroyer(JSContext* aCx)
+  : mCx(aCx) { }
+
+  ~JSAutoContextDestroyer() {
+    if (mCx) {
+      nsContentUtils::XPConnect()->ReleaseJSContext(mCx, PR_TRUE);
+    }
+  }
+
+  operator JSContext*() {
+    return mCx;
+  }
+
+  JSContext* forget() {
+    JSContext* cx = mCx;
+    mCx = nsnull;
+    return cx;
+  }
+
+private:
+  JSContext* mCx;
+};
+
+/**
+ * This class is used as to post an error to the main thread. It logs the error
+ * to the console and calls the pool's onError callback.
+ */
+class nsReportErrorRunnable : public nsRunnable
+{
+public:
+  nsReportErrorRunnable(nsIScriptError* aError, nsDOMWorkerThread* aWorker)
+  : mError(aError), mWorker(aWorker) { }
+
+  NS_IMETHOD Run() {
+    nsresult rv;
+
+    nsCOMPtr<nsIConsoleService> consoleService =
+      do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
+    if (NS_SUCCEEDED(rv)) {
+      consoleService->LogMessage(mError);
+    }
+
+    if (!mWorker->IsCanceled()) {
+#ifdef PR_LOGGING
+      nsAutoString message;
+      mError->GetErrorMessage(message);
+#endif
+      nsRefPtr<nsDOMWorkerPool> pool = mWorker->Pool();
+
+      LOG(("Posting error '%s' to pool [0x%p]",
+           NS_LossyConvertUTF16toASCII(message).get(),
+           static_cast<void*>(pool.get())));
+
+      pool->HandleError(mError, mWorker);
+    }
+    return NS_OK;
+  }
+
+private:
+  // XXX Maybe this should be an nsIException...
+  nsCOMPtr<nsIScriptError> mError;
+
+  // Have to carry a strong ref since this is used as a parameter to the
+  // onError callback.
+  nsRefPtr<nsDOMWorkerThread> mWorker;
+};
+
+/**
+ * Need this to expose an nsIScriptError to content JS (implement nsIClassInfo
+ * with DOM_OBJECT flag set.
+ */
+class nsDOMWorkerScriptError : public nsIClassInfo
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICLASSINFO
+
+  nsDOMWorkerScriptError(nsIScriptError* aError)
+  : mScriptError(this, aError) { }
+
+protected:
+
+  // Lame, nsIScriptError and nsIClassInfo both have 'readonly attribute
+  // unsigned long flags' so we have to use an inner class to do this the
+  // right way...
+  class InnerScriptError : public nsIScriptError
+  {
+  public:
+    NS_FORWARD_NSISUPPORTS(mParent->)
+    NS_FORWARD_NSISCRIPTERROR(mError->)
+    NS_FORWARD_NSICONSOLEMESSAGE(mError->)
+
+    InnerScriptError(nsDOMWorkerScriptError* aParent, nsIScriptError* aError)
+    : mParent(aParent), mError(aError) { }
+
+  protected:
+    nsDOMWorkerScriptError* mParent;
+    nsCOMPtr<nsIScriptError> mError;
+  };
+
+  InnerScriptError mScriptError;
+};
+
+NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerScriptError)
+NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerScriptError)
+
+// More hoops to jump through for the identical IDL methods
+NS_INTERFACE_MAP_BEGIN(nsDOMWorkerScriptError)
+  if (aIID.Equals(NS_GET_IID(nsIScriptError)) ||
+      aIID.Equals(NS_GET_IID(nsIConsoleMessage))) {
+    foundInterface = static_cast<nsIConsoleMessage*>(&mScriptError);
+  }
+  else
+  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerScriptError, nsIScriptError,
+                                                     nsIConsoleMessage)
+
+NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerScriptError)
+
+/**
+ * Used to post an expired timeout to the correct worker.
+ */
+class nsDOMWorkerTimeoutRunnable : public nsRunnable
+{
+public:
+  nsDOMWorkerTimeoutRunnable(nsDOMWorkerTimeout* aTimeout)
+  : mTimeout(aTimeout) { }
+
+  NS_IMETHOD Run() {
+    return mTimeout->Run();
+  }
+protected:
+  nsRefPtr<nsDOMWorkerTimeout> mTimeout;
+};
+
+/**
+ * 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
+{
+  friend class nsDOMThreadService;
+
+public:
+  nsDOMWorkerRunnable(nsDOMWorkerThread* aWorker)
+  : mWorker(aWorker) { }
+
+  virtual ~nsDOMWorkerRunnable() {
+    nsCOMPtr<nsIRunnable> runnable;
+    while ((runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront()))) {
+      // Loop until all the runnables are dead.
+    }
+
+    // Only release mWorker on the main thread!
+    nsDOMWorkerThread* worker = nsnull;
+    mWorker.swap(worker);
+
+    nsISupports* supports = NS_ISUPPORTS_CAST(nsIDOMWorkerThread*, worker);
+    NS_ASSERTION(supports, "This should never be null!");
+
+    nsCOMPtr<nsIThread> mainThread(do_GetMainThread());
+    NS_ProxyRelease(mainThread, supports);
+  }
+
+  void PutRunnable(nsIRunnable* aRunnable) {
+    NS_ASSERTION(aRunnable, "Null pointer!");
+
+    NS_ADDREF(aRunnable);
+
+    // No need to enter the monitor because we should already be in it.
+
+    mRunnables.Push(aRunnable);
+  }
+
+  NS_IMETHOD Run() {
+    // This must have been set up by the thread service
+    NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!");
+
+    // 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!");
+  
+    JS_SetContextPrivate(cx, mWorker);
+
+    // Tell the worker which context it will be using
+    if (mWorker->SetGlobalForContext(cx)) {
+      RunQueue();
+
+      // 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);
+
+      nsAutoMonitor mon(gDOMThreadService->mMonitor);
+      gDOMThreadService->WorkerComplete(this);
+      mon.NotifyAll();
+    }
+
+    return NS_OK;
+  }
+
+protected:
+
+  void RunQueue() {
+    while (1) {
+      nsCOMPtr<nsIRunnable> runnable;
+      {
+        nsAutoMonitor mon(gDOMThreadService->mMonitor);
+
+        runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront());
+
+        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
+          gDOMThreadService->WorkerComplete(this);
+          mon.NotifyAll();
+          return;
+        }
+      }
+
+#ifdef DEBUG
+      nsresult rv =
+#endif
+      runnable->Run();
+      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Runnable failed!");
+    }
+  }
+
+  // Set at construction
+  nsRefPtr<nsDOMWorkerThread> mWorker;
+
+  // Protected by mMonitor
+  nsDeque mRunnables;
+};
+
+/*******************************************************************************
+ * JS environment function and callbacks
+ */
+
+JSBool JS_DLL_CALLBACK
+DOMWorkerOperationCallback(JSContext* aCx)
+{
+  nsDOMWorkerThread* worker = (nsDOMWorkerThread*)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;
+  jsrefcount suspendDepth = 0;
+
+  while (1) {
+    // Kill execution if we're canceled.
+    if (worker->IsCanceled()) {
+      LOG(("Forcefully killing JS for worker [0x%p]",
+           static_cast<void*>(worker)));
+
+      if (wasSuspended) {
+        gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
+        JS_ResumeRequest(aCx, suspendDepth);
+      }
+
+      // Kill exectuion of the currently running JS.
+      return PR_FALSE;
+    }
+
+    // Break out if we're not suspended.
+    if (!worker->IsSuspended()) {
+      if (wasSuspended) {
+        gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
+        JS_ResumeRequest(aCx, suspendDepth);
+      }
+      break;
+    }
+
+    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.
+      if (worker->IsCanceled()) {
+        NS_WARNING("Tried to suspend on a pool that has gone away");
+        return PR_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.
+      gDOMThreadService->ChangeThreadPoolMaxThreads(1);
+
+      // Only do all this setup once.
+      wasSuspended = PR_TRUE;
+    }
+
+    nsAutoMonitor mon(pool->Monitor());
+    mon.Wait();
+  }
+
+  // Since only one thread can access a context at once we don't have to worry
+  // about atomically incrementing this counter
+  if (++worker->mCallbackCount >= CALLBACK_YIELD_THRESHOLD) {
+    // Must call this so that GC can happen on the main thread!
+    JS_YieldRequest(aCx);
+
+    // Start the counter over.
+    worker->mCallbackCount = 0;
+  }
+
+  // Continue execution.
+  return JS_TRUE;
+}
+
+void JS_DLL_CALLBACK
+DOMWorkerErrorReporter(JSContext* aCx,
+                       const char* aMessage,
+                       JSErrorReport* aReport)
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Huh?!");
+
+  nsDOMWorkerThread* worker = (nsDOMWorkerThread*)JS_GetContextPrivate(aCx);
+
+  nsresult rv;
+  nsCOMPtr<nsIScriptError> errorObject =
+    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 =
+    reinterpret_cast<const PRUnichar*>(aReport->uclinebuf);
+
+  PRUint32 column = aReport->uctokenptr - aReport->uclinebuf;
+
+  rv = errorObject->Init(message, filename.get(), line, aReport->lineno,
+                        column, aReport->flags, "DOM Worker javascript");
+  NS_ENSURE_SUCCESS(rv,);
+
+  nsRefPtr<nsDOMWorkerScriptError> domError =
+    new nsDOMWorkerScriptError(errorObject);
+  NS_ENSURE_TRUE(domError,);
+
+  nsCOMPtr<nsIScriptError> scriptError(do_QueryInterface(domError));
+  NS_ENSURE_TRUE(scriptError,);
+
+  nsCOMPtr<nsIThread> mainThread(do_GetMainThread());
+  NS_ENSURE_TRUE(mainThread,);
+
+  nsCOMPtr<nsIRunnable> runnable =
+    new nsReportErrorRunnable(scriptError, worker);
+  NS_ENSURE_TRUE(runnable,);
+
+  rv = mainThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv,);
+}
+
+/*******************************************************************************
+ * nsDOMThreadService
+ */
+
+nsDOMThreadService::nsDOMThreadService()
+: mMonitor(nsnull)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+#ifdef PR_LOGGING
+  if (!gDOMThreadsLog) {
+    gDOMThreadsLog = PR_NewLogModule("nsDOMThreads");
+  }
+#endif
+  LOG(("Initializing DOM Thread service"));
+}
+
+nsDOMThreadService::~nsDOMThreadService()
+{
+  LOG(("DOM Thread service destroyed"));
+
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  Cleanup();
+
+  if (mMonitor) {
+    nsAutoMonitor::DestroyMonitor(mMonitor);
+  }
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS4(nsDOMThreadService, nsIEventTarget,
+                                                  nsIObserver,
+                                                  nsIThreadPoolListener,
+                                                  nsIDOMThreadService)
+
+nsresult
+nsDOMThreadService::Init()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(!gDOMThreadService, "Only one instance should ever be created!");
+
+  nsresult rv;
+  nsCOMPtr<nsIObserverService> obs =
+    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);
+
+  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);
+
+  rv = mThreadPool->SetIdleThreadLimit(THREADPOOL_IDLE_THREADS);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mMonitor = nsAutoMonitor::NewMonitor("nsDOMThreadService::mMonitor");
+  NS_ENSURE_TRUE(mMonitor, NS_ERROR_OUT_OF_MEMORY);
+
+  PRBool success = mWorkersInProgress.Init();
+  NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+
+  nsCOMPtr<nsIJSRuntimeService>
+    runtimeSvc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
+  NS_ENSURE_TRUE(runtimeSvc, NS_ERROR_FAILURE);
+  runtimeSvc.forget(&gJSRuntimeService);
+
+  nsCOMPtr<nsIThreadJSContextStack>
+    contextStack(do_GetService("@mozilla.org/js/xpc/ContextStack;1"));
+  NS_ENSURE_TRUE(contextStack, NS_ERROR_FAILURE);
+  contextStack.forget(&gThreadJSContextStack);
+
+  nsCOMPtr<nsIXPCSecurityManager> secMan(new nsDOMWorkerSecurityManager());
+  NS_ENSURE_TRUE(secMan, NS_ERROR_OUT_OF_MEMORY);
+  secMan.forget(&gWorkerSecurityManager);
+
+  if (gJSContextIndex == BAD_TLS_INDEX &&
+      PR_NewThreadPrivateIndex(&gJSContextIndex, NULL) != PR_SUCCESS) {
+    NS_ERROR("PR_NewThreadPrivateIndex failed!");
+    gJSContextIndex = BAD_TLS_INDEX;
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+/* static */
+already_AddRefed<nsIDOMThreadService>
+nsDOMThreadService::GetOrInitService()
+{
+  if (!gDOMThreadService) {
+    nsRefPtr<nsDOMThreadService> service = new nsDOMThreadService();
+    NS_ENSURE_TRUE(service, nsnull);
+
+    nsresult rv = service->Init();
+    NS_ENSURE_SUCCESS(rv, nsnull);
+
+    service.swap(gDOMThreadService);
+  }
+
+  nsCOMPtr<nsIDOMThreadService> service(gDOMThreadService);
+  return service.forget();
+}
+
+/* static */
+nsDOMThreadService*
+nsDOMThreadService::get()
+{
+  return gDOMThreadService;
+}
+
+/* static */
+void
+nsDOMThreadService::Shutdown()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_IF_RELEASE(gDOMThreadService);
+}
+
+void
+nsDOMThreadService::Cleanup()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  // 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);
+  }
+
+  // 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;
+  }
+
+  // Need to force a GC so that all of our workers get cleaned up.
+  if (gThreadJSContextStack) {
+    JSContext* safeContext;
+    if (NS_SUCCEEDED(gThreadJSContextStack->GetSafeJSContext(&safeContext))) {
+      JS_GC(safeContext);
+    }
+    NS_RELEASE(gThreadJSContextStack);
+  }
+
+  // These must be released after the thread pool is shut down.
+  NS_IF_RELEASE(gJSRuntimeService);
+  NS_IF_RELEASE(gWorkerSecurityManager);
+}
+
+nsresult
+nsDOMThreadService::Dispatch(nsDOMWorkerThread* aWorker,
+                             nsIRunnable* aRunnable)
+{
+  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]",
+         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);
+      return NS_OK;
+    }
+
+    workerRunnable = new nsDOMWorkerRunnable(aWorker);
+    NS_ENSURE_TRUE(workerRunnable, NS_ERROR_OUT_OF_MEMORY);
+
+    workerRunnable->PutRunnable(aRunnable);
+
+    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
+  // infallible malloc implementation.
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to dispatch runnable to thread pool!");
+
+    nsAutoMonitor mon(mMonitor);
+
+    // We exited the monitor after inserting the runnable into the table so make
+    // sure we're removing the right one!
+    nsRefPtr<nsDOMWorkerRunnable> tableRunnable;
+    if (mWorkersInProgress.Get(aWorker, getter_AddRefs(tableRunnable)) &&
+        workerRunnable == tableRunnable) {
+      mWorkersInProgress.Remove(aWorker);
+
+      // And don't forget to tell anyone who's waiting.
+      mon.NotifyAll();
+    }
+
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+nsDOMThreadService::WorkerComplete(nsDOMWorkerRunnable* aRunnable)
+{
+
+  // No need to be in the monitor here because we should already be in it.
+
+#ifdef DEBUG
+  nsRefPtr<nsDOMWorkerThread>& debugWorker = aRunnable->mWorker;
+
+  nsRefPtr<nsDOMWorkerRunnable> runnable;
+  NS_ASSERTION(mWorkersInProgress.Get(debugWorker, getter_AddRefs(runnable)) &&
+               runnable == aRunnable,
+               "Removing a worker that isn't in our hashtable?!");
+#endif
+
+  mWorkersInProgress.Remove(aRunnable->mWorker);
+}
+
+void
+nsDOMThreadService::WaitForCanceledWorker(nsDOMWorkerThread* aWorker)
+{
+  NS_ASSERTION(aWorker->IsCanceled(),
+               "Waiting on a worker that isn't canceled!");
+
+  nsAutoMonitor mon(mMonitor);
+
+  while (mWorkersInProgress.Get(aWorker, nsnull)) {
+    mon.Wait();
+  }
+}
+
+/* static */
+JSContext*
+nsDOMThreadService::CreateJSContext()
+{
+  JSRuntime* rt;
+  gJSRuntimeService->GetRuntime(&rt);
+  NS_ENSURE_TRUE(rt, nsnull);
+
+  JSAutoContextDestroyer cx(JS_NewContext(rt, 8192));
+  NS_ENSURE_TRUE(cx, nsnull);
+
+  JS_SetErrorReporter(cx, DOMWorkerErrorReporter);
+
+  JS_SetOperationCallback(cx, DOMWorkerOperationCallback,
+                          100 * JS_OPERATION_WEIGHT_BASE);
+
+  nsresult rv = nsContentUtils::XPConnect()->
+    SetSecurityManagerForJSContext(cx, gWorkerSecurityManager, 0);
+  NS_ENSURE_SUCCESS(rv, nsnull);
+
+  return cx.forget();
+}
+
+#define LOOP_OVER_POOLS(_func, _args)                     \
+  PR_BEGIN_MACRO                                          \
+    PRUint32 poolCount = mPools.Length();                 \
+    for (PRUint32 i = 0; i < poolCount; i++) {            \
+      mPools[i]-> _func _args ;                           \
+    }                                                     \
+  PR_END_MACRO
+
+void
+nsDOMThreadService::CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  LOOP_OVER_POOLS(CancelWorkersForGlobal, (aGlobalObject));
+}
+
+void
+nsDOMThreadService::SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  LOOP_OVER_POOLS(SuspendWorkersForGlobal, (aGlobalObject));
+}
+
+void
+nsDOMThreadService::ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  LOOP_OVER_POOLS(ResumeWorkersForGlobal, (aGlobalObject));
+}
+
+void
+nsDOMThreadService::NoteDyingPool(nsDOMWorkerPool* aPool)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  NS_ASSERTION(mPools.Contains(aPool), "aPool should be in the array!");
+  mPools.RemoveElement(aPool);
+}
+
+void
+nsDOMThreadService::TimeoutReady(nsDOMWorkerTimeout* aTimeout)
+{
+  nsRefPtr<nsDOMWorkerTimeoutRunnable> runnable =
+    new nsDOMWorkerTimeoutRunnable(aTimeout);
+  NS_ENSURE_TRUE(runnable,);
+
+  Dispatch(aTimeout->GetWorker(), runnable);
+}
+
+nsresult
+nsDOMThreadService::ChangeThreadPoolMaxThreads(PRInt16 aDelta)
+{
+  NS_ENSURE_ARG(aDelta == 1 || aDelta == -1);
+
+  PRUint32 currentThreadCount;
+  nsresult rv = mThreadPool->GetThreadLimit(&currentThreadCount);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRInt32 newThreadCount = (PRInt32)currentThreadCount + (PRInt32)aDelta;
+  NS_ASSERTION(newThreadCount >= THREADPOOL_MAX_THREADS,
+               "Can't go below initial thread count!");
+
+  rv = mThreadPool->SetThreadLimit((PRUint32)newThreadCount);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsIJSRuntimeService*
+nsDOMThreadService::JSRuntimeService()
+{
+  return gJSRuntimeService;
+}
+
+nsIThreadJSContextStack*
+nsDOMThreadService::ThreadJSContextStack()
+{
+  return gThreadJSContextStack;
+}
+
+nsIXPCSecurityManager*
+nsDOMThreadService::WorkerSecurityManager()
+{
+  return gWorkerSecurityManager;
+}
+
+/**
+ * See nsIEventTarget
+ */
+NS_IMETHODIMP
+nsDOMThreadService::Dispatch(nsIRunnable* aEvent,
+                             PRUint32 aFlags)
+{
+  NS_ENSURE_ARG_POINTER(aEvent);
+  NS_ENSURE_FALSE(aFlags & NS_DISPATCH_SYNC, NS_ERROR_NOT_IMPLEMENTED);
+
+  // This should only ever be called by the timer code! We run the event right
+  // now, but all that does is queue the real event for the proper worker.
+
+  aEvent->Run();
+
+  return NS_OK;
+}
+
+/**
+ * See nsIEventTarget
+ */
+NS_IMETHODIMP
+nsDOMThreadService::IsOnCurrentThread(PRBool* _retval)
+{
+  NS_NOTREACHED("No one should call this!");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/**
+ * See nsIObserver
+ */
+NS_IMETHODIMP
+nsDOMThreadService::Observe(nsISupports* aSubject,
+                            const char* aTopic,
+                            const PRUnichar* aData)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    Cleanup();
+    return NS_OK;
+  }
+
+  NS_NOTREACHED("Unknown observer topic!");
+  return NS_OK;
+}
+
+/**
+ * See nsIThreadPoolListener
+ */
+NS_IMETHODIMP
+nsDOMThreadService::OnThreadCreated()
+{
+  LOG(("Thread created"));
+
+  nsIThread* current = NS_GetCurrentThread();
+
+  // We want our worker threads to always have a lower priority than the main
+  // thread. NSPR docs say that this isn't incredibly reliable across all
+  // platforms but we hope for the best.
+  nsCOMPtr<nsISupportsPriority> priority(do_QueryInterface(current));
+  NS_ENSURE_TRUE(priority, NS_ERROR_FAILURE);
+
+  nsresult rv = priority->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!");
+
+  // Set the context up for the worker.
+  JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
+  if (!cx) {
+    cx = nsDOMThreadService::CreateJSContext();
+    NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
+
+    PRStatus status = PR_SetThreadPrivate(gJSContextIndex, cx);
+    if (status != PR_SUCCESS) {
+      NS_WARNING("Failed to set context on thread!");
+      nsContentUtils::XPConnect()->ReleaseJSContext(cx, PR_TRUE);
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  // Make sure that XPConnect knows about this context.
+  gThreadJSContextStack->Push(cx);
+  gThreadJSContextStack->SetSafeJSContext(cx);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMThreadService::OnThreadShuttingDown()
+{
+  LOG(("Thread shutting down"));
+
+  NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!");
+
+  JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
+  NS_WARN_IF_FALSE(cx, "Thread died with no context?");
+  if (cx) {
+    JSContext* pushedCx;
+    gThreadJSContextStack->Pop(&pushedCx);
+    NS_ASSERTION(pushedCx == cx, "Popped the wrong context!");
+
+    gThreadJSContextStack->SetSafeJSContext(nsnull);
+
+    nsContentUtils::XPConnect()->ReleaseJSContext(cx, PR_TRUE);
+  }
+
+  return NS_OK;
+}
+
+/**
+ * See nsIDOMThreadService
+ */
+NS_IMETHODIMP
+nsDOMThreadService::CreatePool(nsIDOMWorkerPool** _retval)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  NS_ENSURE_TRUE(mThreadPool, NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
+
+  nsRefPtr<nsDOMWorkerPool> pool(new nsDOMWorkerPool());
+  NS_ENSURE_TRUE(pool, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv = pool->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  NS_ASSERTION(!mPools.Contains(pool), "Um?!");
+  mPools.AppendElement(pool);
+
+  NS_ADDREF(*_retval = pool);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMThreadService.h
@@ -0,0 +1,144 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
+ *   Ben Turner <bent.mozilla@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __NSDOMTHREADSERVICE_H__
+#define __NSDOMTHREADSERVICE_H__
+
+// Interfaces
+#include "nsIEventTarget.h"
+#include "nsIObserver.h"
+#include "nsIThreadPool.h"
+#include "nsIDOMThreads.h"
+
+// Other includes
+#include "jsapi.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTPtrArray.h"
+#include "prmon.h"
+
+#include "prlog.h"
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gDOMThreadsLog;
+#endif
+
+class nsDOMWorkerPool;
+class nsDOMWorkerRunnable;
+class nsDOMWorkerThread;
+class nsDOMWorkerTimeout;
+class nsIJSRuntimeService;
+class nsIScriptGlobalObject;
+class nsIThreadJSContextStack;
+class nsIXPConnect;
+class nsIXPCSecurityManager;
+
+class nsDOMThreadService : public nsIEventTarget,
+                           public nsIObserver,
+                           public nsIThreadPoolListener,
+                           public nsIDOMThreadService
+{
+  friend class nsDOMWorkerPool;
+  friend class nsDOMWorkerRunnable;
+  friend class nsDOMWorkerThread;
+  friend class nsDOMWorkerTimeout;
+  friend class nsLayoutStatics;
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIEVENTTARGET
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSITHREADPOOLLISTENER
+  NS_DECL_NSIDOMTHREADSERVICE
+
+  // Any DOM consumers that need access to this service should use this method.
+  static already_AddRefed<nsIDOMThreadService> GetOrInitService();
+
+  // Simple getter for this service. This does not create the service if it
+  // hasn't been created already, and it never AddRef's!
+  static nsDOMThreadService* get();
+
+  // Easy access to the services we care about.
+  static nsIJSRuntimeService* JSRuntimeService();
+  static nsIThreadJSContextStack* ThreadJSContextStack();
+  static nsIXPCSecurityManager* WorkerSecurityManager();
+
+  void CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
+  void SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
+  void ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
+
+  nsresult ChangeThreadPoolMaxThreads(PRInt16 aDelta);
+
+private:
+  nsDOMThreadService();
+  ~nsDOMThreadService();
+
+  nsresult Init();
+  void Cleanup();
+
+  static void Shutdown();
+
+  nsresult Dispatch(nsDOMWorkerThread* aWorker,
+                    nsIRunnable* aRunnable);
+
+  void WorkerComplete(nsDOMWorkerRunnable* aRunnable);
+
+  void WaitForCanceledWorker(nsDOMWorkerThread* aWorker);
+
+  static JSContext* CreateJSContext();
+
+  void NoteDyingPool(nsDOMWorkerPool* aPool);
+
+  void TimeoutReady(nsDOMWorkerTimeout* aTimeout);
+
+  // Our internal thread pool.
+  nsCOMPtr<nsIThreadPool> mThreadPool;
+
+  // Weak references, only ever touched on the main thread!
+  nsTPtrArray<nsDOMWorkerPool> mPools;
+
+  // mMonitor protects all access to mWorkersInProgress and
+  // mCreationsInProgress.
+  PRMonitor* mMonitor;
+
+  // A map from nsDOMWorkerThread to nsDOMWorkerRunnable.
+  nsRefPtrHashtable<nsVoidPtrHashKey, nsDOMWorkerRunnable> mWorkersInProgress;
+};
+
+#endif /* __NSDOMTHREADSERVICE_H__ */
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerBase.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDOMWorkerBase.h"
+
+struct JSContext;
+
+// Interfaces
+#include "nsIDOMThreads.h"
+#include "nsIJSContextStack.h"
+#include "nsIXPConnect.h"
+
+// Other includes
+#include "jsapi.h"
+#include "nsAutoPtr.h"
+#include "nsContentUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsThreadUtils.h"
+#include "prlog.h"
+
+#include "nsDOMWorkerThread.h"
+#include "nsDOMWorkerPool.h"
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gDOMThreadsLog;
+#endif
+#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
+
+/**
+ * Calls the message handler function of the target worker on the correct
+ * thread.
+ */
+class nsDOMPostMessageRunnable : public nsRunnable
+{
+public:
+  nsDOMPostMessageRunnable(const nsAString& aMessage,
+                           nsDOMWorkerBase* aSource,
+                           nsDOMWorkerBase* aTarget)
+  : mMessage(aMessage), mSource(aSource), mTarget(aTarget) {
+    NS_ASSERTION(aSource && aTarget, "Must specify both!");
+  }
+
+  NS_IMETHOD Run() {
+#ifdef PR_LOGGING
+    nsCAutoString utf8Message;
+    utf8Message.AssignWithConversion(mMessage);
+
+    static const char* poolStr = "pool";
+    static const char* workerStr = "worker";
+
+    nsCOMPtr<nsIDOMWorkerPool> sourceIsPool;
+    mSource->QueryInterface(NS_GET_IID(nsIDOMWorkerPool),
+                            getter_AddRefs(sourceIsPool));
+
+    nsCOMPtr<nsIDOMWorkerPool> targetIsPool;
+    mTarget->QueryInterface(NS_GET_IID(nsIDOMWorkerPool),
+                            getter_AddRefs(targetIsPool));
+#endif
+
+    LOG(("Posting message '%s' from %s [0x%p] to %s [0x%p]",
+         utf8Message.get(), sourceIsPool ? poolStr : workerStr,
+         static_cast<void*>(mSource.get()), targetIsPool ? poolStr : workerStr,
+         static_cast<void*>(mTarget.get())));
+
+    mTarget->HandleMessage(mMessage, mSource);
+
+    return NS_OK;
+  }
+
+protected:
+  nsString mMessage;
+  nsRefPtr<nsDOMWorkerBase> mSource;
+  nsRefPtr<nsDOMWorkerBase> mTarget;
+};
+
+nsresult
+nsDOMWorkerBase::PostMessageInternal(const nsAString& aMessage,
+                                     nsDOMWorkerBase* aSource)
+{
+  if (IsCanceled() || aSource->IsCanceled()) {
+    return NS_OK;
+  }
+
+  nsRefPtr<nsDOMPostMessageRunnable> runnable =
+    new nsDOMPostMessageRunnable(aMessage, aSource, this);
+  NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv = DispatchMessage(runnable);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+nsDOMWorkerBase::PostMessageInternal(const nsAString& aMessage)
+{
+  nsAXPCNativeCallContext* ncc;
+  nsresult rv = nsContentUtils::XPConnect()->
+    GetCurrentNativeCallContext(&ncc);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<nsDOMWorkerBase> source;
+  if (ncc) {
+    if (NS_IsMainThread()) {
+      // Must be a normal DOM context, use the pool as the source.
+      source = Pool();
+    }
+    else {
+      // Must be a worker context, get the worker from the context private.
+      JSContext* cx;
+      rv = ncc->GetJSContext(&cx);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsRefPtr<nsDOMWorkerThread> worker =
+        (nsDOMWorkerThread*)JS_GetContextPrivate(cx);
+
+      // Only allowed to communicate to other threads in the same pool.
+      nsRefPtr<nsDOMWorkerPool> sourcePool = worker->Pool();
+      NS_ENSURE_TRUE(sourcePool == Pool(), NS_ERROR_NOT_AVAILABLE);
+
+      source = worker;
+    }
+  }
+  else {
+    source = this;
+  }
+
+  rv = PostMessageInternal(aMessage, source);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+void
+nsDOMWorkerBase::Cancel()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  LOG(("Canceling %s [0x%p]", Pool() == this ? "pool" : "worker",
+       static_cast<void*>(this)));
+
+#ifdef DEBUG
+  PRInt32 cancel =
+#endif
+  PR_AtomicSet(&mCanceled, 1);
+  NS_ASSERTION(!cancel, "Canceled more than once?!");
+}
+
+void
+nsDOMWorkerBase::Suspend()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  LOG(("Suspending %s [0x%p]", Pool() == this ? "pool" : "worker",
+       static_cast<void*>(this)));
+
+#ifdef DEBUG
+  PRInt32 suspended =
+#endif
+  PR_AtomicSet(&mSuspended, 1);
+  NS_ASSERTION(!suspended, "Suspended more than once?!");
+}
+
+void
+nsDOMWorkerBase::Resume()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  LOG(("Resuming %s [0x%p]", Pool() == this ? "pool" : "worker",
+       static_cast<void*>(this)));
+
+#ifdef DEBUG
+  PRInt32 suspended =
+#endif
+  PR_AtomicSet(&mSuspended, 0);
+  NS_ASSERTION(suspended, "Not suspended!");
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerBase.h
@@ -0,0 +1,101 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __NSDOMWORKERBASE_H__
+#define __NSDOMWORKERBASE_H__
+
+#include "nsCOMPtr.h"
+#include "nsIDOMThreads.h"
+#include "nsStringGlue.h"
+
+class nsIRunnable;
+class nsDOMWorkerPool;
+
+class nsDOMWorkerBase
+{
+  friend class nsDOMPostMessageRunnable;
+
+public:
+  // Meant to be inherited by an nsISupports-derived class.
+  NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) = 0;
+  NS_IMETHOD_(nsrefcnt) AddRef(void) = 0;
+  NS_IMETHOD_(nsrefcnt) Release(void) = 0;
+
+  PRBool IsCanceled() {
+    return !!mCanceled;
+  }
+
+  PRBool IsSuspended() {
+    return !!mSuspended;
+  }
+
+protected:
+  nsDOMWorkerBase() : mCanceled(0), mSuspended(0) { }
+  virtual ~nsDOMWorkerBase() { }
+
+  void SetMessageListener(nsIDOMWorkerMessageListener* aListener) {
+    mMessageListener = aListener;
+  }
+
+  nsIDOMWorkerMessageListener* GetMessageListener() {
+    return mMessageListener;
+  }
+
+  nsresult PostMessageInternal(const nsAString& aMessage,
+                               nsDOMWorkerBase* aSource);
+
+  nsresult PostMessageInternal(const nsAString& aMessage);
+
+
+  virtual void Cancel();
+  virtual void Suspend();
+  virtual void Resume();
+
+  // Methods that must be implemented by a derived class.
+  virtual nsresult HandleMessage(const nsAString& aMessage,
+                                 nsDOMWorkerBase* aSource) = 0;
+  virtual nsresult DispatchMessage(nsIRunnable* aMessage) = 0;
+  virtual nsDOMWorkerPool* Pool() = 0;
+
+private:
+  nsCOMPtr<nsIDOMWorkerMessageListener> mMessageListener;
+  PRInt32 mCanceled;
+  PRInt32 mSuspended;
+};
+
+#endif /* __NSDOMWORKERBASE_H__ */
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerPool.cpp
@@ -0,0 +1,305 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
+ *   Ben Turner <bent.mozilla@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDOMWorkerPool.h"
+
+// Interfaces
+#include "nsIDOMClassInfo.h"
+#include "nsIJSContextStack.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIServiceManager.h"
+#include "nsIThreadManager.h"
+#include "nsIXPConnect.h"
+#include "nsPIDOMWindow.h"
+
+// Other includes
+#include "nsAutoLock.h"
+#include "nsContentUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsThreadUtils.h"
+
+// DOMWorker includes
+#include "nsDOMThreadService.h"
+#include "nsDOMWorkerThread.h"
+
+#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
+
+#define LOOP_OVER_WORKERS(_func, _args)                   \
+  PR_BEGIN_MACRO                                          \
+    PRUint32 workerCount = mWorkers.Length();             \
+    for (PRUint32 i = 0; i < workerCount; i++) {          \
+      mWorkers[i]-> _func _args ;                         \
+    }                                                     \
+  PR_END_MACRO
+
+nsDOMWorkerPool::nsDOMWorkerPool()
+: mParentGlobal(nsnull)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+}
+
+nsDOMWorkerPool::~nsDOMWorkerPool()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  LOOP_OVER_WORKERS(Cancel, ());
+
+  nsDOMThreadService::get()->NoteDyingPool(this);
+
+  if (mMonitor) {
+    nsAutoMonitor::DestroyMonitor(mMonitor);
+  }
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsDOMWorkerPool, nsIDOMWorkerPool,
+                                               nsIClassInfo)
+
+NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerPool, nsIDOMWorkerPool)
+
+NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerPool)
+
+nsresult
+nsDOMWorkerPool::Init()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  nsAXPCNativeCallContext* ncc;
+  nsresult rv = nsContentUtils::XPConnect()->GetCurrentNativeCallContext(&ncc);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // GetCurrentNativeCallContext can return NS_OK and still hand out a null
+  // context... We shouldn't ever see that here.
+  NS_ENSURE_TRUE(ncc, NS_ERROR_UNEXPECTED);
+
+  JSContext* cx;
+  rv = ncc->GetJSContext(&cx);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsIScriptContext* scriptContext = GetScriptContextFromJSContext(cx);
+  NS_ENSURE_STATE(scriptContext);
+
+  nsIScriptGlobalObject* globalObject = scriptContext->GetGlobalObject();
+  NS_ENSURE_STATE(globalObject);
+
+  nsCOMPtr<nsPIDOMWindow> domWindow(do_QueryInterface(globalObject));
+  NS_ENSURE_TRUE(domWindow, NS_ERROR_NO_INTERFACE);
+
+  nsPIDOMWindow* innerWindow = domWindow->IsOuterWindow() ?
+                               domWindow->GetCurrentInnerWindow() :
+                               domWindow.get();
+  NS_ENSURE_STATE(innerWindow);
+
+  nsCOMPtr<nsISupports> globalSupports(do_QueryInterface(innerWindow));
+  NS_ENSURE_TRUE(globalSupports, NS_ERROR_NO_INTERFACE);
+
+  // We don't want a strong ref, this guy owns us.
+  mParentGlobal = globalSupports.get();
+
+  mMonitor = nsAutoMonitor::NewMonitor("nsDOMWorkerPool::mMonitor");
+  NS_ENSURE_TRUE(mMonitor, NS_ERROR_OUT_OF_MEMORY);
+
+  return NS_OK;
+}
+
+nsresult
+nsDOMWorkerPool::HandleMessage(const nsAString& aMessage,
+                               nsDOMWorkerBase* aSource)
+{
+  nsCOMPtr<nsIDOMWorkerMessageListener> messageListener =
+    nsDOMWorkerBase::GetMessageListener();
+  if (!messageListener) {
+    LOG(("Message received on a worker with no listener!"));
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsISupports> source;
+  aSource->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(source));
+  NS_ASSERTION(source, "Impossible!");
+
+  messageListener->OnMessage(aMessage, source);
+  return NS_OK;
+}
+
+nsresult
+nsDOMWorkerPool::DispatchMessage(nsIRunnable* aRunnable)
+{
+  // Can be called from many different threads!
+
+  nsCOMPtr<nsIThread> mainThread(do_GetMainThread());
+  NS_ENSURE_TRUE(mainThread, NS_ERROR_FAILURE);
+
+  nsresult rv = mainThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+void
+nsDOMWorkerPool::HandleError(nsIScriptError* aError,
+                             nsDOMWorkerThread* aSource)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  if (mErrorListener) {
+    mErrorListener->OnError(aError,
+                            NS_ISUPPORTS_CAST(nsIDOMWorkerThread*, aSource));
+  }
+}
+
+void
+nsDOMWorkerPool::NoteDyingWorker(nsDOMWorkerThread* aWorker)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  NS_ASSERTION(mWorkers.Contains(aWorker), "Worker from a different pool?!");
+  mWorkers.RemoveElement(aWorker);
+}
+
+void
+nsDOMWorkerPool::CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  nsCOMPtr<nsISupports> globalSupports(do_QueryInterface(aGlobalObject));
+  NS_ASSERTION(globalSupports, "Null pointer?!");
+
+  if (globalSupports == mParentGlobal) {
+    LOOP_OVER_WORKERS(Cancel, ());
+    mWorkers.Clear();
+    if (IsSuspended()) {
+      nsAutoMonitor mon(mMonitor);
+      mon.NotifyAll();
+    }
+  }
+}
+
+void
+nsDOMWorkerPool::SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  nsCOMPtr<nsISupports> globalSupports(do_QueryInterface(aGlobalObject));
+  NS_ASSERTION(globalSupports, "Null pointer?!");
+
+  if (globalSupports == mParentGlobal) {
+    LOOP_OVER_WORKERS(Suspend, ());
+    Suspend();
+  }
+}
+
+void
+nsDOMWorkerPool::ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  nsCOMPtr<nsISupports> globalSupports(do_QueryInterface(aGlobalObject));
+  NS_ASSERTION(globalSupports, "Null pointer?!");
+
+  if (globalSupports == mParentGlobal) {
+    LOOP_OVER_WORKERS(Resume, ());
+    Resume();
+
+    nsAutoMonitor mon(mMonitor);
+    mon.NotifyAll();
+  }
+}
+
+NS_IMETHODIMP
+nsDOMWorkerPool::PostMessage(const nsAString& aMessage)
+{
+  nsresult rv = PostMessageInternal(aMessage);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWorkerPool::SetMessageListener(nsIDOMWorkerMessageListener* aListener)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  nsDOMWorkerBase::SetMessageListener(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWorkerPool::GetMessageListener(nsIDOMWorkerMessageListener** aListener)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  nsCOMPtr<nsIDOMWorkerMessageListener> listener = nsDOMWorkerBase::GetMessageListener();
+  listener.forget(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWorkerPool::SetErrorListener(nsIDOMWorkerErrorListener* aListener)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  mErrorListener = aListener;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWorkerPool::GetErrorListener(nsIDOMWorkerErrorListener** aListener)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_IF_ADDREF(*aListener = mErrorListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWorkerPool::CreateWorker(const nsAString& fullScript,
+                              nsIDOMWorkerThread** _retval)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  NS_ENSURE_ARG(!fullScript.IsEmpty());
+  NS_ENSURE_ARG_POINTER(_retval);
+
+  nsRefPtr<nsDOMWorkerThread> worker(new nsDOMWorkerThread(this, fullScript));
+  NS_ENSURE_TRUE(worker, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv = worker->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  NS_ASSERTION(!mWorkers.Contains(worker), "Um?!");
+  mWorkers.AppendElement(worker);
+
+  NS_ADDREF(*_retval = worker);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerPool.h
@@ -0,0 +1,123 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
+ *   Ben Turner <bent.mozilla@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __NSDOMWORKERPOOL_H__
+#define __NSDOMWORKERPOOL_H__
+
+// Bases
+#include "nsDOMWorkerBase.h"
+#include "nsIClassInfo.h"
+#include "nsIDOMThreads.h"
+
+// Other includes
+#include "jsapi.h"
+#include "nsStringGlue.h"
+#include "nsTPtrArray.h"
+#include "prmon.h"
+
+class nsDOMWorkerThread;
+class nsIScriptError;
+class nsIScriptGlobalObject;
+
+/**
+ * The pool is almost always touched only on the main thread.
+ */
+class nsDOMWorkerPool : public nsDOMWorkerBase,
+                        public nsIDOMWorkerPool,
+                        public nsIClassInfo
+{
+  friend class nsDOMThreadService;
+  friend class nsDOMWorkerFunctions;
+  friend class nsDOMWorkerPoolWeakRef;
+  friend class nsDOMWorkerThread;
+  friend class nsReportErrorRunnable;
+  friend JSBool DOMWorkerOperationCallback(JSContext* aCx);
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMWORKERPOOL
+  NS_DECL_NSICLASSINFO
+
+  nsDOMWorkerPool();
+
+  // For nsDOMWorkerBase
+  virtual nsDOMWorkerPool* Pool() {
+    return this;
+  }
+
+private:
+  virtual ~nsDOMWorkerPool();
+
+  nsresult Init();
+
+  // For nsDOMWorkerBase
+  virtual nsresult HandleMessage(const nsAString& aMessage,
+                                 nsDOMWorkerBase* aSourceThread);
+
+  // For nsDOMWorkerBase
+  virtual nsresult DispatchMessage(nsIRunnable* aRunnable);
+
+  void HandleError(nsIScriptError* aError,
+                   nsDOMWorkerThread* aSource);
+
+  void NoteDyingWorker(nsDOMWorkerThread* aWorker);
+
+  void CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
+  void SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
+  void ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
+
+  PRMonitor* Monitor() {
+    return mMonitor;
+  }
+
+  // Weak reference to the window that created and owns this pool.
+  nsISupports* mParentGlobal;
+
+  // Weak array of workers. The idea is that workers can be garbage collected
+  // independently of the owning pool and other workers.
+  nsTPtrArray<nsDOMWorkerThread> mWorkers;
+
+  // An error handler function, may be null.
+  nsCOMPtr<nsIDOMWorkerErrorListener> mErrorListener;
+
+  // Monitor for suspending and resuming workers.
+  PRMonitor* mMonitor;
+};
+
+#endif /* __NSDOMWORKERPOOL_H__ */
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerSecurityManager.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDOMWorkerSecurityManager.h"
+
+// Interfaces
+#include "nsIClassInfo.h"
+
+// Other includes
+#include "jsapi.h"
+
+// DOMWorker includes
+#include "nsDOMThreadService.h"
+
+#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerSecurityManager,
+                              nsIXPCSecurityManager)
+
+NS_IMETHODIMP
+nsDOMWorkerSecurityManager::CanCreateWrapper(JSContext* aJSContext,
+                                             const nsIID& aIID,
+                                             nsISupports* aObj,
+                                             nsIClassInfo* aClassInfo,
+                                             void** aPolicy)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWorkerSecurityManager::CanCreateInstance(JSContext* aJSContext,
+                                              const nsCID& aCID)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWorkerSecurityManager::CanGetService(JSContext* aJSContext,
+                                          const nsCID& aCID)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWorkerSecurityManager::CanAccess(PRUint32 aAction,
+                                      nsAXPCNativeCallContext* aCallContext,
+                                      JSContext* aJSContext,
+                                      JSObject* aJSObject,
+                                      nsISupports* aObj,
+                                      nsIClassInfo* aClassInfo,
+                                      jsval aName,
+                                      void** aPolicy)
+{
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerSecurityManager.h
@@ -0,0 +1,51 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __NSDOMWORKERSECURITYMANAGER_H__
+#define __NSDOMWORKERSECURITYMANAGER_H__
+
+#include "nsIXPCSecurityManager.h"
+
+class nsDOMWorkerSecurityManager : public nsIXPCSecurityManager
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIXPCSECURITYMANAGER
+};
+
+#endif /* __NSDOMWORKERSECURITYMANAGER_H__ */
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerThread.cpp
@@ -0,0 +1,752 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
+ *   Ben Turner <bent.mozilla@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDOMWorkerThread.h"
+
+// Interfaces
+#include "nsIDOMClassInfo.h"
+#include "nsIJSContextStack.h"
+#include "nsIJSRuntimeService.h"
+#include "nsIScriptContext.h"
+#include "nsIXPConnect.h"
+
+// Other includes
+#ifdef MOZ_SHARK
+#include "jsdbgapi.h"
+#endif
+#include "nsAutoLock.h"
+#include "nsContentUtils.h"
+#include "nsJSUtils.h"
+#include "nsJSEnvironment.h"
+
+// DOMWorker includes
+#include "nsDOMWorkerPool.h"
+#include "nsDOMThreadService.h"
+#include "nsDOMWorkerTimeout.h"
+
+#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
+
+// XXX Could make these functions of nsDOMWorkerThread instead.
+class nsDOMWorkerFunctions
+{
+public:
+  // Same as window.dump().
+  static JSBool Dump(JSContext* aCx, JSObject* aObj, uintN aArgc, jsval* aArgv,
+                     jsval* aRval);
+
+  // Debug-only version of window.dump(), like the JS component loader has.
+  static JSBool DebugDump(JSContext* aCx, JSObject* aObj, uintN aArgc,
+                          jsval* aArgv, jsval* aRval);
+
+  // Same as nsIDOMWorkerThread::PostMessage
+  static JSBool PostMessage(JSContext* aCx, JSObject* aObj, uintN aArgc,
+                            jsval* aArgv, jsval* aRval);
+
+  // Same as window.setTimeout().
+  static JSBool SetTimeout(JSContext* aCx, JSObject* aObj, uintN aArgc,
+                           jsval* aArgv, jsval* aRval) {
+    return MakeTimeout(aCx, aObj, aArgc, aArgv, aRval, PR_FALSE);
+  }
+
+  // Same as window.setInterval().
+  static JSBool SetInterval(JSContext* aCx, JSObject* aObj, uintN aArgc,
+                            jsval* aArgv, jsval* aRval) {
+    return MakeTimeout(aCx, aObj, aArgc, aArgv, aRval, PR_TRUE);
+  }
+
+  // Used for both clearTimeout() and clearInterval().
+  static JSBool KillTimeout(JSContext* aCx, JSObject* aObj, uintN aArgc,
+                            jsval* aArgv, jsval* aRval);
+
+private:
+  // Internal helper for SetTimeout and SetInterval.
+  static JSBool MakeTimeout(JSContext* aCx, JSObject* aObj, uintN aArgc,
+                            jsval* aArgv, jsval* aRval, PRBool aIsInterval);
+};
+
+JSBool JS_DLL_CALLBACK
+nsDOMWorkerFunctions::Dump(JSContext* aCx,
+                           JSObject* /* aObj */,
+                           uintN aArgc,
+                           jsval* aArgv,
+                           jsval* /* aRval */)
+{
+  // XXX Expose this to the JS console? Only if that DOM pref is set?
+
+  JSString* str;
+  if (aArgc && (str = JS_ValueToString(aCx, aArgv[0])) && str) {
+    nsDependentJSString string(str);
+    fputs(NS_ConvertUTF16toUTF8(nsDependentJSString(str)).get(), stderr);
+    fflush(stderr);
+  }
+  return JS_TRUE;
+}
+
+JSBool JS_DLL_CALLBACK
+nsDOMWorkerFunctions::DebugDump(JSContext* aCx,
+                                JSObject* aObj,
+                                uintN aArgc,
+                                jsval* aArgv,
+                                jsval* aRval)
+{
+#ifdef DEBUG
+  return nsDOMWorkerFunctions::Dump(aCx, aObj, aArgc, aArgv, aRval);
+#else
+  return JS_TRUE;
+#endif
+}
+
+JSBool JS_DLL_CALLBACK
+nsDOMWorkerFunctions::PostMessage(JSContext* aCx,
+                                  JSObject* /* aObj */,
+                                  uintN aArgc,
+                                  jsval* aArgv,
+                                  jsval* /* aRval */)
+{
+  nsDOMWorkerThread* worker =
+    static_cast<nsDOMWorkerThread*>(JS_GetContextPrivate(aCx));
+  NS_ASSERTION(worker, "This should be set by the DOM thread service!");
+
+  if (worker->IsCanceled()) {
+    return JS_FALSE;
+  }
+
+  nsRefPtr<nsDOMWorkerPool> pool = worker->Pool();
+  NS_ASSERTION(pool, "Shouldn't ever be null!");
+
+  nsresult rv;
+
+  JSString* str;
+  if (aArgc && (str = JS_ValueToString(aCx, aArgv[0])) && str) {
+    rv = pool->PostMessageInternal(nsDependentJSString(str), worker);
+  }
+  else {
+    rv = pool->PostMessageInternal(EmptyString(), worker);
+  }
+  NS_ENSURE_SUCCESS(rv, JS_FALSE);
+
+  return JS_TRUE;
+}
+
+JSBool JS_DLL_CALLBACK
+nsDOMWorkerFunctions::MakeTimeout(JSContext* aCx,
+                                  JSObject* /* aObj */,
+                                  uintN aArgc,
+                                  jsval* aArgv,
+                                  jsval* aRval,
+                                  PRBool aIsInterval)
+{
+  nsDOMWorkerThread* worker =
+    static_cast<nsDOMWorkerThread*>(JS_GetContextPrivate(aCx));
+  NS_ASSERTION(worker, "This should be set by the DOM thread service!");
+
+  if (worker->IsCanceled()) {
+    return JS_FALSE;
+  }
+
+  PRUint32 id = ++worker->mNextTimeoutId;
+
+  nsAutoPtr<nsDOMWorkerTimeout>
+    timeout(new nsDOMWorkerTimeout(worker, id));
+  NS_ENSURE_TRUE(timeout, JS_FALSE);
+
+  nsresult rv = timeout->Init(aCx, aArgc, aArgv, aIsInterval);
+  NS_ENSURE_SUCCESS(rv, JS_FALSE);
+
+  timeout.forget();
+
+  *aRval = INT_TO_JSVAL(id);
+  return JS_TRUE;
+}
+
+JSBool JS_DLL_CALLBACK
+nsDOMWorkerFunctions::KillTimeout(JSContext* aCx,
+                                  JSObject* /* aObj */,
+                                  uintN aArgc,
+                                  jsval* aArgv,
+                                  jsval* /* aRval */)
+{
+  nsDOMWorkerThread* worker =
+    static_cast<nsDOMWorkerThread*>(JS_GetContextPrivate(aCx));
+  NS_ASSERTION(worker, "This should be set by the DOM thread service!");
+
+  // A canceled worker should have already killed all timeouts.
+  if (worker->IsCanceled()) {
+    return JS_TRUE;
+  }
+
+  if (!aArgc) {
+    JS_ReportError(aCx, "Function requires at least 1 parameter");
+    return JS_FALSE;
+  }
+
+  uint32 id;
+  if (!JS_ValueToECMAUint32(aCx, aArgv[0], &id)) {
+    JS_ReportError(aCx, "First argument must be a timeout id");
+    return JS_FALSE;
+  }
+
+  worker->CancelTimeout(PRUint32(id));
+  return JS_TRUE;
+}
+
+JSFunctionSpec gDOMWorkerFunctions[] = {
+  { "dump",                  nsDOMWorkerFunctions::Dump,              1, 0, 0 },
+  { "debug",                 nsDOMWorkerFunctions::DebugDump,         1, 0, 0 },
+  { "postMessageToPool",     nsDOMWorkerFunctions::PostMessage,       1, 0, 0 },
+  { "setTimeout",            nsDOMWorkerFunctions::SetTimeout,        1, 0, 0 },
+  { "clearTimeout",          nsDOMWorkerFunctions::KillTimeout,       1, 0, 0 },
+  { "setInterval",           nsDOMWorkerFunctions::SetInterval,       1, 0, 0 },
+  { "clearInterval",         nsDOMWorkerFunctions::KillTimeout,       1, 0, 0 },
+#ifdef MOZ_SHARK
+  { "startShark",            js_StartShark,                           0, 0, 0 },
+  { "stopShark",             js_StopShark,                            0, 0, 0 },
+  { "connectShark",          js_ConnectShark,                         0, 0, 0 },
+  { "disconnectShark",       js_DisconnectShark,                      0, 0, 0 },
+#endif
+  { nsnull,                  nsnull,                                  0, 0, 0 }
+};
+
+/**
+ * An nsISupports that holds a weak ref to the worker. The worker owns the
+ * thread context so we don't have to worry about nulling this out ever.
+ */
+class nsDOMWorkerThreadWeakRef : public nsIDOMWorkerThread,
+                                 public nsIClassInfo
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_FORWARD_NSIDOMWORKERTHREAD(mWorker->)
+  NS_FORWARD_NSICLASSINFO(mWorker->)
+
+  nsDOMWorkerThreadWeakRef(nsDOMWorkerThread* aWorker)
+  : mWorker(aWorker) { }
+
+protected:
+  nsDOMWorkerThread* mWorker;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsDOMWorkerThreadWeakRef, nsIDOMWorkerThread,
+                                                        nsIClassInfo)
+
+/**
+ * The 'threadContext' object for a worker's JS global object.
+ */
+class nsDOMWorkerThreadContext : public nsIDOMWorkerThreadContext,
+                                 public nsIClassInfo
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMWORKERTHREADCONTEXT
+  NS_DECL_NSICLASSINFO
+
+  nsDOMWorkerThreadContext(nsDOMWorkerThread* aWorker)
+  : mWorker(aWorker) { }
+
+protected:
+  nsDOMWorkerThread* mWorker;
+  nsCOMPtr<nsIDOMWorkerThread> mWeakRef;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsDOMWorkerThreadContext,
+                              nsIDOMWorkerThreadContext,
+                              nsIClassInfo)
+
+NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerThreadContext,
+                             nsIDOMWorkerThreadContext)
+
+NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerThreadContext)
+
+NS_IMETHODIMP
+nsDOMWorkerThreadContext::GetThisThread(nsIDOMWorkerThread** aThisThread)
+{
+  if (!mWeakRef) {
+    mWeakRef = new nsDOMWorkerThreadWeakRef(mWorker);
+    NS_ENSURE_TRUE(mWeakRef, NS_ERROR_OUT_OF_MEMORY);
+  }
+
+  NS_ADDREF(*aThisThread = mWeakRef);
+  return NS_OK;
+}
+
+nsDOMWorkerThread::nsDOMWorkerThread(nsDOMWorkerPool* aPool,
+                                     const nsAString& aSource)
+: mPool(aPool),
+  mSource(aSource),
+  mGlobal(nsnull),
+  mCompiled(PR_FALSE),
+  mCallbackCount(0),
+  mNextTimeoutId(0),
+  mLock(nsnull)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(!aSource.IsEmpty(), "Empty source string!");
+
+  PR_INIT_CLIST(&mTimeouts);
+}
+
+nsDOMWorkerThread::~nsDOMWorkerThread()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  if (!IsCanceled()) {
+    nsRefPtr<nsDOMWorkerPool> pool = Pool();
+    pool->NoteDyingWorker(this);
+  }
+
+  ClearTimeouts();
+
+  // Only clean up if we created a global object
+  if (mGlobal) {
+    JSRuntime* rt;
+    if (NS_SUCCEEDED(nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt))) {
+      JS_RemoveRootRT(rt, &mGlobal);
+    }
+    else {
+      NS_ERROR("This shouldn't fail!");
+    }
+  }
+
+  if (mLock) {
+    nsAutoLock::DestroyLock(mLock);
+  }
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsDOMWorkerThread, nsIDOMWorkerThread,
+                                                 nsIClassInfo)
+NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerThread, nsIDOMWorkerThread)
+
+NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerThread)
+
+nsresult
+nsDOMWorkerThread::Init()
+{
+  mLock = nsAutoLock::NewLock("nsDOMWorkerThread::mLock");
+  NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
+
+  NS_ASSERTION(!mGlobal, "Already got a global?!");
+
+  // 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 nsRunnable());
+  NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv = nsDOMThreadService::get()->Dispatch(this, runnable);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+// From nsDOMWorkerBase
+nsresult
+nsDOMWorkerThread::HandleMessage(const nsAString& aMessage,
+                                 nsDOMWorkerBase* aSource)
+{
+  nsCOMPtr<nsIDOMWorkerMessageListener> messageListener = GetMessageListener();
+  if (!messageListener) {
+    LOG(("Message received on a worker with no listener!"));
+    return NS_OK;
+  }
+
+  // We have to call this manually because XPConnect will replace our error
+  // reporter with its own and we won't properly notify the pool of any
+  // unhandled exceptions...
+
+  JSContext* cx;
+  nsresult rv =
+    nsDOMThreadService::ThreadJSContextStack()->GetSafeJSContext(&cx);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSAutoRequest ar(cx);
+
+  if (JS_IsExceptionPending(cx)) {
+    JS_ClearPendingException(cx);
+  }
+
+  // Get a JS string for the message.
+  JSString* message = JS_NewUCStringCopyN(cx, (jschar*)aMessage.BeginReading(),
+                                          aMessage.Length());
+  NS_ENSURE_TRUE(message, NS_ERROR_FAILURE);
+
+  // Root it
+  jsval messageVal = STRING_TO_JSVAL(message);
+  nsAutoGCRoot rootedMessage(&messageVal, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsIXPConnect* xpc = nsContentUtils::XPConnect();
+
+  nsCOMPtr<nsISupports> source;
+  aSource->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(source));
+  NS_ASSERTION(source, "Impossible!");
+
+  // Wrap the source thread.
+  nsCOMPtr<nsIXPConnectJSObjectHolder> wrappedThread;
+  rv = xpc->WrapNative(cx, mGlobal, source, NS_GET_IID(nsISupports),
+                       getter_AddRefs(wrappedThread));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSObject* sourceThread;
+  rv = wrappedThread->GetJSObject(&sourceThread);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Set up our arguments.
+  jsval argv[2] = {
+    STRING_TO_JSVAL(message),
+    OBJECT_TO_JSVAL(sourceThread)
+  };
+
+  // Get the listener object out of our wrapped listener.
+  nsCOMPtr<nsIXPConnectJSObjectHolder> wrappedListener =
+    do_QueryInterface(messageListener);
+  NS_ENSURE_TRUE(wrappedListener, NS_ERROR_NO_INTERFACE);
+
+  JSObject* listener;
+  rv = wrappedListener->GetJSObject(&listener);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // And call it.
+  jsval rval;
+  PRBool success = JS_CallFunctionValue(cx, mGlobal, OBJECT_TO_JSVAL(listener),
+                                        2, argv, &rval);
+  if (!success) {
+    // Make sure any pending exceptions are converted to errors for the pool.
+    JS_ReportPendingException(cx);
+  }
+
+  // We shouldn't leave any pending exceptions - our error reporter should
+  // clear any exception it reports.
+  NS_ASSERTION(!JS_IsExceptionPending(cx), "Huh?!");
+
+  return NS_OK;
+}
+
+// From nsDOMWorkerBase
+nsresult
+nsDOMWorkerThread::DispatchMessage(nsIRunnable* aRunnable)
+{
+  nsresult rv = nsDOMThreadService::get()->Dispatch(this, aRunnable);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+void
+nsDOMWorkerThread::Cancel()
+{
+  nsDOMWorkerBase::Cancel();
+
+  // If we're suspended there's a good chance that we're already paused waiting
+  // on the pool's monitor. Waiting on the thread service's lock will deadlock.
+  if (!IsSuspended()) {
+    nsDOMThreadService::get()->WaitForCanceledWorker(this);
+  }
+
+  ClearTimeouts();
+}
+
+void
+nsDOMWorkerThread::Suspend()
+{
+  nsDOMWorkerBase::Suspend();
+  SuspendTimeouts();
+}
+
+void
+nsDOMWorkerThread::Resume()
+{
+  nsDOMWorkerBase::Resume();
+  ResumeTimeouts();
+}
+
+PRBool
+nsDOMWorkerThread::SetGlobalForContext(JSContext* aCx)
+{
+  PRBool success = CompileGlobalObject(aCx);
+  NS_ENSURE_TRUE(success, PR_FALSE);
+
+  JS_SetGlobalObject(aCx, mGlobal);
+  return PR_TRUE;
+}
+
+PRBool
+nsDOMWorkerThread::CompileGlobalObject(JSContext* aCx)
+{
+  if (mGlobal) {
+    return PR_TRUE;
+  }
+
+  if (mCompiled) {
+    // Don't try to recompile a bad script.
+    return PR_FALSE;
+  }
+
+  mCompiled = PR_TRUE;
+
+  JSAutoRequest ar(aCx);
+
+  JSObject* global = JS_NewObject(aCx, nsnull, nsnull, nsnull);
+  NS_ENSURE_TRUE(global, PR_FALSE);
+
+  NS_ASSERTION(!JS_GetGlobalObject(aCx), "Global object should be unset!");
+
+  // This call will root global.
+  PRBool success = JS_InitStandardClasses(aCx, global);
+  NS_ENSURE_TRUE(success, PR_FALSE);
+
+  // Set up worker thread functions
+  success = JS_DefineFunctions(aCx, global, gDOMWorkerFunctions);
+  NS_ENSURE_TRUE(success, PR_FALSE);
+
+  nsRefPtr<nsDOMWorkerThreadContext>
+    context(new nsDOMWorkerThreadContext(this));
+  NS_ENSURE_TRUE(context, NS_ERROR_OUT_OF_MEMORY);
+
+  nsIXPConnect* xpc = nsContentUtils::XPConnect();
+  nsresult rv = xpc->InitClasses(aCx, global);
+  NS_ENSURE_SUCCESS(rv, PR_FALSE);
+
+  // XXX Fix this!
+  success = JS_DeleteProperty(aCx, global, "Components");
+  NS_ENSURE_TRUE(success, PR_FALSE);
+
+  nsCOMPtr<nsIXPConnectJSObjectHolder> contextWrapper;
+  rv = xpc->WrapNative(aCx, global,
+                       NS_ISUPPORTS_CAST(nsIDOMWorkerThreadContext*, context),
+                       NS_GET_IID(nsIDOMWorkerThreadContext),
+                       getter_AddRefs(contextWrapper));
+  NS_ENSURE_SUCCESS(rv, PR_FALSE);
+
+  JSObject* contextObj;
+  rv = contextWrapper->GetJSObject(&contextObj);
+  NS_ENSURE_SUCCESS(rv, PR_FALSE);
+
+  // Set up a name for our worker object
+  success = JS_DefineProperty(aCx, global, "threadContext",
+                              OBJECT_TO_JSVAL(contextObj), nsnull, nsnull,
+                              JSPROP_ENUMERATE);
+  NS_ENSURE_TRUE(success, PR_FALSE);
+
+  JSScript* script = JS_CompileUCScript(aCx, global, mSource.BeginReading(),
+                                        mSource.Length(), nsnull, 1);
+  NS_ENSURE_TRUE(script, PR_FALSE);
+
+  JSRuntime* rt;
+  rv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt);
+  NS_ENSURE_SUCCESS(rv, PR_FALSE);
+
+  mGlobal = global;
+  success = JS_AddNamedRootRT(rt, &mGlobal, "nsDOMWorkerThread Global Object");
+  if (!success) {
+    NS_WARNING("Failed to root global object for worker thread!");
+    mGlobal = nsnull;
+    return PR_FALSE;
+  }
+
+  // Execute the script
+  jsval val;
+  success = JS_ExecuteScript(aCx, global, script, &val);
+  if (!success) {
+    NS_WARNING("Failed to evaluate script for worker thread!");
+    JS_RemoveRootRT(rt, &mGlobal);
+    mGlobal = nsnull;
+    return PR_FALSE;
+  }
+
+  // See if the message listener function was defined.
+  nsCOMPtr<nsIDOMWorkerMessageListener> listener;
+  if (JS_LookupProperty(aCx, global, "messageListener", &val) &&
+      JSVAL_IS_OBJECT(val) &&
+      NS_SUCCEEDED(xpc->WrapJS(aCx, JSVAL_TO_OBJECT(val),
+                               NS_GET_IID(nsIDOMWorkerMessageListener),
+                               getter_AddRefs(listener)))) {
+    SetMessageListener(listener);
+  }
+
+  return PR_TRUE;
+}
+
+nsDOMWorkerTimeout*
+nsDOMWorkerThread::FirstTimeout()
+{
+  // Only called within the lock!
+  PRCList* first = PR_LIST_HEAD(&mTimeouts);
+  return first == &mTimeouts ?
+                  nsnull :
+                  static_cast<nsDOMWorkerTimeout*>(first);
+}
+
+nsDOMWorkerTimeout*
+nsDOMWorkerThread::NextTimeout(nsDOMWorkerTimeout* aTimeout)
+{
+  // Only called within the lock!
+  nsDOMWorkerTimeout* next =
+    static_cast<nsDOMWorkerTimeout*>(PR_NEXT_LINK(aTimeout));
+  return next == &mTimeouts ? nsnull : next;
+}
+
+void
+nsDOMWorkerThread::AddTimeout(nsDOMWorkerTimeout* aTimeout)
+{
+  // This should only ever be called on the worker thread... but there's no way
+  // to really assert that since we're using a thread pool.
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aTimeout, "Null pointer!");
+
+  PRIntervalTime newInterval = aTimeout->GetInterval();
+
+  if (IsSuspended()) {
+    aTimeout->Suspend(PR_Now());
+  }
+
+  nsAutoLock lock(mLock);
+
+  // XXX Currently stored in the order that they should execute (like the window
+  //     timeouts are) but we don't flush all expired timeouts the same way that
+  //     the window does... Either we should or this is unnecessary.
+  for (nsDOMWorkerTimeout* timeout = FirstTimeout();
+       timeout;
+       timeout = NextTimeout(timeout)) {
+    if (timeout->GetInterval() > newInterval) {
+      PR_INSERT_BEFORE(aTimeout, timeout);
+      return;
+    }
+  }
+
+  PR_APPEND_LINK(aTimeout, &mTimeouts);
+}
+
+void
+nsDOMWorkerThread::RemoveTimeout(nsDOMWorkerTimeout* aTimeout)
+{
+  nsAutoLock lock(mLock);
+
+  PR_REMOVE_LINK(aTimeout);
+}
+
+void
+nsDOMWorkerThread::ClearTimeouts()
+{
+  nsAutoTArray<nsRefPtr<nsDOMWorkerTimeout>, 20> timeouts;
+  {
+    nsAutoLock lock(mLock);
+    for (nsDOMWorkerTimeout* timeout = FirstTimeout();
+         timeout;
+         timeout = NextTimeout(timeout)) {
+      timeouts.AppendElement(timeout);
+    }
+  }
+
+  PRUint32 count = timeouts.Length();
+  for (PRUint32 i = 0; i < count; i++) {
+    timeouts[i]->Cancel();
+  }
+}
+
+void
+nsDOMWorkerThread::CancelTimeout(PRUint32 aId)
+{
+  nsRefPtr<nsDOMWorkerTimeout> foundTimeout;
+  {
+    nsAutoLock lock(mLock);
+    for (nsDOMWorkerTimeout* timeout = FirstTimeout();
+         timeout;
+         timeout = NextTimeout(timeout)) {
+      if (timeout->GetId() == aId) {
+        foundTimeout = timeout;
+        break;
+      }
+    }
+  }
+
+  if (foundTimeout) {
+    foundTimeout->Cancel();
+  }
+}
+
+void
+nsDOMWorkerThread::SuspendTimeouts()
+{
+  nsAutoTArray<nsRefPtr<nsDOMWorkerTimeout>, 20> timeouts;
+  {
+    nsAutoLock lock(mLock);
+    for (nsDOMWorkerTimeout* timeout = FirstTimeout();
+         timeout;
+         timeout = NextTimeout(timeout)) {
+      timeouts.AppendElement(timeout);
+    }
+  }
+
+  PRTime now = PR_Now();
+
+  PRUint32 count = timeouts.Length();
+  for (PRUint32 i = 0; i < count; i++) {
+    timeouts[i]->Suspend(now);
+  }
+}
+
+void
+nsDOMWorkerThread::ResumeTimeouts()
+{
+  nsAutoTArray<nsRefPtr<nsDOMWorkerTimeout>, 20> timeouts;
+  {
+    nsAutoLock lock(mLock);
+    for (nsDOMWorkerTimeout* timeout = FirstTimeout();
+         timeout;
+         timeout = NextTimeout(timeout)) {
+      NS_ASSERTION(timeout->IsSuspended(), "Should be suspended!");
+      timeouts.AppendElement(timeout);
+    }
+  }
+
+  PRTime now = PR_Now();
+
+  PRUint32 count = timeouts.Length();
+  for (PRUint32 i = 0; i < count; i++) {
+    timeouts[i]->Resume(now);
+  }
+}
+
+NS_IMETHODIMP
+nsDOMWorkerThread::PostMessage(const nsAString& aMessage)
+{
+  nsresult rv = PostMessageInternal(aMessage);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerThread.h
@@ -0,0 +1,186 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
+ *   Ben Turner <bent.mozilla@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __NSDOMWORKERTHREAD_H__
+#define __NSDOMWORKERTHREAD_H__
+
+// Bases
+#include "nsDOMWorkerBase.h"
+#include "nsIClassInfo.h"
+#include "nsIDOMThreads.h"
+
+// Other includes
+#include "jsapi.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsThreadUtils.h"
+#include "prclist.h"
+#include "prlock.h"
+
+// DOMWorker includes
+#include "nsDOMThreadService.h"
+
+// Macro to generate nsIClassInfo methods for these threadsafe DOM classes 
+#define NS_IMPL_THREADSAFE_DOM_CI(_class)                                     \
+NS_IMETHODIMP                                                                 \
+_class::GetInterfaces(PRUint32* _count, nsIID*** _array)                      \
+{                                                                             \
+  return NS_CI_INTERFACE_GETTER_NAME(_class)(_count, _array);                 \
+}                                                                             \
+                                                                              \
+NS_IMETHODIMP                                                                 \
+_class::GetHelperForLanguage(PRUint32 _language, nsISupports** _retval)       \
+{                                                                             \
+  *_retval = nsnull;                                                          \
+  return NS_OK;                                                               \
+}                                                                             \
+                                                                              \
+NS_IMETHODIMP                                                                 \
+_class::GetContractID(char** _contractID)                                     \
+{                                                                             \
+  *_contractID = nsnull;                                                      \
+  return NS_OK;                                                               \
+}                                                                             \
+                                                                              \
+NS_IMETHODIMP                                                                 \
+_class::GetClassDescription(char** _classDescription)                         \
+{                                                                             \
+  *_classDescription = nsnull;                                                \
+  return NS_OK;                                                               \
+}                                                                             \
+                                                                              \
+NS_IMETHODIMP                                                                 \
+_class::GetClassID(nsCID** _classID)                                          \
+{                                                                             \
+  *_classID = nsnull;                                                         \
+  return NS_OK;                                                               \
+}                                                                             \
+                                                                              \
+NS_IMETHODIMP                                                                 \
+_class::GetImplementationLanguage(PRUint32* _language)                        \
+{                                                                             \
+  *_language = nsIProgrammingLanguage::CPLUSPLUS;                             \
+  return NS_OK;                                                               \
+}                                                                             \
+                                                                              \
+NS_IMETHODIMP                                                                 \
+_class::GetFlags(PRUint32* _flags)                                            \
+{                                                                             \
+  *_flags = nsIClassInfo::THREADSAFE | nsIClassInfo::DOM_OBJECT;              \
+  return NS_OK;                                                               \
+}                                                                             \
+                                                                              \
+NS_IMETHODIMP                                                                 \
+_class::GetClassIDNoAlloc(nsCID* _classIDNoAlloc)                             \
+{                                                                             \
+  return NS_ERROR_NOT_AVAILABLE;                                              \
+}
+
+class nsDOMWorkerPool;
+class nsDOMWorkerTimeout;
+
+class nsDOMWorkerThread : public nsDOMWorkerBase,
+                          public nsIDOMWorkerThread,
+                          public nsIClassInfo
+{
+  friend class nsDOMCreateJSContextRunnable;
+  friend class nsDOMWorkerFunctions;
+  friend class nsDOMWorkerPool;
+  friend class nsDOMWorkerRunnable;
+  friend class nsDOMWorkerTimeout;
+
+  friend JSBool DOMWorkerOperationCallback(JSContext* aCx);
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMWORKERTHREAD
+  NS_DECL_NSICLASSINFO
+
+  nsDOMWorkerThread(nsDOMWorkerPool* aPool,
+                    const nsAString& aSource);
+
+  virtual nsDOMWorkerPool* Pool() {
+    NS_ASSERTION(!IsCanceled(), "Don't touch Pool after we've been canceled!");
+    return mPool;
+  }
+
+private:
+  virtual ~nsDOMWorkerThread();
+
+  nsresult Init();
+
+  // For nsDOMWorkerBase
+  virtual nsresult HandleMessage(const nsAString& aMessage,
+                                 nsDOMWorkerBase* aSourceThread);
+
+  // For nsDOMWorkerBase
+  virtual nsresult DispatchMessage(nsIRunnable* aRunnable);
+
+  virtual void Cancel();
+  virtual void Suspend();
+  virtual void Resume();
+
+  PRBool SetGlobalForContext(JSContext* aCx);
+  PRBool CompileGlobalObject(JSContext* aCx);
+
+  inline nsDOMWorkerTimeout* FirstTimeout();
+  inline nsDOMWorkerTimeout* NextTimeout(nsDOMWorkerTimeout* aTimeout);
+
+  void AddTimeout(nsDOMWorkerTimeout* aTimeout);
+  void RemoveTimeout(nsDOMWorkerTimeout* aTimeout);
+  void ClearTimeouts();
+  void CancelTimeout(PRUint32 aId);
+  void SuspendTimeouts();
+  void ResumeTimeouts();
+
+  nsDOMWorkerPool* mPool;
+  nsString mSource;
+
+  JSObject* mGlobal;
+  PRBool mCompiled;
+
+  PRUint32 mCallbackCount;
+
+  PRUint32 mNextTimeoutId;
+
+  PRLock* mLock;
+  PRCList mTimeouts;
+};
+
+#endif /* __NSDOMWORKERTHREAD_H__ */
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerTimeout.cpp
@@ -0,0 +1,515 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDOMWorkerTimeout.h"
+
+// Interfaces
+#include "nsIJSContextStack.h"
+#include "nsIJSRuntimeService.h"
+#include "nsITimer.h"
+#include "nsIXPConnect.h"
+
+// Other includes
+#include "nsContentUtils.h"
+#include "nsJSUtils.h"
+#include "pratom.h"
+
+// DOMWorker includes
+#include "nsDOMThreadService.h"
+
+#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
+
+#define CONSTRUCTOR_ENSURE_TRUE(_cond, _rv) \
+  PR_BEGIN_MACRO \
+    if (NS_UNLIKELY(!(_cond))) { \
+      NS_WARNING("CONSTRUCTOR_ENSURE_TRUE(" #_cond ") failed"); \
+      (_rv) = NS_ERROR_FAILURE; \
+      return; \
+    } \
+  PR_END_MACRO
+
+#define SUSPEND_SPINLOCK_COUNT 5000
+
+static const char* kSetIntervalStr = "setInterval";
+static const char* kSetTimeoutStr = "setTimeout";
+
+nsDOMWorkerTimeout::FunctionCallback::FunctionCallback(PRUint32 aArgc,
+                                                       jsval* aArgv,
+                                                       nsresult* aRv)
+: mCallback(nsnull),
+  mCallbackArgs(nsnull),
+  mCallbackArgsLength(0)
+{
+  MOZ_COUNT_CTOR(nsDOMWorkerTimeout::FunctionCallback);
+
+  JSRuntime* rt;
+  *aRv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt);
+  NS_ENSURE_SUCCESS(*aRv,);
+
+  PRBool success = JS_AddNamedRootRT(rt, &mCallback,
+                                     "nsDOMWorkerTimeout Callback Object");
+  CONSTRUCTOR_ENSURE_TRUE(success, *aRv);
+
+  mCallback = aArgv[0];
+
+  // We want enough space for an extra lateness arg.
+  mCallbackArgsLength = aArgc > 2 ? aArgc - 1 : 1;
+
+  mCallbackArgs = new jsval[mCallbackArgsLength];
+  if (NS_UNLIKELY(!mCallbackArgs)) {
+    // Reset this!
+    mCallbackArgsLength = 0;
+
+    NS_ERROR("Out of memory!");
+    *aRv = NS_ERROR_OUT_OF_MEMORY;
+    return;
+  }
+
+  for (PRUint32 i = 0; i < mCallbackArgsLength - 1; i++) {
+    mCallbackArgs[i] = aArgv[i + 2];
+    success = JS_AddNamedRootRT(rt, &mCallbackArgs[i],
+                                "nsDOMWorkerTimeout Callback Arg");
+    if (NS_UNLIKELY(!success)) {
+      // Set this to i so that the destructor only unroots the right number of
+      // values.
+      mCallbackArgsLength = i;
+
+      NS_WARNING("Failed to add root!");
+      *aRv = NS_ERROR_FAILURE;
+      return;
+    }
+  }
+
+  // Take care of the last arg.
+  mCallbackArgs[mCallbackArgsLength - 1] = 0;
+  success = JS_AddNamedRootRT(rt, &mCallbackArgs[mCallbackArgsLength - 1],
+                              "nsDOMWorkerTimeout Callback Final Arg");
+  if (NS_UNLIKELY(!success)) {
+    // Decrement this so that the destructor only unroots the right number of
+    // values.
+    mCallbackArgsLength -= 1;
+
+    NS_WARNING("Failed to add root!");
+    *aRv = NS_ERROR_FAILURE;
+    return;
+  }
+
+  *aRv = NS_OK;
+}
+
+nsDOMWorkerTimeout::FunctionCallback::~FunctionCallback()
+{
+  MOZ_COUNT_DTOR(nsDOMWorkerTimeout::FunctionCallback);
+
+  if (mCallback) {
+    JSRuntime* rt;
+    nsresult rv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt);
+
+    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Can't unroot callback objects!");
+
+    if (NS_SUCCEEDED(rv)) {
+      for (PRUint32 i = 0; i < mCallbackArgsLength; i++) {
+        JS_RemoveRootRT(rt, &mCallbackArgs[i]);
+      }
+      JS_RemoveRootRT(rt, &mCallback);
+    }
+  }
+
+  delete [] mCallbackArgs;
+}
+
+nsresult
+nsDOMWorkerTimeout::FunctionCallback::Run(nsDOMWorkerTimeout* aTimeout,
+                                          JSContext* aCx)
+{
+  PRInt32 lateness = PR_MAX(0, PRInt32(PR_Now() - aTimeout->mTargetTime)) /
+                     (PRTime)PR_USEC_PER_MSEC;
+  mCallbackArgs[mCallbackArgsLength - 1] = INT_TO_JSVAL(lateness);
+
+  JSObject* global = JS_GetGlobalObject(aCx);
+  NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
+
+  jsval rval;
+  PRBool success =
+    JS_CallFunctionValue(aCx, global, mCallback, mCallbackArgsLength,
+                         mCallbackArgs, &rval);
+  NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
+  return NS_OK;
+}
+
+nsDOMWorkerTimeout::ExpressionCallback::ExpressionCallback(PRUint32 aArgc,
+                                                           jsval* aArgv,
+                                                           JSContext* aCx,
+                                                           nsresult* aRv)
+: mExpression(nsnull),
+  mLineNumber(0)
+{
+  MOZ_COUNT_CTOR(nsDOMWorkerTimeout::ExpressionCallback);
+
+  JSString* expr = JS_ValueToString(aCx, aArgv[0]);
+  *aRv = expr ? NS_OK : NS_ERROR_FAILURE;
+  NS_ENSURE_SUCCESS(*aRv,);
+
+  JSRuntime* rt;
+  *aRv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt);
+  NS_ENSURE_SUCCESS(*aRv,);
+
+  PRBool success = JS_AddNamedRootRT(rt, &mExpression,
+                                     "nsDOMWorkerTimeout Expression");
+  CONSTRUCTOR_ENSURE_TRUE(success, *aRv);
+
+  mExpression = expr;
+
+  // Get the calling location.
+  const char* fileName;
+  PRUint32 lineNumber;
+  if (nsJSUtils::GetCallingLocation(aCx, &fileName, &lineNumber, nsnull)) {
+    CopyUTF8toUTF16(nsDependentCString(fileName), mFileName);
+    mLineNumber = lineNumber;
+  }
+
+  *aRv = NS_OK;
+}
+
+nsDOMWorkerTimeout::ExpressionCallback::~ExpressionCallback()
+{
+  MOZ_COUNT_DTOR(nsDOMWorkerTimeout::ExpressionCallback);
+
+  if (mExpression) {
+    JSRuntime* rt;
+    nsresult rv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt);
+
+    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Can't unroot callback objects!");
+
+    if (NS_SUCCEEDED(rv)) {
+      JS_RemoveRootRT(rt, &mExpression);
+    }
+  }
+}
+
+nsresult
+nsDOMWorkerTimeout::ExpressionCallback::Run(nsDOMWorkerTimeout* aTimeout,
+                                            JSContext* aCx)
+{
+  NS_ERROR("Not yet implemented!");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsDOMWorkerTimeout::nsDOMWorkerTimeout(nsDOMWorkerThread* aWorker,
+                                       PRUint32 aId)
+: mWorker(aWorker),
+  mInterval(0),
+  mIsInterval(PR_FALSE),
+  mId(aId),
+  mSuspendSpinlock(0),
+  mIsSuspended(PR_FALSE),
+  mSuspendInterval(0)
+#ifdef DEBUG
+, mFiredOrCanceled(PR_FALSE)
+#endif
+{
+  MOZ_COUNT_CTOR(nsDOMWorkerTimeout);
+  NS_ASSERTION(mWorker, "Need a worker here!");
+}
+
+nsDOMWorkerTimeout::~nsDOMWorkerTimeout()
+{
+  MOZ_COUNT_DTOR(nsDOMWorkerTimeout);
+
+  // If we have a timer then we assume we added ourselves to the thread's list.
+  if (mTimer) {
+    NS_ASSERTION(mFiredOrCanceled || mWorker->IsCanceled(),
+                 "Timeout should have fired or been canceled!");
+
+    mWorker->RemoveTimeout(this);
+  }
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerTimeout, nsITimerCallback)
+
+nsresult
+nsDOMWorkerTimeout::Init(JSContext* aCx, PRUint32 aArgc, jsval* aArgv,
+                         PRBool aIsInterval)
+{
+  NS_ASSERTION(aCx, "Null pointer!");
+  NS_ASSERTION(aArgv, "Null pointer!");
+
+  JSAutoRequest ar(aCx);
+
+  if (!aArgc) {
+    JS_ReportError(aCx, "Function %s requires at least 1 parameter",
+                   aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  PRUint32 interval;
+  if (aArgc > 1) {
+    if (!JS_ValueToECMAUint32(aCx, aArgv[1], (uint32*)&interval)) {
+      JS_ReportError(aCx, "Second argument to %s must be a millisecond value",
+                     aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
+      return NS_ERROR_INVALID_ARG;
+    }
+  }
+  else {
+    // If no interval was specified, treat this like a timeout, to avoid
+    // setting an interval of 0 milliseconds.
+    aIsInterval = PR_FALSE;
+  }
+
+  mInterval = interval;
+
+  mTargetTime = PR_Now() + interval * (PRTime)PR_USEC_PER_MSEC;
+
+  nsresult rv;
+  switch (JS_TypeOfValue(aCx, aArgv[0])) {
+    case JSTYPE_FUNCTION:
+      mCallback = new FunctionCallback(aArgc, aArgv, &rv);
+      NS_ENSURE_TRUE(mCallback, NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      break;
+
+    case JSTYPE_STRING:
+    case JSTYPE_OBJECT:
+      mCallback = new ExpressionCallback(aArgc, aArgv, aCx, &rv);
+      NS_ENSURE_TRUE(mCallback, NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_SUCCESS(rv, rv);
+      break;
+
+    default:
+      JS_ReportError(aCx, "useless %s call (missing quotes around argument?)",
+                     aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
+  
+      // Return an error that nsGlobalWindow can recognize and turn into NS_OK.
+      return NS_ERROR_INVALID_ARG;
+  }
+
+  PRInt32 type;
+  if (aIsInterval) {
+    type = nsITimer::TYPE_REPEATING_SLACK;
+  }
+  else {
+    type = nsITimer::TYPE_ONE_SHOT;
+  }
+  mIsInterval = aIsInterval;
+
+  mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsIEventTarget* target =
+    static_cast<nsIEventTarget*>(nsDOMThreadService::get());
+
+  rv = mTimer->SetTarget(target);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mTimer->InitWithCallback(this, interval, type);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mWorker->AddTimeout(this);
+
+  return NS_OK;
+}
+
+nsresult
+nsDOMWorkerTimeout::Run()
+{
+  NS_ENSURE_TRUE(mCallback, NS_ERROR_NOT_INITIALIZED);
+  LOG(("Worker [0x%p] running timeout [0x%p] with id %u",
+       static_cast<void*>(mWorker.get()), static_cast<void*>(this), mId));
+
+#ifdef DEBUG
+  mFiredOrCanceled = PR_TRUE;
+#endif
+
+  JSContext* cx;
+  nsresult rv =
+    nsDOMThreadService::ThreadJSContextStack()->GetSafeJSContext(&cx);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSAutoRequest ar(cx);
+
+  rv = mCallback->Run(this, cx);
+
+  // Make sure any pending exceptions are converted to errors for the pool.
+  JS_ReportPendingException(cx);
+
+  if (mIsInterval) {
+    mTargetTime = PR_Now() + mInterval * (PRTime)PR_USEC_PER_MSEC;
+  }
+
+  return rv;
+}
+
+void
+nsDOMWorkerTimeout::Cancel()
+{
+  NS_ASSERTION(mTimer, "Impossible to get here without a timer!");
+
+  LOG(("Worker [0x%p] canceling timeout [0x%p] with id %u",
+       static_cast<void*>(mWorker.get()), static_cast<void*>(this), mId));
+
+#ifdef DEBUG
+  mFiredOrCanceled = PR_TRUE;
+#endif
+
+  {
+    AutoSpinlock lock(this);
+
+    if (IsSuspendedNoLock()) {
+      mIsSuspended = PR_FALSE;
+      // This should kill us when all is said and done.
+      mSuspendedRef = nsnull;
+    }
+  }
+
+  // This call to Cancel should kill us.
+  mTimer->Cancel();
+}
+
+void
+nsDOMWorkerTimeout::Suspend(PRTime aNow)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(mTimer, "Impossible to get here without a timer!");
+
+  AutoSpinlock lock(this);
+
+  if (!mIsSuspended) {
+    mIsSuspended = PR_TRUE;
+    mSuspendedRef = this;
+  }
+
+  mTimer->Cancel();
+
+  mSuspendInterval = PR_MAX(0, PRInt32(mTargetTime - aNow)) /
+                     (PRTime)PR_USEC_PER_MSEC;
+
+  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(PRTime aNow)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  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!");
+
+  mTargetTime = aNow + mSuspendInterval * (PRTime)PR_USEC_PER_MSEC;
+
+#ifdef DEBUG
+  nsresult rv =
+#endif
+  mTimer->InitWithCallback(this, mSuspendInterval, nsITimer::TYPE_ONE_SHOT);
+  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to init timer!");
+}
+
+void
+nsDOMWorkerTimeout::AcquireSpinlock()
+{
+  PRUint32 loopCount = 0;
+  while (PR_AtomicSet(&mSuspendSpinlock, 1) == 1) {
+    if (++loopCount > SUSPEND_SPINLOCK_COUNT) {
+      LOG(("AcquireSpinlock taking too long (looped %u times), yielding.",
+           loopCount));
+      loopCount = 0;
+      PR_Sleep(PR_INTERVAL_NO_WAIT);
+    }
+  }
+#ifdef PR_LOGGING
+  if (loopCount) {
+    LOG(("AcquireSpinlock needed %u loops", loopCount));
+  }
+#endif
+}
+
+void
+nsDOMWorkerTimeout::ReleaseSpinlock()
+{
+#ifdef DEBUG
+  PRInt32 suspended =
+#endif
+  PR_AtomicSet(&mSuspendSpinlock, 0);
+  NS_ASSERTION(suspended == 1, "Huh?!");
+}
+
+NS_IMETHODIMP
+nsDOMWorkerTimeout::Notify(nsITimer* aTimer)
+{
+  // Should be on the timer thread.
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aTimer == mTimer, "Wrong timer?!");
+
+  PRUint32 type;
+  nsresult rv = aTimer->GetType(&type);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // We only care about one-shot timers here because that may be the one that
+  // we set from Resume().
+  if (type == nsITimer::TYPE_ONE_SHOT) {
+    AutoSpinlock lock(this);
+    if (mIsSuspended) {
+      if (mIsInterval) {
+        //LOG(("Timeout [0x%p] resuming normal interval (%u) with id %u",
+             //static_cast<void*>(this), mInterval, mId));
+
+        // This is the first fire since we resumed. Set our interval back to the
+        // real interval.
+        mTargetTime = PR_Now() + mInterval * (PRTime)PR_USEC_PER_MSEC;
+
+        rv = aTimer->InitWithCallback(this, mInterval,
+                                      nsITimer::TYPE_REPEATING_SLACK);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      mIsSuspended = PR_FALSE;
+      mSuspendedRef = nsnull;
+    }
+  }
+
+  nsDOMThreadService::get()->TimeoutReady(this);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerTimeout.h
@@ -0,0 +1,189 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __NSDOMWORKERTIMEOUT_H__
+#define __NSDOMWORKERTIMEOUT_H__
+
+// Interfaces
+#include "nsITimer.h"
+
+// Other includes
+#include "jsapi.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "prclist.h"
+
+// DOMWorker includes
+#include "nsDOMWorkerThread.h"
+
+/**
+ * The nsDOMWorkerTimeout has a slightly complicated life cycle. It's created
+ * by an nsDOMWorkerThread (or one of its JS context functions) and immediately
+ * takes a strong reference to the worker that created it. It does this so that
+ * the worker can't be collected while a timeout is outstanding. However, the
+ * worker needs a weak reference to the timeout so that it can be canceled if
+ * the worker is canceled (in the event that the page falls out of the fastback
+ * cache or the application is exiting, for instance). The only thing that holds
+ * the timeout alive is it's mTimer via the nsITimerCallback interface. If the
+ * timer is single-shot and has run already or if the timer is canceled then
+ * this object should die.
+ */
+class nsDOMWorkerTimeout : public PRCList,
+                           public nsITimerCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITIMERCALLBACK
+
+  nsDOMWorkerTimeout(nsDOMWorkerThread* aWorker, PRUint32 aId);
+  ~nsDOMWorkerTimeout();
+
+  nsresult Init(JSContext* aCx, PRUint32 aArgc, jsval* aArgv,
+                PRBool aIsInterval);
+
+  nsresult Run();
+
+  void Cancel();
+  void Suspend(PRTime aNow);
+  void Resume(PRTime aNow);
+
+  PRIntervalTime GetInterval() {
+    return mInterval;
+  }
+
+  nsDOMWorkerThread* GetWorker() {
+    return mWorker;
+  }
+
+  PRUint32 GetId() {
+    return mId;
+  }
+
+  PRBool IsSuspended() {
+    AutoSpinlock lock(this);
+    return IsSuspendedNoLock();
+  }
+
+private:
+  void AcquireSpinlock();
+  void ReleaseSpinlock();
+
+  PRBool IsSuspendedNoLock() {
+    return mIsSuspended;
+  }
+
+  class AutoSpinlock
+  {
+  public:
+    AutoSpinlock(nsDOMWorkerTimeout* aTimeout)
+    : mTimeout(aTimeout) {
+      aTimeout->AcquireSpinlock();
+    }
+
+    ~AutoSpinlock() {
+      mTimeout->ReleaseSpinlock();
+    }
+  private:
+    nsDOMWorkerTimeout* mTimeout;
+  };
+
+  // We support two types of callbacks (functions and expressions) just like the
+  // normal window timeouts. Each type has its own member and rooting needs so
+  // we split them into two classes with a common base.
+  class CallbackBase
+  {
+  public:
+    virtual ~CallbackBase() { }
+    virtual nsresult Run(nsDOMWorkerTimeout* aTimeout,
+                         JSContext* aCx) = 0;
+  };
+
+  class FunctionCallback : public CallbackBase
+  {
+  public:
+    FunctionCallback(PRUint32 aArgc, jsval* aArgv, nsresult* aRv);
+    virtual ~FunctionCallback();
+    virtual nsresult Run(nsDOMWorkerTimeout* aTimeout,
+                         JSContext* aCx);
+  protected:
+    jsval mCallback;
+    jsval* mCallbackArgs;
+    PRUint32 mCallbackArgsLength;
+  };
+  
+  class ExpressionCallback : public CallbackBase
+  {
+  public:
+    ExpressionCallback(PRUint32 aArgc, jsval* aArgv, JSContext* aCx,
+                       nsresult* aRv);
+    virtual ~ExpressionCallback();
+    virtual nsresult Run(nsDOMWorkerTimeout* aTimeout,
+                         JSContext* aCx);
+  protected:
+    JSString* mExpression;
+    nsString mFileName;
+    PRUint32 mLineNumber;
+  };
+
+  // Hold the worker alive!
+  nsRefPtr<nsDOMWorkerThread> mWorker;
+
+  // Hold this object alive!
+  nsCOMPtr<nsITimer> mTimer;
+
+  PRUint32 mInterval;
+  PRBool mIsInterval;
+
+  PRTime mTargetTime;
+
+  nsAutoPtr<CallbackBase> mCallback;
+
+  PRUint32 mId;
+
+  PRInt32 mSuspendSpinlock;
+  PRBool mIsSuspended;
+  PRUint32 mSuspendInterval;
+  nsRefPtr<nsDOMWorkerTimeout> mSuspendedRef;
+
+#ifdef DEBUG
+  PRBool mFiredOrCanceled;
+#endif
+};
+
+#endif /* __NSDOMWORKERTIMEOUT_H__ */
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/Makefile.in
@@ -0,0 +1,57 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is worker threads.
+#
+# The Initial Developer of the Original Code is
+#   Mozilla Corporation
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
+#   Ben Turner <bent.mozilla@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH            = ../../../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+relativesrcdir   = dom/src/threads/tests
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_TEST_FILES = \
+  test_simpleThread.html \
+  test_threadErrors.html \
+  test_threadTimeouts.html \
+  test_longThread.html \
+  $(NULL)
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_longThread.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads (Bug 437152)
+-->
+<head>
+  <title>Test for DOM Worker Threads (Bug 437152)</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+              getService(Components.interfaces.nsIPrefBranch);
+  prefs.setIntPref("javascript.options.gczeal", 2);
+
+  var workerScript =
+    "function messageListener(message, source) { " +
+    "  switch (message) { " +
+    "    case 'start': " +
+    "      /* do a ton of stuff! */ " +
+    "      for (var i = 0; i < 10000000; i++) { } " +
+    "      dump('done!\\n'); " +
+    "      /* pass message to source */ " +
+    "      source.postMessage('done'); " +
+    "      break; " +
+    "    default: " +
+    "      throw 'Bad message: ' + message; " +
+    "  } " +
+    "} " +
+    "";
+
+  var pool = navigator.newWorkerPool();
+  ok(pool, "Couldn't get worker pool");
+
+  const numThreads = 10;
+  var doneThreads = 0;
+
+  pool.messageListener = function(message, source) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    switch (message) {
+      case "done":
+        if (++doneThreads == numThreads) {
+          prefs.setIntPref("javascript.options.gczeal", 0);
+          SimpleTest.finish();
+        }
+        break;
+      default:
+        ok(false, "Unexpected message");
+        prefs.setIntPref("javascript.options.gczeal", 0);
+        SimpleTest.finish();
+    }
+  };
+
+  pool.errorListener = function(error, source) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    ok(false, "Worker had an error");
+    prefs.setIntPref("javascript.options.gczeal", 0);
+    SimpleTest.finish();
+  };
+
+  for (var i = 0; i < numThreads; i++) {
+    var worker = pool.createWorker(workerScript);
+    worker.postMessage("start");
+  }
+
+  SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_simpleThread.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads (Bug 437152)
+-->
+<head>
+  <title>Test for DOM Worker Threads (Bug 437152)</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+              getService(Components.interfaces.nsIPrefBranch);
+  prefs.setIntPref("javascript.options.gczeal", 2);
+
+  var workerScript =
+    "function messageListener(message, source) { " +
+    "  switch (message) { " +
+    "    case 'no-op': " +
+    "      break; " +
+    "    case 'start': " +
+    "      /* do a ton of stuff! */ " +
+    "      for (var i = 0; i < 1000; i++) { } " +
+    "      /* pass message to source */ " +
+    "      source.postMessage('started'); " +
+    "      break; " +
+    "    case 'stop': " +
+    "      /* do some more stuff! */ " +
+    "      for (var i = 0; i < 1000; i++) { } " +
+    "      /* pass message to self */ " +
+    "      threadContext.thisThread.postMessage('no-op'); " +
+    "      /* pass message to pool */ " +
+    "      postMessageToPool('stopped'); " +
+    "      break; " +
+    "    default: " +
+    "      throw 'Bad message: ' + message; " +
+    "  } " +
+    "} " +
+    "";
+
+  var pool = navigator.newWorkerPool();
+  ok(pool, "Couldn't get worker pool");
+
+  pool.messageListener = function(message, source) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    switch (message) {
+      case "no-op":
+        break;
+      case "started":
+        // pass message to self
+        pool.postMessage("no-op");
+        // pass message to source
+        source.postMessage("stop");
+        break;
+      case "stopped":
+        // pass message to worker
+        worker.postMessage("no-op");
+        prefs.setIntPref("javascript.options.gczeal", 0);
+        SimpleTest.finish();
+        break;
+      default:
+        ok(false, "Unexpected message");
+        prefs.setIntPref("javascript.options.gczeal", 0);
+        SimpleTest.finish();
+    }
+  };
+
+  pool.errorListener = function(error, source) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    ok(false, "Worker had an error");
+    prefs.setIntPref("javascript.options.gczeal", 0);
+    SimpleTest.finish();
+  };
+
+  var worker = pool.createWorker(workerScript);
+  ok(worker, "Couldn't make worker");
+
+  pool.postMessage("no-op");
+  worker.postMessage("start");
+
+  SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_threadErrors.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads (Bug 437152)
+-->
+<head>
+  <title>Test for DOM Worker Threads (Bug 437152)</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+              getService(Components.interfaces.nsIPrefBranch);
+  prefs.setIntPref("javascript.options.gczeal", 2);
+
+  var badWorkerScripts = [
+    // Syntax error
+    "function messageListener(message, source) { " +
+    "  for (var i = 0; i < 10) { } " +
+    "} " +
+    "",
+
+    // Bad function error
+    "function messageListener(message, source) { " +
+    "  foopy(); " +
+    "} " +
+    "",
+
+    // Unhandled exception in body
+    "function messageListener(message, source) { " +
+    "} " +
+    "throw new Error('Bah!'); " +
+    "",
+
+    // Throwing message listener
+    "function messageListener(message, source) { " +
+    "  throw 'Bad message: ' + message; " +
+    "} " +
+    ""
+  ];
+
+  var expectedErrorCount = badWorkerScripts.length;
+
+  var pool = navigator.newWorkerPool();
+
+  pool.messageListener = function(message, source) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+    prefs.setIntPref("javascript.options.gczeal", 0);
+    ok(false, "Unexpected message");
+    SimpleTest.finish();
+  };
+
+  var actualErrorCount = 0;
+  var failedWorkers = [];
+
+  pool.errorListener = function(error, source) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+    dump("Worker[" + actualErrorCount + "]: " + source + "\n\nError: " + error + "\n\nArray: " + failedWorkers + "\n\n");
+
+    if (failedWorkers.indexOf(source) != -1) {
+      dump("Already seen worker: " + source + "\n");
+      ok(false, "Seen an extra error from this worker");
+      prefs.setIntPref("javascript.options.gczeal", 0);
+      SimpleTest.finish();
+      return;
+    }
+
+    failedWorkers.push(source);
+    actualErrorCount++;
+
+    if (actualErrorCount == expectedErrorCount) {
+      prefs.setIntPref("javascript.options.gczeal", 0);
+      SimpleTest.finish();
+    }
+  };
+
+  for (var i = 0; i < expectedErrorCount; i++) {
+    var worker = pool.createWorker(badWorkerScripts[i]);
+    worker.postMessage("Hi");
+  }
+
+  SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_threadTimeouts.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads (Bug 437152)
+-->
+<head>
+  <title>Test for DOM Worker Threads (Bug 437152)</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+              getService(Components.interfaces.nsIPrefBranch);
+  prefs.setIntPref("javascript.options.gczeal", 2);
+
+  var workerScript =
+    "var gTimeoutId; " +
+    "var gTimeoutCount = 0; " +
+    "var gIntervalCount = 0; " +
+    "" +
+    "function timeoutFunc() { " +
+    "  if (++gTimeoutCount > 1) { " +
+    "    throw new Error('Timeout called more than once!'); " +
+    "  } " +
+    "  postMessageToPool('timeoutFinished'); " +
+    "} " +
+    "" +
+    "function intervalFunc() { " +
+    "  if (++gIntervalCount == 2) { " +
+    "    postMessageToPool('intervalFinished'); " +
+    "  } " +
+    "} " +
+    "" +
+    "function messageListener(message, source) { " +
+    "  switch (message) { " +
+    "    case 'startTimeout': " +
+    "      gTimeoutId = setTimeout(timeoutFunc, 5000); " +
+    "      clearTimeout(gTimeoutId); " +
+    "      gTimeoutId = setTimeout(timeoutFunc, 5000); " +
+    "      break; " +
+    "    case 'startInterval': " +
+    "      gTimeoutId = setInterval(intervalFunc, 5000); " +
+    "      break; " +
+    "    case 'cancelInterval': " +
+    "      clearInterval(gTimeoutId); " +
+    "      postMessageToPool('intervalCanceled'); " +
+    "      break; " +
+    "    default: " +
+    "      throw 'Bad message: ' + message; " +
+    "  } " +
+    "} " +
+    "";
+
+  var pool = navigator.newWorkerPool();
+  ok(pool, "Couldn't get worker pool");
+
+  pool.messageListener = function(message, source) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    switch (message) {
+      case "timeoutFinished":
+        source.postMessage("startInterval");
+        break;
+      case "intervalFinished":
+        source.postMessage("cancelInterval");
+        break;
+      case "intervalCanceled":
+        prefs.setIntPref("javascript.options.gczeal", 0);
+        SimpleTest.finish();
+        break;
+      default:
+        ok(false, "Unexpected message");
+        prefs.setIntPref("javascript.options.gczeal", 0);
+        SimpleTest.finish();
+    }
+  };
+
+  pool.errorListener = function(error, source) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    ok(false, "Worker had an error");
+    prefs.setIntPref("javascript.options.gczeal", 0);
+    SimpleTest.finish();
+  };
+
+  var worker = pool.createWorker(workerScript);
+  ok(worker, "Couldn't make worker");
+
+  worker.postMessage("startTimeout");
+
+  SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -134,16 +134,17 @@ SHARED_LIBRARY_LIBS = \
 	$(DEPTH)/view/src/$(LIB_PREFIX)gkview_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/base/$(LIB_PREFIX)jsdombase_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/events/$(LIB_PREFIX)jsdomevents_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/json/$(LIB_PREFIX)json_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/jsurl/$(LIB_PREFIX)jsurl_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/storage/$(LIB_PREFIX)jsdomstorage_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/offline/$(LIB_PREFIX)jsdomoffline_s.$(LIB_SUFFIX) \
  	$(DEPTH)/dom/src/geolocation/$(LIB_PREFIX)jsdomgeolocation_s.$(LIB_SUFFIX) \
+	$(DEPTH)/dom/src/threads/$(LIB_PREFIX)domthreads_s.$(LIB_SUFFIX) \
 	$(DEPTH)/editor/libeditor/text/$(LIB_PREFIX)texteditor_s.$(LIB_SUFFIX) \
 	$(DEPTH)/editor/libeditor/base/$(LIB_PREFIX)editorbase_s.$(LIB_SUFFIX) \
 	$(NULL)
 
 ifdef MOZ_MEDIA
 SHARED_LIBRARY_LIBS 	+= \
 	$(DEPTH)/content/media/video/src/$(LIB_PREFIX)gkconvideo_s.$(LIB_SUFFIX) \
 	$(NULL)
@@ -282,16 +283,17 @@ LOCAL_INCLUDES	+= -I$(srcdir)/../base \
 		   -I$(topsrcdir)/content/xbl/src \
 		   -I$(topsrcdir)/view/src \
 		   -I$(topsrcdir)/dom/src/base \
 		   -I$(topsrcdir)/dom/src/json \
 		   -I$(topsrcdir)/dom/src/jsurl \
 		   -I$(topsrcdir)/dom/src/storage \
 		   -I$(topsrcdir)/dom/src/offline \
 		   -I$(topsrcdir)/dom/src/geolocation \
+		   -I$(topsrcdir)/dom/src/threads \
 		   -I. \
 		   -I$(topsrcdir)/editor/libeditor/base \
 		   -I$(topsrcdir)/editor/libeditor/text \
 		   -I$(topsrcdir)/editor/libeditor/html \
 		   -I$(topsrcdir)/editor/txtsvc/src \
 		   -I$(topsrcdir)/editor/composer/src \
 		   $(NULL)
 
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -78,16 +78,17 @@
 #include "nsDOMStorage.h"
 #include "nsCellMap.h"
 #include "nsTextFrameTextRunCache.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsTextFragment.h"
 #include "nsCSSRuleProcessor.h"
 #include "nsXMLHttpRequest.h"
 #include "nsIFocusEventSuppressor.h"
+#include "nsDOMThreadService.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #include "nsXULContentUtils.h"
 #include "nsXULElement.h"
 #include "nsXULPrototypeCache.h"
 #include "nsXULTooltipListener.h"
 
@@ -331,16 +332,18 @@ nsLayoutStatics::Shutdown()
   nsXBLWindowKeyHandler::ShutDown();
   nsAutoCopyListener::Shutdown();
 
 #ifndef MOZILLA_PLAINTEXT_EDITOR_ONLY
   nsHTMLEditor::Shutdown();
   nsTextServicesDocument::Shutdown();
 #endif
 
+  nsDOMThreadService::Shutdown();
+
   NS_ShutdownFocusSuppressor();
 
 #ifdef MOZ_OGG
   nsAudioStream::ShutdownLibrary();
 #endif
 }
 
 void