Bug 466257 - 'Workers: Errors in sub workers aren't propagated to top-most parents like they should.' r+sr=sicking, a=blocking1.9.1+.
authorBen Turner <bent.mozilla@gmail.com>
Thu, 27 Nov 2008 01:16:41 -0500
changeset 22040 c78bf8d3cb06b1278e6a4f46ee898df9673b82b3
parent 22039 2f6b84f2e48c6bef1834ffb9b40c0e6bcf0471f6
child 22041 d50fb8c0d589999bbc8560223457cb37723cf9e0
push idunknown
push userunknown
push dateunknown
reviewersblocking1.9.1
bugs466257
milestone1.9.1b3pre
Bug 466257 - 'Workers: Errors in sub workers aren't propagated to top-most parents like they should.' r+sr=sicking, a=blocking1.9.1+.
dom/src/threads/nsDOMThreadService.cpp
dom/src/threads/nsDOMThreadService.h
dom/src/threads/nsDOMWorker.h
dom/src/threads/nsDOMWorkerEvents.cpp
dom/src/threads/nsDOMWorkerEvents.h
dom/src/threads/nsDOMWorkerMacros.h
dom/src/threads/test/Makefile.in
dom/src/threads/test/errorPropagation_worker1.js
dom/src/threads/test/errorPropagation_worker2.js
dom/src/threads/test/test_errorPropagation.html
--- a/dom/src/threads/nsDOMThreadService.cpp
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -36,16 +36,17 @@
  * 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 "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMNavigator.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsIEventTarget.h"
 #include "nsIGenericFactory.h"
 #include "nsIJSContextStack.h"
 #include "nsIJSRuntimeService.h"
@@ -154,31 +155,106 @@ private:
 /**
  * This class is used as to post an error to the worker's outer handler.
  */
 class nsReportErrorRunnable : public nsIRunnable
 {
 public:
   NS_DECL_ISUPPORTS
 
-  nsReportErrorRunnable(nsDOMWorker* aWorker, nsIWorkerMessageEvent* aEvent)
-  : mWorker(aWorker), mWorkerWN(aWorker->GetWrappedNative()), mEvent(aEvent) { }
+  nsReportErrorRunnable(nsDOMWorker* aWorker, nsIScriptError* aScriptError)
+  : mWorker(aWorker), mWorkerWN(aWorker->GetWrappedNative()),
+    mScriptError(aScriptError) {
+      NS_ASSERTION(aScriptError, "Null pointer!");
+    }
 
   NS_IMETHOD Run() {
     if (mWorker->IsCanceled()) {
       return NS_OK;
     }
 
-    return mWorker->DispatchEvent(mEvent, nsnull);
+#ifdef DEBUG
+    {
+      nsRefPtr<nsDOMWorker> parent = mWorker->GetParent();
+      if (NS_IsMainThread()) {
+        NS_ASSERTION(!parent, "Shouldn't have a parent on the main thread!");
+      }
+      else {
+        NS_ASSERTION(parent, "Should have a parent!");
+
+        JSContext* cx = nsDOMThreadService::get()->GetCurrentContext();
+        NS_ASSERTION(cx, "No context!");
+
+        nsDOMWorker* currentWorker = (nsDOMWorker*)JS_GetContextPrivate(cx);
+        NS_ASSERTION(currentWorker == parent, "Wrong worker!");
+      }
+    }
+#endif
+
+    NS_NAMED_LITERAL_STRING(errorStr, "error");
+
+    PRBool hasListener = PR_FALSE, stopPropagation = PR_FALSE;
+    nsresult rv = NS_OK;
+
+    if (mWorker->mOuterHandler->HasListeners(errorStr)) {
+      hasListener = PR_TRUE;
+      nsRefPtr<nsDOMWorkerMessageEvent> event(new nsDOMWorkerMessageEvent());
+      if (event) {
+        nsCString errorMessage;
+        rv = mScriptError->ToString(errorMessage);
+        if (NS_SUCCEEDED(rv)) {
+          rv = event->InitMessageEvent(errorStr, PR_FALSE, PR_FALSE,
+                                       NS_ConvertUTF8toUTF16(errorMessage),
+                                       EmptyString(), nsnull);
+          if (NS_SUCCEEDED(rv)) {
+            event->SetTarget(mWorker);
+            rv = mWorker->DispatchEvent(static_cast<nsDOMWorkerEvent*>(event),
+                                        &stopPropagation);
+            if (NS_FAILED(rv)) {
+              stopPropagation = PR_FALSE;
+            }
+          }
+        }
+      }
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (stopPropagation) {
+      return NS_OK;
+    }
+
+    nsRefPtr<nsDOMWorker> parent = mWorker->GetParent();
+    if (!parent) {
+      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+      nsCOMPtr<nsIConsoleService> consoleService =
+        do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+      if (consoleService) {
+        rv = consoleService->LogMessage(mScriptError);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+      return NS_OK;
+    }
+
+    nsRefPtr<nsReportErrorRunnable> runnable =
+      new nsReportErrorRunnable(parent, mScriptError);
+    if (runnable) {
+      nsRefPtr<nsDOMWorker> grandparent = parent->GetParent();
+      rv = grandparent ?
+           nsDOMThreadService::get()->Dispatch(grandparent, runnable) :
+           NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    return NS_OK;
   }
 
 private:
   nsRefPtr<nsDOMWorker> mWorker;
   nsCOMPtr<nsIXPConnectWrappedNative> mWorkerWN;
-  nsCOMPtr<nsIWorkerMessageEvent> mEvent;
+  nsCOMPtr<nsIScriptError> mScriptError;
 };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsReportErrorRunnable, nsIRunnable)
 
 /**
  * Used to post an expired timeout to the correct worker.
  */
 class nsDOMWorkerTimeoutRunnable : public nsRunnable
@@ -404,50 +480,37 @@ DOMWorkerErrorReporter(JSContext* aCx,
   if (worker->IsCanceled()) {
     // We don't want to report errors from canceled workers. It's very likely
     // that we only returned an error in the first place because the worker was
     // already canceled.
     return;
   }
 
   nsresult rv;
-  nsCOMPtr<nsIScriptError> errorObject =
+  nsCOMPtr<nsIScriptError> scriptError =
     do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv,);
 
   const PRUnichar* message =
     reinterpret_cast<const PRUnichar*>(aReport->ucmessage);
 
   nsAutoString filename;
   filename.AssignWithConversion(aReport->filename);
 
   const PRUnichar* line =
     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,);
-
-  nsCString finalMessage;
-  rv = errorObject->ToString(finalMessage);
+  rv = scriptError->Init(message, filename.get(), line, aReport->lineno,
+                         column, aReport->flags, "DOM Worker javascript");
   NS_ENSURE_SUCCESS(rv,);
 
-  nsRefPtr<nsDOMWorkerMessageEvent> event(new nsDOMWorkerMessageEvent());
-  NS_ENSURE_TRUE(event,);
-
-  rv = event->InitMessageEvent(NS_LITERAL_STRING("error"), PR_FALSE, PR_FALSE,
-                               NS_ConvertUTF8toUTF16(finalMessage),
-                               EmptyString(), nsnull);
-  NS_ENSURE_SUCCESS(rv,);
-
-  event->SetTarget(worker);
-
-  nsCOMPtr<nsIRunnable> runnable(new nsReportErrorRunnable(worker, event));
+  nsCOMPtr<nsIRunnable> runnable =
+    new nsReportErrorRunnable(worker, scriptError);
   NS_ENSURE_TRUE(runnable,);
 
   nsRefPtr<nsDOMWorker> parent = worker->GetParent();
 
   // If this worker has a parent then we need to send the message through the
   // thread service to be run on the parent's thread. Otherwise it is a
   // top-level worker and we send the message to the main thread.
   rv = parent ? nsDOMThreadService::get()->Dispatch(parent, runnable)
--- a/dom/src/threads/nsDOMThreadService.h
+++ b/dom/src/threads/nsDOMThreadService.h
@@ -77,16 +77,17 @@ class nsDOMThreadService : public nsIEve
   friend class nsDOMWorkerNavigator;
   friend class nsDOMWorkerPool;
   friend class nsDOMWorkerRunnable;
   friend class nsDOMWorkerThread;
   friend class nsDOMWorkerTimeout;
   friend class nsDOMWorkerXHR;
   friend class nsDOMWorkerXHRProxy;
   friend class nsLayoutStatics;
+  friend class nsReportErrorRunnable;
 
   friend void DOMWorkerErrorReporter(JSContext* aCx,
                                      const char* aMessage,
                                      JSErrorReport* aReport);
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIEVENTTARGET
--- a/dom/src/threads/nsDOMWorker.h
+++ b/dom/src/threads/nsDOMWorker.h
@@ -72,16 +72,17 @@ class nsDOMWorker : public nsIWorker,
 {
   friend class nsDOMWorkerFeature;
   friend class nsDOMWorkerFunctions;
   friend class nsDOMWorkerRefPtr;
   friend class nsDOMWorkerScope;
   friend class nsDOMWorkerScriptLoader;
   friend class nsDOMWorkerTimeout;
   friend class nsDOMWorkerXHR;
+  friend class nsReportErrorRunnable;
 
   friend JSBool DOMWorkerOperationCallback(JSContext* aCx);
   friend void DOMWorkerErrorReporter(JSContext* aCx,
                                      const char* aMessage,
                                      JSErrorReport* aReport);
 
 #ifdef DEBUG
   // For fun assertions.
--- a/dom/src/threads/nsDOMWorkerEvents.cpp
+++ b/dom/src/threads/nsDOMWorkerEvents.cpp
@@ -46,29 +46,49 @@
 #include "nsDOMWorkerXHR.h"
 #include "nsDOMWorkerXHRProxy.h"
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIDOMWorkerPrivateEvent,
                               NS_IDOMWORKERPRIVATEEVENT_IID)
 
 nsDOMWorkerPrivateEvent::nsDOMWorkerPrivateEvent(nsIDOMEvent* aEvent)
 : mEvent(aEvent),
+  mProgressEvent(do_QueryInterface(aEvent)),
+  mMessageEvent(do_QueryInterface(aEvent)),
   mPreventDefaultCalled(PR_FALSE)
 {
   NS_ASSERTION(aEvent, "Null pointer!");
 }
 
-NS_IMPL_THREADSAFE_ISUPPORTS3(nsDOMWorkerPrivateEvent, nsIDOMEvent,
-                                                       nsIDOMWorkerPrivateEvent,
-                                                       nsIClassInfo)
+NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerPrivateEvent)
+NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerPrivateEvent)
+
+NS_INTERFACE_MAP_BEGIN(nsDOMWorkerPrivateEvent)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWorkerPrivateEvent)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEvent, nsIDOMWorkerPrivateEvent)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMWorkerPrivateEvent)
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMProgressEvent, mProgressEvent)
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIWorkerMessageEvent, mMessageEvent)
+  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
+NS_INTERFACE_MAP_END
 
-NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerPrivateEvent, nsIDOMEvent,
-                                                      nsIDOMWorkerPrivateEvent)
+NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerPrivateEvent, nsIDOMEvent)
+
+NS_IMPL_THREADSAFE_DOM_CI_HELPER(nsDOMWorkerPrivateEvent)
+NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(nsDOMWorkerPrivateEvent)
 
-NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerPrivateEvent)
+NS_IMETHODIMP
+nsDOMWorkerPrivateEvent::GetInterfaces(PRUint32* aCount, nsIID*** aArray)
+{
+  nsCOMPtr<nsIClassInfo> ci(do_QueryInterface(mEvent));
+  if (ci) {
+    return ci->GetInterfaces(aCount, aArray);
+  }
+  return NS_CI_INTERFACE_GETTER_NAME(nsDOMWorkerPrivateEvent)(aCount, aArray);
+}
 
 NS_IMETHODIMP
 nsDOMWorkerPrivateEvent::PreventDefault()
 {
   mPreventDefaultCalled = PR_TRUE;
   return mEvent->PreventDefault();
 }
 
@@ -76,16 +96,48 @@ NS_IMETHODIMP
 nsDOMWorkerPrivateEvent::InitEvent(const nsAString& aEventType,
                                    PRBool aCanBubble,
                                    PRBool aCancelable)
 {
   mPreventDefaultCalled = PR_FALSE;
   return mEvent->InitEvent(aEventType, aCanBubble, aCancelable);
 }
 
+NS_IMETHODIMP
+nsDOMWorkerPrivateEvent::InitProgressEvent(const nsAString& aTypeArg,
+                                           PRBool aCanBubbleArg,
+                                           PRBool aCancelableArg,
+                                           PRBool aLengthComputableArg,
+                                           PRUint64 aLoadedArg,
+                                           PRUint64 aTotalArg)
+{
+  NS_ASSERTION(mProgressEvent, "Impossible!");
+
+  mPreventDefaultCalled = PR_FALSE;
+  return mProgressEvent->InitProgressEvent(aTypeArg, aCanBubbleArg,
+                                           aCancelableArg, aLengthComputableArg,
+                                           aLoadedArg, aTotalArg);
+}
+
+NS_IMETHODIMP
+nsDOMWorkerPrivateEvent::InitMessageEvent(const nsAString& aTypeArg,
+                                          PRBool aCanBubbleArg,
+                                          PRBool aCancelableArg,
+                                          const nsAString& aDataArg,
+                                          const nsAString& aOriginArg,
+                                          nsISupports* aSourceArg)
+{
+  NS_ASSERTION(mMessageEvent, "Impossible!");
+
+  mPreventDefaultCalled = PR_FALSE;
+  return mMessageEvent->InitMessageEvent(aTypeArg, aCanBubbleArg,
+                                         aCancelableArg, aDataArg, aOriginArg,
+                                         aSourceArg);
+}
+
 PRBool
 nsDOMWorkerPrivateEvent::PreventDefaultCalled()
 {
   return mPreventDefaultCalled;
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS2(nsDOMWorkerEvent, nsIDOMEvent,
                                                 nsIClassInfo)
--- a/dom/src/threads/nsDOMWorkerEvents.h
+++ b/dom/src/threads/nsDOMWorkerEvents.h
@@ -67,36 +67,90 @@ class nsIXPConnectWrappedNative;
 
 class nsIDOMWorkerPrivateEvent : public nsIDOMEvent
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOMWORKERPRIVATEEVENT_IID)
   virtual PRBool PreventDefaultCalled() = 0;
 };
 
+#define NS_FORWARD_NSIDOMEVENT_SPECIAL                                        \
+  NS_IMETHOD GetType(nsAString& aType)                                        \
+    { return mEvent->GetType(aType); }                                        \
+  NS_IMETHOD GetTarget(nsIDOMEventTarget** aTarget)                           \
+    { return mEvent->GetTarget(aTarget); }                                    \
+  NS_IMETHOD GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget)             \
+    { return mEvent->GetCurrentTarget(aCurrentTarget); }                      \
+  NS_IMETHOD GetEventPhase(PRUint16* aEventPhase)                             \
+    { return mEvent->GetEventPhase(aEventPhase); }                            \
+  NS_IMETHOD GetBubbles(PRBool* aBubbles)                                     \
+    { return mEvent->GetBubbles(aBubbles); }                                  \
+  NS_IMETHOD GetCancelable(PRBool* aCancelable)                               \
+    { return mEvent->GetCancelable(aCancelable); }                            \
+  NS_IMETHOD GetTimeStamp(DOMTimeStamp* aTimeStamp)                           \
+    { return mEvent->GetTimeStamp(aTimeStamp); }                              \
+  NS_IMETHOD StopPropagation()                                                \
+    { return mEvent->StopPropagation(); }
+
+#define NS_FORWARD_NSIDOMPROGRESSEVENT_SPECIAL                                \
+  NS_IMETHOD GetLengthComputable(PRBool* aLengthComputable)                   \
+    { return mProgressEvent->GetLengthComputable(aLengthComputable); }        \
+  NS_IMETHOD GetLoaded(PRUint64* aLoaded)                                     \
+    { return mProgressEvent->GetLoaded(aLoaded); }                            \
+  NS_IMETHOD GetTotal(PRUint64* aTotal)                                       \
+    { return mProgressEvent->GetTotal(aTotal); }
+
+#define NS_FORWARD_NSIWORKERMESSAGEEVENT_SPECIAL                              \
+  NS_IMETHOD GetData(nsAString& aData)                                        \
+    { return mMessageEvent->GetData(aData); }                                 \
+  NS_IMETHOD GetOrigin(nsAString& aOrigin)                                    \
+    { return mMessageEvent->GetOrigin(aOrigin); }                             \
+  NS_IMETHOD GetSource(nsISupports** aSource)                                 \
+    { return mMessageEvent->GetSource(aSource); }
+
 class nsDOMWorkerPrivateEvent : public nsIDOMWorkerPrivateEvent,
+                                public nsIDOMProgressEvent,
+                                public nsIWorkerMessageEvent,
                                 public nsIClassInfo
 {
 public:
   NS_DECL_ISUPPORTS
   NS_FORWARD_NSIDOMEVENT_SPECIAL
+  NS_FORWARD_NSIWORKERMESSAGEEVENT_SPECIAL
+  NS_FORWARD_NSIDOMPROGRESSEVENT_SPECIAL
   NS_DECL_NSICLASSINFO
 
   nsDOMWorkerPrivateEvent(nsIDOMEvent* aEvent);
 
   NS_IMETHOD PreventDefault();
 
   NS_IMETHOD InitEvent(const nsAString& aEventType,
                        PRBool aCanBubble,
                        PRBool aCancelable);
 
+  NS_IMETHOD InitProgressEvent(const nsAString& aTypeArg,
+                               PRBool aCanBubbleArg,
+                               PRBool aCancelableArg,
+                               PRBool aLengthComputableArg,
+                               PRUint64 aLoadedArg,
+                               PRUint64 aTotalArg); 
+
+  NS_IMETHOD InitMessageEvent(const nsAString& aTypeArg,
+                              PRBool aCanBubbleArg,
+                              PRBool aCancelableArg,
+                              const nsAString& aDataArg,
+                              const nsAString& aOriginArg,
+                              nsISupports* aSourceArg);
+
   virtual PRBool PreventDefaultCalled();
 
 private:
   nsCOMPtr<nsIDOMEvent> mEvent;
+  nsCOMPtr<nsIDOMProgressEvent> mProgressEvent;
+  nsCOMPtr<nsIWorkerMessageEvent> mMessageEvent;
   PRBool mPreventDefaultCalled;
 };
 
 class nsDOMWorkerEvent : public nsIDOMEvent,
                          public nsIClassInfo
 {
 public:
   NS_DECL_ISUPPORTS
--- a/dom/src/threads/nsDOMWorkerMacros.h
+++ b/dom/src/threads/nsDOMWorkerMacros.h
@@ -116,34 +116,16 @@ NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(_
   NS_IMETHOD GetFlags(PRUint32* aFlags)                                       \
     { return _to GetFlags(aFlags); }                                          \
   NS_IMETHOD GetClassIDNoAlloc(nsCID* aClassIDNoAlloc)                        \
     { return _to GetClassIDNoAlloc(aClassIDNoAlloc); }
 
 #define NS_DECL_NSICLASSINFO_GETINTERFACES                                    \
   NS_IMETHOD GetInterfaces(PRUint32* aCount, nsIID*** aArray);
 
-#define NS_FORWARD_NSIDOMEVENT_SPECIAL                                        \
-  NS_IMETHOD GetType(nsAString& aType)                                        \
-    { return mEvent->GetType(aType); }                                        \
-  NS_IMETHOD GetTarget(nsIDOMEventTarget** aTarget)                           \
-    { return mEvent->GetTarget(aTarget); }                                    \
-  NS_IMETHOD GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget)             \
-    { return mEvent->GetCurrentTarget(aCurrentTarget); }                      \
-  NS_IMETHOD GetEventPhase(PRUint16* aEventPhase)                             \
-    { return mEvent->GetEventPhase(aEventPhase); }                            \
-  NS_IMETHOD GetBubbles(PRBool* aBubbles)                                     \
-    { return mEvent->GetBubbles(aBubbles); }                                  \
-  NS_IMETHOD GetCancelable(PRBool* aCancelable)                               \
-    { return mEvent->GetCancelable(aCancelable); }                            \
-  NS_IMETHOD GetTimeStamp(DOMTimeStamp* aTimeStamp)                           \
-    { return mEvent->GetTimeStamp(aTimeStamp); }                              \
-  NS_IMETHOD StopPropagation()                                                \
-    { return mEvent->StopPropagation(); }
-
 // 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(); }
 
--- a/dom/src/threads/test/Makefile.in
+++ b/dom/src/threads/test/Makefile.in
@@ -42,16 +42,19 @@ srcdir           = @srcdir@
 VPATH            = @srcdir@
 
 relativesrcdir   = dom/src/threads/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
+  test_errorPropagation.html \
+  errorPropagation_worker1.js \
+  errorPropagation_worker2.js \
   test_importScripts.html \
   importScripts_worker.js \
   importScripts_worker_imported1.js \
   importScripts_worker_imported2.js \
   importScripts_worker_imported3.js \
   importScripts_worker_imported4.js \
   test_longThread.html \
   longThread_worker.js \
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/errorPropagation_worker1.js
@@ -0,0 +1,26 @@
+var worker = new Worker("errorPropagation_worker2.js");
+
+var errorCount = 0;
+worker.onerror = function(event) {
+  switch (errorCount++) {
+    case 0:
+    case 1:
+      // Let it propagate.
+      break;
+    case 2:
+      // Stop and rethrow.
+      event.preventDefault();
+      throw event.data;
+      break;
+    case 3:
+      event.preventDefault();
+      postMessage(event.data);
+      worker.onerror = null;
+      break;
+    default:
+  }
+};
+
+onmessage = function(event) {
+  worker.postMessage(event.data);
+};
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/errorPropagation_worker2.js
@@ -0,0 +1,3 @@
+onmessage = function(event) {
+  throw event.data;
+};
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_errorPropagation.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads
+-->
+<head>
+  <title>Test for DOM Worker Threads</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+  const Cc = Components.classes;
+  const Ci = Components.interfaces;
+  const Cr = Components.results;
+
+  var consoleService =
+    Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
+
+  var errors = [
+    "This exception should show up in the JS console",
+    "This exception should make it to onerror",
+    "This exception should too",
+    "This exception should should show up in onmessage"
+  ];
+
+  var errorIndex = -1;
+
+  var worker = new Worker("errorPropagation_worker1.js");
+
+  worker.onmessage = function(event) {
+    is(event.target, worker);
+    ok(event.data.indexOf(errors[errorIndex]) != -1,
+       "Wrong message!");
+    ok(errorIndex == errors.length - 1, "Wrong number of errors seen!");
+    SimpleTest.finish();
+  }
+
+  function nextError() {
+    worker.postMessage(errors[++errorIndex]);
+  }
+
+  function errorHandler(event) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+    is(event.target, worker);
+    ok(event.data.indexOf(errors[errorIndex]) != -1,
+       "Wrong message!");
+
+    switch (errorIndex) {
+      case 1:
+        consoleService.unregisterListener(consoleListener);
+        consoleService.reset();
+        consoleListener = null;
+        nextError();
+        break;
+      case 2:
+        nextError();
+        break;
+      default:
+        ok(false, "Too many errors!");
+    }
+  }
+
+  var consoleListener = {
+    count: 0,
+
+    observe: function(message) {
+      netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+      if (this.count++) {
+        ok(false, "Seen too many errors!");
+        SimpleTest.finish();
+        return;
+      }
+
+      ok(message.message.indexOf(errors[errorIndex]) != -1, "Wrong message!");
+      worker.onerror = errorHandler;
+      nextError();
+    },
+
+    QueryInterface: function(iid) {
+      netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+      if (iid.equals(Ci.nsIConsoleListener) ||
+          iid.equals(Ci.nsISupports)) {
+        return this;
+      }
+      throw Cr.NS_NOINTERFACE;
+    }
+  };
+
+  consoleService.reset();
+  consoleService.registerListener(consoleListener);
+
+  nextError();
+
+  SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+