Bug 757133: Implement a WorkerDebuggerManager; r=khuey
authorEddy Bruël <ejpbruel@gmail.com>
Mon, 27 Oct 2014 18:00:05 +0100
changeset 212485 d5fcb5f05f03386b67ee2745acd3d30ea477fa64
parent 212484 2add539cd09aeb853d4b2197fd67d6a3575c60d7
child 212486 06ec442314d4c251b64b9760170780a7eeed9d78
push id27716
push userkwierso@gmail.com
push dateTue, 28 Oct 2014 00:27:17 +0000
treeherdermozilla-central@2042c38f13ae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs757133
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 757133: Implement a WorkerDebuggerManager; r=khuey
browser/installer/package-manifest.in
dom/workers/WorkerDebuggerManager.cpp
dom/workers/WorkerDebuggerManager.h
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
dom/workers/moz.build
dom/workers/nsIWorkerDebugger.idl
dom/workers/nsIWorkerDebuggerManager.idl
dom/workers/test/WorkerDebuggerManager_childWorker.js
dom/workers/test/WorkerDebuggerManager_parentWorker.js
dom/workers/test/chrome.ini
dom/workers/test/dom_worker_helper.js
dom/workers/test/test_WorkerDebuggerManager.xul
dom/workers/test/test_extension.xul
dom/workers/test/test_extensionBootstrap.xul
layout/build/nsLayoutModule.cpp
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -230,16 +230,17 @@
 @BINPATH@/components/dom_storage.xpt
 @BINPATH@/components/dom_stylesheets.xpt
 @BINPATH@/components/dom_telephony.xpt
 @BINPATH@/components/dom_traversal.xpt
 @BINPATH@/components/dom_voicemail.xpt
 #ifdef MOZ_WEBSPEECH
 @BINPATH@/components/dom_webspeechrecognition.xpt
 #endif
+@BINPATH@/components/dom_workers.xpt
 @BINPATH@/components/dom_xbl.xpt
 @BINPATH@/components/dom_xpath.xpt
 @BINPATH@/components/dom_xul.xpt
 #ifdef MOZ_GAMEPAD
 @BINPATH@/components/dom_gamepad.xpt
 #endif
 @BINPATH@/components/dom_payment.xpt
 @BINPATH@/components/downloads.xpt
new file mode 100644
--- /dev/null
+++ b/dom/workers/WorkerDebuggerManager.cpp
@@ -0,0 +1,236 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WorkerDebuggerManager.h"
+
+#include "nsISimpleEnumerator.h"
+
+#include "WorkerPrivate.h"
+
+USING_WORKERS_NAMESPACE
+
+class RegisterDebuggerRunnable MOZ_FINAL : public nsRunnable
+{
+  nsRefPtr<WorkerDebuggerManager> mManager;
+  nsRefPtr<WorkerDebugger> mDebugger;
+  bool mHasListeners;
+
+public:
+  RegisterDebuggerRunnable(WorkerDebuggerManager* aManager,
+                           WorkerDebugger* aDebugger,
+                           bool aHasListeners)
+  : mManager(aManager), mDebugger(aDebugger), mHasListeners(aHasListeners)
+  { }
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+private:
+  ~RegisterDebuggerRunnable()
+  { }
+
+  NS_IMETHOD
+  Run() MOZ_OVERRIDE
+  {
+    mManager->RegisterDebuggerOnMainThread(mDebugger, mHasListeners);
+
+    return NS_OK;
+  }
+};
+
+NS_IMPL_ISUPPORTS(RegisterDebuggerRunnable, nsIRunnable);
+
+BEGIN_WORKERS_NAMESPACE
+
+class WorkerDebuggerEnumerator MOZ_FINAL : public nsISimpleEnumerator
+{
+  nsTArray<nsCOMPtr<nsISupports>> mDebuggers;
+  uint32_t mIndex;
+
+public:
+  WorkerDebuggerEnumerator(const nsTArray<WorkerDebugger*>& aDebuggers)
+  : mIndex(0)
+  {
+    mDebuggers.AppendElements(aDebuggers);
+  }
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISIMPLEENUMERATOR
+
+private:
+  ~WorkerDebuggerEnumerator() {}
+};
+
+NS_IMPL_ISUPPORTS(WorkerDebuggerEnumerator, nsISimpleEnumerator);
+
+NS_IMETHODIMP
+WorkerDebuggerEnumerator::HasMoreElements(bool* aResult)
+{
+  *aResult = mIndex < mDebuggers.Length();
+  return NS_OK;
+};
+
+NS_IMETHODIMP
+WorkerDebuggerEnumerator::GetNext(nsISupports** aResult)
+{
+  if (mIndex == mDebuggers.Length()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsISupports> element = mDebuggers.ElementAt(mIndex++);
+  element.forget(aResult);
+  return NS_OK;
+};
+
+WorkerDebuggerManager::WorkerDebuggerManager()
+: mMutex("WorkerDebuggerManager::mMutex")
+{
+  AssertIsOnMainThread();
+}
+
+WorkerDebuggerManager::~WorkerDebuggerManager()
+{
+  AssertIsOnMainThread();
+}
+
+NS_IMPL_ISUPPORTS(WorkerDebuggerManager, nsIWorkerDebuggerManager);
+
+NS_IMETHODIMP
+WorkerDebuggerManager::GetWorkerDebuggerEnumerator(
+                                                  nsISimpleEnumerator** aResult)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  nsRefPtr<WorkerDebuggerEnumerator> enumerator =
+    new WorkerDebuggerEnumerator(mDebuggers);
+  enumerator.forget(aResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebuggerManager::AddListener(nsIWorkerDebuggerManagerListener* aListener)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  if (mListeners.Contains(aListener)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  mListeners.AppendElement(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebuggerManager::RemoveListener(
+                                    nsIWorkerDebuggerManagerListener* aListener)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mListeners.Contains(aListener)) {
+    return NS_OK;
+  }
+
+  mListeners.RemoveElement(aListener);
+  return NS_OK;
+}
+
+void
+WorkerDebuggerManager::RegisterDebugger(WorkerDebugger* aDebugger)
+{
+  // May be called on any thread!
+
+  bool hasListeners = false;
+
+  {
+    MutexAutoLock lock(mMutex);
+
+    hasListeners = !mListeners.IsEmpty();
+  }
+
+  if (NS_IsMainThread()) {
+    RegisterDebuggerOnMainThread(aDebugger, hasListeners);
+  } else {
+    nsCOMPtr<nsIRunnable> runnable =
+      new RegisterDebuggerRunnable(this, aDebugger, hasListeners);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL)));
+
+    if (hasListeners) {
+      aDebugger->WaitIsEnabled(true);
+    }
+  }
+}
+
+void
+WorkerDebuggerManager::UnregisterDebugger(WorkerDebugger* aDebugger)
+{
+  // May be called on any thread!
+
+  if (NS_IsMainThread()) {
+    UnregisterDebuggerOnMainThread(aDebugger);
+  } else {
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethodWithArg<nsRefPtr<WorkerDebugger>>(this,
+        &WorkerDebuggerManager::UnregisterDebuggerOnMainThread, aDebugger);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL)));
+
+    aDebugger->WaitIsEnabled(false);
+  }
+}
+
+void
+WorkerDebuggerManager::RegisterDebuggerOnMainThread(WorkerDebugger* aDebugger,
+                                                    bool aHasListeners)
+{
+  AssertIsOnMainThread();
+
+  MOZ_ASSERT(!mDebuggers.Contains(aDebugger));
+  mDebuggers.AppendElement(aDebugger);
+
+  nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> listeners;
+  {
+    MutexAutoLock lock(mMutex);
+
+    listeners.AppendElements(mListeners);
+  }
+
+  if (aHasListeners) {
+    for (size_t index = 0; index < listeners.Length(); ++index) {
+      listeners[index]->OnRegister(aDebugger);
+    }
+  }
+
+  aDebugger->Enable();
+}
+
+void
+WorkerDebuggerManager::UnregisterDebuggerOnMainThread(WorkerDebugger* aDebugger)
+{
+  AssertIsOnMainThread();
+
+  MOZ_ASSERT(mDebuggers.Contains(aDebugger));
+  mDebuggers.RemoveElement(aDebugger);
+
+  nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> listeners;
+  {
+    MutexAutoLock lock(mMutex);
+
+    listeners.AppendElements(mListeners);
+  }
+
+  for (size_t index = 0; index < listeners.Length(); ++index) {
+    listeners[index]->OnUnregister(aDebugger);
+  }
+
+  aDebugger->Disable();
+}
+
+END_WORKERS_NAMESPACE
new file mode 100644
--- /dev/null
+++ b/dom/workers/WorkerDebuggerManager.h
@@ -0,0 +1,95 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_workerdebuggermanager_h
+#define mozilla_dom_workers_workerdebuggermanager_h
+
+#include "Workers.h"
+
+#include "nsIWorkerDebuggerManager.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+
+#define WORKERDEBUGGERMANAGER_CID \
+  { 0x62ec8731, 0x55ad, 0x4246, \
+    { 0xb2, 0xea, 0xf2, 0x6c, 0x1f, 0xe1, 0x9d, 0x2d } }
+#define WORKERDEBUGGERMANAGER_CONTRACTID \
+  "@mozilla.org/dom/workers/workerdebuggermanager;1"
+
+class RegisterDebuggerRunnable;
+
+BEGIN_WORKERS_NAMESPACE
+
+class WorkerDebugger;
+
+class WorkerDebuggerManager MOZ_FINAL : public nsIWorkerDebuggerManager
+{
+  friend class ::RegisterDebuggerRunnable;
+
+  mozilla::Mutex mMutex;
+
+  // Protected by mMutex.
+  nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> mListeners;
+
+  // Only touched on the main thread.
+  nsTArray<WorkerDebugger*> mDebuggers;
+
+public:
+  static WorkerDebuggerManager*
+  GetOrCreateService()
+  {
+    nsCOMPtr<nsIWorkerDebuggerManager> wdm =
+      do_GetService(WORKERDEBUGGERMANAGER_CONTRACTID);
+    return static_cast<WorkerDebuggerManager*>(wdm.get());
+  }
+
+  WorkerDebuggerManager();
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIWORKERDEBUGGERMANAGER
+
+  void RegisterDebugger(WorkerDebugger* aDebugger);
+
+  void UnregisterDebugger(WorkerDebugger* aDebugger);
+
+private:
+  virtual ~WorkerDebuggerManager();
+
+  void RegisterDebuggerOnMainThread(WorkerDebugger* aDebugger,
+                                    bool aHasListeners);
+
+  void UnregisterDebuggerOnMainThread(WorkerDebugger* aDebugger);
+};
+
+inline nsresult
+RegisterWorkerDebugger(WorkerDebugger* aDebugger)
+{
+  nsRefPtr<WorkerDebuggerManager> manager =
+    WorkerDebuggerManager::GetOrCreateService();
+  if (!manager) {
+    return NS_ERROR_FAILURE;
+  }
+
+  manager->RegisterDebugger(aDebugger);
+  return NS_OK;
+}
+
+inline nsresult
+UnregisterWorkerDebugger(WorkerDebugger* aDebugger)
+{
+  nsRefPtr<WorkerDebuggerManager> manager =
+    WorkerDebuggerManager::GetOrCreateService();
+  if (!manager) {
+    return NS_ERROR_FAILURE;
+  }
+
+  manager->UnregisterDebugger(aDebugger);
+  return NS_OK;
+}
+
+END_WORKERS_NAMESPACE
+
+#endif // mozilla_dom_workers_workerdebuggermanager_h
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -22,16 +22,17 @@
 #include "nsIScriptSecurityManager.h"
 #include "nsPerformance.h"
 #include "nsPIDOMWindow.h"
 #include "nsITextToSubURI.h"
 #include "nsIThreadInternal.h"
 #include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsIURL.h"
+#include "nsIWorkerDebugger.h"
 #include "nsIXPConnect.h"
 
 #include <algorithm>
 #include "jsfriendapi.h"
 #include "js/MemoryMetrics.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ContentEvents.h"
@@ -75,16 +76,17 @@
 
 #include "MessagePort.h"
 #include "Navigator.h"
 #include "Principal.h"
 #include "RuntimeService.h"
 #include "ScriptLoader.h"
 #include "ServiceWorkerManager.h"
 #include "SharedWorker.h"
+#include "WorkerDebuggerManager.h"
 #include "WorkerFeature.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 
 // JS_MaybeGC will run once every second during normal execution.
 #define PERIODIC_GC_TIMER_DELAY_SEC 1
 
 // A shrinking GC will run five seconds after the last event is processed.
@@ -651,16 +653,18 @@ private:
       new MainThreadReleaseRunnable(doomed, hostObjectURIs);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       NS_WARNING("Failed to dispatch, going to leak!");
     }
 
     RuntimeService* runtime = RuntimeService::GetService();
     NS_ASSERTION(runtime, "This should never be null!");
 
+    mFinishedWorker->DisableDebugger();
+
     runtime->UnregisterWorker(aCx, mFinishedWorker);
 
     mFinishedWorker->ClearSelfRef();
     return true;
   }
 };
 
 class TopLevelWorkerFinishedRunnable MOZ_FINAL : public nsRunnable
@@ -685,16 +689,18 @@ private:
     AssertIsOnMainThread();
 
     RuntimeService* runtime = RuntimeService::GetService();
     MOZ_ASSERT(runtime);
 
     AutoSafeJSContext cx;
     JSAutoRequest ar(cx);
 
+    mFinishedWorker->DisableDebugger();
+
     runtime->UnregisterWorker(cx, mFinishedWorker);
 
     nsTArray<nsCOMPtr<nsISupports> > doomed;
     mFinishedWorker->ForgetMainThreadObjects(doomed);
 
     nsTArray<nsCString> hostObjectURIs;
     mFinishedWorker->StealHostObjectURIs(hostObjectURIs);
 
@@ -2138,16 +2144,52 @@ WorkerPrivateParent<Derived>::DispatchPr
 
     mCondVar.Notify();
   }
 
   return NS_OK;
 }
 
 template <class Derived>
+void
+WorkerPrivateParent<Derived>::EnableDebugger()
+{
+  AssertIsOnParentThread();
+
+  WorkerPrivate* self = ParentAsWorkerPrivate();
+
+  MOZ_ASSERT(!self->mDebugger);
+  self->mDebugger = new WorkerDebugger(self);
+
+  if (NS_FAILED(RegisterWorkerDebugger(self->mDebugger))) {
+    NS_WARNING("Failed to register worker debugger!");
+    self->mDebugger = nullptr;
+  }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::DisableDebugger()
+{
+  AssertIsOnParentThread();
+
+  WorkerPrivate* self = ParentAsWorkerPrivate();
+
+  if (!self->mDebugger) {
+    return;
+  }
+
+  if (NS_FAILED(UnregisterWorkerDebugger(self->mDebugger))) {
+    NS_WARNING("Failed to unregister worker debugger!");
+  }
+
+  self->mDebugger = nullptr;
+}
+
+template <class Derived>
 nsresult
 WorkerPrivateParent<Derived>::DispatchControlRunnable(
                                   WorkerControlRunnable* aWorkerControlRunnable)
 {
   // May be called on any thread!
 
   MOZ_ASSERT(aWorkerControlRunnable);
 
@@ -3443,16 +3485,104 @@ WorkerPrivateParent<Derived>::AssertInne
   AssertIsOnMainThread();
 
   nsPIDOMWindow* outer = mLoadInfo.mWindow->GetOuterWindow();
   NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow,
                "Inner window no longer correct!");
 }
 
 #endif
+
+WorkerDebugger::WorkerDebugger(WorkerPrivate* aWorkerPrivate)
+: mMutex("WorkerDebugger::mMutex"),
+  mCondVar(mMutex, "WorkerDebugger::mCondVar"),
+  mWorkerPrivate(aWorkerPrivate),
+  mIsEnabled(false)
+{
+  mWorkerPrivate->AssertIsOnParentThread();
+}
+
+WorkerDebugger::~WorkerDebugger()
+{
+  MOZ_ASSERT(!mWorkerPrivate);
+}
+
+NS_IMPL_ISUPPORTS(WorkerDebugger, nsIWorkerDebugger)
+
+NS_IMETHODIMP
+WorkerDebugger::GetIsClosed(bool* aResult)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  *aResult = !mWorkerPrivate;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetUrl(nsAString& aResult)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mWorkerPrivate) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  aResult = mWorkerPrivate->ScriptURL();
+  return NS_OK;
+}
+
+void
+WorkerDebugger::WaitIsEnabled(bool aIsEnabled)
+{
+  MutexAutoLock lock(mMutex);
+
+  while (mIsEnabled != aIsEnabled) {
+    mCondVar.Wait();
+  }
+}
+
+void
+WorkerDebugger::NotifyIsEnabled(bool aIsEnabled)
+{
+  mMutex.AssertCurrentThreadOwns();
+
+  MOZ_ASSERT(mIsEnabled != aIsEnabled);
+  mIsEnabled = aIsEnabled;
+  mCondVar.Notify();
+}
+
+void
+WorkerDebugger::Enable()
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  MOZ_ASSERT(mWorkerPrivate);
+
+  NotifyIsEnabled(true);
+}
+
+void
+WorkerDebugger::Disable()
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  MOZ_ASSERT(mWorkerPrivate);
+  mWorkerPrivate = nullptr;
+
+  NotifyIsEnabled(false);
+}
+
 WorkerPrivate::WorkerPrivate(JSContext* aCx,
                              WorkerPrivate* aParent,
                              const nsAString& aScriptURL,
                              bool aIsChromeWorker, WorkerType aWorkerType,
                              const nsACString& aSharedWorkerName,
                              LoadInfo& aLoadInfo)
 : WorkerPrivateParent<WorkerPrivate>(aCx, aParent, aScriptURL,
                                      aIsChromeWorker, aWorkerType,
@@ -3616,16 +3746,18 @@ WorkerPrivate::Constructor(JSContext* aC
     new WorkerPrivate(aCx, parent, aScriptURL, aIsChromeWorker,
                       aWorkerType, aSharedWorkerName, *aLoadInfo);
 
   if (!runtimeService->RegisterWorker(aCx, worker)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
+  worker->EnableDebugger();
+
   nsRefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(worker);
   if (!compiler->Dispatch(aCx)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   worker->mSelfRef = worker;
 
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_workers_workerprivate_h__
 #define mozilla_dom_workers_workerprivate_h__
 
 #include "Workers.h"
 
 #include "nsIContentSecurityPolicy.h"
+#include "nsIWorkerDebugger.h"
 #include "nsPIDOMWindow.h"
 
 #include "mozilla/CondVar.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDataHashtable.h"
@@ -56,16 +57,17 @@ BEGIN_WORKERS_NAMESPACE
 
 class AutoSyncLoopHolder;
 class MessagePort;
 class SharedWorker;
 class WorkerControlRunnable;
 class WorkerGlobalScope;
 class WorkerPrivate;
 class WorkerRunnable;
+class WorkerDebugger;
 
 enum WorkerType
 {
   WorkerTypeDedicated,
   WorkerTypeShared,
   WorkerTypeService
 };
 
@@ -290,16 +292,22 @@ public:
   virtual JSObject*
   WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WorkerPrivateParent,
                                                          DOMEventTargetHelper)
 
   void
+  EnableDebugger();
+
+  void
+  DisableDebugger();
+
+  void
   ClearSelfRef()
   {
     AssertIsOnParentThread();
     MOZ_ASSERT(mSelfRef);
     mSelfRef = nullptr;
   }
 
   nsresult
@@ -716,16 +724,44 @@ public:
   { }
 
   void
   AssertInnerWindowIsCorrect() const
   { }
 #endif
 };
 
+class WorkerDebugger : public nsIWorkerDebugger {
+  mozilla::Mutex mMutex;
+  mozilla::CondVar mCondVar;
+
+  // Protected by mMutex
+  WorkerPrivate* mWorkerPrivate;
+  bool mIsEnabled;
+
+public:
+  WorkerDebugger(WorkerPrivate* aWorkerPrivate);
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIWORKERDEBUGGER
+
+  void AssertIsOnParentThread();
+
+  void WaitIsEnabled(bool aIsEnabled);
+
+  void Enable();
+
+  void Disable();
+
+private:
+  virtual ~WorkerDebugger();
+
+  void NotifyIsEnabled(bool aIsEnabled);
+};
+
 class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
 {
   friend class WorkerPrivateParent<WorkerPrivate>;
   typedef WorkerPrivateParent<WorkerPrivate> ParentType;
   friend class AutoSyncLoopHolder;
 
   struct TimeoutInfo;
 
@@ -734,16 +770,18 @@ class WorkerPrivate : public WorkerPriva
 
   enum GCTimerMode
   {
     PeriodicTimer = 0,
     IdleTimer,
     NoTimer
   };
 
+  nsRefPtr<WorkerDebugger> mDebugger;
+
   Queue<WorkerControlRunnable*, 4> mControlQueue;
 
   // Touched on multiple threads, protected with mMutex.
   JSContext* mJSContext;
   nsRefPtr<WorkerCrossThreadDispatcher> mCrossThreadDispatcher;
   nsTArray<nsCOMPtr<nsIRunnable>> mUndispatchedRunnablesForSyncLoop;
   nsCOMPtr<nsIThread> mThread;
 
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -11,16 +11,17 @@ EXPORTS.mozilla.dom += [
     'ServiceWorkerRegistration.h',
     'WorkerPrivate.h',
     'WorkerRunnable.h',
     'WorkerScope.h',
 ]
 
 EXPORTS.mozilla.dom.workers += [
     'ServiceWorkerManager.h',
+    'WorkerDebuggerManager.h',
     'Workers.h',
 ]
 
 # Stuff needed for the bindings, not really public though.
 EXPORTS.mozilla.dom.workers.bindings += [
     'DataStore.h',
     'DataStoreCursor.h',
     'FileReaderSync.h',
@@ -31,16 +32,23 @@ EXPORTS.mozilla.dom.workers.bindings += 
     'ServiceWorker.h',
     'SharedWorker.h',
     'URL.h',
     'WorkerFeature.h',
     'XMLHttpRequest.h',
     'XMLHttpRequestUpload.h',
 ]
 
+XPIDL_MODULE = 'dom_workers'
+
+XPIDL_SOURCES += [
+    'nsIWorkerDebugger.idl',
+    'nsIWorkerDebuggerManager.idl',
+]
+
 UNIFIED_SOURCES += [
     'ChromeWorkerScope.cpp',
     'DataStore.cpp',
     'DataStoreCursor.cpp',
     'FileReaderSync.cpp',
     'Location.cpp',
     'MessagePort.cpp',
     'Navigator.cpp',
@@ -51,16 +59,17 @@ UNIFIED_SOURCES += [
     'ScriptLoader.cpp',
     'ServiceWorker.cpp',
     'ServiceWorkerContainer.cpp',
     'ServiceWorkerEvents.cpp',
     'ServiceWorkerManager.cpp',
     'ServiceWorkerRegistration.cpp',
     'SharedWorker.cpp',
     'URL.cpp',
+    'WorkerDebuggerManager.cpp',
     'WorkerPrivate.cpp',
     'WorkerRunnable.cpp',
     'WorkerScope.cpp',
     'XMLHttpRequest.cpp',
     'XMLHttpRequestUpload.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
new file mode 100644
--- /dev/null
+++ b/dom/workers/nsIWorkerDebugger.idl
@@ -0,0 +1,9 @@
+#include "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(0833b363-bffe-4cdb-ad50-1c4563e0C8ff)]
+interface nsIWorkerDebugger : nsISupports
+{
+  readonly attribute bool isClosed;
+
+  readonly attribute DOMString url;
+};
new file mode 100644
--- /dev/null
+++ b/dom/workers/nsIWorkerDebuggerManager.idl
@@ -0,0 +1,22 @@
+#include "nsISupports.idl"
+
+interface nsISimpleEnumerator;
+interface nsIWorkerDebugger;
+
+[scriptable, uuid(d2aa74ee-6b98-4d5d-8173-4e23422daf1e)]
+interface nsIWorkerDebuggerManagerListener : nsISupports
+{
+  void onRegister(in nsIWorkerDebugger debugger);
+
+  void onUnregister(in nsIWorkerDebugger debugger);
+};
+
+[scriptable, builtinclass, uuid(056d7918-dc86-452a-b4e6-86da3405f015)]
+interface nsIWorkerDebuggerManager : nsISupports
+{
+  nsISimpleEnumerator getWorkerDebuggerEnumerator();
+
+  void addListener(in nsIWorkerDebuggerManagerListener listener);
+
+  void removeListener(in nsIWorkerDebuggerManagerListener listener);
+};
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerManager_childWorker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+onmessage = function () {};
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerManager_parentWorker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+var worker = new Worker("WorkerDebuggerManager_childWorker.js");
--- a/dom/workers/test/chrome.ini
+++ b/dom/workers/test/chrome.ini
@@ -1,30 +1,35 @@
 [DEFAULT]
 support-files =
+  WorkerDebuggerManager_childWorker.js
+  WorkerDebuggerManager_parentWorker.js
   WorkerTest.jsm
   WorkerTest_subworker.js
   WorkerTest_worker.js
   chromeWorker_subworker.js
   chromeWorker_worker.js
   dom_worker_helper.js
+  file_url.jsm
+  file_worker.js
   fileBlobSubWorker_worker.js
   fileBlob_worker.js
   filePosting_worker.js
   fileReadSlice_worker.js
   fileReaderSyncErrors_worker.js
   fileReaderSync_worker.js
   fileSlice_worker.js
   fileSubWorker_worker.js
   file_worker.js
   jsm_url_worker.js
   workersDisabled_worker.js
   file_url.jsm
   bug1062920_worker.js
 
+[test_WorkerDebuggerManager.xul]
 [test_bug883784.jsm]
 [test_bug883784.xul]
 [test_chromeWorker.xul]
 [test_chromeWorkerJSM.xul]
 [test_extension.xul]
 [test_extensionBootstrap.xul]
 [test_file.xul]
 [test_fileBlobPosting.xul]
--- a/dom/workers/test/dom_worker_helper.js
+++ b/dom/workers/test/dom_worker_helper.js
@@ -1,21 +1,102 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+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);
+
 var gRemainingTests = 0;
 
 function waitForWorkerFinish() {
   if (gRemainingTests == 0) {
     SimpleTest.waitForExplicitFinish();
   }
   ++gRemainingTests;
 }
 
 function finish() {
   --gRemainingTests;
   if (gRemainingTests == 0) {
     SimpleTest.finish();
   }
 }
 
+function assertThrows(fun, message) {
+  let throws = false;
+  try {
+    fun();
+  } catch (e) {
+    throws = true;
+  }
+  ok(throws, message);
+}
+
+function* generateDebuggers() {
+  let e = wdm.getWorkerDebuggerEnumerator();
+  while (e.hasMoreElements()) {
+    let dbg = e.getNext().QueryInterface(Ci.nsIWorkerDebugger);
+    yield dbg;
+  }
+}
+
+function findDebugger(predicate) {
+  for (let dbg of generateDebuggers()) {
+    if (predicate(dbg)) {
+      return dbg;
+    }
+  }
+  return null;
+}
+
+function waitForRegister(predicate = () => true) {
+  return new Promise(function (resolve) {
+    wdm.addListener({
+      onRegister: function (dbg) {
+        if (!predicate(dbg)) {
+          return;
+        }
+        wdm.removeListener(this);
+        resolve(dbg);
+      }
+    });
+  });
+}
+
+function waitForUnregister(predicate = () => true) {
+  return new Promise(function (resolve) {
+    wdm.addListener({
+      onUnregister: function (dbg) {
+        if (!predicate(dbg)) {
+          return;
+        }
+        wdm.removeListener(this);
+        resolve(dbg);
+      }
+    });
+  });
+}
+
+function waitForMultiple(promises) {
+  return new Promise(function (resolve) {
+    let results = [];
+    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);
+        }
+      });
+    }
+  });
+};
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerManager.xul
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebuggerManager"
+        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 PARENT_WORKER_URL = "WorkerDebuggerManager_parentWorker.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");
+
+        let promise = waitForMultiple([
+          waitForRegister((dbg) => dbg.url === PARENT_WORKER_URL),
+          waitForRegister((dbg) => dbg.url === 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");
+
+        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");
+
+        promise = waitForMultiple([
+          waitForUnregister((dbg) => dbg.url === CHILD_WORKER_URL),
+          waitForUnregister((dbg) => dbg.url === PARENT_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");
+
+        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");
+
+        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_extension.xul
+++ b/dom/workers/test/test_extension.xul
@@ -11,23 +11,16 @@
           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 Cc = Components.classes;
-    const Ci = Components.interfaces;
-    const Cu = Components.utils;
-
-    Cu.import("resource://gre/modules/Services.jsm");
-    Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
     function test() {
       const message = "woohoo";
 
       var workertest =
         Cc["@mozilla.org/test/workertest;1"].createInstance(Ci.nsIWorkerTest);
 
       workertest.callback = {
         onmessage: function(data) {
--- a/dom/workers/test/test_extensionBootstrap.xul
+++ b/dom/workers/test/test_extensionBootstrap.xul
@@ -11,24 +11,16 @@
           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 Cc = Components.classes;
-    const Ci = Components.interfaces;
-    const Cu = Components.utils;
-
-    Cu.import("resource://gre/modules/AddonManager.jsm");
-    Cu.import("resource://gre/modules/Services.jsm");
-    Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
     function test() {
       const message = "woohoo";
 
       var observer = {
         observe: function(subject, topic, data) {
           is(topic, "message", "Correct type of event");
           is(data, message, "Correct message");
 
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -85,16 +85,17 @@
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/DOMRequest.h"
 #include "mozilla/dom/network/TCPSocketChild.h"
 #include "mozilla/dom/network/TCPSocketParent.h"
 #include "mozilla/dom/network/TCPServerSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
+#include "mozilla/dom/workers/WorkerDebuggerManager.h"
 #include "mozilla/OSFileConstants.h"
 #include "mozilla/Services.h"
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/FakeSpeechRecognitionService.h"
 #include "mozilla/dom/nsSynthVoiceRegistry.h"
 #endif
 
@@ -243,16 +244,17 @@ static void Shutdown();
 #include "GMPService.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using mozilla::dom::alarm::AlarmHalService;
 using mozilla::dom::power::PowerManagerService;
 using mozilla::dom::quota::QuotaManager;
 using mozilla::dom::workers::ServiceWorkerManager;
+using mozilla::dom::workers::WorkerDebuggerManager;
 using mozilla::dom::TCPSocketChild;
 using mozilla::dom::TCPSocketParent;
 using mozilla::dom::TCPServerSocketChild;
 using mozilla::dom::UDPSocketChild;
 using mozilla::dom::time::TimeService;
 using mozilla::net::StreamingProtocolControllerService;
 using mozilla::gmp::GeckoMediaPluginService;
 
@@ -282,16 +284,18 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(Exception
 NS_GENERIC_FACTORY_CONSTRUCTOR(DOMSessionStorageManager)
 NS_GENERIC_FACTORY_CONSTRUCTOR(DOMLocalStorageManager)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DOMRequestService,
                                          DOMRequestService::FactoryCreate)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(QuotaManager,
                                          QuotaManager::FactoryCreate)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ServiceWorkerManager,
                                          ServiceWorkerManager::FactoryCreate)
+NS_GENERIC_FACTORY_CONSTRUCTOR(WorkerDebuggerManager)
+
 #ifdef MOZ_WIDGET_GONK
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(SystemWorkerManager,
                                          SystemWorkerManager::FactoryCreate)
 #endif
 #ifdef MOZ_B2G_BT
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(BluetoothService,
                                          BluetoothService::FactoryCreate)
 #endif
@@ -710,16 +714,17 @@ NS_DEFINE_NAMED_CID(NS_XMLHTTPREQUEST_CI
 NS_DEFINE_NAMED_CID(NS_DOMPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMSESSIONSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMLOCALSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMJSON_CID);
 NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID);
 NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID);
 NS_DEFINE_NAMED_CID(QUOTA_MANAGER_CID);
 NS_DEFINE_NAMED_CID(SERVICEWORKERMANAGER_CID);
+NS_DEFINE_NAMED_CID(WORKERDEBUGGERMANAGER_CID);
 #ifdef MOZ_WIDGET_GONK
 NS_DEFINE_NAMED_CID(SYSTEMWORKERMANAGER_CID);
 #endif
 #ifdef MOZ_B2G_BT
 NS_DEFINE_NAMED_CID(BLUETOOTHSERVICE_CID);
 #endif
 #ifdef MOZ_WIDGET_GONK
 NS_DEFINE_NAMED_CID(NS_AUDIOMANAGER_CID);
@@ -1000,16 +1005,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_XPCEXCEPTION_CID, false, nullptr, ExceptionConstructor },
   { &kNS_DOMSESSIONSTORAGEMANAGER_CID, false, nullptr, DOMSessionStorageManagerConstructor },
   { &kNS_DOMLOCALSTORAGEMANAGER_CID, false, nullptr, DOMLocalStorageManagerConstructor },
   { &kNS_DOMJSON_CID, false, nullptr, NS_NewJSON },
   { &kNS_TEXTEDITOR_CID, false, nullptr, nsPlaintextEditorConstructor },
   { &kDOMREQUEST_SERVICE_CID, false, nullptr, DOMRequestServiceConstructor },
   { &kQUOTA_MANAGER_CID, false, nullptr, QuotaManagerConstructor },
   { &kSERVICEWORKERMANAGER_CID, false, nullptr, ServiceWorkerManagerConstructor },
+  { &kWORKERDEBUGGERMANAGER_CID, true, nullptr, WorkerDebuggerManagerConstructor },
 #ifdef MOZ_WIDGET_GONK
   { &kSYSTEMWORKERMANAGER_CID, true, nullptr, SystemWorkerManagerConstructor },
 #endif
 #ifdef MOZ_B2G_BT
   { &kBLUETOOTHSERVICE_CID, true, nullptr, BluetoothServiceConstructor },
 #endif
 #ifdef MOZ_WIDGET_GONK
   { &kNS_AUDIOMANAGER_CID, true, nullptr, AudioManagerConstructor },
@@ -1157,16 +1163,17 @@ static const mozilla::Module::ContractID
   // Keeping the old ContractID for backward compatibility
   { "@mozilla.org/dom/storagemanager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID },
   { "@mozilla.org/dom/sessionStorage-manager;1", &kNS_DOMSESSIONSTORAGEMANAGER_CID },
   { "@mozilla.org/dom/json;1", &kNS_DOMJSON_CID },
   { "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID },
   { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID },
   { QUOTA_MANAGER_CONTRACTID, &kQUOTA_MANAGER_CID },
   { SERVICEWORKERMANAGER_CONTRACTID, &kSERVICEWORKERMANAGER_CID },
+  { WORKERDEBUGGERMANAGER_CONTRACTID, &kWORKERDEBUGGERMANAGER_CID },
 #ifdef MOZ_WIDGET_GONK
   { SYSTEMWORKERMANAGER_CONTRACTID, &kSYSTEMWORKERMANAGER_CID },
 #endif
 #ifdef MOZ_B2G_BT
   { BLUETOOTHSERVICE_CONTRACTID, &kBLUETOOTHSERVICE_CID },
 #endif
 #ifdef MOZ_WIDGET_GONK
   { NS_AUDIOMANAGER_CONTRACTID, &kNS_AUDIOMANAGER_CID },