Bug 1092102 - Implement WorkerDebuggerGlobalScope.enterEventLoop;r=khuey
authorEddy Bruël <ejpbruel@gmail.com>
Fri, 27 Mar 2015 07:17:16 +0100
changeset 266389 3b30137b0f6aef6f8b17bae314a175f727a66e66
parent 266388 cc3988bc43b6a6d86a4a02df13eb4f06bb54dcee
child 266390 1c4030c686d6cf5d143be795c6228368729581c5
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs1092102
milestone39.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 1092102 - Implement WorkerDebuggerGlobalScope.enterEventLoop;r=khuey
dom/webidl/WorkerDebuggerGlobalScope.webidl
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
dom/workers/WorkerScope.cpp
dom/workers/WorkerScope.h
dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js
dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js
dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js
dom/workers/test/chrome.ini
dom/workers/test/test_WorkerDebugger.postMessage.xul
dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xul
--- a/dom/webidl/WorkerDebuggerGlobalScope.webidl
+++ b/dom/webidl/WorkerDebuggerGlobalScope.webidl
@@ -2,16 +2,20 @@
 /* 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/. */
 
 [Global=(WorkerDebugger), Exposed=WorkerDebugger]
 interface WorkerDebuggerGlobalScope : EventTarget {
   readonly attribute object global;
 
+  void enterEventLoop();
+
+  void leaveEventLoop();
+
   void postMessage(DOMString message);
 
   attribute EventHandler onmessage;
 
   void reportError(DOMString message);
 };
 
 // So you can debug while you debug
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4578,16 +4578,17 @@ WorkerPrivate::WorkerPrivate(JSContext* 
                              bool aIsChromeWorker, WorkerType aWorkerType,
                              const nsACString& aSharedWorkerName,
                              WorkerLoadInfo& aLoadInfo)
   : WorkerPrivateParent<WorkerPrivate>(aCx, aParent, aScriptURL,
                                        aIsChromeWorker, aWorkerType,
                                        aSharedWorkerName, aLoadInfo)
   , mJSContext(nullptr)
   , mPRThread(nullptr)
+  , mDebuggerEventLoopLevel(0)
   , mErrorHandlerRecursionCount(0)
   , mNextTimeoutId(1)
   , mStatus(Pending)
   , mSuspended(false)
   , mTimerRunning(false)
   , mRunningExpiredTimeouts(false)
   , mCloseHandlerStarted(false)
   , mCloseHandlerFinished(false)
@@ -6182,16 +6183,86 @@ WorkerPrivate::PostMessageToParentMessag
     return;
   }
 
   PostMessageToParentInternal(aCx, aMessage, aTransferable, true,
                               aMessagePortSerial, aRv);
 }
 
 void
+WorkerPrivate::EnterDebuggerEventLoop()
+{
+  AssertIsOnWorkerThread();
+
+  JSContext* cx = GetJSContext();
+  MOZ_ASSERT(cx);
+
+  uint32_t currentEventLoopLevel = ++mDebuggerEventLoopLevel;
+
+  while (currentEventLoopLevel <= mDebuggerEventLoopLevel) {
+    bool debuggerRunnablesPending = false;
+
+    {
+      MutexAutoLock lock(mMutex);
+
+      debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
+    }
+
+    // Don't block with the periodic GC timer running.
+    if (!debuggerRunnablesPending) {
+      SetGCTimerMode(IdleTimer);
+    }
+
+    // Wait for something to do
+    {
+      MutexAutoLock lock(mMutex);
+
+      while (mControlQueue.IsEmpty() &&
+             !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty())) {
+        WaitForWorkerEvents();
+      }
+
+      ProcessAllControlRunnablesLocked();
+    }
+
+    if (debuggerRunnablesPending) {
+      // Start the periodic GC timer if it is not already running.
+      SetGCTimerMode(PeriodicTimer);
+
+      WorkerRunnable* runnable;
+
+      {
+        MutexAutoLock lock(mMutex);
+
+        mDebuggerQueue.Pop(runnable);
+      }
+
+      MOZ_ASSERT(runnable);
+      static_cast<nsIRunnable*>(runnable)->Run();
+      runnable->Release();
+
+      // Now *might* be a good time to GC. Let the JS engine make the decision.
+      JS_MaybeGC(cx);
+    }
+  }
+}
+
+void
+WorkerPrivate::LeaveDebuggerEventLoop()
+{
+  AssertIsOnWorkerThread();
+
+  MutexAutoLock lock(mMutex);
+
+  if (mDebuggerEventLoopLevel > 0) {
+    --mDebuggerEventLoopLevel;
+  }
+}
+
+void
 WorkerPrivate::PostMessageToDebugger(const nsAString& aMessage)
 {
   mDebugger->PostMessageToDebugger(aMessage);
 }
 
 void
 WorkerPrivate::ReportErrorToDebugger(const nsAString& aFilename,
                                      uint32_t aLineno,
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -810,16 +810,17 @@ class WorkerPrivate : public WorkerPriva
   PRThread* mPRThread;
 
   // Things touched on worker thread only.
   nsRefPtr<WorkerGlobalScope> mScope;
   nsRefPtr<WorkerDebuggerGlobalScope> mDebuggerScope;
   nsTArray<ParentType*> mChildWorkers;
   nsTArray<WorkerFeature*> mFeatures;
   nsTArray<nsAutoPtr<TimeoutInfo>> mTimeouts;
+  uint32_t mDebuggerEventLoopLevel;
 
   struct SyncLoopInfo
   {
     explicit SyncLoopInfo(EventTarget* aEventTarget);
 
     nsRefPtr<EventTarget> mEventTarget;
     bool mCompleted;
     bool mResult;
@@ -971,16 +972,22 @@ public:
   PostMessageToParentMessagePort(
                              JSContext* aCx,
                              uint64_t aMessagePortSerial,
                              JS::Handle<JS::Value> aMessage,
                              const Optional<Sequence<JS::Value>>& aTransferable,
                              ErrorResult& aRv);
 
   void
+  EnterDebuggerEventLoop();
+
+  void
+  LeaveDebuggerEventLoop();
+
+  void
   PostMessageToDebugger(const nsAString& aMessage);
 
   void
   ReportErrorToDebugger(const nsAString& aFilename, uint32_t aLineno,
                         const nsAString& aMessage);
 
   bool
   NotifyInternal(JSContext* aCx, Status aStatus);
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -509,16 +509,28 @@ WorkerDebuggerGlobalScope::WrapGlobalObj
 void
 WorkerDebuggerGlobalScope::GetGlobal(JSContext* aCx,
                                      JS::MutableHandle<JSObject*> aGlobal)
 {
   aGlobal.set(mWorkerPrivate->GetOrCreateGlobalScope(aCx)->GetWrapper());
 }
 
 void
+WorkerDebuggerGlobalScope::EnterEventLoop()
+{
+  mWorkerPrivate->EnterDebuggerEventLoop();
+}
+
+void
+WorkerDebuggerGlobalScope::LeaveEventLoop()
+{
+  mWorkerPrivate->LeaveDebuggerEventLoop();
+}
+
+void
 WorkerDebuggerGlobalScope::PostMessage(const nsAString& aMessage)
 {
   mWorkerPrivate->PostMessageToDebugger(aMessage);
 }
 
 void
 WorkerDebuggerGlobalScope::ReportError(JSContext* aCx,
                                        const nsAString& aMessage)
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -260,16 +260,22 @@ public:
   {
     return GetWrapper();
   }
 
   void
   GetGlobal(JSContext* aCx, JS::MutableHandle<JSObject*> aGlobal);
 
   void
+  EnterEventLoop();
+
+  void
+  LeaveEventLoop();
+
+  void
   PostMessage(const nsAString& aMessage);
 
   IMPL_EVENT_HANDLER(message)
 
   void
   ReportError(JSContext* aCx, const nsAString& aMessage);
 
   void
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js
@@ -0,0 +1,14 @@
+"use strict";
+
+function f() {
+  debugger;
+}
+
+self.onmessage = function (event) {
+  switch (event.data) {
+  case "ping":
+    debugger;
+    postMessage("pong");
+    break;
+  };
+};
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js
@@ -0,0 +1,29 @@
+"use strict";
+
+let frames = [];
+
+var dbg = new Debugger(global);
+dbg.onDebuggerStatement = function (frame) {
+  frames.push(frame);
+  postMessage("paused");
+  enterEventLoop();
+  frames.pop();
+  postMessage("resumed");
+};
+
+this.onmessage = function (event) {
+  switch (event.data) {
+  case "eval":
+    frames[frames.length - 1].eval("f()");
+    postMessage("evalled");
+    break;
+
+  case "ping":
+    postMessage("pong");
+    break;
+
+  case "resume":
+    leaveEventLoop();
+    break;
+  };
+};
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js
@@ -0,0 +1,25 @@
+"use strict";
+
+function f() {
+  debugger;
+}
+
+var worker = new Worker("WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js");
+
+worker.onmessage = function (event) {
+  postMessage("child:" + event.data);
+};
+
+self.onmessage = function (event) {
+  var message = event.data;
+  if (message.indexOf(":") >= 0) {
+    worker.postMessage(message.split(":")[1]);
+    return;
+  }
+  switch (message) {
+  case "ping":
+    debugger;
+    postMessage("pong");
+    break;
+  };
+};
--- a/dom/workers/test/chrome.ini
+++ b/dom/workers/test/chrome.ini
@@ -2,16 +2,19 @@
 skip-if = buildapp == 'b2g'
 support-files =
   WorkerDebugger.initialize_childWorker.js
   WorkerDebugger.initialize_debugger.js
   WorkerDebugger.initialize_worker.js
   WorkerDebugger.postMessage_childWorker.js
   WorkerDebugger.postMessage_debugger.js
   WorkerDebugger.postMessage_worker.js
+  WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js
+  WorkerDebuggerGlobalScope.enterEventLoop_debugger.js
+  WorkerDebuggerGlobalScope.enterEventLoop_worker.js
   WorkerDebuggerGlobalScope.reportError_childWorker.js
   WorkerDebuggerGlobalScope.reportError_debugger.js
   WorkerDebuggerGlobalScope.reportError_worker.js
   WorkerDebuggerManager_childWorker.js
   WorkerDebuggerManager_worker.js
   WorkerDebugger_childWorker.js
   WorkerDebugger_worker.js
   WorkerDebugger_sharedWorker.js
@@ -35,16 +38,17 @@ support-files =
   jsm_url_worker.js
   workersDisabled_worker.js
   file_url.jsm
   bug1062920_worker.js
 
 [test_WorkerDebugger.xul]
 [test_WorkerDebugger.initialize.xul]
 [test_WorkerDebugger.postMessage.xul]
+[test_WorkerDebuggerGlobalScope.enterEventLoop.xul]
 [test_WorkerDebuggerGlobalScope.reportError.xul]
 [test_WorkerDebuggerManager.xul]
 [test_bug883784.jsm]
 [test_bug883784.xul]
 [test_chromeWorker.xul]
 [test_chromeWorkerJSM.xul]
 [test_extension.xul]
 [test_extensionBootstrap.xul]
--- a/dom/workers/test/test_WorkerDebugger.postMessage.xul
+++ b/dom/workers/test/test_WorkerDebugger.postMessage.xul
@@ -28,24 +28,24 @@
              "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);
         let [dbg, childDbg] = yield promise;
 
-        info("Check that posting a ping message to the worker debugger " +
-             "results in a pong message being received.");
+        info("Send a request to the worker debugger. This should cause a " +
+             "response to be received from the worker debugger.");
         promise = waitForDebuggerMessage(dbg, "pong");
         dbg.postMessage("ping");
         yield promise;
 
-        info("Check that posting a ping message to the child worker " +
-             "debugger results in a pong message being received.");
+        info("Send a request to the child worker debugger. This should cause " +
+             "a response to be received from the child worker debugger.");
         promise = waitForDebuggerMessage(childDbg, "pong");
         childDbg.postMessage("ping");
         yield promise;
 
         SimpleTest.finish();
       });
     }
 
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xul
@@ -0,0 +1,124 @@
+<?xml version="1.0"?>
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebuggerGlobalScope.enterEventLoop"
+        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 = "WorkerDebuggerGlobalScope.enterEventLoop_worker.js";
+    const CHILD_WORKER_URL = "WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js";
+    const DEBUGGER_URL = BASE_URL + "WorkerDebuggerGlobalScope.enterEventLoop_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);
+        let [dbg, childDbg] = yield promise;
+
+        info("Send a request to the child worker. This should cause a nested " +
+             "event loop to be entered.");
+        promise = waitForDebuggerMessage(childDbg, "paused");
+        worker.postMessage("child:ping");
+        yield promise;
+
+        info("Send a request to the child worker debugger. This should cause " +
+             "a second nested event loop to be entered.");
+        promise = waitForDebuggerMessage(childDbg, "paused");
+        childDbg.postMessage("eval");
+        yield promise;
+
+        info("Send a request to the child worker debugger. This should cause " +
+             "the second nested event loop to be left. Check that a response " +
+             "for the previous request is not received from the child worker " +
+             "debugger until after the event loop is left.");
+        promise = waitForMultiple([
+          waitForDebuggerMessage(childDbg, "resumed"),
+          waitForDebuggerMessage(childDbg, "evalled")
+        ]);
+        childDbg.postMessage("resume");
+        yield promise;
+
+        info("Send a request to the child worker debugger. This should cause " +
+             "the first nested event loop to be left. Check that a response " +
+             "for the previous request is not received from the child worker " +
+             "until after the event loop is left.");
+        promise = waitForMultiple([
+          waitForDebuggerMessage(childDbg, "resumed"),
+          waitForWorkerMessage(worker, "child:pong")
+        ]);
+        childDbg.postMessage("resume");
+        yield promise;
+
+        info("Send a request to the worker. This should cause a nested event " +
+             "loop to be entered.");
+        promise = waitForDebuggerMessage(dbg, "paused");
+        worker.postMessage("ping");
+        yield promise;
+
+        info("Terminate the worker. This should not cause the worker " +
+             "debugger to stop running.");
+        worker.terminate();
+
+        worker.onmessage = function () {
+          ok(false, "Worker should have been terminated.");
+        };
+
+        info("Send a request to the worker debugger. This should cause a " +
+             "second nested event loop to be entered.");
+        promise = waitForDebuggerMessage(dbg, "paused");
+        dbg.postMessage("eval");
+        yield promise;
+
+        info("Send a request to the worker debugger. This should cause the " +
+             "second nested event loop to be left. Check that a response for " +
+             "the previous request is not received from the worker " +
+             "debugger until after the event loop is left.");
+        promise = waitForMultiple([
+          waitForDebuggerMessage(dbg, "resumed"),
+          waitForDebuggerMessage(dbg, "evalled")
+        ]);
+        dbg.postMessage("resume");
+        yield promise;
+
+        info("Send a request to the child worker debugger. This should cause " +
+             "the first nested event loop to be left. No response for the " +
+             "previous request should be received from the worker debugger, " +
+             "since it has been terminated.");
+        promise = waitForMultiple([
+          waitForDebuggerMessage(dbg, "resumed"),
+        ]);
+        dbg.postMessage("resume");
+        yield promise;
+
+        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>