Bug 568698 - Instead of fixing two globals in the jetpack process, allow jetpack to create sandboxes in which to load user code and implementation modules, r=bent
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 08 Jul 2010 09:40:07 -0700
changeset 47305 053bf6cd63e9182f3b2aad9993b81fcc46163559
parent 47304 c72536855cad751d7410d333425bb3d4c0eafd79
child 47306 9d22ad64d6f062b1f7853f18e287fe2155de8020
push idunknown
push userunknown
push dateunknown
reviewersbent
bugs568698
milestone2.0b2pre
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 568698 - Instead of fixing two globals in the jetpack process, allow jetpack to create sandboxes in which to load user code and implementation modules, r=bent
js/jetpack/JetpackActorCommon.cpp
js/jetpack/JetpackChild.cpp
js/jetpack/JetpackChild.h
js/jetpack/JetpackParent.cpp
js/jetpack/PJetpack.ipdl
js/jetpack/nsIJetpack.idl
js/jetpack/tests/unit/impl.js
js/jetpack/tests/unit/test_jetpack.js
testing/xpcshell/runxpcshelltests.py
--- a/js/jetpack/JetpackActorCommon.cpp
+++ b/js/jetpack/JetpackActorCommon.cpp
@@ -66,17 +66,17 @@ public:
   typedef size_t IdType;
   typedef js::HashMap<
     KeyType, IdType,
     js::DefaultHasher<KeyType>,
     js::SystemAllocPolicy
   > MapType;
 
   OpaqueSeenType() {
-    NS_ASSERTION(map.init(1), "Failed to initialize map");
+    (void) map.init(1);
   }
 
   bool ok() { return map.initialized(); }
 
   // Reserving 0 as an invalid ID means starting the valid IDs at 1.  We
   // could have reserved a dummy first element of rmap, but that would
   // have wasted space.
   static const IdType kInvalidId = 0;
@@ -447,34 +447,28 @@ JetpackActorCommon::RecvMessage(JSContex
 
   for (PRUint32 i = 0; i < data.Length(); ++i)
     if (!jsval_from_Variant(cx, data.ElementAt(i), argv + i + 1))
       return false;
 
   JSObject* implGlobal = JS_GetGlobalObject(cx);
   js::AutoValueRooter rval(cx);
 
-  const uint32 savedOptions =
-    JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_DONT_REPORT_UNCAUGHT);
-
   for (PRUint32 i = 0; i < snapshot.Length(); ++i) {
     Variant* vp = results ? results->AppendElement() : NULL;
     rval.set(JSVAL_VOID);
     if (!JS_CallFunctionValue(cx, implGlobal, snapshot[i], argc, argv,
                               rval.addr())) {
-      // If a receiver throws, we drop the exception on the floor.
-      JS_ClearPendingException(cx);
+      (void) JS_ReportPendingException(cx);
       if (vp)
         *vp = void_t();
     } else if (vp && !jsval_to_Variant(cx, rval.value(), vp))
       *vp = void_t();
   }
 
-  JS_SetOptions(cx, savedOptions);
-
   return true;
 }
 
 JetpackActorCommon::RecList::~RecList()
 {
   while (mHead) {
     RecNode* old = mHead;
     mHead = mHead->down;
--- a/js/jetpack/JetpackChild.cpp
+++ b/js/jetpack/JetpackChild.cpp
@@ -39,149 +39,132 @@
 #include "jscntxt.h"
 #include "nsXULAppAPI.h"
 
 #include "mozilla/jetpack/JetpackChild.h"
 #include "mozilla/jetpack/Handle.h"
 
 #include "jsarray.h"
 
+#include <stdio.h>
+
 namespace mozilla {
 namespace jetpack {
 
 JetpackChild::JetpackChild()
 {
 }
 
 JetpackChild::~JetpackChild()
 {
 }
 
-#define IMPL_PROP_FLAGS (JSPROP_SHARED | \
-                         JSPROP_ENUMERATE | \
-                         JSPROP_READONLY | \
-                         JSPROP_PERMANENT)
-const JSPropertySpec
-JetpackChild::sImplProperties[] = {
-  { "jetpack", 0, IMPL_PROP_FLAGS, UserJetpackGetter, NULL },
-  { 0, 0, 0, NULL, NULL }
-};
-
-#undef IMPL_PROP_FLAGS
-
 #define IMPL_METHOD_FLAGS (JSFUN_FAST_NATIVE |  \
                            JSPROP_ENUMERATE | \
                            JSPROP_READONLY | \
                            JSPROP_PERMANENT)
 const JSFunctionSpec
 JetpackChild::sImplMethods[] = {
   JS_FN("sendMessage", SendMessage, 3, IMPL_METHOD_FLAGS),
   JS_FN("callMessage", CallMessage, 2, IMPL_METHOD_FLAGS),
   JS_FN("registerReceiver", RegisterReceiver, 2, IMPL_METHOD_FLAGS),
   JS_FN("unregisterReceiver", UnregisterReceiver, 2, IMPL_METHOD_FLAGS),
   JS_FN("unregisterReceivers", UnregisterReceivers, 1, IMPL_METHOD_FLAGS),
   JS_FN("wrap", Wrap, 1, IMPL_METHOD_FLAGS),
   JS_FN("createHandle", CreateHandle, 0, IMPL_METHOD_FLAGS),
+  JS_FN("createSandbox", CreateSandbox, 0, IMPL_METHOD_FLAGS),
+  JS_FN("evalInSandbox", EvalInSandbox, 2, IMPL_METHOD_FLAGS),
   JS_FS_END
 };
 
 #undef IMPL_METHOD_FLAGS
 
 const JSClass
 JetpackChild::sGlobalClass = {
   "JetpackChild::sGlobalClass", JSCLASS_GLOBAL_FLAGS,
   JS_PropertyStub,  JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
   JS_EnumerateStub, JS_ResolveStub,  JS_ConvertStub,  JS_FinalizeStub,
   JSCLASS_NO_OPTIONAL_MEMBERS
 };
-  
+
+static void
+ReportJetpackErrors(JSContext* cx, const char* message, JSErrorReport* report)
+{
+  const char* filename = "<unknown>";
+  if (report && report->filename)
+    filename = report->filename;
+  int lineno = -1;
+  if (report)
+    lineno = report->lineno;
+
+  fprintf(stderr, "Jetpack JavaScript Error: %s:%i, %s\n",
+          filename, lineno, message);
+}
+
 bool
 JetpackChild::Init(base::ProcessHandle aParentProcessHandle,
                    MessageLoop* aIOLoop,
                    IPC::Channel* aChannel)
 {
   if (!Open(aChannel, aParentProcessHandle, aIOLoop))
     return false;
 
   if (!(mRuntime = JS_NewRuntime(32L * 1024L * 1024L)) ||
-      !(mImplCx = JS_NewContext(mRuntime, 8192)) ||
-      !(mUserCx = JS_NewContext(mRuntime, 8192)))
+      !(mCx = JS_NewContext(mRuntime, 8192)))
     return false;
 
+  JS_SetErrorReporter(mCx, ReportJetpackErrors);
+
   {
-    JSAutoRequest request(mImplCx);
-    JS_SetContextPrivate(mImplCx, this);
+    JSAutoRequest request(mCx);
+    JS_SetContextPrivate(mCx, this);
     JSObject* implGlobal =
-      JS_NewGlobalObject(mImplCx, const_cast<JSClass*>(&sGlobalClass));
+      JS_NewGlobalObject(mCx, const_cast<JSClass*>(&sGlobalClass));
     if (!implGlobal ||
-        !JS_InitStandardClasses(mImplCx, implGlobal) ||
-        !JS_DefineProperties(mImplCx, implGlobal,
-                             const_cast<JSPropertySpec*>(sImplProperties)) ||
-        !JS_DefineFunctions(mImplCx, implGlobal,
+        !JS_InitStandardClasses(mCx, implGlobal) ||
+        !JS_DefineFunctions(mCx, implGlobal,
                             const_cast<JSFunctionSpec*>(sImplMethods)))
       return false;
   }
 
-  {
-    JSAutoRequest request(mUserCx);
-    JS_SetContextPrivate(mUserCx, this);
-    JSObject* userGlobal =
-      JS_NewGlobalObject(mUserCx, const_cast<JSClass*>(&sGlobalClass));
-    if (!userGlobal ||
-        !JS_InitStandardClasses(mUserCx, userGlobal))
-      return false;
-  }
-
   return true;
 }
 
 void
 JetpackChild::CleanUp()
 {
   ClearReceivers();
-  JS_DestroyContext(mUserCx);
-  JS_DestroyContext(mImplCx);
+  JS_DestroyContext(mCx);
   JS_DestroyRuntime(mRuntime);
   JS_ShutDown();
 }
 
 void
 JetpackChild::ActorDestroy(ActorDestroyReason why)
 {
   XRE_ShutdownChildProcess();
 }
 
 bool
 JetpackChild::RecvSendMessage(const nsString& messageName,
                               const nsTArray<Variant>& data)
 {
-  JSAutoRequest request(mImplCx);
-  return JetpackActorCommon::RecvMessage(mImplCx, messageName, data, NULL);
-}
-
-static bool
-Evaluate(JSContext* cx, const nsCString& code)
-{
-  JSAutoRequest request(cx);
-  js::AutoValueRooter ignored(cx);
-  JS_EvaluateScript(cx, JS_GetGlobalObject(cx), code.get(),
-                    code.Length(), "", 1, ignored.addr());
-  return true;
+  JSAutoRequest request(mCx);
+  return JetpackActorCommon::RecvMessage(mCx, messageName, data, NULL);
 }
 
 bool
-JetpackChild::RecvLoadImplementation(const nsCString& code)
+JetpackChild::RecvEvalScript(const nsString& code)
 {
-  return Evaluate(mImplCx, code);
-}
+  JSAutoRequest request(mCx);
 
-bool
-JetpackChild::RecvLoadUserScript(const nsCString& code)
-{
-  return Evaluate(mUserCx, code);
+  js::AutoValueRooter ignored(mCx);
+  (void) JS_EvaluateUCScript(mCx, JS_GetGlobalObject(mCx), code.get(),
+                             code.Length(), "", 1, ignored.addr());
+  return true;
 }
 
 PHandleChild*
 JetpackChild::AllocPHandle()
 {
   return new HandleChild();
 }
 
@@ -192,30 +175,20 @@ JetpackChild::DeallocPHandle(PHandleChil
   return true;
 }
 
 JetpackChild*
 JetpackChild::GetThis(JSContext* cx)
 {
   JetpackChild* self =
     static_cast<JetpackChild*>(JS_GetContextPrivate(cx));
-  JS_ASSERT(cx == self->mImplCx ||
-            cx == self->mUserCx);
+  JS_ASSERT(cx == self->mCx);
   return self;
 }
 
-JSBool
-JetpackChild::UserJetpackGetter(JSContext* cx, JSObject* obj, jsval id,
-                                jsval* vp)
-{
-  JSObject* userGlobal = JS_GetGlobalObject(GetThis(cx)->mUserCx);
-  JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(userGlobal));
-  return JS_TRUE;
-}
-
 struct MessageResult {
   nsString msgName;
   nsTArray<Variant> data;
 };
 
 static JSBool
 MessageCommon(JSContext* cx, uintN argc, jsval* vp,
               MessageResult* result)
@@ -423,10 +396,54 @@ JetpackChild::CreateHandle(JSContext* cx
     return JS_FALSE;
   }
 
   JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(hobj));
 
   return JS_TRUE;
 }
 
+JSBool
+JetpackChild::CreateSandbox(JSContext* cx, uintN argc, jsval* vp)
+{
+  if (argc > 0) {
+    JS_ReportError(cx, "createSandbox takes zero arguments");
+    return JS_FALSE;
+  }
+
+  JSObject* obj = JS_NewGlobalObject(cx, const_cast<JSClass*>(&sGlobalClass));
+  if (!obj)
+    return JS_FALSE;
+
+  JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
+  return JS_InitStandardClasses(cx, obj);
+}
+
+JSBool
+JetpackChild::EvalInSandbox(JSContext* cx, uintN argc, jsval* vp)
+{
+  if (argc != 2) {
+    JS_ReportError(cx, "evalInSandbox takes two arguments");
+    return JS_FALSE;
+  }
+
+  jsval* argv = JS_ARGV(cx, vp);
+
+  JSObject* obj;
+  if (!JSVAL_IS_OBJECT(argv[0]) ||
+      !(obj = JSVAL_TO_OBJECT(argv[0])) ||
+      &sGlobalClass != JS_GetClass(cx, obj) ||
+      obj == JS_GetGlobalObject(cx)) {
+    JS_ReportError(cx, "The first argument to evalInSandbox must be a global object created using createSandbox.");
+    return JS_FALSE;
+  }
+
+  JSString* str = JS_ValueToString(cx, argv[1]);
+  if (!str)
+    return JS_FALSE;
+
+  js::AutoValueRooter ignored(cx);
+  return JS_EvaluateUCScript(cx, obj, JS_GetStringChars(str), JS_GetStringLength(str), "", 1,
+                             ignored.addr());
+}
+
 } // namespace jetpack
 } // namespace mozilla
--- a/js/jetpack/JetpackChild.h
+++ b/js/jetpack/JetpackChild.h
@@ -64,40 +64,37 @@ public:
 
   void CleanUp();
 
 protected:
   NS_OVERRIDE virtual void ActorDestroy(ActorDestroyReason why);
 
   NS_OVERRIDE virtual bool RecvSendMessage(const nsString& messageName,
                                            const nsTArray<Variant>& data);
-  NS_OVERRIDE virtual bool RecvLoadImplementation(const nsCString& code);
-  NS_OVERRIDE virtual bool RecvLoadUserScript(const nsCString& code);
+  NS_OVERRIDE virtual bool RecvEvalScript(const nsString& script);
 
   NS_OVERRIDE virtual PHandleChild* AllocPHandle();
   NS_OVERRIDE virtual bool DeallocPHandle(PHandleChild* actor);
 
 private:
   JSRuntime* mRuntime;
-  JSContext *mImplCx, *mUserCx;
+  JSContext *mCx;
 
   static JetpackChild* GetThis(JSContext* cx);
 
-  static const JSPropertySpec sImplProperties[];
-  static JSBool UserJetpackGetter(JSContext* cx, JSObject* obj, jsval idval,
-                                  jsval* vp);
-
   static const JSFunctionSpec sImplMethods[];
   static JSBool SendMessage(JSContext* cx, uintN argc, jsval *vp);
   static JSBool CallMessage(JSContext* cx, uintN argc, jsval *vp);
   static JSBool RegisterReceiver(JSContext* cx, uintN argc, jsval *vp);
   static JSBool UnregisterReceiver(JSContext* cx, uintN argc, jsval *vp);
   static JSBool UnregisterReceivers(JSContext* cx, uintN argc, jsval *vp);
   static JSBool Wrap(JSContext* cx, uintN argc, jsval *vp);
   static JSBool CreateHandle(JSContext* cx, uintN argc, jsval *vp);
+  static JSBool CreateSandbox(JSContext* cx, uintN argc, jsval *vp);
+  static JSBool EvalInSandbox(JSContext* cx, uintN argc, jsval *vp);
 
   static const JSClass sGlobalClass;
 
   DISALLOW_EVIL_CONSTRUCTORS(JetpackChild);
 };
 
 } // namespace jetpack
 } // namespace mozilla
--- a/js/jetpack/JetpackParent.cpp
+++ b/js/jetpack/JetpackParent.cpp
@@ -58,48 +58,16 @@ JetpackParent::JetpackParent(JSContext* 
 JetpackParent::~JetpackParent()
 {
   if (mSubprocess)
     Destroy();
 }
 
 NS_IMPL_ISUPPORTS1(JetpackParent, nsIJetpack)
 
-static nsresult
-ReadFromURI(const nsAString& aURI,
-            nsCString* content)
-{
-  nsCOMPtr<nsIURI> uri;
-  nsresult rv = NS_NewURI(getter_AddRefs(uri),
-                          NS_ConvertUTF16toUTF8(aURI));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIChannel> channel;
-  NS_NewChannel(getter_AddRefs(channel), uri);
-  NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
-
-  nsCOMPtr<nsIInputStream> input;
-  rv = channel->Open(getter_AddRefs(input));
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ASSERTION(input, "Channel opened successfully but stream was null?");
-
-  char buffer[256];
-  PRUint32 avail = 0;
-  input->Available(&avail);
-  if (avail) {
-    PRUint32 read = 0;
-    while (NS_SUCCEEDED(input->Read(buffer, sizeof(buffer), &read)) && read) {
-      content->Append(buffer, read);
-      read = 0;
-    }
-  }
-
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 JetpackParent::SendMessage(const nsAString& aMessageName)
 {
   nsresult rv;
   nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAXPCNativeCallContext* ncc = NULL;
@@ -154,41 +122,22 @@ JetpackParent::UnregisterReceiver(const 
 NS_IMETHODIMP
 JetpackParent::UnregisterReceivers(const nsAString& aMessageName)
 {
   JetpackActorCommon::UnregisterReceivers(nsString(aMessageName));
   return NS_OK;
 }
 
 NS_IMETHODIMP
-JetpackParent::LoadImplementation(const nsAString& aURI)
+JetpackParent::EvalScript(const nsAString& aScript)
 {
-  nsCString code;
-  nsresult rv = ReadFromURI(aURI, &code);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!code.IsEmpty() &&
-      !SendLoadImplementation(code))
-    rv = NS_ERROR_FAILURE;
-
-  return rv;
-}
+  if (!SendEvalScript(nsString(aScript)))
+    return NS_ERROR_FAILURE;
 
-NS_IMETHODIMP
-JetpackParent::LoadUserScript(const nsAString& aURI)
-{
-  nsCString code;
-  nsresult rv = ReadFromURI(aURI, &code);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!code.IsEmpty() &&
-      !SendLoadUserScript(code))
-    rv = NS_ERROR_FAILURE;
-
-  return rv;
+  return NS_OK;
 }
 
 bool
 JetpackParent::RecvSendMessage(const nsString& messageName,
                                const nsTArray<Variant>& data)
 {
   JSAutoRequest request(mContext);
   return JetpackActorCommon::RecvMessage(mContext, messageName, data, NULL);
--- a/js/jetpack/PJetpack.ipdl
+++ b/js/jetpack/PJetpack.ipdl
@@ -73,18 +73,17 @@ sync protocol PJetpack
 {
   manages PHandle;
 both:
   async SendMessage(nsString messageName,
                     Variant[] data);
   async PHandle();
 
 child:
-  async LoadImplementation(nsCString code);
-  async LoadUserScript(nsCString code);
+  async EvalScript(nsString code);
 
 parent:
   sync CallMessage(nsString messageName,
                    Variant[] data)
     returns (Variant[] results);
 
 };
 
--- a/js/jetpack/nsIJetpack.idl
+++ b/js/jetpack/nsIJetpack.idl
@@ -48,15 +48,14 @@ interface nsIJetpack : nsISupports
                       ... */);
 
   void registerReceiver(in AString aMessageName,
                         in jsval aReceiver);
   void unregisterReceiver(in AString aMessageName,
                           in jsval aReceiver);
   void unregisterReceivers(in AString aMessageName);
 
-  void loadImplementation(in AString aURI);
-  void loadUserScript(in AString aURI);
+  void evalScript(in AString aScript);
 
   nsIVariant createHandle();
 
   void destroy();
 };
--- a/js/jetpack/tests/unit/impl.js
+++ b/js/jetpack/tests/unit/impl.js
@@ -36,8 +36,48 @@ registerReceiver("testarray",
 
 registerReceiver("test primitive types", echo);
 
 registerReceiver("drop methods", echo);
 
 registerReceiver("exception coping", echo);
 
 registerReceiver("duplicate receivers", echo);
+
+function ok(c, msg)
+{
+  sendMessage("test result", c, msg);
+}
+
+registerReceiver("test sandbox", function() {
+  var addon = createSandbox();
+  ok(typeof(addon) == "object", "typeof(addon)");
+  ok("Date" in addon, "addon.Date exists");
+  ok(addon.Date !== Date, "Date objects are different");
+
+  var fn = "var x; var c = 3; function doit() { x = 12; return 4; }";
+  evalInSandbox(addon, fn);
+
+  ok(addon.x === undefined, "x is undefined");
+  ok(addon.c == 3, "c is 3");
+  ok(addon.doit() == 4, "doit called successfully");
+  ok(addon.x == 12, "x is now 12");
+
+  var fn2 = "let function barbar{}";
+  try {
+    evalInSandbox(addon, fn2);
+    ok(false, "bad syntax should throw");
+  }
+  catch(e) {
+    ok(true, "bad syntax should throw");
+  }
+
+  var fn3 = "throw new Error('just kidding')";
+  try {
+    evalInSandbox(addon, fn3);
+    ok(false, "thrown error should be caught");
+  }
+  catch(e) {
+    ok(true, "thrown error should be caught");
+  }
+
+  sendMessage("sandbox done");
+});
--- a/js/jetpack/tests/unit/test_jetpack.js
+++ b/js/jetpack/tests/unit/test_jetpack.js
@@ -1,22 +1,46 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
 var jps = Components.classes["@mozilla.org/jetpack/service;1"]
   .getService(Components.interfaces.nsIJetpackService);
 var jetpack = null;
 
 load("handle_tests.js");
 function createHandle() {
   return jetpack.createHandle();
 }
 
+const PR_RDONLY = 0x1;
+
+function read_file(f)
+{
+  var fis = Cc["@mozilla.org/network/file-input-stream;1"]
+    .createInstance(Ci.nsIFileInputStream);
+  fis.init(f, PR_RDONLY, 0444, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+  var lis = Cc["@mozilla.org/intl/converter-input-stream;1"]
+    .createInstance(Ci.nsIConverterInputStream);
+  lis.init(fis, "UTF-8", 1024, 0);
+
+  var data = "";
+
+  var r = {};
+  while (lis.readString(0x0FFFFFFF, r))
+    data += r.value;
+
+  return data;
+}
+
 function run_test() {
   jetpack = jps.createJetpack();
   run_handle_tests();
 
-  jetpack.loadImplementation("file://" + do_get_file("impl.js").path);
+  jetpack.evalScript(read_file(do_get_file("impl.js")));
 
   var circ1 = {},
       circ2 = {},
       circ3 = {},
       ok = false;
   ((circ1.next = circ2).next = circ3).next = circ1;
   try {
     jetpack.sendMessage("ignored", circ3, circ1);
@@ -157,16 +181,23 @@ function run_test() {
   jetpack.registerReceiver("duplicate receivers",
                            function() {
                              do_check_eq(calls, ".");
                              jetpack.unregisterReceivers("duplicate receivers");
                            });
   jetpack.registerReceiver("duplicate receivers",
                            function() { do_test_finished() });
 
+  jetpack.registerReceiver("test result", function(name, c, msg) {
+    dump("TEST-INFO | test_jetpack.js | remote check '" + msg + "' result: " + c + "\n");
+    do_check_true(c);
+  });
+  jetpack.registerReceiver("sandbox done", do_test_finished);
+
+  do_test_pending();
   do_test_pending();
   do_test_pending();
   do_test_pending();
   do_test_pending();
   do_test_pending();
   do_test_pending();
   do_test_pending();
   do_test_pending();
@@ -178,14 +209,17 @@ function run_test() {
   jetpack.sendMessage("gimmeHandle");
   jetpack.sendMessage("echo2", obj1, obj2);
   jetpack.sendMessage("multireturn begin");
   jetpack.sendMessage("testarray", testarray);
   jetpack.sendMessage("test primitive types",
                       undefined, null, true, false, 1, 2, 999, 1/4, "oyez");
   jetpack.sendMessage("drop methods", drop);
   jetpack.sendMessage("exception coping");
+
   jetpack.sendMessage("duplicate receivers");
 
+  jetpack.sendMessage("test sandbox");
+
   do_register_cleanup(function() {
     jetpack.destroy();
   });
 }
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -170,17 +170,17 @@ class XPCShellTests(object):
     """
       Add arguments to run the test or make it interactive.
     """
     if self.interactive:
       self.xpcsRunArgs = [
       '-e', 'print("To start the test, type |_execute_test();|.");',
       '-i']
     else:
-      self.xpcsRunArgs = ['-e', '_execute_test();']
+      self.xpcsRunArgs = ['-e', '_execute_test(); quit(0);']
 
   def getPipes(self):
     """
       Determine the value of the stdout and stderr for the test.
       Return value is a list (pStdout, pStderr).
     """
     if self.interactive or self.verbose:
       pStdout = None