Bug 450449 - " Implement 'importScripts' for worker threads". r=jst+mrbkap, sr=jst.
authorBen Turner <bent.mozilla@gmail.com>
Mon, 08 Sep 2008 00:08:24 -0700
changeset 18949 8d748ca9fec99cdbeb844bb89d63c4a34cfe2fc1
parent 18948 d33ad280baa5575388523bb6b6d95b77c96a281b
child 18950 6c2c634949be89437fdd76a7c62cf43fff015200
push id1853
push userbturner@mozilla.com
push dateMon, 08 Sep 2008 07:08:53 +0000
treeherdermozilla-central@8d748ca9fec9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst, jst
bugs450449
milestone1.9.1b1pre
Bug 450449 - " Implement 'importScripts' for worker threads". r=jst+mrbkap, sr=jst.
dom/public/idl/threads/nsIDOMThreads.idl
dom/src/threads/Makefile.in
dom/src/threads/nsAutoJSObjectHolder.h
dom/src/threads/nsDOMThreadService.cpp
dom/src/threads/nsDOMWorkerPool.cpp
dom/src/threads/nsDOMWorkerPool.h
dom/src/threads/nsDOMWorkerScriptLoader.cpp
dom/src/threads/nsDOMWorkerScriptLoader.h
dom/src/threads/nsDOMWorkerSecurityManager.cpp
dom/src/threads/nsDOMWorkerSecurityManager.h
dom/src/threads/nsDOMWorkerThread.cpp
dom/src/threads/nsDOMWorkerThread.h
dom/src/threads/test/Makefile.in
dom/src/threads/test/importScripts_worker.js
dom/src/threads/test/importScripts_worker_imported1.js
dom/src/threads/test/importScripts_worker_imported2.js
dom/src/threads/test/importScripts_worker_imported3.js
dom/src/threads/test/importScripts_worker_imported4.js
dom/src/threads/test/test_importScripts.html
--- a/dom/public/idl/threads/nsIDOMThreads.idl
+++ b/dom/public/idl/threads/nsIDOMThreads.idl
@@ -117,16 +117,25 @@ interface nsIDOMWorkerPool : nsISupports
   /**
    * 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);
+
+  /**
+   * Create a new worker object by evaluating the given script.
+   *
+   * @param aSourceURL (in AString)
+   *        The script url to load and compile. See below for details on the
+   *        scope in which the script will run.
+   */
+  nsIDOMWorkerThread createWorkerFromURL(in AString aSourceURL);
 };
 
 [scriptable, uuid(0f2a52ea-afc9-49e6-86dd-2d0cb65b5dd5)]
 interface nsIDOMThreadService : nsISupports
 {
   /**
    * Creates a new DOM worker pool.
    */
--- a/dom/src/threads/Makefile.in
+++ b/dom/src/threads/Makefile.in
@@ -48,27 +48,29 @@ LIBRARY_NAME     = domthreads_s
 LIBXUL_LIBRARY   = 1
 FORCE_STATIC_LIB = 1
 
 REQUIRES = \
   caps \
   content \
   js \
   layout \
+  necko \
   pref \
   string \
   widget \
   xpcom \
   xpconnect \
   $(NULL)
 
 CPPSRCS = \
   nsDOMThreadService.cpp \
   nsDOMWorkerBase.cpp \
   nsDOMWorkerPool.cpp \
+  nsDOMWorkerScriptLoader.cpp \
   nsDOMWorkerSecurityManager.cpp \
   nsDOMWorkerThread.cpp \
   nsDOMWorkerTimeout.cpp \
   $(NULL)
 
 LOCAL_INCLUDES = \
   -I$(topsrcdir)/dom/src/base \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsAutoJSObjectHolder.h
@@ -0,0 +1,160 @@
+/* -*- 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 __NSAUTOJSOBJECTHOLDER_H__
+#define __NSAUTOJSOBJECTHOLDER_H__
+
+#include "jsapi.h"
+
+/**
+ * Simple class that looks and acts like a JSObject* except that it unroots
+ * itself automatically if Root() is ever called. Designed to be rooted on the
+ * context or runtime (but not both!). Also automatically nulls its JSObject*
+ * on Unroot and asserts that Root has been called prior to assigning an object.
+ */
+class nsAutoJSObjectHolder
+{
+public:
+  /**
+   * Default constructor, no holding.
+   */
+  nsAutoJSObjectHolder()
+  : mRt(NULL), mObj(NULL), mHeld(PR_FALSE) { }
+
+  /**
+   * Hold by rooting on the context's runtime in the constructor, passing the
+   * result out.
+   */
+  nsAutoJSObjectHolder(JSContext* aCx, JSBool* aRv = NULL,
+                       JSObject* aObj = NULL)
+  : mRt(NULL), mObj(aObj), mHeld(JS_FALSE) {
+    JSBool rv = Hold(aCx);
+    if (aRv) {
+      *aRv = rv;
+    }
+  }
+
+  /**
+   * Hold by rooting on the runtime in the constructor, passing the result out.
+   */
+  nsAutoJSObjectHolder(JSRuntime* aRt, JSBool* aRv = NULL,
+                       JSObject* aObj = NULL)
+  : mRt(aRt), mObj(aObj), mHeld(JS_FALSE) {
+    JSBool rv = Hold(aRt);
+    if (aRv) {
+      *aRv = rv;
+    }
+  }
+
+  /**
+   * Always release on destruction.
+   */
+  ~nsAutoJSObjectHolder() {
+    Release();
+  }
+
+  /**
+   * Hold by rooting on the context's runtime.
+   */
+  JSBool Hold(JSContext* aCx) {
+    return Hold(JS_GetRuntime(aCx));
+  }
+
+  /**
+   * Hold by rooting on the runtime.
+   */
+  JSBool Hold(JSRuntime* aRt) {
+    if (!mHeld) {
+      mHeld = JS_AddNamedRootRT(aRt, &mObj, "nsAutoRootedJSObject");
+      if (mHeld) {
+        mRt = aRt;
+      }
+    }
+    return mHeld;
+  }
+
+  /**
+   * Manually release.
+   */
+  void Release() {
+    NS_ASSERTION(!mHeld || mRt, "Bad!");
+    if (mHeld) {
+      mHeld = !JS_RemoveRootRT(mRt, &mObj);
+      if (!mHeld) {
+        mRt = NULL;
+      }
+      mObj = NULL;
+    }
+  }
+
+  /**
+   * Determine if Hold has been called.
+   */
+  JSBool IsHeld() {
+    return mHeld;
+  }
+
+  /**
+   * Pretend to be a JSObject*.
+   */
+  JSObject* get() const {
+    return mObj;
+  }
+
+  /**
+   * Pretend to be a JSObject*.
+   */
+  operator JSObject*() const {
+    return get();
+  }
+
+  /**
+   * Pretend to be a JSObject*. Assert if not held.
+   */
+  JSObject* operator=(JSObject* aOther) {
+    NS_ASSERTION(mHeld, "Not rooted!");
+    return mObj = aOther;
+  }
+
+private:
+  JSRuntime* mRt;
+  JSObject* mObj;
+  JSBool mHeld;
+};
+
+#endif /* __NSAUTOJSOBJECTHOLDER_H__ */
--- a/dom/src/threads/nsDOMThreadService.cpp
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -37,16 +37,18 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsDOMThreadService.h"
 
 // Interfaces
 #include "nsIComponentManager.h"
 #include "nsIConsoleService.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.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"
@@ -83,16 +85,24 @@ PRLogModuleInfo *gDOMThreadsLog = nsnull
 
 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);
 
+// As we suspend threads for various reasons (navigating away from the page,
+// loading scripts, etc.) we open another slot in the thread pool for another
+// worker to use. We can't do this forever so we set an absolute cap on the
+// number of threads we'll allow to prevent DOS attacks.
+#define THREADPOOL_THREAD_CAP 20
+
+PR_STATIC_ASSERT(THREADPOOL_THREAD_CAP >= THREADPOOL_MAX_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...
@@ -381,37 +391,42 @@ DOMWorkerOperationCallback(JSContext* aC
 {
   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;
+  PRBool extraThreadAllowed = 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);
+        if (extraThreadAllowed) {
+          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);
+        if (extraThreadAllowed) {
+          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.
@@ -422,18 +437,20 @@ DOMWorkerOperationCallback(JSContext* aC
 
       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);
+      // in the thread pool for other workers. Must check the return value to
+      // make sure we don't decrement when we failed.
+      extraThreadAllowed =
+        NS_SUCCEEDED(gDOMThreadService->ChangeThreadPoolMaxThreads(1));
 
       // Only do all this setup once.
       wasSuspended = PR_TRUE;
     }
 
     nsAutoMonitor mon(pool->Monitor());
     mon.Wait();
   }
@@ -760,16 +777,24 @@ nsDOMThreadService::CreateJSContext()
   JSAutoContextDestroyer cx(JS_NewContext(rt, 8192));
   NS_ENSURE_TRUE(cx, nsnull);
 
   JS_SetErrorReporter(cx, DOMWorkerErrorReporter);
 
   JS_SetOperationCallback(cx, DOMWorkerOperationCallback,
                           100 * JS_OPERATION_WEIGHT_BASE);
 
+  static JSSecurityCallbacks securityCallbacks = {
+    nsDOMWorkerSecurityManager::JSCheckAccess,
+    NULL,
+    NULL
+  };
+
+  JS_SetSecurityCallbacks(cx, &securityCallbacks);
+
   nsresult rv = nsContentUtils::XPConnect()->
     SetSecurityManagerForJSContext(cx, gWorkerSecurityManager, 0);
   NS_ENSURE_SUCCESS(rv, nsnull);
 
   return cx.forget();
 }
 
 #define LOOP_OVER_POOLS(_func, _args)                     \
@@ -828,16 +853,21 @@ nsDOMThreadService::ChangeThreadPoolMaxT
   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!");
 
+  if (newThreadCount > THREADPOOL_THREAD_CAP) {
+    NS_WARNING("Thread pool cap reached!");
+    return NS_ERROR_FAILURE;
+  }
+
   rv = mThreadPool->SetThreadLimit((PRUint32)newThreadCount);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsIJSRuntimeService*
 nsDOMThreadService::JSRuntimeService()
@@ -973,17 +1003,23 @@ nsDOMThreadService::OnThreadShuttingDown
  */
 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());
+  nsIDOMDocument* domDocument = nsContentUtils::GetDocumentFromCaller();
+  NS_ENSURE_TRUE(domDocument, NS_ERROR_UNEXPECTED);
+
+  nsCOMPtr<nsIDocument> callingDocument(do_QueryInterface(domDocument));
+  NS_ENSURE_TRUE(callingDocument, NS_ERROR_NO_INTERFACE);
+
+  nsRefPtr<nsDOMWorkerPool> pool(new nsDOMWorkerPool(callingDocument));
   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);
 
--- a/dom/src/threads/nsDOMWorkerPool.cpp
+++ b/dom/src/threads/nsDOMWorkerPool.cpp
@@ -64,20 +64,22 @@
 #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)
+nsDOMWorkerPool::nsDOMWorkerPool(nsIDocument* aDocument)
+: mParentGlobal(nsnull),
+  mParentDocument(aDocument)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aDocument, "Must have a document!");
 }
 
 nsDOMWorkerPool::~nsDOMWorkerPool()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   LOOP_OVER_WORKERS(Cancel, ());
 
@@ -229,16 +231,24 @@ nsDOMWorkerPool::ResumeWorkersForGlobal(
     LOOP_OVER_WORKERS(Resume, ());
     Resume();
 
     nsAutoMonitor mon(mMonitor);
     mon.NotifyAll();
   }
 }
 
+nsIDocument*
+nsDOMWorkerPool::GetParentDocument()
+{
+  NS_ASSERTION(NS_IsMainThread(),
+               "Don't touch the non-threadsafe document off the main thread!");
+  return mParentDocument;
+}
+
 NS_IMETHODIMP
 nsDOMWorkerPool::PostMessage(const nsAString& aMessage)
 {
   nsresult rv = PostMessageInternal(aMessage);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
@@ -271,28 +281,52 @@ 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,
+nsDOMWorkerPool::CreateWorker(const nsAString& aFullScript,
                               nsIDOMWorkerThread** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  NS_ENSURE_ARG(!fullScript.IsEmpty());
+  NS_ENSURE_ARG(!aFullScript.IsEmpty());
   NS_ENSURE_ARG_POINTER(_retval);
 
-  nsRefPtr<nsDOMWorkerThread> worker(new nsDOMWorkerThread(this, fullScript));
+  nsRefPtr<nsDOMWorkerThread> worker =
+    new nsDOMWorkerThread(this, aFullScript, PR_FALSE);
   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;
 }
+
+NS_IMETHODIMP
+nsDOMWorkerPool::CreateWorkerFromURL(const nsAString& aScriptURL,
+                                     nsIDOMWorkerThread** _retval)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  NS_ENSURE_ARG(!aScriptURL.IsEmpty());
+  NS_ENSURE_ARG_POINTER(_retval);
+
+  nsRefPtr<nsDOMWorkerThread> worker =
+    new nsDOMWorkerThread(this, aScriptURL, PR_TRUE);
+  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;
+}
--- a/dom/src/threads/nsDOMWorkerPool.h
+++ b/dom/src/threads/nsDOMWorkerPool.h
@@ -47,39 +47,42 @@
 
 // Other includes
 #include "jsapi.h"
 #include "nsStringGlue.h"
 #include "nsTPtrArray.h"
 #include "prmon.h"
 
 class nsDOMWorkerThread;
+class nsIDocument;
 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 nsDOMWorkerScriptLoader;
+  friend class nsDOMWorkerStreamObserver;
   friend class nsDOMWorkerThread;
   friend class nsReportErrorRunnable;
   friend JSBool DOMWorkerOperationCallback(JSContext* aCx);
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMWORKERPOOL
   NS_DECL_NSICLASSINFO
 
-  nsDOMWorkerPool();
+  nsDOMWorkerPool(nsIDocument* aDocument);
 
   // For nsDOMWorkerBase
   virtual nsDOMWorkerPool* Pool() {
     return this;
   }
 
 private:
   virtual ~nsDOMWorkerPool();
@@ -101,19 +104,24 @@ private:
   void CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
   void SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
   void ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
 
   PRMonitor* Monitor() {
     return mMonitor;
   }
 
+  nsIDocument* GetParentDocument();
+
   // Weak reference to the window that created and owns this pool.
   nsISupports* mParentGlobal;
 
+  // Weak reference to the document that created this pool.
+  nsIDocument* mParentDocument;
+
   // 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.
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerScriptLoader.cpp
@@ -0,0 +1,790 @@
+/* -*- 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 "nsDOMWorkerScriptLoader.h"
+
+// Interfaces
+#include "nsIContentPolicy.h"
+#include "nsIIOService.h"
+#include "nsIRequest.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIStreamLoader.h"
+
+// Other includes
+#include "nsAutoLock.h"
+#include "nsContentErrors.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "nsNetError.h"
+#include "nsNetUtil.h"
+#include "nsScriptLoader.h"
+#include "nsThreadUtils.h"
+#include "pratom.h"
+
+// DOMWorker includes
+#include "nsDOMWorkerPool.h"
+#include "nsDOMThreadService.h"
+#include "nsDOMWorkerTimeout.h"
+
+#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
+
+nsDOMWorkerScriptLoader::nsDOMWorkerScriptLoader()
+: mWorker(nsnull),
+  mTarget(nsnull),
+  mCx(NULL),
+  mScriptCount(0),
+  mCanceled(PR_FALSE),
+  mTrackedByWorker(PR_FALSE)
+{
+  // Created on worker thread.
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+}
+
+nsDOMWorkerScriptLoader::~nsDOMWorkerScriptLoader()
+{
+  // Can't touch mWorker's lock
+  if (!mCanceled) {
+    // Destroyed on worker thread, unless canceled (and then who knows!).
+    NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+    if (mTrackedByWorker) {
+      jsrefcount suspendDepth;
+      if (mCx) {
+        suspendDepth = JS_SuspendRequest(mCx);
+      }
+
+      nsAutoLock lock(mWorker->Lock());
+  #ifdef DEBUG
+      PRBool removed =
+  #endif
+      mWorker->mScriptLoaders.RemoveElement(this);
+      NS_ASSERTION(removed, "Something is wrong here!");
+
+      if (mCx) {
+        JS_ResumeRequest(mCx, suspendDepth);
+      }
+    }
+  }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerScriptLoader, nsRunnable,
+                                                      nsIStreamLoaderObserver)
+
+nsresult
+nsDOMWorkerScriptLoader::LoadScripts(nsDOMWorkerThread* aWorker,
+                                     JSContext* aCx,
+                                     const nsTArray<nsString>& aURLs)
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aWorker, "Null worker!");
+  NS_ASSERTION(aCx, "Null context!");
+
+  NS_ASSERTION(!mWorker, "Not designed to be used more than once!");
+
+  mWorker = aWorker;
+  mCx = aCx;
+
+  mTarget = NS_GetCurrentThread();
+  NS_ASSERTION(mTarget, "This should never be null!");
+
+  {
+    JSAutoSuspendRequest asr(aCx);
+    nsAutoLock lock(mWorker->Lock());
+    mTrackedByWorker = nsnull != mWorker->mScriptLoaders.AppendElement(this);
+    NS_ASSERTION(mTrackedByWorker, "Failed to add loader to worker's array!");
+  }
+
+  if (mCanceled) {
+    return NS_ERROR_ABORT;
+  }
+
+  mScriptCount = aURLs.Length();
+  if (!mScriptCount) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // Do all the memory work for these arrays now rather than checking for
+  // failures all along the way.
+  PRBool success = mLoadInfos.SetCapacity(mScriptCount);
+  NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+
+  // Need one runnable per script and then an extra for the finished
+  // notification.
+  success = mPendingRunnables.SetCapacity(mScriptCount + 1);
+  NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+
+  for (PRUint32 index = 0; index < mScriptCount; index++) {
+    ScriptLoadInfo* newInfo = mLoadInfos.AppendElement();
+    NS_ASSERTION(newInfo, "Shouldn't fail if SetCapacity succeeded above!");
+
+    newInfo->url.Assign(aURLs[index]);
+    if (newInfo->url.IsEmpty()) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    success = newInfo->scriptObj.Hold(aCx);
+    NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+  }
+
+  // Don't want timeouts, etc., from queuing up while we're waiting on the
+  // network or compiling.
+  AutoSuspendWorkerEvents aswe(this);
+
+  nsresult rv = DoRunLoop();
+
+  {
+    JSAutoSuspendRequest asr(aCx);
+    nsAutoLock lock(mWorker->Lock());
+#ifdef DEBUG
+    PRBool removed =
+#endif
+    mWorker->mScriptLoaders.RemoveElement(this);
+    NS_ASSERTION(removed, "Something is wrong here!");
+    mTrackedByWorker = PR_FALSE;
+   }
+
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Verify that all scripts downloaded and compiled.
+  rv = VerifyScripts();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = ExecuteScripts();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsDOMWorkerScriptLoader::LoadScript(nsDOMWorkerThread* aWorker,
+                                    JSContext* aCx,
+                                    const nsString& aURL)
+{
+  nsAutoTArray<nsString, 1> url;
+  url.AppendElement(aURL);
+
+  return LoadScripts(aWorker, aCx, url);
+}
+
+nsresult
+nsDOMWorkerScriptLoader::DoRunLoop()
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  volatile PRBool done = PR_FALSE;
+  mDoneRunnable = new ScriptLoaderDone(this, &done);
+  NS_ENSURE_TRUE(mDoneRunnable, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv = NS_DispatchToMainThread(this);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!(done || mCanceled)) {
+    // Since we're going to lock up this thread we might as well allow the
+    // thread service to schedule another worker on a new thread.
+    nsDOMThreadService* threadService = nsDOMThreadService::get();
+    PRBool changed = NS_SUCCEEDED(threadService->ChangeThreadPoolMaxThreads(1));
+
+    while (!(done || mCanceled)) {
+      JSAutoSuspendRequest asr(mCx);
+      NS_ProcessNextEvent(mTarget);
+    }
+
+    if (changed) {
+      threadService->ChangeThreadPoolMaxThreads(-1);
+    }
+  }
+
+  return mCanceled ? NS_ERROR_ABORT : NS_OK;
+}
+
+nsresult
+nsDOMWorkerScriptLoader::VerifyScripts()
+{
+  nsresult rv = NS_OK;
+
+  for (PRUint32 index = 0; index < mScriptCount; index++) {
+    ScriptLoadInfo& loadInfo = mLoadInfos[index];
+    NS_ASSERTION(loadInfo.done, "Inconsistent state!");
+
+    if (NS_SUCCEEDED(loadInfo.result) && loadInfo.scriptObj) {
+      continue;
+    }
+
+    NS_ASSERTION(!loadInfo.scriptObj, "Inconsistent state!");
+
+    // Flag failure before worrying about whether or not to report an error.
+    rv = NS_FAILED(loadInfo.result) ? loadInfo.result : NS_ERROR_FAILURE;
+
+    // If loadInfo.result is a success code then the compiler probably reported
+    // an error already. Also we don't really care about NS_BINDING_ABORTED
+    // since that's the code we set when some other script had a problem and the
+    // rest were canceled.
+    if (NS_SUCCEEDED(loadInfo.result) || loadInfo.result == NS_BINDING_ABORTED) {
+      continue;
+    }
+
+    // Ok, this is the script that caused us to fail.
+
+    // Only throw an error there is no other pending exception.
+    if (!JS_IsExceptionPending(mCx)) {
+      NS_ConvertUTF16toUTF8 url(loadInfo.url);
+      JS_ReportError(mCx, "Failed to compile script: %s", url.get());
+    }
+    break;
+  }
+
+  return rv;
+}
+
+nsresult
+nsDOMWorkerScriptLoader::ExecuteScripts()
+{
+  // Now execute all the scripts.
+  for (PRUint32 index = 0; index < mScriptCount; index++) {
+    ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+    JSScript* script =
+      static_cast<JSScript*>(JS_GetPrivate(mCx, loadInfo.scriptObj));
+    NS_ASSERTION(script, "This shouldn't ever be null!");
+
+    JSObject* global = mWorker->mGlobal ?
+                       mWorker->mGlobal :
+                       JS_GetGlobalObject(mCx);
+    NS_ENSURE_STATE(global);
+
+    // Because we may have nested calls to this function we don't want the
+    // execution to automatically report errors. We let them propagate instead.
+    uint32 oldOpts =
+      JS_SetOptions(mCx, JS_GetOptions(mCx) | JSOPTION_DONT_REPORT_UNCAUGHT);
+
+    jsval val;
+    PRBool success = JS_ExecuteScript(mCx, global, script, &val);
+
+    JS_SetOptions(mCx, oldOpts);
+
+    if (!success) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  return NS_OK;
+}
+
+void
+nsDOMWorkerScriptLoader::Cancel()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  NS_ASSERTION(!mCanceled, "Cancel called more than once!");
+  mCanceled = PR_TRUE;
+
+  for (PRUint32 index = 0; index < mScriptCount; index++) {
+    ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+    nsIRequest* request =
+      static_cast<nsIRequest*>(loadInfo.channel.get());
+    if (request) {
+#ifdef DEBUG
+      nsresult rv =
+#endif
+      request->Cancel(NS_BINDING_ABORTED);
+      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to cancel channel!");
+    }
+  }
+
+  nsAutoTArray<ScriptLoaderRunnable*, 10> runnables;
+  {
+    nsAutoLock lock(mWorker->Lock());
+    runnables.AppendElements(mPendingRunnables);
+    mPendingRunnables.Clear();
+  }
+
+  PRUint32 runnableCount = runnables.Length();
+  for (PRUint32 index = 0; index < runnableCount; index++) {
+    runnables[index]->Revoke();
+  }
+
+  // We're about to post a revoked event to the worker thread, which seems
+  // silly, but we have to do this because the worker thread may be sleeping
+  // waiting on its event queue.
+  NotifyDone();
+}
+
+NS_IMETHODIMP
+nsDOMWorkerScriptLoader::Run()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  // We may have been canceled already.
+  if (mCanceled) {
+    return NS_BINDING_ABORTED;
+  }
+
+  nsresult rv = RunInternal();
+  if (NS_SUCCEEDED(rv)) {
+    return rv;
+  }
+
+  // Ok, something failed beyond a normal cancel.
+
+  // If necko is holding a ref to us then we'll end up notifying in the
+  // OnStreamComplete method, not here.
+  PRBool needsNotify = PR_TRUE;
+
+  // Cancel any async channels that were already opened.
+  for (PRUint32 index = 0; index < mScriptCount; index++) {
+    ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+    nsIRequest* request = static_cast<nsIRequest*>(loadInfo.channel.get());
+    if (request) {
+#ifdef DEBUG
+      nsresult rvInner =
+#endif
+      request->Cancel(NS_BINDING_ABORTED);
+      NS_WARN_IF_FALSE(NS_SUCCEEDED(rvInner), "Failed to cancel channel!");
+
+      // Necko is holding a ref to us so make sure that the OnStreamComplete
+      // code sends the done event.
+      needsNotify = PR_FALSE;
+    }
+    else {
+      // Make sure to set this so that the OnStreamComplete code will dispatch
+      // the done event.
+      loadInfo.done = PR_TRUE;
+    }
+  }
+
+  if (needsNotify) {
+    NotifyDone();
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+nsDOMWorkerScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
+                                          nsISupports* aContext,
+                                          nsresult aStatus,
+                                          PRUint32 aStringLen,
+                                          const PRUint8* aString)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  // We may have been canceled already.
+  if (mCanceled) {
+    return NS_BINDING_ABORTED;
+  }
+
+  nsresult rv = OnStreamCompleteInternal(aLoader, aContext, aStatus, aStringLen,
+                                         aString);
+
+  // Dispatch the done event if we've received all the data.
+  for (PRUint32 index = 0; index < mScriptCount; index++) {
+    if (!mLoadInfos[index].done) {
+      // Some async load is still outstanding, don't notify yet.
+      break;
+    }
+
+    if (index == mScriptCount - 1) {
+      // All loads complete, signal the thread.
+      NotifyDone();
+    }
+  }
+
+  return rv;
+}
+
+nsresult
+nsDOMWorkerScriptLoader::RunInternal()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  // Things we need to make all this work...
+  nsIDocument* parentDoc = mWorker->Pool()->GetParentDocument();
+  NS_ASSERTION(parentDoc, "Null parent document?!");
+
+  // All of these can potentially be null, but that should be ok. We'll either
+  // succeed without them or fail below.
+  nsIURI* parentBaseURI = parentDoc->GetBaseURI();
+  nsCOMPtr<nsILoadGroup> loadGroup(parentDoc->GetDocumentLoadGroup());
+  nsCOMPtr<nsIIOService> ios(do_GetIOService());
+
+  for (PRUint32 index = 0; index < mScriptCount; index++) {
+    ScriptLoadInfo& loadInfo = mLoadInfos[index];
+    nsresult& rv = loadInfo.result;
+
+    nsCOMPtr<nsIURI>& uri = loadInfo.finalURI;
+    rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
+                                                   loadInfo.url, parentDoc,
+                                                   parentBaseURI);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+    NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);
+
+    rv =
+      secMan->CheckLoadURIWithPrincipal(parentDoc->NodePrincipal(), uri,
+                                        nsIScriptSecurityManager::ALLOW_CHROME);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
+    rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
+                                   uri,
+                                   parentDoc->NodePrincipal(),
+                                   parentDoc,
+                                   NS_LITERAL_CSTRING("text/javascript"),
+                                   nsnull,
+                                   &shouldLoad,
+                                   nsContentUtils::GetContentPolicy(),
+                                   secMan);
+    if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+      if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
+        return NS_ERROR_CONTENT_BLOCKED;
+      }
+      return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
+    }
+
+    // We need to know which index we're on in OnStreamComplete so we know where
+    // to put the result.
+    nsCOMPtr<nsISupportsPRUint32> indexSupports =
+      do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = indexSupports->SetData(index);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // We don't care about progress so just use the simple stream loader for
+    // OnStreamComplete notification only.
+    nsCOMPtr<nsIStreamLoader> loader;
+    rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = NS_NewChannel(getter_AddRefs(loadInfo.channel), uri, ios, loadGroup);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = loadInfo.channel->AsyncOpen(loader, indexSupports);
+    if (NS_FAILED(rv)) {
+      // Null this out so we don't try to cancel it later.
+      loadInfo.channel = nsnull;
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsDOMWorkerScriptLoader::OnStreamCompleteInternal(nsIStreamLoader* aLoader,
+                                                  nsISupports* aContext,
+                                                  nsresult aStatus,
+                                                  PRUint32 aStringLen,
+                                                  const PRUint8* aString)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  nsCOMPtr<nsISupportsPRUint32> indexSupports(do_QueryInterface(aContext));
+  NS_ENSURE_TRUE(indexSupports, NS_ERROR_NO_INTERFACE);
+
+  PRUint32 index = PR_UINT32_MAX;
+  indexSupports->GetData(&index);
+
+  if (index >= mScriptCount) {
+    NS_NOTREACHED("This really can't fail or we'll hang!");
+    return NS_ERROR_FAILURE;
+  }
+
+  ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+  NS_ASSERTION(!loadInfo.done, "Got complete on the same load twice!");
+  loadInfo.done = PR_TRUE;
+
+#ifdef DEBUG
+  // Make sure we're seeing the channel that we expect.
+  nsCOMPtr<nsIRequest> request;
+  nsresult rvDebug = aLoader->GetRequest(getter_AddRefs(request));
+
+  // When we cancel sometimes we get null here. That should be ok, but only if
+  // we're canceled.
+  NS_ASSERTION(NS_SUCCEEDED(rvDebug) || mCanceled, "GetRequest failed!");
+
+  if (NS_SUCCEEDED(rvDebug)) {
+    nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
+    NS_ASSERTION(channel, "QI failed!");
+
+    nsCOMPtr<nsISupports> thisChannel(do_QueryInterface(channel));
+    NS_ASSERTION(thisChannel, "QI failed!");
+
+    nsCOMPtr<nsISupports> ourChannel(do_QueryInterface(loadInfo.channel));
+    NS_ASSERTION(ourChannel, "QI failed!");
+
+    NS_ASSERTION(thisChannel == ourChannel, "Wrong channel!");
+  }
+#endif
+
+  // Use an alias to keep rv and loadInfo.result in sync.
+  nsresult& rv = loadInfo.result = aStatus;
+
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (!(aStringLen && aString)) {
+    return rv = NS_ERROR_UNEXPECTED;
+  }
+
+  nsIDocument* parentDoc = mWorker->Pool()->GetParentDocument();
+  NS_ASSERTION(parentDoc, "Null parent document?!");
+
+  // Use the regular nsScriptLoader for this grunt work! Should be just fine
+  // because we're running on the main thread.
+  rv = nsScriptLoader::ConvertToUTF16(loadInfo.channel, aString, aStringLen,
+                                      EmptyString(), parentDoc,
+                                      loadInfo.scriptText);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (loadInfo.scriptText.IsEmpty()) {
+    return rv = NS_ERROR_FAILURE;
+  }
+
+  nsCString filename;
+  loadInfo.finalURI->GetSpec(filename);
+
+  if (filename.IsEmpty()) {
+    filename.Assign(NS_LossyConvertUTF16toASCII(loadInfo.url));
+  }
+  else {
+    // This will help callers figure out what their script url resolved to in
+    // case of errors.
+    loadInfo.url.Assign(NS_ConvertUTF8toUTF16(filename));
+  }
+
+  nsRefPtr<ScriptCompiler> compiler =
+    new ScriptCompiler(this, mCx, loadInfo.scriptText, filename,
+                       loadInfo.scriptObj);
+  NS_ASSERTION(compiler, "Out of memory!");
+  if (!compiler) {
+    return rv = NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  rv = mTarget->Dispatch(compiler, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return rv;
+}
+
+void
+nsDOMWorkerScriptLoader::NotifyDone()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  if (!mDoneRunnable) {
+    // We've already completed, no need to cancel anything.
+    return;
+  }
+
+  for (PRUint32 index = 0; index < mScriptCount; index++) {
+    ScriptLoadInfo& loadInfo = mLoadInfos[index];
+    // Null both of these out because they aren't threadsafe and must be
+    // destroyed on this thread.
+    loadInfo.channel = nsnull;
+    loadInfo.finalURI = nsnull;
+
+    if (mCanceled) {
+      // Simulate a complete, yet failed, load.
+      loadInfo.done = PR_TRUE;
+      loadInfo.result = NS_BINDING_ABORTED;
+    }
+  }
+
+#ifdef DEBUG
+  nsresult rv =
+#endif
+  mTarget->Dispatch(mDoneRunnable, NS_DISPATCH_NORMAL);
+  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Couldn't dispatch done event!");
+
+  mDoneRunnable = nsnull;
+}
+
+void
+nsDOMWorkerScriptLoader::SuspendWorkerEvents()
+{
+  NS_ASSERTION(mWorker, "No worker yet!");
+  mWorker->SuspendTimeouts();
+}
+
+void
+nsDOMWorkerScriptLoader::ResumeWorkerEvents()
+{
+  NS_ASSERTION(mWorker, "No worker yet!");
+  mWorker->ResumeTimeouts();
+}
+
+nsDOMWorkerScriptLoader::
+ScriptLoaderRunnable::ScriptLoaderRunnable(nsDOMWorkerScriptLoader* aLoader)
+: mRevoked(PR_FALSE),
+  mLoader(aLoader)
+{
+  nsAutoLock lock(aLoader->Lock());
+#ifdef DEBUG
+  nsDOMWorkerScriptLoader::ScriptLoaderRunnable** added =
+#endif
+  aLoader->mPendingRunnables.AppendElement(this);
+  NS_ASSERTION(added, "This shouldn't fail because we SetCapacity earlier!");
+}
+
+nsDOMWorkerScriptLoader::
+ScriptLoaderRunnable::~ScriptLoaderRunnable()
+{
+  if (!mRevoked) {
+    nsAutoLock lock(mLoader->Lock());
+#ifdef DEBUG
+    PRBool removed =
+#endif
+    mLoader->mPendingRunnables.RemoveElement(this);
+    NS_ASSERTION(removed, "Someone has changed the array!");
+  }
+}
+
+void
+nsDOMWorkerScriptLoader::ScriptLoaderRunnable::Revoke()
+{
+  mRevoked = PR_TRUE;
+}
+
+nsDOMWorkerScriptLoader::
+ScriptCompiler::ScriptCompiler(nsDOMWorkerScriptLoader* aLoader,
+                               JSContext* aCx,
+                               const nsString& aScriptText,
+                               const nsCString& aFilename,
+                               nsAutoJSObjectHolder& aScriptObj)
+: ScriptLoaderRunnable(aLoader),
+  mCx(aCx),
+  mScriptText(aScriptText),
+  mFilename(aFilename),
+  mScriptObj(aScriptObj)
+{
+  NS_ASSERTION(aCx, "Null context!");
+  NS_ASSERTION(!aScriptText.IsEmpty(), "No script to compile!");
+  NS_ASSERTION(aScriptObj.IsHeld(), "Should be held!");
+}
+
+NS_IMETHODIMP
+nsDOMWorkerScriptLoader::ScriptCompiler::Run()
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  if (mRevoked) {
+    return NS_OK;
+  }
+
+  NS_ASSERTION(!mScriptObj, "Already have a script object?!");
+  NS_ASSERTION(mScriptObj.IsHeld(), "Not held?!");
+  NS_ASSERTION(!mScriptText.IsEmpty(), "Shouldn't have empty source here!");
+
+  JSAutoRequest ar(mCx);
+
+  JSObject* global = JS_GetGlobalObject(mCx);
+  NS_ENSURE_STATE(global);
+
+  // Because we may have nested calls to this function we don't want the
+  // execution to automatically report errors. We let them propagate instead.
+  uint32 oldOpts =
+    JS_SetOptions(mCx, JS_GetOptions(mCx) | JSOPTION_DONT_REPORT_UNCAUGHT);
+
+  JSScript* script = JS_CompileUCScript(mCx, global, mScriptText.BeginReading(),
+                                        mScriptText.Length(), mFilename.get(),
+                                        1);
+  JS_SetOptions(mCx, oldOpts);
+
+  if (!script) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mScriptObj = JS_NewScriptObject(mCx, script);
+  NS_ENSURE_STATE(mScriptObj);
+
+  return NS_OK;
+}
+
+nsDOMWorkerScriptLoader::
+ScriptLoaderDone::ScriptLoaderDone(nsDOMWorkerScriptLoader* aLoader,
+                                   volatile PRBool* aDoneFlag)
+: ScriptLoaderRunnable(aLoader),
+  mDoneFlag(aDoneFlag)
+{
+  NS_ASSERTION(aDoneFlag && !*aDoneFlag, "Bad setup!");
+}
+
+NS_IMETHODIMP
+nsDOMWorkerScriptLoader::ScriptLoaderDone::Run()
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  if (mRevoked) {
+    return NS_OK;
+  }
+
+  *mDoneFlag = PR_TRUE;
+  return NS_OK;
+}
+
+nsDOMWorkerScriptLoader::
+AutoSuspendWorkerEvents::AutoSuspendWorkerEvents(nsDOMWorkerScriptLoader* aLoader)
+: mLoader(aLoader)
+{
+  NS_ASSERTION(aLoader, "Don't hand me null!");
+  aLoader->SuspendWorkerEvents();
+}
+
+nsDOMWorkerScriptLoader::
+AutoSuspendWorkerEvents::~AutoSuspendWorkerEvents()
+{
+  mLoader->ResumeWorkerEvents();
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerScriptLoader.h
@@ -0,0 +1,224 @@
+/* -*- 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 __NSDOMWORKERSCRIPTLOADER_H__
+#define __NSDOMWORKERSCRIPTLOADER_H__
+
+// Bases
+#include "nsThreadUtils.h"
+#include "nsIStreamLoader.h"
+
+// Interfaces
+#include "nsIChannel.h"
+#include "nsIURI.h"
+
+// Other includes
+#include "jsapi.h"
+#include "nsAutoPtr.h"
+#include "nsAutoJSObjectHolder.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsTArray.h"
+#include "prlock.h"
+
+// DOMWorker includes
+#include "nsDOMWorkerThread.h"
+
+/**
+ * This class takes a list of script URLs, downloads the scripts, compiles the
+ * scripts, and then finally executes them. Due to platform limitations all
+ * network operations must happen on the main thread so this object sends events
+ * back and forth from the worker thread to the main thread. The flow goes like
+ * this:
+ *
+ *  1. (Worker thread) nsDOMWorkerScriptLoader created.
+ *  2. (Worker thread) LoadScript(s) called. Some simple argument validation is
+ *                     performed (currently limited to ensuring that all
+ *                     arguments are strings). nsDOMWorkerScriptLoader is then
+ *                     dispatched to the main thread.
+ *  3. (Main thread)   Arguments validated as URIs, security checks performed,
+ *                     content policy consulted. Network loads begin.
+ *  4. (Necko thread)  Necko stuff!
+ *  5. (Main thread)   Completed downloads are packaged in a ScriptCompiler
+ *                     runnable and sent to the worker thread.
+ *  6. (Worker thread) ScriptCompiler runnables are processed (i.e. their
+ *                     scripts are compiled) in the order in which the necko
+ *                     downloads completed.
+ *  7. (Worker thread) After all loads complete and all compilation succeeds
+ *                     the scripts are executed in the order that the URLs were
+ *                     given to LoadScript(s).
+ *
+ * Currently if *anything* after 2 fails then we cancel any pending loads and
+ * bail out entirely.
+ */
+class nsDOMWorkerScriptLoader : public nsRunnable,
+                                public nsIStreamLoaderObserver
+{
+  friend class AutoSuspendWorkerEvents;
+  friend class nsDOMWorkerFunctions;
+  friend class nsDOMWorkerThread;
+  friend class ScriptLoaderRunnable;
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIRUNNABLE
+  NS_DECL_NSISTREAMLOADEROBSERVER
+
+  nsDOMWorkerScriptLoader();
+
+  nsresult LoadScripts(nsDOMWorkerThread* aWorker,
+                       JSContext* aCx,
+                       const nsTArray<nsString>& aURLs);
+
+  nsresult LoadScript(nsDOMWorkerThread* aWorker,
+                       JSContext* aCx,
+                       const nsString& aURL);
+
+  void Cancel();
+
+private:
+  ~nsDOMWorkerScriptLoader();
+
+  nsresult DoRunLoop();
+  nsresult VerifyScripts();
+  nsresult ExecuteScripts();
+
+  nsresult RunInternal();
+
+  nsresult OnStreamCompleteInternal(nsIStreamLoader* aLoader,
+                                    nsISupports* aContext,
+                                    nsresult aStatus,
+                                    PRUint32 aStringLen,
+                                    const PRUint8* aString);
+
+  void NotifyDone();
+
+  void SuspendWorkerEvents();
+  void ResumeWorkerEvents();
+
+  PRLock* Lock() {
+    return mWorker->Lock();
+  }
+
+  class ScriptLoaderRunnable : public nsRunnable
+  {
+  protected:
+    // Meant to be inherited.
+    ScriptLoaderRunnable(nsDOMWorkerScriptLoader* aLoader);
+    virtual ~ScriptLoaderRunnable();
+
+  public:
+    void Revoke();
+
+  protected:
+    PRBool mRevoked;
+
+  private:
+    nsDOMWorkerScriptLoader* mLoader;
+  };
+
+  class ScriptCompiler : public ScriptLoaderRunnable
+  {
+  public:
+    NS_DECL_NSIRUNNABLE
+
+    ScriptCompiler(nsDOMWorkerScriptLoader* aLoader,
+                   JSContext* aCx,
+                   const nsString& aScriptText,
+                   const nsCString& aFilename,
+                   nsAutoJSObjectHolder& aScriptObj);
+
+  private:
+    JSContext* mCx;
+    nsString mScriptText;
+    nsCString mFilename;
+    nsAutoJSObjectHolder& mScriptObj;
+  };
+
+  class ScriptLoaderDone : public ScriptLoaderRunnable
+  {
+  public:
+    NS_DECL_NSIRUNNABLE
+
+    ScriptLoaderDone(nsDOMWorkerScriptLoader* aLoader,
+                     volatile PRBool* aDoneFlag);
+
+  private:
+    volatile PRBool* mDoneFlag;
+  };
+
+  class AutoSuspendWorkerEvents
+  {
+  public:
+    AutoSuspendWorkerEvents(nsDOMWorkerScriptLoader* aLoader);
+    ~AutoSuspendWorkerEvents();
+
+  private:
+    nsDOMWorkerScriptLoader* mLoader;
+  };
+
+  struct ScriptLoadInfo
+  {
+    ScriptLoadInfo() : done(PR_FALSE), result(NS_ERROR_NOT_INITIALIZED) { }
+
+    nsString url;
+    nsString scriptText;
+    PRBool done;
+    nsresult result;
+    nsCOMPtr<nsIURI> finalURI;
+    nsCOMPtr<nsIChannel> channel;
+    nsAutoJSObjectHolder scriptObj;
+  };
+
+  nsDOMWorkerThread* mWorker;
+  nsIThread* mTarget;
+  JSContext* mCx;
+
+  nsRefPtr<ScriptLoaderDone> mDoneRunnable;
+
+  PRUint32 mScriptCount;
+  nsTArray<ScriptLoadInfo> mLoadInfos;
+
+  PRPackedBool mCanceled;
+  PRPackedBool mTrackedByWorker;
+
+  // Protected by mWorker's lock!
+  nsTArray<ScriptLoaderRunnable*> mPendingRunnables;
+};
+
+#endif /* __NSDOMWORKERSCRIPTLOADER_H__ */
--- a/dom/src/threads/nsDOMWorkerSecurityManager.cpp
+++ b/dom/src/threads/nsDOMWorkerSecurityManager.cpp
@@ -83,8 +83,18 @@ nsDOMWorkerSecurityManager::CanAccess(PR
                                       JSObject* aJSObject,
                                       nsISupports* aObj,
                                       nsIClassInfo* aClassInfo,
                                       jsval aName,
                                       void** aPolicy)
 {
   return NS_OK;
 }
+
+JSBool
+nsDOMWorkerSecurityManager::JSCheckAccess(JSContext *cx,
+                                          JSObject *obj,
+                                          jsval id,
+                                          JSAccessMode mode,
+                                          jsval *vp)
+{
+  return JS_TRUE;
+}
--- a/dom/src/threads/nsDOMWorkerSecurityManager.h
+++ b/dom/src/threads/nsDOMWorkerSecurityManager.h
@@ -35,17 +35,22 @@
  * 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"
+#include "jsapi.h"
 
 class nsDOMWorkerSecurityManager : public nsIXPCSecurityManager
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIXPCSECURITYMANAGER
+
+  static JSBool JSCheckAccess(JSContext *cx, JSObject *obj, jsval id,
+                              JSAccessMode mode, jsval *vp);
+
 };
 
 #endif /* __NSDOMWORKERSECURITYMANAGER_H__ */
--- a/dom/src/threads/nsDOMWorkerThread.cpp
+++ b/dom/src/threads/nsDOMWorkerThread.cpp
@@ -49,19 +49,21 @@
 // Other includes
 #ifdef MOZ_SHARK
 #include "jsdbgapi.h"
 #endif
 #include "nsAutoLock.h"
 #include "nsContentUtils.h"
 #include "nsJSUtils.h"
 #include "nsJSEnvironment.h"
+#include "nsThreadUtils.h"
 
 // DOMWorker includes
 #include "nsDOMWorkerPool.h"
+#include "nsDOMWorkerScriptLoader.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
 {
@@ -89,16 +91,19 @@ public:
                             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);
 
+  static JSBool LoadScripts(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
 nsDOMWorkerFunctions::Dump(JSContext* aCx,
@@ -154,17 +159,21 @@ nsDOMWorkerFunctions::PostMessage(JSCont
 
   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);
+
+  if (NS_FAILED(rv)) {
+    JS_ReportError(aCx, "Failed to post message!");
+    return JS_FALSE;
+  }
 
   return JS_TRUE;
 }
 
 JSBool
 nsDOMWorkerFunctions::MakeTimeout(JSContext* aCx,
                                   JSObject* /* aObj */,
                                   uintN aArgc,
@@ -179,20 +188,26 @@ nsDOMWorkerFunctions::MakeTimeout(JSCont
   if (worker->IsCanceled()) {
     return JS_FALSE;
   }
 
   PRUint32 id = ++worker->mNextTimeoutId;
 
   nsAutoPtr<nsDOMWorkerTimeout>
     timeout(new nsDOMWorkerTimeout(worker, id));
-  NS_ENSURE_TRUE(timeout, JS_FALSE);
+  if (!timeout) {
+    JS_ReportOutOfMemory(aCx);
+    return JS_FALSE;
+  }
 
   nsresult rv = timeout->Init(aCx, aArgc, aArgv, aIsInterval);
-  NS_ENSURE_SUCCESS(rv, JS_FALSE);
+  if (NS_FAILED(rv)) {
+    JS_ReportError(aCx, "Failed to initialize timeout!");
+    return JS_FALSE;
+  }
 
   timeout.forget();
 
   *aRval = INT_TO_JSVAL(id);
   return JS_TRUE;
 }
 
 JSBool
@@ -221,24 +236,86 @@ nsDOMWorkerFunctions::KillTimeout(JSCont
     JS_ReportError(aCx, "First argument must be a timeout id");
     return JS_FALSE;
   }
 
   worker->CancelTimeout(PRUint32(id));
   return JS_TRUE;
 }
 
+JSBool JS_DLL_CALLBACK
+nsDOMWorkerFunctions::LoadScripts(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;
+  }
+
+  if (!aArgc) {
+    JS_ReportError(aCx, "Function must have at least one argument!");
+    return JS_FALSE;
+  }
+
+  nsAutoTArray<nsString, 5> urls;
+
+  if (!urls.SetCapacity((PRUint32)aArgc)) {
+    JS_ReportOutOfMemory(aCx);
+    return JS_FALSE;
+  }
+
+  for (uintN index = 0; index < aArgc; index++) {
+    jsval val = aArgv[index];
+
+    if (!JSVAL_IS_STRING(val)) {
+      JS_ReportError(aCx, "Argument %d must be a string", index);
+      return JS_FALSE;
+    }
+
+    JSString* str = JS_ValueToString(aCx, val);
+    if (!str) {
+      JS_ReportError(aCx, "Couldn't convert argument %d to a string", index);
+      return JS_FALSE;
+    }
+
+    nsString* newURL = urls.AppendElement();
+    NS_ASSERTION(newURL, "Shouldn't fail if SetCapacity succeeded above!");
+
+    newURL->Assign(nsDependentJSString(str));
+  }
+
+  nsRefPtr<nsDOMWorkerScriptLoader> loader = new nsDOMWorkerScriptLoader();
+  if (!loader) {
+    JS_ReportOutOfMemory(aCx);
+    return JS_FALSE;
+  }
+
+  nsresult rv = loader->LoadScripts(worker, aCx, urls);
+  if (NS_FAILED(rv)) {
+    return JS_FALSE;
+  }
+
+  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 },
+  { "loadScripts",           nsDOMWorkerFunctions::LoadScripts,       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 }
 };
@@ -301,53 +378,49 @@ nsDOMWorkerThreadContext::GetThisThread(
     NS_ENSURE_TRUE(mWeakRef, NS_ERROR_OUT_OF_MEMORY);
   }
 
   NS_ADDREF(*aThisThread = mWeakRef);
   return NS_OK;
 }
 
 nsDOMWorkerThread::nsDOMWorkerThread(nsDOMWorkerPool* aPool,
-                                     const nsAString& aSource)
+                                     const nsAString& aSource,
+                                     PRBool aSourceIsURL)
 : 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!");
+
+  if (aSourceIsURL) {
+    mSourceURL.Assign(aSource);
+    NS_ASSERTION(!mSourceURL.IsEmpty(), "Empty source url!");
+  }
+  else {
+    mSource.Assign(aSource);
+    NS_ASSERTION(!mSource.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)
@@ -357,23 +430,30 @@ NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerThr
 nsresult
 nsDOMWorkerThread::Init()
 {
   mLock = nsAutoLock::NewLock("nsDOMWorkerThread::mLock");
   NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
 
   NS_ASSERTION(!mGlobal, "Already got a global?!");
 
+  JSRuntime* rt;
+  nsresult rv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRBool success = mGlobal.Hold(rt);
+  NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
   // 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);
+  rv = nsDOMThreadService::get()->Dispatch(this, runnable);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 // From nsDOMWorkerBase
 nsresult
 nsDOMWorkerThread::HandleMessage(const nsAString& aMessage,
@@ -440,25 +520,21 @@ nsDOMWorkerThread::HandleMessage(const n
   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) {
+  if (!success && JS_IsExceptionPending(cx)) {
     // 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);
@@ -467,16 +543,19 @@ nsDOMWorkerThread::DispatchMessage(nsIRu
   return NS_OK;
 }
 
 void
 nsDOMWorkerThread::Cancel()
 {
   nsDOMWorkerBase::Cancel();
 
+  // Do this before waiting on the thread service below!
+  CancelScriptLoaders();
+
   // 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();
 }
@@ -494,17 +573,19 @@ nsDOMWorkerThread::Resume()
   nsDOMWorkerBase::Resume();
   ResumeTimeouts();
 }
 
 PRBool
 nsDOMWorkerThread::SetGlobalForContext(JSContext* aCx)
 {
   PRBool success = CompileGlobalObject(aCx);
-  NS_ENSURE_TRUE(success, PR_FALSE);
+  if (!success) {
+    return PR_FALSE;
+  }
 
   JS_SetGlobalObject(aCx, mGlobal);
   return PR_TRUE;
 }
 
 PRBool
 nsDOMWorkerThread::CompileGlobalObject(JSContext* aCx)
 {
@@ -558,42 +639,48 @@ nsDOMWorkerThread::CompileGlobalObject(J
   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,
-                                        reinterpret_cast<const jschar*>
-                                            (mSource.BeginReading()),
-                                        mSource.Length(), nsnull, 1);
-  NS_ENSURE_TRUE(script, PR_FALSE);
+  jsval val;
 
-  JSRuntime* rt;
-  rv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt);
-  NS_ENSURE_SUCCESS(rv, PR_FALSE);
-
+  // From here on out we have to remember to null mGlobal if something fails!
   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;
-  }
+
+  if (mSource.IsEmpty()) {
+    NS_ASSERTION(!mSourceURL.IsEmpty(), "Must have a url here!");
+
+    nsRefPtr<nsDOMWorkerScriptLoader> loader = new nsDOMWorkerScriptLoader();
+    NS_ASSERTION(loader, "Out of memory!");
+    if (!loader) {
+      mGlobal = NULL;
+      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;
+    rv = loader->LoadScript(this, aCx, mSourceURL);
+    JS_ReportPendingException(aCx);
+    if (NS_FAILED(rv)) {
+      mGlobal = NULL;
+      return PR_FALSE;
+    }
+  }
+  else {
+    NS_ASSERTION(!mSource.IsEmpty(), "No source text!");
+
+    // Evaluate and execute the script
+    success = JS_EvaluateUCScript(aCx, global, mSource.get(), mSource.Length(),
+                                  "DOMWorker inline script", 1, &val);
+    if (!success) {
+      mGlobal = NULL;
+      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),
@@ -740,15 +827,35 @@ nsDOMWorkerThread::ResumeTimeouts()
   PRTime now = PR_Now();
 
   PRUint32 count = timeouts.Length();
   for (PRUint32 i = 0; i < count; i++) {
     timeouts[i]->Resume(now);
   }
 }
 
+void
+nsDOMWorkerThread::CancelScriptLoaders()
+{
+  nsAutoTArray<nsDOMWorkerScriptLoader*, 20> loaders;
+
+  // Must call cancel on the loaders outside the lock!
+  {
+    nsAutoLock lock(mLock);
+    loaders.AppendElements(mScriptLoaders);
+
+    // Don't clear mScriptLoaders, they'll remove themselves as they get
+    // destroyed.
+  }
+
+  PRUint32 loaderCount = loaders.Length();
+  for (PRUint32 index = 0; index < loaderCount; index++) {
+    loaders[index]->Cancel();
+  }
+}
+
 NS_IMETHODIMP
 nsDOMWorkerThread::PostMessage(const nsAString& aMessage)
 {
   nsresult rv = PostMessageInternal(aMessage);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
--- a/dom/src/threads/nsDOMWorkerThread.h
+++ b/dom/src/threads/nsDOMWorkerThread.h
@@ -42,18 +42,20 @@
 
 // Bases
 #include "nsDOMWorkerBase.h"
 #include "nsIClassInfo.h"
 #include "nsIDOMThreads.h"
 
 // Other includes
 #include "jsapi.h"
+#include "nsAutoJSObjectHolder.h"
 #include "nsCOMPtr.h"
 #include "nsStringGlue.h"
+#include "nsTArray.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 
@@ -118,27 +120,29 @@ class nsDOMWorkerTimeout;
 class nsDOMWorkerThread : public nsDOMWorkerBase,
                           public nsIDOMWorkerThread,
                           public nsIClassInfo
 {
   friend class nsDOMCreateJSContextRunnable;
   friend class nsDOMWorkerFunctions;
   friend class nsDOMWorkerPool;
   friend class nsDOMWorkerRunnable;
+  friend class nsDOMWorkerScriptLoader;
   friend class nsDOMWorkerTimeout;
 
   friend JSBool DOMWorkerOperationCallback(JSContext* aCx);
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMWORKERTHREAD
   NS_DECL_NSICLASSINFO
 
   nsDOMWorkerThread(nsDOMWorkerPool* aPool,
-                    const nsAString& aSource);
+                    const nsAString& aSource,
+                    PRBool aSourceIsURL);
 
   virtual nsDOMWorkerPool* Pool() {
     NS_ASSERTION(!IsCanceled(), "Don't touch Pool after we've been canceled!");
     return mPool;
   }
 
 private:
   virtual ~nsDOMWorkerThread();
@@ -164,23 +168,32 @@ private:
 
   void AddTimeout(nsDOMWorkerTimeout* aTimeout);
   void RemoveTimeout(nsDOMWorkerTimeout* aTimeout);
   void ClearTimeouts();
   void CancelTimeout(PRUint32 aId);
   void SuspendTimeouts();
   void ResumeTimeouts();
 
+  void CancelScriptLoaders();
+
+  PRLock* Lock() {
+    return mLock;
+  }
+
   nsDOMWorkerPool* mPool;
   nsString mSource;
+  nsString mSourceURL;
 
-  JSObject* mGlobal;
+  nsAutoJSObjectHolder mGlobal;
   PRBool mCompiled;
 
   PRUint32 mCallbackCount;
 
   PRUint32 mNextTimeoutId;
 
   PRLock* mLock;
   PRCList mTimeouts;
+
+  nsTArray<nsDOMWorkerScriptLoader*> mScriptLoaders;
 };
 
 #endif /* __NSDOMWORKERTHREAD_H__ */
--- a/dom/src/threads/test/Makefile.in
+++ b/dom/src/threads/test/Makefile.in
@@ -42,16 +42,22 @@ srcdir           = @srcdir@
 VPATH            = @srcdir@
 
 relativesrcdir   = dom/src/threads/tests
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
+  importScripts_worker.js \
+  importScripts_worker_imported1.js \
+  importScripts_worker_imported2.js \
+  importScripts_worker_imported3.js \
+  importScripts_worker_imported4.js \
+  test_importScripts.html \
   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/importScripts_worker.js
@@ -0,0 +1,54 @@
+function messageListener(message, source) {
+  switch (message) {
+    case 'start':
+      loadScripts("importScripts_worker_imported2.js");
+      importedScriptFunction2();
+      tryBadScripts();
+      source.postMessage('started');
+      break;
+    case 'stop':
+      tryBadScripts();
+      postMessageToPool('stopped');
+      break;
+    default:
+      throw new Error("Bad message: " + message);
+      break;
+  }
+}
+
+// This caused security exceptions in the past, make sure it doesn't!
+var constructor = {}.constructor;
+
+loadScripts("importScripts_worker_imported1.js");
+
+// Try to call a function defined in the imported script.
+importedScriptFunction();
+
+function tryBadScripts() {
+  var badScripts = [
+    // Has a syntax error
+    "importScripts_worker_imported3.js",
+    // Throws an exception
+    "importScripts_worker_imported4.js",
+    // Shouldn't exist!
+    "http://flippety.com/floppety/foo.js",
+    // Not a valid url
+    "http://flippety::foo_js ftw"
+  ];
+
+  for (var i = 0; i < badScripts.length; i++) {
+    var caughtException = false;
+    var url = badScripts[i];
+    try {
+      loadScripts(url);
+    }
+    catch (e) {
+      caughtException = true;
+    }
+    if (!caughtException) {
+      throw "Bad script didn't throw exception: " + url;
+    }
+  }
+}
+
+tryBadScripts();
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/importScripts_worker_imported1.js
@@ -0,0 +1,7 @@
+// This caused security exceptions in the past, make sure it doesn't!
+var myConstructor = {}.constructor;
+
+// Try to call a function defined in the imported script.
+function importedScriptFunction() {
+  dump("running importedScriptFunction\n");
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/importScripts_worker_imported2.js
@@ -0,0 +1,7 @@
+// This caused security exceptions in the past, make sure it doesn't!
+var myConstructor2 = {}.constructor;
+
+// Try to call a function defined in the imported script.
+function importedScriptFunction2() {
+  dump("running importedScriptFunction2\n");
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/importScripts_worker_imported3.js
@@ -0,0 +1,2 @@
+// Deliberate syntax error, should generate a worker exception!
+for (var index = 0; index < 100) {}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/importScripts_worker_imported4.js
@@ -0,0 +1,2 @@
+// Deliberate throw, should generate a worker exception!
+throw new Error("Bah!");
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_importScripts.html
@@ -0,0 +1,49 @@
+<!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">
+
+  var pool = navigator.newWorkerPool();
+  pool.messageListener = function(message, source) {
+    switch (message) {
+      case "started":
+        source.postMessage("stop");
+        break;
+      case "stopped":
+        SimpleTest.finish();
+        break;
+      default:
+        ok(false, "Unexpected message:" + message);
+        SimpleTest.finish();
+    }
+  };
+
+  pool.errorListener = function(error, source) {
+    ok(false, "Worker had an error:" + error);
+    SimpleTest.finish();
+  }
+
+  var worker = pool.createWorkerFromURL("importScripts_worker.js");
+  worker.postMessage("start");
+
+  SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+