Bug 757133 - Implement a WorkerDebugger;r=khuey
authorEddy Bruël <ejpbruel@gmail.com>
Wed, 29 Oct 2014 21:11:33 +0100
changeset 213012 e30de6729affd6aa103bbccb40be280af22cad93
parent 213011 0f69fbc0d65d60b169b4d1d3f87359c753344a5e
child 213013 a009be3f978a5493b4451ed51caf640d6c5bb520
push id27738
push usercbook@mozilla.com
push dateThu, 30 Oct 2014 13:46:07 +0000
treeherdermozilla-central@1aa1b23d799e [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 WorkerDebugger;r=khuey
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
dom/workers/nsIWorkerDebugger.idl
dom/workers/test/WorkerDebugger_childWorker.js
dom/workers/test/WorkerDebugger_parentWorker.js
dom/workers/test/WorkerDebugger_sharedWorker.js
dom/workers/test/chrome.ini
dom/workers/test/dom_worker_helper.js
dom/workers/test/test_WorkerDebugger.xul
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -2176,18 +2176,16 @@ WorkerPrivateParent<Derived>::DisableDeb
 
   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!
@@ -3499,46 +3497,163 @@ WorkerDebugger::WorkerDebugger(WorkerPri
   mIsEnabled(false)
 {
   mWorkerPrivate->AssertIsOnParentThread();
 }
 
 WorkerDebugger::~WorkerDebugger()
 {
   MOZ_ASSERT(!mWorkerPrivate);
+  MOZ_ASSERT(!mIsEnabled);
+
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIThread> mainThread;
+    if (NS_FAILED(NS_GetMainThread(getter_AddRefs(mainThread)))) {
+      NS_WARNING("Failed to proxy release of listeners, leaking instead!");
+    }
+
+    for (size_t index = 0; index < mListeners.Length(); ++index) {
+      nsIWorkerDebuggerListener* listener = nullptr;
+      mListeners[index].forget(&listener);
+      if (NS_FAILED(NS_ProxyRelease(mainThread, listener))) {
+        NS_WARNING("Failed to proxy release of listener, leaking instead!");
+      }
+    }
+  }
 }
 
 NS_IMPL_ISUPPORTS(WorkerDebugger, nsIWorkerDebugger)
 
 NS_IMETHODIMP
 WorkerDebugger::GetIsClosed(bool* aResult)
 {
   AssertIsOnMainThread();
 
   MutexAutoLock lock(mMutex);
 
   *aResult = !mWorkerPrivate;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+WorkerDebugger::GetIsChrome(bool* aResult)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mWorkerPrivate) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  *aResult = mWorkerPrivate->IsChromeWorker();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetParent(nsIWorkerDebugger** aResult)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mWorkerPrivate) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  WorkerPrivate* parent = mWorkerPrivate->GetParent();
+  if (!parent) {
+    *aResult = nullptr;
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(mWorkerPrivate->IsDedicatedWorker());
+
+  nsCOMPtr<nsIWorkerDebugger> debugger = parent->Debugger();
+  debugger.forget(aResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetType(uint32_t* aResult)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mWorkerPrivate) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  *aResult = mWorkerPrivate->Type();
+  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;
 }
 
+NS_IMETHODIMP
+WorkerDebugger::GetWindow(nsIDOMWindow** aResult)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mWorkerPrivate) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (mWorkerPrivate->GetParent() || !mWorkerPrivate->IsDedicatedWorker()) {
+    *aResult = nullptr;
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsPIDOMWindow> window = mWorkerPrivate->GetWindow();
+  window.forget(aResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::AddListener(nsIWorkerDebuggerListener* aListener)
+{
+  AssertIsOnMainThread();
+
+  if (mListeners.Contains(aListener)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  mListeners.AppendElement(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener* aListener)
+{
+  AssertIsOnMainThread();
+
+  if (!mListeners.Contains(aListener)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  mListeners.RemoveElement(aListener);
+  return NS_OK;
+}
+
 void
 WorkerDebugger::WaitIsEnabled(bool aIsEnabled)
 {
   MutexAutoLock lock(mMutex);
 
   while (mIsEnabled != aIsEnabled) {
     mCondVar.Wait();
   }
@@ -3571,16 +3686,25 @@ WorkerDebugger::Disable()
 {
   AssertIsOnMainThread();
 
   MutexAutoLock lock(mMutex);
 
   MOZ_ASSERT(mWorkerPrivate);
   mWorkerPrivate = nullptr;
 
+  {
+    MutexAutoUnlock unlock(mMutex);
+
+    nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
+    for (size_t index = 0; index < listeners.Length(); ++index) {
+      listeners[index]->OnClose();
+    }
+  }
+
   NotifyIsEnabled(false);
 }
 
 WorkerPrivate::WorkerPrivate(JSContext* aCx,
                              WorkerPrivate* aParent,
                              const nsAString& aScriptURL,
                              bool aIsChromeWorker, WorkerType aWorkerType,
                              const nsACString& aSharedWorkerName,
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -59,16 +59,18 @@ class AutoSyncLoopHolder;
 class MessagePort;
 class SharedWorker;
 class WorkerControlRunnable;
 class WorkerGlobalScope;
 class WorkerPrivate;
 class WorkerRunnable;
 class WorkerDebugger;
 
+// If you change this, the corresponding list in nsIWorkerDebugger.idl needs to
+// be updated too.
 enum WorkerType
 {
   WorkerTypeDedicated,
   WorkerTypeShared,
   WorkerTypeService
 };
 
 // SharedMutex is a small wrapper around an (internal) reference-counted Mutex
@@ -658,16 +660,22 @@ public:
   // The ability to be a chrome worker is orthogonal to the type of
   // worker [Dedicated|Shared|Service].
   bool
   IsChromeWorker() const
   {
     return mIsChromeWorker;
   }
 
+  WorkerType
+  Type() const
+  {
+    return mWorkerType;
+  }
+
   bool
   IsDedicatedWorker() const
   {
     return mWorkerType == WorkerTypeDedicated;
   }
 
   bool
   IsSharedWorker() const
@@ -732,16 +740,19 @@ public:
 class WorkerDebugger : public nsIWorkerDebugger {
   mozilla::Mutex mMutex;
   mozilla::CondVar mCondVar;
 
   // Protected by mMutex
   WorkerPrivate* mWorkerPrivate;
   bool mIsEnabled;
 
+  // Only touched on the main thread.
+  nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> mListeners;
+
 public:
   explicit WorkerDebugger(WorkerPrivate* aWorkerPrivate);
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIWORKERDEBUGGER
 
   void AssertIsOnParentThread();
 
@@ -863,16 +874,24 @@ public:
   static bool
   WorkerAvailable(JSContext* /* unused */, JSObject* /* unused */);
 
   static nsresult
   GetLoadInfo(JSContext* aCx, nsPIDOMWindow* aWindow, WorkerPrivate* aParent,
               const nsAString& aScriptURL, bool aIsChromeWorker,
               LoadInfo* aLoadInfo);
 
+  WorkerDebugger*
+  Debugger() const
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mDebugger);
+    return mDebugger;
+  }
+
   void
   DoRunLoop(JSContext* aCx);
 
   bool
   InterruptCallback(JSContext* aCx);
 
   nsresult
   IsOnCurrentThread(bool* aIsOnCurrentThread);
--- a/dom/workers/nsIWorkerDebugger.idl
+++ b/dom/workers/nsIWorkerDebugger.idl
@@ -1,9 +1,33 @@
 #include "nsISupports.idl"
 
+interface nsIDOMWindow;
+
+[scriptable, uuid(54fd2dd3-c01b-4f71-888f-462f37a54f57)]
+interface nsIWorkerDebuggerListener : nsISupports
+{
+  void onClose();
+};
+
 [scriptable, builtinclass, uuid(0833b363-bffe-4cdb-ad50-1c4563e0C8ff)]
 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;
 
+  readonly attribute bool isChrome;
+
+  readonly attribute nsIWorkerDebugger parent;
+
+  readonly attribute unsigned long type;
+
   readonly attribute DOMString url;
+
+  readonly attribute nsIDOMWindow window;
+
+  void addListener(in nsIWorkerDebuggerListener listener);
+
+  void removeListener(in nsIWorkerDebuggerListener listener);
 };
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_childWorker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+onmessage = function () {};
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_parentWorker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+var worker = new Worker("WorkerDebugger_childWorker.js");
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_sharedWorker.js
@@ -0,0 +1,11 @@
+"use strict";
+
+onconnect = function (event) {
+  event.ports[0].onmessage = function (event) {
+    switch (event.data) {
+    case "close":
+      close();
+      break;
+    }
+  };
+};
--- a/dom/workers/test/chrome.ini
+++ b/dom/workers/test/chrome.ini
@@ -1,12 +1,15 @@
 [DEFAULT]
 support-files =
   WorkerDebuggerManager_childWorker.js
   WorkerDebuggerManager_parentWorker.js
+  WorkerDebugger_childWorker.js
+  WorkerDebugger_parentWorker.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
   file_worker.js
@@ -19,16 +22,17 @@ support-files =
   fileSlice_worker.js
   fileSubWorker_worker.js
   file_worker.js
   jsm_url_worker.js
   workersDisabled_worker.js
   file_url.jsm
   bug1062920_worker.js
 
+[test_WorkerDebugger.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
@@ -79,16 +79,30 @@ function waitForUnregister(predicate = (
         }
         wdm.removeListener(this);
         resolve(dbg);
       }
     });
   });
 }
 
+function waitForDebuggerClose(dbg, predicate = () => true) {
+  return new Promise(function (resolve) {
+    dbg.addListener({
+      onClose: function () {
+        if (!predicate()) {
+          return;
+        }
+        dbg.removeListener(this);
+        resolve();
+      }
+    });
+  });
+}
+
 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");
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger.xul
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebugger"
+        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 = "WorkerDebugger_parentWorker.js";
+    const CHILD_WORKER_URL = "WorkerDebugger_childWorker.js";
+    const SHARED_WORKER_URL = "WorkerDebugger_sharedWorker.js";
+
+    function test() {
+      Task.spawn(function* () {
+        SimpleTest.waitForExplicitFinish();
+
+        let promise = waitForMultiple([
+          waitForRegister((dbg) => dbg.url === PARENT_WORKER_URL),
+          waitForRegister((dbg) => dbg.url === 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");
+
+        promise = waitForMultiple([
+          waitForUnregister((dbg) => dbg.url === CHILD_WORKER_URL),
+          waitForDebuggerClose(dbgs[1]),
+          waitForUnregister((dbg) => dbg.url === PARENT_WORKER_URL),
+          waitForDebuggerClose(dbgs[0]),
+        ]);
+        worker.terminate();
+        yield promise;
+
+        promise = waitForRegister();
+        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 listener = {
+          onRegistered: function () {
+            ok(false,
+               "debugger for shared worker should not be registered twice");
+          },
+        };
+        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();
+          }
+        });
+        worker.port.start();
+        worker.port.postMessage("close");
+      });
+    }
+
+  ]]>
+  </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>