Fix testshell to use the new callback commands, and shut down properly
authorBen Turner <bent.mozilla@gmail.com>
Tue, 25 Aug 2009 16:07:22 -0700
changeset 35869 5be0cf05da79c683b6c25edeb3656add9890a5dc
parent 35868 c3b494310a9ff401ca9c2bbbf14666a5bc7a18a1
child 35870 cd905e6a8261fe2add944c18986fdf0479366c8c
push idunknown
push userunknown
push dateunknown
milestone1.9.2a1pre
Fix testshell to use the new callback commands, and shut down properly
dom/ipc/ContentProcessChild.cpp
dom/ipc/ContentProcessChild.h
ipc/chromium/src/base/message_loop.h
ipc/glue/AsyncChannel.cpp
ipc/glue/MessagePump.cpp
ipc/testshell/TestShell.ipdl
ipc/testshell/TestShellChild.cpp
ipc/testshell/TestShellChild.h
ipc/testshell/TestShellCommand.ipdl
ipc/testshell/TestShellParent.cpp
ipc/testshell/TestShellParent.h
ipc/testshell/XPCShellEnvironment.cpp
ipc/testshell/XPCShellEnvironment.h
ipc/testshell/ipdl.mk
toolkit/xre/nsEmbedFunctions.cpp
--- a/dom/ipc/ContentProcessChild.cpp
+++ b/dom/ipc/ContentProcessChild.cpp
@@ -1,16 +1,20 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: sw=4 ts=4 et : */
 
 #include "ContentProcessChild.h"
 #include "TabChild.h"
 
 #include "mozilla/ipc/TestShellChild.h"
 
+#include "mozilla/XPCOM.h"
+#include "nsXPFEComponentsCID.h"
+#include "nsIAppStartup.h"
+
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace dom {
 
 ContentProcessChild* ContentProcessChild::sSingleton;
 
 ContentProcessChild::ContentProcessChild()
@@ -30,33 +34,55 @@ ContentProcessChild::Init(MessageLoop* a
     sSingleton = this;
 
     return true;
 }
 
 IFrameEmbeddingProtocolChild*
 ContentProcessChild::IFrameEmbeddingConstructor(const MagicWindowHandle& hwnd)
 {
-    return new TabChild(hwnd);
+    IFrameEmbeddingProtocolChild* iframe = new TabChild(hwnd);
+    if (iframe && mIFrames.AppendElement(iframe)) {
+        return iframe;
+    }
+    delete iframe;
+    return nsnull;
 }
 
 nsresult
 ContentProcessChild::IFrameEmbeddingDestructor(IFrameEmbeddingProtocolChild* iframe)
 {
-    delete iframe;
+    mIFrames.RemoveElement(iframe);
     return NS_OK;
 }
 
 TestShellProtocolChild*
 ContentProcessChild::TestShellConstructor()
 {
-  return new TestShellChild();
+    TestShellProtocolChild* testshell = new TestShellChild();
+    if (testshell && mTestShells.AppendElement(testshell)) {
+        return testshell;
+    }
+    delete testshell;
+    return nsnull;
 }
 
 nsresult
 ContentProcessChild::TestShellDestructor(TestShellProtocolChild* shell)
 {
-  delete shell;
-  return NS_OK;
+    mTestShells.RemoveElement(shell);
+    return NS_OK;
+}
+
+void
+ContentProcessChild::Quit()
+{
+    mIFrames.Clear();
+    mTestShells.Clear();
+
+    nsCOMPtr<nsIAppStartup> appStartup(do_GetService(NS_APPSTARTUP_CONTRACTID));
+    if (appStartup) {
+        appStartup->Quit(nsIAppStartup::eForceQuit);
+    }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentProcessChild.h
+++ b/dom/ipc/ContentProcessChild.h
@@ -1,16 +1,19 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: sw=4 ts=4 et : */
 
 #ifndef mozilla_dom_ContentProcessChild_h
 #define mozilla_dom_ContentProcessChild_h
 
 #include "mozilla/dom/ContentProcessProtocolChild.h"
 
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+
 namespace mozilla {
 namespace dom {
 
 class ContentProcessChild
     : public ContentProcessProtocolChild
 {
 public:
     ContentProcessChild();
@@ -21,21 +24,26 @@ public:
     static ContentProcessChild* GetSingleton() {
         NS_ASSERTION(sSingleton, "not initialized");
         return sSingleton;
     }
 
     virtual IFrameEmbeddingProtocolChild* IFrameEmbeddingConstructor(const MagicWindowHandle& hwnd);
     virtual nsresult IFrameEmbeddingDestructor(IFrameEmbeddingProtocolChild*);
 
-  virtual TestShellProtocolChild* TestShellConstructor();
-  virtual nsresult TestShellDestructor(TestShellProtocolChild*);
+    virtual TestShellProtocolChild* TestShellConstructor();
+    virtual nsresult TestShellDestructor(TestShellProtocolChild*);
+
+    void Quit();
 
 private:
     static ContentProcessChild* sSingleton;
 
+    nsTArray<nsAutoPtr<IFrameEmbeddingProtocolChild> > mIFrames;
+    nsTArray<nsAutoPtr<TestShellProtocolChild> > mTestShells;
+
     DISALLOW_EVIL_CONSTRUCTORS(ContentProcessChild);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/ipc/chromium/src/base/message_loop.h
+++ b/ipc/chromium/src/base/message_loop.h
@@ -21,16 +21,20 @@
 #if defined(OS_WIN)
 // We need this to declare base::MessagePumpWin::Dispatcher, which we should
 // really just eliminate.
 #include "base/message_pump_win.h"
 #elif defined(OS_POSIX)
 #include "base/message_pump_libevent.h"
 #endif
 
+#ifdef CHROMIUM_MOZILLA_BUILD
+class DoWorkRunnable;
+#endif
+
 // A MessageLoop is used to process events for a particular thread.  There is
 // at most one MessageLoop instance per thread.
 //
 // Events include at a minimum Task instances submitted to PostTask or those
 // managed by TimerManager.  Depending on the type of message pump used by the
 // MessageLoop other events such as UI messages may be processed.  On Windows
 // APC calls (as time permits) and signals sent to a registered set of HANDLEs
 // may also be processed.
@@ -52,17 +56,22 @@
 //   HRESULT hr = DoDragDrop(...); // Implicitly runs a modal message loop here.
 //   MessageLoop::current()->SetNestableTasksAllowed(old_state);
 //   // Process hr  (the result returned by DoDragDrop().
 //
 // Please be SURE your task is reentrant (nestable) and all global variables
 // are stable and accessible before calling SetNestableTasksAllowed(true).
 //
 class MessageLoop : public base::MessagePump::Delegate {
- public:
+
+#ifdef CHROMIUM_MOZILLA_BUILD
+  friend class DoWorkRunnable;
+#endif
+
+public:
   static void EnableHistogrammer(bool enable_histogrammer);
 
   // A DestructionObserver is notified when the current MessageLoop is being
   // destroyed.  These obsevers are notified prior to MessageLoop::current()
   // being changed to return NULL.  This gives interested parties the chance to
   // do final cleanup that depends on the MessageLoop.
   //
   // NOTE: Any tasks posted to the MessageLoop during this notification will
--- a/ipc/glue/AsyncChannel.cpp
+++ b/ipc/glue/AsyncChannel.cpp
@@ -35,25 +35,36 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "mozilla/ipc/AsyncChannel.h"
 #include "mozilla/ipc/GeckoThread.h"
 
+#include "mozilla/dom/ContentProcessChild.h"
+using mozilla::dom::ContentProcessChild;
+
 #include "nsDebug.h"
+#include "nsXULAppAPI.h"
 
 template<>
 struct RunnableMethodTraits<mozilla::ipc::AsyncChannel>
 {
     static void RetainCallee(mozilla::ipc::AsyncChannel* obj) { }
     static void ReleaseCallee(mozilla::ipc::AsyncChannel* obj) { }
 };
 
+template<>
+struct RunnableMethodTraits<ContentProcessChild>
+{
+    static void RetainCallee(ContentProcessChild* obj) { }
+    static void ReleaseCallee(ContentProcessChild* obj) { }
+};
+
 namespace mozilla {
 namespace ipc {
 
 bool
 AsyncChannel::Open(Transport* aTransport, MessageLoop* aIOLoop)
 {
     NS_PRECONDITION(!mTransport, "Open() called > once");
     NS_PRECONDITION(aTransport, "need transport layer");
@@ -138,36 +149,54 @@ AsyncChannel::OnDispatchMessage(const Me
 //
 // The methods below run in the context of the IO thread, and can proxy
 // back to the methods above
 //
 
 void
 AsyncChannel::OnMessageReceived(const Message& msg)
 {
+    NS_ASSERTION(mChannelState != ChannelError, "Shouldn't get here!");
+
     // wake up the worker, there's work to do
     mWorkerLoop->PostTask(FROM_HERE,
                           NewRunnableMethod(this,
                                             &AsyncChannel::OnDispatchMessage,
                                             msg));
 }
 
 void
 AsyncChannel::OnChannelConnected(int32 peer_pid)
 {
     mChannelState = ChannelConnected;
 }
 
 void
 AsyncChannel::OnChannelError()
 {
-    NS_WARNING("Channel error, quitting IO loop!");
-    // FIXME/cjones impl
     mChannelState = ChannelError;
-    MessageLoop::current()->Quit();
+
+    if (XRE_GetProcessType() == GeckoProcessType_Default) {
+        // Parent process, one of our children died. Notify?
+    }
+    else {
+        // Child process, initiate quit sequence.
+#ifdef DEBUG
+        // XXXbent this is totally out of place, but works for now.
+        mWorkerLoop->PostTask(FROM_HERE,
+            NewRunnableMethod(ContentProcessChild::GetSingleton(),
+                              &ContentProcessChild::Quit));
+
+        // Must exit the IO loop, which will then join with the UI loop.
+        MessageLoop::current()->Quit();
+#else
+        // Go ahead and abort here.
+        NS_DebugBreak(NS_DEBUG_ABORT, nsnull, nsnull, nsnull, 0);
+#endif
+    }
 }
 
 void
 AsyncChannel::OnChannelOpened()
 {
     mChannelState = ChannelOpening;
     /*assert*/mTransport->Connect();
 }
--- a/ipc/glue/MessagePump.cpp
+++ b/ipc/glue/MessagePump.cpp
@@ -64,20 +64,33 @@ TimerCallback(nsITimer* aTimer,
               void* aClosure)
 {
   MessagePump* messagePump = reinterpret_cast<MessagePump*>(aClosure);
   messagePump->ScheduleWork();
 }
 
 } /* anonymous namespace */
 
+class DoWorkRunnable : public nsRunnable
+{
+public:
+  NS_IMETHOD Run() {
+    MessageLoop* loop = MessageLoop::current();
+    NS_ASSERTION(loop, "Shouldn't be null!");
+    if (loop) {
+      loop->DoWork();
+    }
+    return NS_OK;
+  }
+};
+
 MessagePump::MessagePump()
 : mThread(nsnull)
 {
-  mDummyEvent = new nsRunnable();
+  mDummyEvent = new DoWorkRunnable();
   // I'm tired of adding OOM checks.
   NS_ADDREF(mDummyEvent);
 }
 
 MessagePump::~MessagePump()
 {
   NS_RELEASE(mDummyEvent);
 }
--- a/ipc/testshell/TestShell.ipdl
+++ b/ipc/testshell/TestShell.ipdl
@@ -31,23 +31,30 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 include protocol "ContentProcess.ipdl";
+include protocol "TestShellCommand.ipdl";
 
 namespace mozilla {
 namespace ipc {
 
-sync protocol TestShell
+protocol TestShell
 {
   manager ContentProcess;
 
+  manages TestShellCommand;
+
 child:
-  async SendCommand(nsString aCommand);
-  sync SendCommandWithResponse(nsString aCommand) returns (nsString aResponse);
+  ExecuteCommand(nsString aCommand);
+
+  TestShellCommand(nsString aCommand);
+
+parent:
+  ~TestShellCommand(nsString aResponse);
 };
 
 } // namespace ipc
 } // namespace mozilla
--- a/ipc/testshell/TestShellChild.cpp
+++ b/ipc/testshell/TestShellChild.cpp
@@ -34,45 +34,79 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "TestShellChild.h"
 
 #include "XPCShellEnvironment.h"
 
 using mozilla::ipc::TestShellChild;
+using mozilla::ipc::TestShellCommandProtocolChild;
 using mozilla::ipc::XPCShellEnvironment;
 
 TestShellChild::TestShellChild()
-  : mXPCShell(XPCShellEnvironment::CreateEnvironment())
+: mXPCShell(nsnull)
 {
-
+  XPCShellEnvironment* env = XPCShellEnvironment::CreateEnvironment();
+  if (env) {
+    if (env->DefineIPCCommands(this)) {
+      mXPCShell = env;
+    }
+    else {
+      XPCShellEnvironment::DestroyEnvironment(env);
+    }
+  }
 }
 
 TestShellChild::~TestShellChild()
 {
-
+  if (mXPCShell) {
+    XPCShellEnvironment::DestroyEnvironment(mXPCShell);
+  }
 }
 
 nsresult
-TestShellChild::RecvSendCommand(const nsString& aCommand)
+TestShellChild::RecvExecuteCommand(const nsString& aCommand)
 {
   if (mXPCShell->IsQuitting()) {
     NS_WARNING("Commands sent after quit command issued!");
     return NS_ERROR_UNEXPECTED;
   }
 
   return mXPCShell->EvaluateString(aCommand) ? NS_OK : NS_ERROR_FAILURE;
 }
 
+TestShellCommandProtocolChild*
+TestShellChild::TestShellCommandConstructor(const nsString& aCommand)
+{
+  return new TestShellCommandProtocolChild();
+}
+
 nsresult
-TestShellChild::RecvSendCommandWithResponse(const nsString& aCommand,
-                                            nsString* aResponse)
+TestShellChild::TestShellCommandDestructor(TestShellCommandProtocolChild* aCommand,
+                                           const nsString& aResponse)
 {
+  NS_ENSURE_ARG_POINTER(aCommand);
+  delete aCommand;
+  return NS_OK;
+}
+
+nsresult
+TestShellChild::RecvTestShellCommandConstructor(TestShellCommandProtocolChild* aActor,
+                                                const nsString& aCommand)
+{
+  NS_ASSERTION(aActor, "Shouldn't be null!");
+
   if (mXPCShell->IsQuitting()) {
     NS_WARNING("Commands sent after quit command issued!");
     return NS_ERROR_UNEXPECTED;
   }
 
-  return mXPCShell->EvaluateString(aCommand, aResponse) ?
-         NS_OK :
-         NS_ERROR_FAILURE;
+  nsString response;
+  if (!mXPCShell->EvaluateString(aCommand, &response)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = SendTestShellCommandDestructor(aActor, response);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
 }
--- a/ipc/testshell/TestShellChild.h
+++ b/ipc/testshell/TestShellChild.h
@@ -33,31 +33,42 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _IPC_TESTSHELL_TESTSHELLCHILD_H_
 #define _IPC_TESTSHELL_TESTSHELLCHILD_H_
 
 #include "mozilla/ipc/TestShellProtocolChild.h"
+#include "mozilla/ipc/TestShellCommandProtocolChild.h"
 
 namespace mozilla {
 namespace ipc {
 
 class XPCShellEnvironment;
 
 class TestShellChild : public TestShellProtocolChild
 {
 public:
   TestShellChild();
-  virtual ~TestShellChild();
+  ~TestShellChild();
+
+  nsresult
+  RecvExecuteCommand(const nsString& aCommand);
+
+  TestShellCommandProtocolChild*
+  TestShellCommandConstructor(const nsString& aCommand);
 
-  virtual nsresult RecvSendCommand(const nsString& aCommand);
-  virtual nsresult RecvSendCommandWithResponse(const nsString& aCommand,
-                                               nsString* aResponse);
+  nsresult
+  RecvTestShellCommandConstructor(TestShellCommandProtocolChild* aActor,
+                                  const nsString& aCommand);
+
+  nsresult
+  TestShellCommandDestructor(TestShellCommandProtocolChild* aCommand,
+                             const nsString& aResponse);
 
   void SetXPCShell(XPCShellEnvironment* aXPCShell) {
     mXPCShell = aXPCShell;
   }
 
 private:
   XPCShellEnvironment* mXPCShell;
 };
new file mode 100644
--- /dev/null
+++ b/ipc/testshell/TestShellCommand.ipdl
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla IPCShell.
+ *
+ * The Initial Developer of the Original Code is
+ *   Ben Turner <bent.mozilla@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+include protocol "TestShell.ipdl";
+
+namespace mozilla {
+namespace ipc {
+
+protocol TestShellCommand
+{
+  manager TestShell;
+};
+
+} // namespace ipc
+} // namespace mozilla
--- a/ipc/testshell/TestShellParent.cpp
+++ b/ipc/testshell/TestShellParent.cpp
@@ -31,19 +31,101 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "TestShellParent.h"
 
+#include "nsAutoPtr.h"
+#include "XPCShellEnvironment.h"
+
 using mozilla::ipc::TestShellParent;
+using mozilla::ipc::TestShellCommandParent;
+using mozilla::ipc::TestShellCommandProtocolParent;
+using mozilla::ipc::XPCShellEnvironment;
 
-TestShellParent::TestShellParent()
+TestShellCommandProtocolParent*
+TestShellParent::TestShellCommandConstructor(const nsString& aCommand)
 {
+  return new TestShellCommandParent();
+}
 
+nsresult
+TestShellParent::TestShellCommandDestructor(TestShellCommandProtocolParent* aActor,
+                                            const nsString& aResponse)
+{
+  NS_ENSURE_ARG_POINTER(aActor);
+  delete aActor;
+  return NS_OK;
 }
 
-TestShellParent::~TestShellParent()
+nsresult
+TestShellParent::RecvTestShellCommandDestructor(TestShellCommandProtocolParent* aActor,
+                                                const nsString& aResponse)
+{
+  NS_ENSURE_ARG_POINTER(aActor);
+
+  TestShellCommandParent* command =
+    static_cast<TestShellCommandParent*>(aActor);
+
+  JSBool ok = command->RunCallback(aResponse);
+  command->ReleaseCallback();
+
+  if (!mXPCShell) {
+    NS_WARNING("Processing a child message after exiting, need to spin events "
+               "somehow to process this result");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  NS_WARN_IF_FALSE(mXPCShell->EventLoopDepth(), "EventLoopDepth mismatch!");
+  if (mXPCShell->EventLoopDepth()) {
+    mXPCShell->DecrementEventLoopDepth();
+  }
+
+  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+
+  return NS_OK;
+}
+
+JSBool
+TestShellCommandParent::SetCallback(JSContext* aCx,
+                                    jsval aCallback)
 {
+  if (!mCallback.Hold(aCx)) {
+    return JS_FALSE;
+  }
 
+  mCallback = aCallback;
+  mCx = aCx;
+
+  return JS_TRUE;
 }
+
+JSBool
+TestShellCommandParent::RunCallback(const nsString& aResponse)
+{
+  NS_ENSURE_TRUE(mCallback && mCx, JS_FALSE);
+
+  JSAutoRequest ar(mCx);
+
+  JSObject* global = JS_GetGlobalObject(mCx);
+  NS_ENSURE_TRUE(global, JS_FALSE);
+
+  JSString* str = JS_NewUCStringCopyN(mCx, aResponse.get(), aResponse.Length());
+  NS_ENSURE_TRUE(str, JS_FALSE);
+
+  jsval argv[] = { STRING_TO_JSVAL(str) };
+  int argc = NS_ARRAY_LENGTH(argv);
+
+  jsval rval;
+  JSBool ok = JS_CallFunctionValue(mCx, global, mCallback, argc, argv, &rval);
+  NS_ENSURE_TRUE(ok, JS_FALSE);
+
+  return JS_TRUE;
+}
+
+void
+TestShellCommandParent::ReleaseCallback()
+{
+  mCallback.Release();
+}
--- a/ipc/testshell/TestShellParent.h
+++ b/ipc/testshell/TestShellParent.h
@@ -33,23 +33,65 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _IPC_TESTSHELL_TESTSHELLPARENT_H_
 #define _IPC_TESTSHELL_TESTSHELLPARENT_H_
 
 #include "mozilla/ipc/TestShellProtocolParent.h"
+#include "mozilla/ipc/TestShellCommandProtocolParent.h"
+
+#include "jsapi.h"
+#include "nsAutoJSValHolder.h"
+#include "nsStringGlue.h"
 
 namespace mozilla {
 namespace ipc {
 
+class XPCShellEnvironment;
+
+class TestShellCommandParent : public TestShellCommandProtocolParent
+{
+public:
+  TestShellCommandParent() : mCx(NULL) { }
+
+  JSBool SetCallback(JSContext* aCx,
+                     jsval aCallback);
+
+  JSBool RunCallback(const nsString& aResponse);
+
+  void ReleaseCallback();
+
+private:
+  JSContext* mCx;
+  nsAutoJSValHolder mCallback;
+};
+
 class TestShellParent : public TestShellProtocolParent
 {
 public:
-  TestShellParent();
-  ~TestShellParent();
+  TestShellParent() : mXPCShell(nsnull) { }
+
+  void
+  SetXPCShell(XPCShellEnvironment* aXPCShell) {
+    mXPCShell = aXPCShell;
+  }
+
+  TestShellCommandProtocolParent*
+  TestShellCommandConstructor(const nsString& aCommand);
+
+  nsresult
+  TestShellCommandDestructor(TestShellCommandProtocolParent* aActor,
+                             const nsString& aResponse);
+
+  nsresult
+  RecvTestShellCommandDestructor(TestShellCommandProtocolParent* aActor,
+                                 const nsString& aResponse);
+
+private:
+  XPCShellEnvironment* mXPCShell;
 };
 
 } /* namespace ipc */
 } /* namespace mozilla */
 
 #endif /* _IPC_TESTSHELL_TESTSHELLPARENT_H_ */
--- a/ipc/testshell/XPCShellEnvironment.cpp
+++ b/ipc/testshell/XPCShellEnvironment.cpp
@@ -59,31 +59,33 @@
 #include "nsIJSRuntimeService.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIURI.h"
 #include "nsIXPConnect.h"
 #include "nsIXPCScriptable.h"
 
 #include "nsJSUtils.h"
+#include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 
 #include "TestShellChild.h"
 #include "TestShellParent.h"
 
 #define EXITCODE_RUNTIME_ERROR 3
 #define EXITCODE_FILE_NOT_FOUND 4
 
 using mozilla::ipc::XPCShellEnvironment;
 using mozilla::ipc::TestShellChild;
 using mozilla::ipc::TestShellParent;
+using mozilla::ipc::TestShellCommandProtocolParent;
 
 namespace {
 
-static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js";
+static const char kDefaultRuntimeScriptFilename[] = "ipcshell.js";
 
 class FullTrustSecMan : public nsIScriptSecurityManager
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIXPCSECURITYMANAGER
     NS_DECL_NSISCRIPTSECURITYMANAGER
 
@@ -769,16 +771,17 @@ PrintUsage(void)
 static void
 ProcessFile(JSContext *cx,
             JSObject *obj,
             const char *filename,
             FILE *file,
             JSBool forceTTY)
 {
     XPCShellEnvironment* env = Environment(cx);
+    XPCShellEnvironment::AutoContextPusher pusher(env);
 
     JSScript *script;
     jsval result;
     int lineno, startline;
     JSBool ok, hitEOF;
     char *bufp, buffer[4096];
     JSString *str;
 
@@ -870,16 +873,96 @@ ProcessFile(JSContext *cx,
             }
             JS_DestroyScript(cx, script);
         }
     } while (!hitEOF && !env->IsQuitting());
 
     fprintf(stdout, "\n");
 }
 
+static JSBool
+SendCommand(JSContext *cx,
+            JSObject *obj,
+            uintN argc,
+            jsval *argv,
+            jsval *rval)
+{
+    if (argc == 0) {
+        JS_ReportError(cx, "Function takes at least one argument!");
+        return JS_FALSE;
+    }
+
+    JSString* str = JS_ValueToString(cx, argv[0]);
+    if (!str) {
+        JS_ReportError(cx, "Could not convert argument 1 to string!");
+        return JS_FALSE;
+    }
+
+    nsDependentJSString command(str);
+    JSBool ok;
+
+    if (argc > 1) {
+        if (JS_TypeOfValue(cx, argv[1]) != JSTYPE_FUNCTION) {
+            JS_ReportError(cx, "Could not convert argument 2 to function!");
+            return JS_FALSE;
+        }
+        ok = Environment(cx)->DoSendCommand(command, cx, argv[1]);
+        if (ok) {
+            Environment(cx)->IncrementEventLoopDepth();
+        }
+    }
+    else {
+        ok = Environment(cx)->DoSendCommand(command);
+    }
+
+    if (!ok) {
+        JS_ReportError(cx, "Failed to send command!");
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+static JSBool
+RunEventLoop(JSContext *cx,
+             JSObject *obj,
+             uintN argc,
+             jsval *argv,
+             jsval *rval)
+{
+    NS_ASSERTION(Environment(cx)->EventLoopDepth() >= 0, "Bad depth!");
+    Environment(cx)->IncrementEventLoopDepth();
+    return JS_TRUE;
+}
+
+static JSBool
+StopEventLoop(JSContext *cx,
+              JSObject *obj,
+              uintN argc,
+              jsval *argv,
+              jsval *rval)
+{
+    XPCShellEnvironment* env = Environment(cx);
+    if (env->EventLoopDepth() < 1) {
+        JS_ReportError(cx, "Mismatched call to DecrementEventLoopDepth");
+        return JS_FALSE;
+    }
+
+    env->DecrementEventLoopDepth();
+    return JS_TRUE;
+}
+
+JSFunctionSpec gParentFunctions[] =
+{
+    {"sendCommand",             SendCommand,             1, 0, 0},
+    {"runEventLoop",            RunEventLoop,            0, 0, 0},
+    {"stopEventLoop",           StopEventLoop,           0, 0, 0},
+    {nsnull,                    nsnull,                  0, 0, 0}
+};
+
 } /* anonymous namespace */
 
 NS_INTERFACE_MAP_BEGIN(FullTrustSecMan)
     NS_INTERFACE_MAP_ENTRY(nsIXPCSecurityManager)
     NS_INTERFACE_MAP_ENTRY(nsIScriptSecurityManager)
     NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCSecurityManager)
 NS_INTERFACE_MAP_END
 
@@ -1174,16 +1257,36 @@ XPCShellDirProvider::GetFile(const char 
         *persistent = PR_TRUE;
         NS_ADDREF(*result = mGREDir);
         return NS_OK;
     }
 
     return NS_ERROR_FAILURE;
 }
 
+XPCShellEnvironment::
+AutoContextPusher::AutoContextPusher(XPCShellEnvironment* aEnv)
+{
+    NS_ASSERTION(aEnv->mCx, "Null context?!");
+
+    if (NS_SUCCEEDED(aEnv->mCxStack->Push(aEnv->mCx))) {
+        mEnv = aEnv;
+    }
+}
+
+XPCShellEnvironment::
+AutoContextPusher::~AutoContextPusher()
+{
+    if (mEnv) {
+        JSContext* cx;
+        mEnv->mCxStack->Pop(&cx);
+        NS_ASSERTION(cx == mEnv->mCx, "Wrong context on the stack!");
+    }
+}
+
 // static
 XPCShellEnvironment*
 XPCShellEnvironment::CreateEnvironment()
 {
     XPCShellEnvironment* env = new XPCShellEnvironment();
     if (env && !env->Init()) {
         delete env;
         env = nsnull;
@@ -1197,16 +1300,17 @@ XPCShellEnvironment::DestroyEnvironment(
 {
     delete aEnv;
 }
 
 XPCShellEnvironment::XPCShellEnvironment()
 :   mCx(NULL),
     mJSPrincipals(NULL),
     mExitCode(0),
+    mEventLoopDepth(0),
     mQuitting(JS_FALSE),
     mReportWarnings(JS_TRUE),
     mCompileOnly(JS_FALSE),
     mParent(nsnull)
 {
 }
 
 XPCShellEnvironment::~XPCShellEnvironment()
@@ -1217,23 +1321,17 @@ XPCShellEnvironment::~XPCShellEnvironmen
         JSObject* global = GetGlobalObject();
         if (global) {
             JS_ClearScope(mCx, global);
         }
         mGlobalHolder.Release();
 
         JS_GC(mCx);
 
-        if (mCxStack) {
-            JSContext *oldCx;
-            mCxStack->Pop(&oldCx);
-            NS_ASSERTION(oldCx == mCx, "JS thread context push/pop mismatch");
-
-            JS_GC(mCx);
-        }
+        mCxStack = nsnull;
 
         if (mJSPrincipals) {
             JSPRINCIPALS_DROP(mCx, mJSPrincipals);
         }
 
         JSRuntime* rt = gOldContextCallback ? JS_GetRuntime(mCx) : NULL;
 
         JS_DestroyContext(mCx);
@@ -1321,22 +1419,19 @@ XPCShellEnvironment::Init()
     }
 
     nsCOMPtr<nsIJSContextStack> cxStack =
         do_GetService("@mozilla.org/js/xpc/ContextStack;1");
     if (!cxStack) {
         NS_ERROR("failed to get the nsThreadJSContextStack service!");
         return false;
     }
+    mCxStack = cxStack;
 
-    if(NS_FAILED(cxStack->Push(cx))) {
-        NS_ERROR("failed to push the current JSContext on the nsThreadJSContextStack!");
-        return false;
-    }
-    mCxStack = cxStack;
+    AutoContextPusher pusher(this);
 
     nsCOMPtr<nsIXPCScriptable> backstagePass;
     rv = rtsvc->GetBackstagePass(getter_AddRefs(backstagePass));
     if (NS_FAILED(rv)) {
         NS_ERROR("Failed to get backstage pass from rtsvc!");
         return false;
     }
 
@@ -1387,106 +1482,49 @@ XPCShellEnvironment::Init()
                     runtimeScriptFile, JS_FALSE);
         fclose(runtimeScriptFile);
     }
 
     return true;
 }
 
 void
-XPCShellEnvironment::Process(const char* aFilename,
-                             JSBool aIsInteractive)
+XPCShellEnvironment::Process(const char* aFilename)
 {
     NS_ASSERTION(GetGlobalObject(), "Should never be null!");
 
     FILE* file;
-    if (!aFilename || aIsInteractive) {
+    if (!aFilename) {
         file = stdin;
     } else {
         file = fopen(aFilename, "r");
         if (!file) {
             JS_ReportErrorNumber(mCx, GetErrorMessage, NULL,
                                  JSSMSG_CANT_OPEN,
                                  aFilename, strerror(errno));
             mExitCode = EXITCODE_FILE_NOT_FOUND;
             return;
         }
     }
 
-    ProcessFile(mCx, GetGlobalObject(), aFilename, file, aIsInteractive);
-    if (file != stdin)
+    ProcessFile(mCx, GetGlobalObject(), aFilename, file, !aFilename);
+    if (file != stdin) {
         fclose(file);
-}
-
-namespace {
-
-static JSBool
-SendCommand(JSContext *cx,
-            JSObject *obj,
-            uintN argc,
-            jsval *argv,
-            jsval *rval)
-{
-  if (argc != 1) {
-    JS_ReportError(cx, "Function takes only one argument!");
-    return JS_FALSE;
-  }
-
-  JSString* str = JS_ValueToString(cx, argv[0]);
-  if (!str) {
-    JS_ReportError(cx, "Could not convert argument to string!");
-    return JS_FALSE;
-  }
-
-  nsDependentJSString command(str);
-  if (!Environment(cx)->DoSendCommand(command)) {
-    JS_ReportError(cx, "Failed to send command!");
-    return JS_FALSE;
-  }
-
-  return JS_TRUE;
-}
+    }
 
-static JSBool
-SendCommandWithResponse(JSContext *cx,
-                        JSObject *obj,
-                        uintN argc,
-                        jsval *argv,
-                        jsval *rval)
-{
-  if (argc != 1) {
-    JS_ReportError(cx, "Function takes only one argument!");
-    return JS_FALSE;
-  }
-
-  JSString* str = JS_ValueToString(cx, argv[0]);
-  if (!str) {
-    JS_ReportError(cx, "Could not convert argument to string!");
-    return JS_FALSE;
-  }
+    if (EventLoopDepth()) {
+        nsCOMPtr<nsIThread> currentThread;
+        NS_GetCurrentThread(getter_AddRefs(currentThread));
 
-  nsDependentJSString command(str);
-  nsAutoString result;
-  if (!Environment(cx)->DoSendCommand(command, &result)) {
-    JS_ReportError(cx, "Failed to send command!");
-    return JS_FALSE;
-  }
-
-  JSString* resultStr = JS_NewUCStringCopyN(cx, result.get(), result.Length());
-  if (!resultStr) {
-    JS_ReportError(cx, "Failed to convert response to string!");
-    return JS_FALSE;
-  }
-
-  *rval = STRING_TO_JSVAL(resultStr);
-  return JS_TRUE;
+        while (EventLoopDepth()) {
+            NS_ProcessNextEvent(currentThread, PR_TRUE);
+        }
+    }
 }
 
-} /* anonymous namespace */
-
 bool
 XPCShellEnvironment::DefineIPCCommands(TestShellChild* aChild)
 {
     NS_ASSERTION(aChild, "Don't hand me null!");
 
     // XXXbent Nothing here yet, soon though!
     return true;
 }
@@ -1497,42 +1535,45 @@ XPCShellEnvironment::DefineIPCCommands(T
     NS_ASSERTION(aParent, "Don't hand me null!");
 
     mParent = aParent;
 
     JSObject* global = GetGlobalObject();
 
     JSAutoRequest ar(mCx);
 
-    JSFunction* fun = JS_DefineFunction(mCx, global, "sendCommand",
-                                        SendCommand, 1, JSPROP_ENUMERATE);
-    if (!fun) {
-      NS_WARNING("Failed to define sendCommand function!");
-      return false;
-    }
-
-    fun = JS_DefineFunction(mCx, global, "sendCommandWithResponse",
-                            SendCommandWithResponse, 1, JSPROP_ENUMERATE);
-    if (!fun) {
-      NS_WARNING("Failed to define sendCommandWithResponse function!");
-      return false;
+    if (!JS_DefineFunctions(mCx, global, gParentFunctions)) {
+        NS_ERROR("JS_DefineFunctions failed!");
+        return false;
     }
 
     return true;
 }
 
 JSBool
 XPCShellEnvironment::DoSendCommand(const nsString& aCommand,
-                                   nsString* aResult)
+                                   JSContext* aCx,
+                                   jsval aCallback)
 {
-  nsresult rv = aResult ?
-                mParent->SendSendCommandWithResponse(aCommand, aResult) :
-                mParent->SendSendCommand(aCommand);
+  if (aCx) {
+      TestShellCommandParent* command = static_cast<TestShellCommandParent*>(
+          mParent->SendTestShellCommandConstructor(aCommand));
+      NS_ENSURE_TRUE(command, JS_FALSE);
 
-  return NS_SUCCEEDED(rv) ? JS_TRUE : JS_FALSE;
+      if (!command->SetCallback(aCx, aCallback)) {
+          NS_WARNING("Failed to set callback!");
+          return JS_FALSE;
+      }
+  }
+  else {
+      nsresult rv = mParent->SendExecuteCommand(aCommand);
+      NS_ENSURE_SUCCESS(rv, JS_FALSE);
+  }
+
+  return JS_TRUE;
 }
 
 bool
 XPCShellEnvironment::EvaluateString(const nsString& aString,
                                     nsString* aResult)
 {
   JSAutoRequest ar(mCx);
 
--- a/ipc/testshell/XPCShellEnvironment.h
+++ b/ipc/testshell/XPCShellEnvironment.h
@@ -60,24 +60,24 @@ class TestShellChild;
 class TestShellParent;
 
 class XPCShellEnvironment
 {
 public:
     static XPCShellEnvironment* CreateEnvironment();
     static void DestroyEnvironment(XPCShellEnvironment* aEnv);
 
-    void Process(const char* aFilename = nsnull,
-                 JSBool aIsInteractive = JS_FALSE);
+    void Process(const char* aFilename = nsnull);
 
     bool DefineIPCCommands(TestShellChild* aChild);
     bool DefineIPCCommands(TestShellParent* aParent);
 
     JSBool DoSendCommand(const nsString& aCommand,
-                         nsString* aResult = nsnull);
+                         JSContext* aCx = nsnull,
+                         jsval aCallback = JSVAL_VOID);
 
     bool EvaluateString(const nsString& aString,
                         nsString* aResult = nsnull);
 
     JSPrincipals* GetPrincipal() {
         return mJSPrincipals;
     }
 
@@ -108,29 +108,49 @@ public:
 
     void SetShouldCompoleOnly(JSBool aCompileOnly) {
         mCompileOnly = aCompileOnly;
     }
     JSBool ShouldCompileOnly() {
         return mCompileOnly;
     }
 
+    int EventLoopDepth() {
+        return mEventLoopDepth;
+    }
+    void IncrementEventLoopDepth() {
+        ++mEventLoopDepth;
+    }
+    void DecrementEventLoopDepth() {
+        --mEventLoopDepth;
+    }
+
+    class AutoContextPusher
+    {
+    public:
+        AutoContextPusher(XPCShellEnvironment* aEnv);
+        ~AutoContextPusher();
+    private:
+        XPCShellEnvironment* mEnv;
+    };
+
 protected:
     XPCShellEnvironment();
     ~XPCShellEnvironment();
 
     bool Init();
 
 private:
     JSContext* mCx;
     nsAutoJSValHolder mGlobalHolder;
     nsCOMPtr<nsIJSContextStack> mCxStack;
     JSPrincipals* mJSPrincipals;
 
     int mExitCode;
+    int mEventLoopDepth;
     JSBool mQuitting;
     JSBool mReportWarnings;
     JSBool mCompileOnly;
 
     TestShellParent* mParent;
 };
 
 } /* namespace ipc */
--- a/ipc/testshell/ipdl.mk
+++ b/ipc/testshell/ipdl.mk
@@ -1,3 +1,4 @@
 IPDLSRCS = \
   TestShell.ipdl \
+  TestShellCommand.ipdl \
   $(NULL)
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -332,18 +332,18 @@ XRE_InitParentProcess(int aArgc,
                       void* aMainFunctionData)
 {
   NS_ENSURE_ARG_MIN(aArgc, 1);
   NS_ENSURE_ARG_POINTER(aArgv);
   NS_ENSURE_ARG_POINTER(aArgv[0]);
 
   base::AtExitManager exitManager;
   CommandLine::Init(aArgc, aArgv);
+  MessageLoopForUI mainMessageLoop;
   ScopedXREEmbed embed;
-  MessageLoopForUI mainMessageLoop;
 
   {
     // Make chromium's IPC thread
 #if defined(OS_LINUX)
     // The lifetime of the BACKGROUND_X11 thread is a subset of the IO thread so
     // we start it now.
     scoped_ptr<base::Thread> x11Thread(
       new BrowserProcessSubThread(BrowserProcessSubThread::BACKGROUND_X11));
@@ -417,26 +417,31 @@ TestShellMain(int argc, char** argv)
 
   nsresult rv = NS_DispatchToCurrentThread(quitRunnable);
   NS_ENSURE_SUCCESS(rv, 1);
 
   nsAutoRef<XPCShellEnvironment> env(XPCShellEnvironment::CreateEnvironment());
   NS_ENSURE_TRUE(env, 1);
 
   ContentProcessParent* childProcess = ContentProcessParent::GetSingleton();
-  if (!childProcess)
-    return 1;
+  NS_ENSURE_TRUE(childProcess, 1);
 
   TestShellParent* testShellParent = childProcess->CreateTestShell();
+  NS_ENSURE_TRUE(testShellParent, 1);
 
-  env->DefineIPCCommands(testShellParent);
+  testShellParent->SetXPCShell(env);
+
+  bool ok = env->DefineIPCCommands(testShellParent);
+  NS_ENSURE_TRUE(ok, 1);
 
   const char* filename = argc > 1 ? argv[1] : nsnull;
   env->Process(filename);
 
+  testShellParent->SetXPCShell(nsnull);
+
   return env->ExitCode();
 }
 
 struct TestShellData {
   int* result;
   int argc;
   char** argv;
 };