Bug 1092102 - Implement WorkerDebugger.initialize;r=khuey
authorEddy Bruël <ejpbruel@gmail.com>
Tue, 17 Mar 2015 11:15:19 +0100
changeset 251104 757de8e339e038b0ef6faa6239c38fd52313f13a
parent 251103 bb97bc37bf3b70ae3e023578a66c2c33a8398561
child 251105 aede36df2ed98e680d25cd522aed894c4c8a2a92
push id1096
push userbcampen@mozilla.com
push dateTue, 17 Mar 2015 18:28:18 +0000
reviewerskhuey
bugs1092102
milestone39.0a1
Bug 1092102 - Implement WorkerDebugger.initialize;r=khuey
dom/workers/RuntimeService.cpp
dom/workers/ScriptLoader.cpp
dom/workers/ScriptLoader.h
dom/workers/WorkerDebuggerManager.cpp
dom/workers/WorkerDebuggerManager.h
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
dom/workers/nsIWorkerDebugger.idl
dom/workers/test/WorkerDebugger.initialize_childWorker.js
dom/workers/test/WorkerDebugger.initialize_debugger.js
dom/workers/test/WorkerDebugger.initialize_worker.js
dom/workers/test/WorkerDebuggerManager_childWorker.js
dom/workers/test/WorkerDebuggerManager_parentWorker.js
dom/workers/test/WorkerDebuggerManager_worker.js
dom/workers/test/WorkerDebugger_childWorker.js
dom/workers/test/WorkerDebugger_parentWorker.js
dom/workers/test/WorkerDebugger_sharedWorker.js
dom/workers/test/WorkerDebugger_worker.js
dom/workers/test/chrome.ini
dom/workers/test/dom_worker_helper.js
dom/workers/test/test_WorkerDebugger.initialize.xul
dom/workers/test/test_WorkerDebugger.xul
dom/workers/test/test_WorkerDebuggerManager.xul
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -55,16 +55,17 @@
 #include "xpcpublic.h"
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
 #include "Principal.h"
 #include "SharedWorker.h"
+#include "WorkerDebuggerManager.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerThread.h"
 
 #ifdef ENABLE_TESTS
 #include "BackgroundChildImpl.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "prrng.h"
@@ -1913,16 +1914,22 @@ void
 RuntimeService::Shutdown()
 {
   AssertIsOnMainThread();
 
   MOZ_ASSERT(!mShuttingDown);
   // That's it, no more workers.
   mShuttingDown = true;
 
+  // Remove all listeners from the worker debugger manager to ensure that it
+  // gets properly destroyed.
+  if (NS_FAILED(ClearWorkerDebuggerManagerListeners())) {
+    NS_WARNING("Failed to clear worker debugger manager listeners!");
+  }
+
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   NS_WARN_IF_FALSE(obs, "Failed to get observer service?!");
 
   // Tell anyone that cares that they're about to lose worker support.
   if (obs && NS_FAILED(obs->NotifyObservers(nullptr, WORKERS_SHUTDOWN_TOPIC,
                                             nullptr))) {
     NS_WARNING("NotifyObservers failed!");
   }
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -48,17 +48,18 @@ namespace {
 nsresult
 ChannelFromScriptURL(nsIPrincipal* principal,
                      nsIURI* baseURI,
                      nsIDocument* parentDoc,
                      nsILoadGroup* loadGroup,
                      nsIIOService* ios,
                      nsIScriptSecurityManager* secMan,
                      const nsAString& aScriptURL,
-                     bool aIsWorkerScript,
+                     bool aIsMainScript,
+                     WorkerScriptType aWorkerScriptType,
                      nsIChannel** aChannel)
 {
   AssertIsOnMainThread();
 
   nsresult rv;
   nsCOMPtr<nsIURI> uri;
   rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
                                                  aScriptURL, parentDoc,
@@ -79,20 +80,32 @@ ChannelFromScriptURL(nsIPrincipal* princ
     if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
       if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
         return rv = NS_ERROR_CONTENT_BLOCKED;
       }
       return rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
     }
   }
 
-  // If this script loader is being used to make a new worker then we need
-  // to do a same-origin check. Otherwise we need to clear the load with the
-  // security manager.
-  if (aIsWorkerScript) {
+  if (aWorkerScriptType == DebuggerScript) {
+    bool isChrome = false;
+    NS_ENSURE_SUCCESS(uri->SchemeIs("chrome", &isChrome),
+                      NS_ERROR_DOM_SECURITY_ERR);
+
+    bool isResource = false;
+    NS_ENSURE_SUCCESS(uri->SchemeIs("resource", &isResource),
+                      NS_ERROR_DOM_SECURITY_ERR);
+
+    if (!isChrome && !isResource) {
+      return NS_ERROR_DOM_SECURITY_ERR;
+    }
+  } else if (aIsMainScript) {
+    // If this script loader is being used to make a new worker then we need
+    // to do a same-origin check. Otherwise we need to clear the load with the
+    // security manager.
     nsCString scheme;
     rv = uri->GetScheme(scheme);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // We pass true as the 3rd argument to checkMayLoad here.
     // This allows workers in sandboxed documents to load data URLs
     // (and other URLs that inherit their principal from their
     // creator.)
@@ -186,16 +199,19 @@ public:
                          nsIEventTarget* aSyncLoopTarget, uint32_t aFirstIndex,
                          uint32_t aLastIndex);
 
 private:
   ~ScriptExecutorRunnable()
   { }
 
   virtual bool
+  IsDebuggerRunnable() const MOZ_OVERRIDE;
+
+  virtual bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE;
 
   virtual void
   PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
           MOZ_OVERRIDE;
 
   NS_DECL_NSICANCELABLERUNNABLE
 
@@ -209,34 +225,36 @@ class ScriptLoaderRunnable MOZ_FINAL : p
                                        public nsIRunnable,
                                        public nsIStreamLoaderObserver
 {
   friend class ScriptExecutorRunnable;
 
   WorkerPrivate* mWorkerPrivate;
   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   nsTArray<ScriptLoadInfo> mLoadInfos;
-  bool mIsWorkerScript;
+  bool mIsMainScript;
+  WorkerScriptType mWorkerScriptType;
   bool mCanceled;
   bool mCanceledMainThread;
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate,
                        nsIEventTarget* aSyncLoopTarget,
                        nsTArray<ScriptLoadInfo>& aLoadInfos,
-                       bool aIsWorkerScript)
+                       bool aIsMainScript,
+                       WorkerScriptType aWorkerScriptType)
   : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget),
-    mIsWorkerScript(aIsWorkerScript), mCanceled(false),
-    mCanceledMainThread(false)
+    mIsMainScript(aIsMainScript), mWorkerScriptType(aWorkerScriptType),
+    mCanceled(false), mCanceledMainThread(false)
   {
     aWorkerPrivate->AssertIsOnWorkerThread();
     MOZ_ASSERT(aSyncLoopTarget);
-    MOZ_ASSERT_IF(aIsWorkerScript, aLoadInfos.Length() == 1);
+    MOZ_ASSERT_IF(aIsMainScript, aLoadInfos.Length() == 1);
 
     mLoadInfos.SwapElements(aLoadInfos);
   }
 
 private:
   ~ScriptLoaderRunnable()
   { }
 
@@ -295,16 +313,22 @@ private:
         JS_ReportError(aCx, "Failed to cancel script loader!");
         return false;
       }
     }
 
     return true;
   }
 
+  bool
+  IsMainWorkerScript() const
+  {
+    return mIsMainScript && mWorkerScriptType == WorkerScript;
+  }
+
   void
   CancelMainThread()
   {
     AssertIsOnMainThread();
 
     if (mCanceledMainThread) {
       return;
     }
@@ -333,27 +357,27 @@ private:
 
     WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
 
     // Figure out which principal to use.
     nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
     nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
     if (!principal) {
       NS_ASSERTION(parentWorker, "Must have a principal!");
-      NS_ASSERTION(mIsWorkerScript, "Must have a principal for importScripts!");
+      NS_ASSERTION(mIsMainScript, "Must have a principal for importScripts!");
 
       principal = parentWorker->GetPrincipal();
       loadGroup = parentWorker->GetLoadGroup();
     }
     NS_ASSERTION(principal, "This should never be null here!");
     MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
 
     // Figure out our base URI.
     nsCOMPtr<nsIURI> baseURI;
-    if (mIsWorkerScript) {
+    if (mIsMainScript) {
       if (parentWorker) {
         baseURI = parentWorker->GetBaseURI();
         NS_ASSERTION(baseURI, "Should have been set already!");
       }
       else {
         // May be null.
         baseURI = mWorkerPrivate->GetBaseURI();
       }
@@ -362,34 +386,34 @@ private:
       baseURI = mWorkerPrivate->GetBaseURI();
       NS_ASSERTION(baseURI, "Should have been set already!");
     }
 
     // May be null.
     nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument();
 
     nsCOMPtr<nsIChannel> channel;
-    if (mIsWorkerScript) {
+    if (IsMainWorkerScript()) {
       // May be null.
       channel = mWorkerPrivate->ForgetWorkerChannel();
     }
 
     nsCOMPtr<nsIIOService> ios(do_GetIOService());
 
     nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
     NS_ASSERTION(secMan, "This should never be null!");
 
     for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
       ScriptLoadInfo& loadInfo = mLoadInfos[index];
       nsresult& rv = loadInfo.mLoadResult;
 
       if (!channel) {
         rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios,
-                                  secMan, loadInfo.mURL, mIsWorkerScript,
-                                                getter_AddRefs(channel));
+                                  secMan, loadInfo.mURL, mIsMainScript,
+                                  mWorkerScriptType, getter_AddRefs(channel));
         if (NS_FAILED(rv)) {
           return rv;
         }
       }
 
       // We need to know which index we're on in OnStreamComplete so we know
       // where to put the result.
       nsCOMPtr<nsISupportsPRUint32> indexSupports =
@@ -484,17 +508,17 @@ private:
     if (!filename.IsEmpty()) {
       // This will help callers figure out what their script url resolved to in
       // case of errors.
       aLoadInfo.mURL.Assign(NS_ConvertUTF8toUTF16(filename));
     }
 
     // Update the principal of the worker and its base URI if we just loaded the
     // worker's primary script.
-    if (mIsWorkerScript) {
+    if (IsMainWorkerScript()) {
       // Take care of the base URI first.
       mWorkerPrivate->SetBaseURI(finalURI);
 
       // Now to figure out which principal to give this worker.
       WorkerPrivate* parent = mWorkerPrivate->GetParent();
 
       NS_ASSERTION(mWorkerPrivate->GetPrincipal() || parent,
                    "Must have one of these!");
@@ -572,17 +596,17 @@ private:
     return NS_OK;
   }
 
   void
   ExecuteFinishedScripts()
   {
     AssertIsOnMainThread();
 
-    if (mIsWorkerScript) {
+    if (IsMainWorkerScript()) {
       mWorkerPrivate->WorkerScriptLoaded();
     }
 
     uint32_t firstIndex = UINT32_MAX;
     uint32_t lastIndex = UINT32_MAX;
 
     // Find firstIndex based on whether mExecutionScheduled is unset.
     for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
@@ -700,16 +724,25 @@ ScriptExecutorRunnable::ScriptExecutorRu
 : MainThreadWorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncLoopTarget),
   mScriptLoader(aScriptLoader), mFirstIndex(aFirstIndex), mLastIndex(aLastIndex)
 {
   MOZ_ASSERT(aFirstIndex <= aLastIndex);
   MOZ_ASSERT(aLastIndex < aScriptLoader.mLoadInfos.Length());
 }
 
 bool
+ScriptExecutorRunnable::IsDebuggerRunnable() const
+{
+  // ScriptExecutorRunnable is used to execute both worker and debugger scripts.
+  // In the latter case, the runnable needs to be dispatched to the debugger
+  // queue.
+  return mScriptLoader.mWorkerScriptType == DebuggerScript;
+}
+
+bool
 ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
 {
   nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos;
 
   // Don't run if something else has already failed.
   for (uint32_t index = 0; index < mFirstIndex; index++) {
     ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index);
 
@@ -757,16 +790,20 @@ ScriptExecutorRunnable::WorkerRun(JSCont
     }
 
     NS_ConvertUTF16toUTF8 filename(loadInfo.mURL);
 
     JS::CompileOptions options(aCx);
     options.setFileAndLine(filename.get(), 1)
            .setNoScriptRval(true);
 
+    if (mScriptLoader.mWorkerScriptType == DebuggerScript) {
+      options.setVersion(JSVERSION_LATEST);
+    }
+
     JS::SourceBufferHolder srcBuf(loadInfo.mScriptTextBuf,
                                   loadInfo.mScriptTextLength,
                                   JS::SourceBufferHolder::GiveOwnership);
     loadInfo.mScriptTextBuf = nullptr;
     loadInfo.mScriptTextLength = 0;
 
     JS::Rooted<JS::Value> unused(aCx);
     if (!JS::Evaluate(aCx, options, srcBuf, &unused)) {
@@ -814,26 +851,27 @@ ScriptExecutorRunnable::ShutdownScriptLo
                                              bool aResult)
 {
   aWorkerPrivate->RemoveFeature(aCx, &mScriptLoader);
   aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, aResult);
 }
 
 bool
 LoadAllScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsWorkerScript)
+               nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsMainScript,
+               WorkerScriptType aWorkerScriptType)
 {
   aWorkerPrivate->AssertIsOnWorkerThread();
   NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!");
 
   AutoSyncLoopHolder syncLoop(aWorkerPrivate);
 
   nsRefPtr<ScriptLoaderRunnable> loader =
     new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.EventTarget(),
-                             aLoadInfos, aIsWorkerScript);
+                             aLoadInfos, aIsMainScript, aWorkerScriptType);
 
   NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!");
 
   if (!aWorkerPrivate->AddFeature(aCx, loader)) {
     return false;
   }
 
   if (NS_FAILED(NS_DispatchToMainThread(loader))) {
@@ -863,17 +901,17 @@ ChannelFromScriptURLMainThread(nsIPrinci
   AssertIsOnMainThread();
 
   nsCOMPtr<nsIIOService> ios(do_GetIOService());
 
   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   NS_ASSERTION(secMan, "This should never be null!");
 
   return ChannelFromScriptURL(aPrincipal, aBaseURI, aParentDoc, aLoadGroup,
-                              ios, secMan, aScriptURL, true, aChannel);
+                              ios, secMan, aScriptURL, true, WorkerScript, aChannel);
 }
 
 nsresult
 ChannelFromScriptURLWorkerThread(JSContext* aCx,
                                  WorkerPrivate* aParent,
                                  const nsAString& aScriptURL,
                                  nsIChannel** aChannel)
 {
@@ -922,27 +960,28 @@ void ReportLoadError(JSContext* aCx, con
       break;
 
     default:
       JS_ReportError(aCx, "Failed to load script (nsresult = 0x%x)", aLoadResult);
   }
 }
 
 bool
-LoadWorkerScript(JSContext* aCx)
+LoadMainScript(JSContext* aCx, const nsAString& aScriptURL,
+               WorkerScriptType aWorkerScriptType)
 {
   WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
   NS_ASSERTION(worker, "This should never be null!");
 
   nsTArray<ScriptLoadInfo> loadInfos;
 
   ScriptLoadInfo* info = loadInfos.AppendElement();
-  info->mURL = worker->ScriptURL();
+  info->mURL = aScriptURL;
 
-  return LoadAllScripts(aCx, worker, loadInfos, true);
+  return LoadAllScripts(aCx, worker, loadInfos, true, aWorkerScriptType);
 }
 
 void
 Load(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
      const Sequence<nsString>& aScriptURLs, ErrorResult& aRv)
 {
   const uint32_t urlCount = aScriptURLs.Length();
 
@@ -957,17 +996,17 @@ Load(JSContext* aCx, WorkerPrivate* aWor
 
   nsTArray<ScriptLoadInfo> loadInfos;
   loadInfos.SetLength(urlCount);
 
   for (uint32_t index = 0; index < urlCount; index++) {
     loadInfos[index].mURL = aScriptURLs[index];
   }
 
-  if (!LoadAllScripts(aCx, aWorkerPrivate, loadInfos, false)) {
+  if (!LoadAllScripts(aCx, aWorkerPrivate, loadInfos, false, WorkerScript)) {
     // LoadAllScripts can fail if we're shutting down.
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 } // namespace scriptloader
 
 END_WORKERS_NAMESPACE
--- a/dom/workers/ScriptLoader.h
+++ b/dom/workers/ScriptLoader.h
@@ -24,16 +24,21 @@ namespace dom {
 template <typename T>
 class Sequence;
 
 } // namespace dom
 } // namespace mozilla
 
 BEGIN_WORKERS_NAMESPACE
 
+enum WorkerScriptType {
+  WorkerScript,
+  DebuggerScript
+};
+
 namespace scriptloader {
 
 nsresult
 ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal,
                                nsIURI* aBaseURI,
                                nsIDocument* aParentDoc,
                                nsILoadGroup* aLoadGroup,
                                const nsAString& aScriptURL,
@@ -43,17 +48,18 @@ nsresult
 ChannelFromScriptURLWorkerThread(JSContext* aCx,
                                  WorkerPrivate* aParent,
                                  const nsAString& aScriptURL,
                                  nsIChannel** aChannel);
 
 void ReportLoadError(JSContext* aCx, const nsAString& aURL,
                      nsresult aLoadResult, bool aIsMainThread);
 
-bool LoadWorkerScript(JSContext* aCx);
+bool LoadMainScript(JSContext* aCx, const nsAString& aScriptURL,
+                    WorkerScriptType aWorkerScriptType);
 
 void Load(JSContext* aCx,
           WorkerPrivate* aWorkerPrivate,
           const mozilla::dom::Sequence<nsString>& aScriptURLs,
           mozilla::ErrorResult& aRv);
 
 } // namespace scriptloader
 
--- a/dom/workers/WorkerDebuggerManager.cpp
+++ b/dom/workers/WorkerDebuggerManager.cpp
@@ -137,16 +137,26 @@ WorkerDebuggerManager::RemoveListener(
     return NS_OK;
   }
 
   mListeners.RemoveElement(aListener);
   return NS_OK;
 }
 
 void
+WorkerDebuggerManager::ClearListeners()
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  mListeners.Clear();
+}
+
+void
 WorkerDebuggerManager::RegisterDebugger(WorkerDebugger* aDebugger)
 {
   // May be called on any thread!
 
   bool hasListeners = false;
 
   {
     MutexAutoLock lock(mMutex);
--- a/dom/workers/WorkerDebuggerManager.h
+++ b/dom/workers/WorkerDebuggerManager.h
@@ -36,40 +36,55 @@ class WorkerDebuggerManager MOZ_FINAL : 
 
   // Only touched on the main thread.
   nsTArray<WorkerDebugger*> mDebuggers;
 
 public:
   static WorkerDebuggerManager*
   GetOrCreateService()
   {
-    nsCOMPtr<nsIWorkerDebuggerManager> wdm =
+    nsCOMPtr<nsIWorkerDebuggerManager> manager =
       do_GetService(WORKERDEBUGGERMANAGER_CONTRACTID);
-    return static_cast<WorkerDebuggerManager*>(wdm.get());
+    return static_cast<WorkerDebuggerManager*>(manager.get());
   }
 
   WorkerDebuggerManager();
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIWORKERDEBUGGERMANAGER
 
+  void ClearListeners();
+
   void RegisterDebugger(WorkerDebugger* aDebugger);
 
   void UnregisterDebugger(WorkerDebugger* aDebugger);
 
 private:
   virtual ~WorkerDebuggerManager();
 
   void RegisterDebuggerOnMainThread(WorkerDebugger* aDebugger,
                                     bool aHasListeners);
 
   void UnregisterDebuggerOnMainThread(WorkerDebugger* aDebugger);
 };
 
 inline nsresult
+ClearWorkerDebuggerManagerListeners()
+{
+  nsRefPtr<WorkerDebuggerManager> manager =
+    WorkerDebuggerManager::GetOrCreateService();
+  if (!manager) {
+    return NS_ERROR_FAILURE;
+  }
+
+  manager->ClearListeners();
+  return NS_OK;
+}
+
+inline nsresult
 RegisterWorkerDebugger(WorkerDebugger* aDebugger)
 {
   nsRefPtr<WorkerDebuggerManager> manager =
     WorkerDebuggerManager::GetOrCreateService();
   if (!manager) {
     return NS_ERROR_FAILURE;
   }
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1037,42 +1037,76 @@ private:
     }
     // Don't do anything here as it's possible that aWorkerPrivate has been
     // deleted.
   }
 };
 
 class CompileScriptRunnable MOZ_FINAL : public WorkerRunnable
 {
+  nsString mScriptURL;
+
 public:
-  explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate)
-  : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
+  explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate,
+                                 const nsAString& aScriptURL)
+  : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
+    mScriptURL(aScriptURL)
   { }
 
 private:
   virtual bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     WorkerGlobalScope* globalScope =
       aWorkerPrivate->GetOrCreateGlobalScope(aCx);
     if (!globalScope) {
       NS_WARNING("Failed to make global!");
       return false;
     }
 
     JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());
+
     JSAutoCompartment ac(aCx, global);
-    bool result = scriptloader::LoadWorkerScript(aCx);
+    bool result = scriptloader::LoadMainScript(aCx, mScriptURL, WorkerScript);
     if (result) {
       aWorkerPrivate->SetWorkerScriptExecutedSuccessfully();
     }
     return result;
   }
 };
 
+class CompileDebuggerScriptRunnable MOZ_FINAL : public WorkerDebuggerRunnable
+{
+  nsString mScriptURL;
+
+public:
+  CompileDebuggerScriptRunnable(WorkerPrivate* aWorkerPrivate,
+                                const nsAString& aScriptURL)
+  : WorkerDebuggerRunnable(aWorkerPrivate),
+    mScriptURL(aScriptURL)
+  { }
+
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    WorkerDebuggerGlobalScope* globalScope =
+      aWorkerPrivate->CreateDebuggerGlobalScope(aCx);
+    if (!globalScope) {
+      NS_WARNING("Failed to make global!");
+      return false;
+    }
+
+    JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());
+
+    JSAutoCompartment ac(aCx, global);
+    return scriptloader::LoadMainScript(aCx, mScriptURL, DebuggerScript);
+  }
+};
+
 class CloseEventRunnable MOZ_FINAL : public WorkerRunnable
 {
 public:
   explicit CloseEventRunnable(WorkerPrivate* aWorkerPrivate)
   : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
   { }
 
 private:
@@ -4130,17 +4164,18 @@ WorkerPrivateParent<Derived>::AssertInne
 }
 
 #endif
 
 WorkerDebugger::WorkerDebugger(WorkerPrivate* aWorkerPrivate)
 : mMutex("WorkerDebugger::mMutex"),
   mCondVar(mMutex, "WorkerDebugger::mCondVar"),
   mWorkerPrivate(aWorkerPrivate),
-  mIsEnabled(false)
+  mIsEnabled(false),
+  mIsInitialized(false)
 {
   mWorkerPrivate->AssertIsOnParentThread();
 }
 
 WorkerDebugger::~WorkerDebugger()
 {
   MOZ_ASSERT(!mWorkerPrivate);
   MOZ_ASSERT(!mIsEnabled);
@@ -4260,16 +4295,38 @@ WorkerDebugger::GetWindow(nsIDOMWindow**
   }
 
   nsCOMPtr<nsPIDOMWindow> window = mWorkerPrivate->GetWindow();
   window.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+WorkerDebugger::Initialize(const nsAString& aURL, JSContext* aCx)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mWorkerPrivate || mIsInitialized) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsRefPtr<CompileDebuggerScriptRunnable> runnable =
+    new CompileDebuggerScriptRunnable(mWorkerPrivate, aURL);
+  if (!runnable->Dispatch(aCx)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mIsInitialized = true;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 WorkerDebugger::AddListener(nsIWorkerDebuggerListener* aListener)
 {
   AssertIsOnMainThread();
 
   if (mListeners.Contains(aListener)) {
     return NS_ERROR_INVALID_ARG;
   }
 
@@ -4521,17 +4578,18 @@ WorkerPrivate::Constructor(JSContext* aC
 
   if (!runtimeService->RegisterWorker(aCx, worker)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   worker->EnableDebugger();
 
-  nsRefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(worker);
+  nsRefPtr<CompileScriptRunnable> compiler =
+    new CompileScriptRunnable(worker, aScriptURL);
   if (!compiler->Dispatch(aCx)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   worker->mSelfRef = worker;
 
   return worker.forget();
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -724,16 +724,17 @@ class WorkerDebugger : public nsIWorkerD
   mozilla::Mutex mMutex;
   mozilla::CondVar mCondVar;
 
   // Protected by mMutex
   WorkerPrivate* mWorkerPrivate;
   bool mIsEnabled;
 
   // Only touched on the main thread.
+  bool mIsInitialized;
   nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> mListeners;
 
 public:
   explicit WorkerDebugger(WorkerPrivate* aWorkerPrivate);
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIWORKERDEBUGGER
 
--- a/dom/workers/nsIWorkerDebugger.idl
+++ b/dom/workers/nsIWorkerDebugger.idl
@@ -3,17 +3,17 @@
 interface nsIDOMWindow;
 
 [scriptable, uuid(54fd2dd3-c01b-4f71-888f-462f37a54f57)]
 interface nsIWorkerDebuggerListener : nsISupports
 {
   void onClose();
 };
 
-[scriptable, builtinclass, uuid(0833b363-bffe-4cdb-ad50-1c4563e0C8ff)]
+[scriptable, builtinclass, uuid(b0ea6da8-8bd9-446a-94e2-2ee979903205)]
 interface nsIWorkerDebugger : nsISupports
 {
   const unsigned long TYPE_DEDICATED = 0;
   const unsigned long TYPE_SHARED = 1;
   const unsigned long TYPE_SERVICE = 2;
 
   readonly attribute bool isClosed;
 
@@ -22,12 +22,15 @@ interface nsIWorkerDebugger : nsISupport
   readonly attribute nsIWorkerDebugger parent;
 
   readonly attribute unsigned long type;
 
   readonly attribute DOMString url;
 
   readonly attribute nsIDOMWindow window;
 
+  [implicit_jscontext]
+  void initialize(in DOMString url);
+
   void addListener(in nsIWorkerDebuggerListener listener);
 
   void removeListener(in nsIWorkerDebuggerListener listener);
 };
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_childWorker.js
@@ -0,0 +1,6 @@
+"use strict";
+
+self.onmessage = function () {};
+
+debugger;
+postMessage("worker");
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_debugger.js
@@ -0,0 +1,6 @@
+"use strict";
+
+var dbg = new Debugger(global);
+dbg.onDebuggerStatement = function (frame) {
+  frame.eval("postMessage('debugger');");
+};
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_worker.js
@@ -0,0 +1,9 @@
+"use strict";
+
+var worker = new Worker("WorkerDebugger.initialize_childWorker.js");
+worker.onmessage = function (event) {
+  postMessage("child:" + event.data);
+};
+
+debugger;
+postMessage("worker");
--- a/dom/workers/test/WorkerDebuggerManager_childWorker.js
+++ b/dom/workers/test/WorkerDebuggerManager_childWorker.js
@@ -1,3 +1,3 @@
 "use strict";
 
-onmessage = function () {};
+self.onmessage = function () {};
deleted file mode 100644
--- a/dom/workers/test/WorkerDebuggerManager_parentWorker.js
+++ /dev/null
@@ -1,3 +0,0 @@
-"use strict";
-
-var worker = new Worker("WorkerDebuggerManager_childWorker.js");
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerManager_worker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+var worker = new Worker("WorkerDebuggerManager_childWorker.js");
--- a/dom/workers/test/WorkerDebugger_childWorker.js
+++ b/dom/workers/test/WorkerDebugger_childWorker.js
@@ -1,3 +1,3 @@
 "use strict";
 
-onmessage = function () {};
+self.onmessage = function () {};
deleted file mode 100644
--- a/dom/workers/test/WorkerDebugger_parentWorker.js
+++ /dev/null
@@ -1,3 +0,0 @@
-"use strict";
-
-var worker = new Worker("WorkerDebugger_childWorker.js");
--- a/dom/workers/test/WorkerDebugger_sharedWorker.js
+++ b/dom/workers/test/WorkerDebugger_sharedWorker.js
@@ -1,11 +1,11 @@
 "use strict";
 
-onconnect = function (event) {
+self.onconnect = function (event) {
   event.ports[0].onmessage = function (event) {
     switch (event.data) {
     case "close":
       close();
       break;
     }
   };
 };
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_worker.js
@@ -0,0 +1,8 @@
+"use strict";
+
+var worker = new Worker("WorkerDebugger_childWorker.js");
+self.onmessage = function (event) {
+  postMessage("child:" + event.data);
+};
+debugger;
+postMessage("worker");
--- a/dom/workers/test/chrome.ini
+++ b/dom/workers/test/chrome.ini
@@ -1,15 +1,18 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g'
 support-files =
+  WorkerDebugger.initialize_childWorker.js
+  WorkerDebugger.initialize_debugger.js
+  WorkerDebugger.initialize_worker.js
   WorkerDebuggerManager_childWorker.js
-  WorkerDebuggerManager_parentWorker.js
+  WorkerDebuggerManager_worker.js
   WorkerDebugger_childWorker.js
-  WorkerDebugger_parentWorker.js
+  WorkerDebugger_worker.js
   WorkerDebugger_sharedWorker.js
   WorkerTest.jsm
   WorkerTest_subworker.js
   WorkerTest_worker.js
   chromeWorker_subworker.js
   chromeWorker_worker.js
   dom_worker_helper.js
   file_url.jsm
@@ -24,16 +27,17 @@ support-files =
   fileSubWorker_worker.js
   file_worker.js
   jsm_url_worker.js
   workersDisabled_worker.js
   file_url.jsm
   bug1062920_worker.js
 
 [test_WorkerDebugger.xul]
+[test_WorkerDebugger.initialize.xul]
 [test_WorkerDebuggerManager.xul]
 [test_bug883784.jsm]
 [test_bug883784.xul]
 [test_chromeWorker.xul]
 [test_chromeWorkerJSM.xul]
 [test_extension.xul]
 [test_extensionBootstrap.xul]
 [test_file.xul]
--- a/dom/workers/test/dom_worker_helper.js
+++ b/dom/workers/test/dom_worker_helper.js
@@ -8,16 +8,18 @@ const { classes: Cc, interfaces: Ci, uti
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].
             getService(Ci.nsIWorkerDebuggerManager);
 
+const BASE_URL = "chrome://mochitests/content/chrome/dom/workers/test/";
+
 var gRemainingTests = 0;
 
 function waitForWorkerFinish() {
   if (gRemainingTests == 0) {
     SimpleTest.waitForExplicitFinish();
   }
   ++gRemainingTests;
 }
@@ -42,75 +44,93 @@ function assertThrows(fun, message) {
 function* generateDebuggers() {
   let e = wdm.getWorkerDebuggerEnumerator();
   while (e.hasMoreElements()) {
     let dbg = e.getNext().QueryInterface(Ci.nsIWorkerDebugger);
     yield dbg;
   }
 }
 
-function findDebugger(predicate) {
+function findDebugger(url) {
   for (let dbg of generateDebuggers()) {
-    if (predicate(dbg)) {
+    if (dbg.url === url) {
       return dbg;
     }
   }
   return null;
 }
 
-function waitForRegister(predicate = () => true) {
+function waitForRegister(url, dbgUrl) {
   return new Promise(function (resolve) {
     wdm.addListener({
       onRegister: function (dbg) {
-        if (!predicate(dbg)) {
+        if (dbg.url !== url) {
           return;
         }
+        ok(true, "Debugger with url " + url + " should be registered.");
         wdm.removeListener(this);
+        if (dbgUrl) {
+          info("Initializing worker debugger with url " + url + ".");
+          dbg.initialize(dbgUrl);
+        }
         resolve(dbg);
       }
     });
   });
 }
 
-function waitForUnregister(predicate = () => true) {
+function waitForUnregister(url) {
   return new Promise(function (resolve) {
     wdm.addListener({
       onUnregister: function (dbg) {
-        if (!predicate(dbg)) {
+        if (dbg.url !== url) {
           return;
         }
+        ok(true, "Debugger with url " + url + " should be unregistered.");
         wdm.removeListener(this);
-        resolve(dbg);
+        resolve();
       }
     });
   });
 }
 
-function waitForDebuggerClose(dbg, predicate = () => true) {
+function waitForDebuggerClose(dbg) {
   return new Promise(function (resolve) {
     dbg.addListener({
       onClose: function () {
-        if (!predicate()) {
-          return;
-        }
+        ok(true, "Debugger should be closed.");
         dbg.removeListener(this);
         resolve();
       }
     });
   });
 }
 
+function waitForWorkerMessage(worker, message) {
+  return new Promise(function (resolve) {
+    worker.addEventListener("message", function onmessage(event) {
+      if (event.data !== message) {
+        return;
+      }
+      ok(true, "Should receive " + message + " message from worker.");
+      worker.removeEventListener("message", onmessage);
+      resolve();
+    });
+  });
+}
+
 function waitForMultiple(promises) {
   return new Promise(function (resolve) {
-    let results = [];
+    let values = [];
     for (let i = 0; i < promises.length; ++i) {
-      let promise = promises[i];
       let index = i;
-      promise.then(function (result) {
-        is(results.length, index, "events should occur in the specified order");
-        results.push(result);
-        if (results.length === promises.length) {
-          resolve(results);
+      promises[i].then(function (value) {
+        is(index + 1, values.length + 1,
+           "Promise " + (values.length + 1) + " out of " + promises.length +
+           " should be resolved.");
+        values.push(value);
+        if (values.length === promises.length) {
+          resolve(values);
         }
       });
     }
   });
 };
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger.initialize.xul
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebugger.initialize"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="test();">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+  <script type="application/javascript" src="dom_worker_helper.js"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+
+    const WORKER_URL = "WorkerDebugger.initialize_worker.js";
+    const CHILD_WORKER_URL = "WorkerDebugger.initialize_childWorker.js";
+    const DEBUGGER_URL = BASE_URL + "WorkerDebugger.initialize_debugger.js";
+
+    function test() {
+      Task.spawn(function* () {
+        SimpleTest.waitForExplicitFinish();
+
+        info("Create a worker that creates a child worker, wait for their " +
+             "debuggers to be registered, and initialize them.");
+        let promise = waitForMultiple([
+          waitForRegister(WORKER_URL, DEBUGGER_URL),
+          waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL)
+        ]);
+        let worker = new Worker(WORKER_URL);
+        yield promise;
+
+        info("Check that the debuggers are initialized before the workers " +
+             "start running.");
+        yield waitForMultiple([
+          waitForWorkerMessage(worker, "debugger"),
+          waitForWorkerMessage(worker, "worker"),
+          waitForWorkerMessage(worker, "child:debugger"),
+          waitForWorkerMessage(worker, "child:worker")
+        ]);
+
+        SimpleTest.finish();
+      });
+    }
+
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+  </body>
+  <label id="test-result"/>
+</window>
--- a/dom/workers/test/test_WorkerDebugger.xul
+++ b/dom/workers/test/test_WorkerDebugger.xul
@@ -11,83 +11,107 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
   <script type="application/javascript" src="dom_worker_helper.js"/>
 
   <script type="application/javascript">
   <![CDATA[
 
-    const PARENT_WORKER_URL = "WorkerDebugger_parentWorker.js";
+    const WORKER_URL = "WorkerDebugger_worker.js";
     const CHILD_WORKER_URL = "WorkerDebugger_childWorker.js";
     const SHARED_WORKER_URL = "WorkerDebugger_sharedWorker.js";
 
     function test() {
       Task.spawn(function* () {
         SimpleTest.waitForExplicitFinish();
 
+        info("Create a top-level chrome worker that creates a non-top-level " +
+             "content worker and wait for their debuggers to be registered.");
         let promise = waitForMultiple([
-          waitForRegister((dbg) => dbg.url === PARENT_WORKER_URL),
-          waitForRegister((dbg) => dbg.url === CHILD_WORKER_URL),
+          waitForRegister(WORKER_URL),
+          waitForRegister(CHILD_WORKER_URL)
         ]);
-        worker = new ChromeWorker(PARENT_WORKER_URL);
-        let dbgs = yield promise;
-        is(dbgs[0].isChrome, true, "debugger should be for chrome worker");
-        is(dbgs[0].parent, null,
-           "debugger for a top-level worker should not have parent");
-        is(dbgs[0].type, Ci.nsIWorkerDebugger.TYPE_DEDICATED,
-           "debugger should be for dedicated worker");
-        is(dbgs[0].window, window,
-           "debugger for top-level dedicated worker should have window");
-        is(dbgs[1].isChrome, false, "debugger should be for content worker");
-        is(dbgs[1].parent, dbgs[0],
-           "debugger for child worker should have parent");
-        is(dbgs[1].type, Ci.nsIWorkerDebugger.TYPE_DEDICATED);
-        is(dbgs[1].window, null,
-           "debugger for non-top-level worker should not have window");
+        worker = new ChromeWorker(WORKER_URL);
+        let [dbg, childDbg] = yield promise;
+
+        info("Check that the top-level chrome worker debugger has the " +
+             "correct properties.");
+        is(dbg.isChrome, true,
+           "Chrome worker debugger should be chrome.");
+        is(dbg.parent, null,
+           "Top-level debugger should not have parent.");
+        is(dbg.type, Ci.nsIWorkerDebugger.TYPE_DEDICATED,
+           "Chrome worker debugger should be dedicated.");
+        is(dbg.window, window,
+           "Top-level dedicated worker debugger should have window.");
 
+        info("Check that the non-top-level content worker debugger has the " +
+             "correct properties.");
+        is(childDbg.isChrome, false,
+           "Content worker debugger should be content.");
+        is(childDbg.parent, dbg,
+           "Non-top-level worker debugger should have parent.");
+        is(childDbg.type, Ci.nsIWorkerDebugger.TYPE_DEDICATED,
+           "Content worker debugger should be dedicated.");
+        is(childDbg.window, null,
+           "Non-top-level worker debugger should not have window.");
+
+        info("Terminate the top-level chrome worker and the non-top-level " +
+             "content worker, and wait for their debuggers to be " + 
+             "unregistered and closed.");
         promise = waitForMultiple([
-          waitForUnregister((dbg) => dbg.url === CHILD_WORKER_URL),
-          waitForDebuggerClose(dbgs[1]),
-          waitForUnregister((dbg) => dbg.url === PARENT_WORKER_URL),
-          waitForDebuggerClose(dbgs[0]),
+          waitForUnregister(CHILD_WORKER_URL),
+          waitForDebuggerClose(childDbg),
+          waitForUnregister(WORKER_URL),
+          waitForDebuggerClose(dbg),
         ]);
         worker.terminate();
         yield promise;
 
-        promise = waitForRegister();
+        info("Create a shared worker and wait for its debugger to be " + 
+             "registered");
+        promise = waitForRegister(SHARED_WORKER_URL);
         worker = new SharedWorker(SHARED_WORKER_URL);
-        let dbg = yield promise;
-        is(dbg.isChrome, false, "debugger should be for content worker");
-        is(dbg.parent, null,
-           "debugger for top-level worker should not have parent");
-        is(dbg.type, Ci.nsIWorkerDebugger.TYPE_SHARED,
-           "debugger should be for shared worker");
-        is(dbg.window, null,
-           "debugger for non-dedicated worker should not have window");
+        let sharedDbg = yield promise;
 
+        info("Check that the shared worker debugger has the correct " +
+             "properties.");
+        is(sharedDbg.isChrome, false,
+           "Shared worker debugger should be content.");
+        is(sharedDbg.parent, null,
+           "Shared worker debugger should not have parent.");
+        is(sharedDbg.type, Ci.nsIWorkerDebugger.TYPE_SHARED,
+           "Shared worker debugger should be shared.");
+        is(sharedDbg.window, null,
+           "Shared worker debugger should not have window.");
+
+        info("Create a shared worker with the same URL and check that its " +
+             "debugger is not registered again.");
         let listener = {
           onRegistered: function () {
             ok(false,
-               "debugger for shared worker should not be registered twice");
+               "Shared worker debugger should not be registered again.");
           },
         };
         wdm.addListener(listener);
         worker = new SharedWorker(SHARED_WORKER_URL);
 
-        dbg.addListener({
-          onClose: function () {
-            is(dbg.isClosed, true, "debugger should be closed");
-            wdm.removeListener(listener);
-            dbg.removeListener(this);
-            SimpleTest.finish();
-          }
-        });
+        info("Send a message to the shared worker to tell it to close " +
+             "itself, and wait for its debugger to be closed.");
+        promise = waitForMultiple([
+          waitForUnregister(SHARED_WORKER_URL),
+          waitForDebuggerClose(sharedDbg)
+        ]);
         worker.port.start();
         worker.port.postMessage("close");
+        yield promise;
+
+        wdm.removeListener(listener);
+        SimpleTest.finish();
       });
     }
 
   ]]>
   </script>
 
   <body xmlns="http://www.w3.org/1999/xhtml">
     <p id="display"></p>
--- a/dom/workers/test/test_WorkerDebuggerManager.xul
+++ b/dom/workers/test/test_WorkerDebuggerManager.xul
@@ -11,75 +11,89 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
   <script type="application/javascript" src="dom_worker_helper.js"/>
 
   <script type="application/javascript">
   <![CDATA[
 
-    const PARENT_WORKER_URL = "WorkerDebuggerManager_parentWorker.js";
+    const WORKER_URL = "WorkerDebuggerManager_worker.js";
     const CHILD_WORKER_URL = "WorkerDebuggerManager_childWorker.js";
 
     function test() {
       Task.spawn(function* () {
         SimpleTest.waitForExplicitFinish();
 
-        ok(!findDebugger((dbg) => dbg.url === PARENT_WORKER_URL),
-           "debugger for parent worker should not be enumerated before it is " +
-           "registered");
-        ok(!findDebugger((dbg) => dbg.url === CHILD_WORKER_URL),
-           "debugger for child worker should not be enumerated before it is " +
-           "registered");
+        info("Check that worker debuggers are not enumerated before they are " +
+             "registered.");
+        ok(!findDebugger(WORKER_URL),
+           "Worker debugger should not be enumerated before it is registered.");
+        ok(!findDebugger(CHILD_WORKER_URL),
+           "Child worker debugger should not be enumerated before it is " +
+           "registered.");
 
+        info("Create a worker that creates a child worker, and wait for " +
+             "their debuggers to be registered.");
         let promise = waitForMultiple([
-          waitForRegister((dbg) => dbg.url === PARENT_WORKER_URL),
-          waitForRegister((dbg) => dbg.url === CHILD_WORKER_URL),
+          waitForRegister(WORKER_URL),
+          waitForRegister(CHILD_WORKER_URL)
         ]);
-        let worker = new Worker(PARENT_WORKER_URL);
-        let dbgs = yield promise;
-        is(dbgs[0].isClosed, false,
-           "debugger for parent worker should not be closed after it is " +
-           "registered");
-        is(dbgs[1].isClosed, false,
-           "debugger for child worker should not be closed after it is " +
-            "registered");
+        let worker = new Worker(WORKER_URL);
+        let [dbg, childDbg] = yield promise;
+
+        info("Check that worker debuggers are enumerated after they are " +
+             "registered.");
+        ok(findDebugger(WORKER_URL),
+           "Worker debugger should be enumerated after it is registered.");
+        ok(findDebugger(CHILD_WORKER_URL),
+           "Child worker debugger should be enumerated after it is " +
+           "registered.");
 
-        ok(findDebugger((dbg) => dbg.url === PARENT_WORKER_URL),
-           "debugger for parent worker should be enumerated after it is " +
-           "registered");
-        ok(findDebugger((dbg) => dbg.url === CHILD_WORKER_URL),
-           "debugger for child worker should be enumerated after it is " +
-           "registered");
+        info("Check that worker debuggers are not closed before they are " +
+             "unregistered.");
+        is(dbg.isClosed, false,
+           "Worker debugger should not be closed before it is unregistered.");
+        is(childDbg.isClosed, false,
+           "Child worker debugger should not be closed before it is " +
+           "unregistered");
 
+        info("Terminate the worker and the child worker, and wait for their " +
+             "debuggers to be unregistered");
         promise = waitForMultiple([
-          waitForUnregister((dbg) => dbg.url === CHILD_WORKER_URL),
-          waitForUnregister((dbg) => dbg.url === PARENT_WORKER_URL),
+          waitForUnregister(CHILD_WORKER_URL),
+          waitForUnregister(WORKER_URL),
         ]);
         worker.terminate();
-        dbgs = yield promise;
-        is(dbgs[0].isClosed, true,
-           "debugger for parent worker should be closed after it is " +
-           "unregistered");
-        is(dbgs[1].isClosed, true,
-           "debugger for child worker should be closed after it is " +
-           "unregistered");
-        assertThrows(() => dbgs[0].url,
-                     "accessing debugger for parent worker should throw " +
-                     "after it is closed");
-        assertThrows(() => dbgs[0].url,
-                     "accessing debugger for child worker should throw after " +
-                     "it is closed");
+        yield promise;
+
+        info("Check that worker debuggers are not enumerated after they are " +
+             "unregistered.");
+        ok(!findDebugger(WORKER_URL),
+           "Worker debugger should not be enumerated after it is " +
+           "unregistered.");
+        ok(!findDebugger(CHILD_WORKER_URL),
+           "Child worker debugger should not be enumerated after it is " +
+           "unregistered.");
 
-        ok(!findDebugger((dbg) => dbg.url === PARENT_WORKER_URL),
-           "debugger for parent worker should not be enumerated after it is " +
-           "unregistered");
-        ok(!findDebugger((dbg) => dbg.url === CHILD_WORKER_URL),
-           "debugger for child worker should not be enumerated after it is " +
-           "unregistered");
+        info("Check that worker debuggers are closed after they are " +
+             "unregistered.");
+        is(dbg.isClosed, true,
+           "Worker debugger should be closed after it is unregistered.");
+        is(childDbg.isClosed, true,
+           "Child worker debugger should be closed after it is unregistered.");
+
+        info("Check that property accesses on worker debuggers throws " +
+             "after they are closed.");
+        assertThrows(() => dbg.url,
+                     "Property accesses on worker debugger should throw " +
+                     "after it is closed.");
+        assertThrows(() => childDbg.url,
+                     "Property accesses on child worker debugger should " +
+                     "throw after it is closed.");
 
         SimpleTest.finish();
       });
     }
 
   ]]>
   </script>