Implement the multi-process Jetpack API (bug 556846). r=bsmedberg,bent
authorBen Newman <bnewman@mozilla.com>
Fri, 25 Jun 2010 08:00:37 -0700
changeset 46232 810a18af65ae217133a203b481706258a52c9faf
parent 46231 8ffaff868ccfb4d12bfcbfb3e086e7072114f30b
child 46233 5ee6584ed7867e03b8f91d4b6ac2fdf13c13950d
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg, bent
bugs556846
milestone1.9.3a6pre
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
Implement the multi-process Jetpack API (bug 556846). r=bsmedberg,bent
js/jetpack/Handle.h
js/jetpack/JetpackActorCommon.cpp
js/jetpack/JetpackActorCommon.h
js/jetpack/JetpackChild.cpp
js/jetpack/JetpackChild.h
js/jetpack/JetpackParent.cpp
js/jetpack/JetpackParent.h
js/jetpack/JetpackService.cpp
js/jetpack/Makefile.in
js/jetpack/PHandle.ipdl
js/jetpack/PJetpack.ipdl
js/jetpack/ipdl.mk
js/jetpack/nsIJetpack.idl
js/jetpack/tests/Makefile.in
js/jetpack/tests/unit/handle_tests.js
js/jetpack/tests/unit/impl.js
js/jetpack/tests/unit/test_jetpack.js
new file mode 100644
--- /dev/null
+++ b/js/jetpack/Handle.h
@@ -0,0 +1,305 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sw=4 et tw=80:
+ *
+ * ***** 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.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Newman <mozilla@benjamn.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_jetpack_HandleParent_h
+#define mozilla_jetpack_HandleParent_h
+
+#include "mozilla/jetpack/PHandleParent.h"
+#include "mozilla/jetpack/PHandleChild.h"
+
+#include "jsapi.h"
+#include "jsobj.h"
+#include "jscntxt.h"
+
+namespace mozilla {
+namespace jetpack {
+
+/**
+ * BaseType should be one of PHandleParent or PHandleChild; see the
+ * HandleParent and HandleChild typedefs at the bottom of this file.
+ */
+template <class BaseType>
+class Handle
+  : public BaseType
+{
+  Handle(Handle* parent)
+    : mParent(parent)
+    , mObj(NULL)
+    , mRuntime(NULL)
+  {}
+
+  BaseType* AllocPHandle() {
+    return new Handle(this);
+  }
+
+  bool DeallocPHandle(BaseType* actor) {
+    delete actor;
+    return true;
+  }
+
+public:
+
+  Handle()
+    : mParent(NULL)
+    , mObj(NULL)
+    , mRuntime(NULL)
+  {}
+
+  ~Handle() { TearDown(); }
+
+  static Handle* FromJSObject(JSContext* cx, JSObject* obj) {
+    // TODO Convert non-Handles to Handles somehow?
+    return Unwrap(cx, obj);
+  }
+
+  static Handle* FromJSVal(JSContext* cx, jsval val) {
+    if (!JSVAL_IS_OBJECT(val))
+      return NULL;
+    return Unwrap(cx, JSVAL_TO_OBJECT(val));
+  }
+
+  JSObject* ToJSObject(JSContext* cx) const {
+    if (!mObj && !mRuntime) {
+      JSAutoRequest request(cx);
+
+      JSClass* clasp = const_cast<JSClass*>(&sHandle_JSClass);
+      JSObject* obj = JS_NewObject(cx, clasp, NULL, NULL);
+      if (!obj)
+        return NULL;
+      js::AutoObjectRooter root(cx, obj);
+
+      JSPropertySpec* ps = const_cast<JSPropertySpec*>(sHandle_Properties);
+      JSFunctionSpec* fs = const_cast<JSFunctionSpec*>(sHandle_Functions);
+      JSRuntime* rt;
+
+      const char* name = IsParent(this)
+        ? "mozilla::jetpack::Handle<PHandleParent>::mObj"
+        : "mozilla::jetpack::Handle<PHandleChild>::mObj";
+
+      if (JS_SetPrivate(cx, obj, (void*)this) &&
+          JS_DefineProperties(cx, obj, ps) &&
+          JS_DefineFunctions(cx, obj, fs) &&
+          JS_AddNamedRootRT(rt = JS_GetRuntime(cx), (void*)&mObj, name))
+      {
+        mObj = obj;
+        mRuntime = rt;
+      }
+    }
+    return mObj;
+  }
+
+protected:
+
+  void ActorDestroy(typename Handle::ActorDestroyReason why) {
+    TearDown();
+  }
+
+private:
+
+  static bool IsParent(const PHandleParent* handle) { return true; }
+  static bool IsParent(const PHandleChild* handle) { return false; }
+
+  void TearDown() {
+    if (mObj) {
+      mObj->setPrivate(NULL);
+      mObj = NULL;
+      // Nulling out mObj effectively unroots the object, but we still
+      // need to remove the root, else the JS engine will complain at
+      // shutdown.
+      NS_ASSERTION(mRuntime, "Should have a JSRuntime if we had an object");
+      JS_RemoveRootRT(mRuntime, (void*)&mObj);
+      // By not nulling out mRuntime, we prevent ToJSObject from
+      // reviving an invalidated/destroyed handle.
+    }
+  }
+
+  static const JSClass        sHandle_JSClass;
+  static const JSPropertySpec sHandle_Properties[];
+  static const JSFunctionSpec sHandle_Functions[];
+
+  Handle* const mParent;
+
+  // Used to cache the JSObject returned by ToJSObject, which is
+  // otherwise a const method.
+  mutable JSObject*  mObj;
+  mutable JSRuntime* mRuntime;
+
+  static Handle*
+  Unwrap(JSContext* cx, JSObject* obj) {
+    while (obj && obj->getClass() != &sHandle_JSClass)
+      obj = obj->getProto();
+
+    if (!obj)
+      return NULL;
+
+    Handle* self = static_cast<Handle*>(JS_GetPrivate(cx, obj));
+
+    NS_ASSERTION(!self || self->ToJSObject(cx) == obj,
+                 "Wrapper and wrapped object disagree?");
+
+    return self;
+  }
+
+  static JSBool
+  GetParent(JSContext* cx, JSObject* obj, jsval, jsval* vp) {
+    JS_SET_RVAL(cx, vp, JSVAL_NULL);
+
+    Handle* self = Unwrap(cx, obj);
+    if (!self)
+      return JS_TRUE;
+
+    Handle* parent = self->mParent;
+    if (!parent)
+      return JS_TRUE;
+
+    JSObject* pobj = parent->ToJSObject(cx);
+    JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(pobj));
+
+    return JS_TRUE;
+  }
+
+  static JSBool
+  GetIsValid(JSContext* cx, JSObject* obj, jsval, jsval* vp) {
+    Handle* self = Unwrap(cx, obj);
+    JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(!!self));
+    return JS_TRUE;
+  }
+
+  static JSBool
+  Invalidate(JSContext* cx, uintN argc, jsval* vp) {
+    if (argc > 0) {
+      JS_ReportError(cx, "invalidate takes zero arguments");
+      return JS_FALSE;
+    }
+
+    Handle* self = Unwrap(cx, JS_THIS_OBJECT(cx, vp));
+    if (self) {
+      JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(JS_TRUE));
+      if (!Send__delete__(self)) {
+        JS_ReportError(cx, "Failed to send __delete__ while invalidating");
+        return JS_FALSE;
+      }
+    } else {
+      JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(JS_FALSE));
+    }
+
+    return JS_TRUE;
+  }
+
+  static JSBool
+  CreateHandle(JSContext* cx, uintN argc, jsval* vp) {
+    if (argc > 0) {
+      JS_ReportError(cx, "createHandle takes zero arguments");
+      return JS_FALSE;
+    }
+
+    Handle* self = Unwrap(cx, JS_THIS_OBJECT(cx, vp));
+    if (!self) {
+      JS_ReportError(cx, "Tried to create child from invalid handle");
+      return JS_FALSE;
+    }
+
+    BaseType* child = self->SendPHandleConstructor();
+    if (!child) {
+      JS_ReportError(cx, "Failed to construct child");
+      return JS_FALSE;
+    }
+
+    JSObject* obj = static_cast<Handle*>(child)->ToJSObject(cx);
+    JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
+
+    return JS_TRUE;
+  }
+
+  static void
+  Finalize(JSContext* cx, JSObject* obj) {
+    Handle* self = Unwrap(cx, obj);
+    // Avoid warnings about unused return values:
+    self && Send__delete__(self);
+  }
+
+};
+
+template <class BaseType>
+const JSClass
+Handle<BaseType>::sHandle_JSClass = {
+  "IPDL Handle", JSCLASS_HAS_PRIVATE,
+  JS_PropertyStub, JS_PropertyStub,
+  JS_PropertyStub, JS_PropertyStub,
+  JS_EnumerateStub, JS_ResolveStub,
+  JS_ConvertStub, Handle::Finalize,
+  JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+#define HANDLE_PROP_FLAGS (JSPROP_READONLY | JSPROP_PERMANENT)
+
+template <class BaseType>
+const JSPropertySpec
+Handle<BaseType>::sHandle_Properties[] = {
+  { "parent",  0, HANDLE_PROP_FLAGS, GetParent,  NULL },
+  { "isValid", 0, HANDLE_PROP_FLAGS, GetIsValid, NULL },
+  { 0, 0, 0, NULL, NULL }
+};
+
+#undef HANDLE_PROP_FLAGS
+
+#define HANDLE_FUN_FLAGS (JSFUN_FAST_NATIVE |   \
+                          JSPROP_READONLY |     \
+                          JSPROP_PERMANENT)
+
+template <class BaseType>
+const JSFunctionSpec
+Handle<BaseType>::sHandle_Functions[] = {
+  JS_FN("invalidate",   Invalidate,   0, HANDLE_FUN_FLAGS),
+  JS_FN("createHandle", CreateHandle, 0, HANDLE_FUN_FLAGS),
+  JS_FS_END
+};
+
+#undef HANDLE_FUN_FLAGS
+
+// The payoff for using templates is that these two implementations are
+// guaranteed to be perfectly symmetric:
+typedef Handle<PHandleParent> HandleParent;
+typedef Handle<PHandleChild> HandleChild;
+
+} // namespace jetpack
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/js/jetpack/JetpackActorCommon.cpp
@@ -0,0 +1,560 @@
+/* -*- 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 Firefox.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation <http://www.mozilla.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Newman <mozilla@benjamn.com> (original author)
+ *
+ * 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 "base/basictypes.h"
+#include "jscntxt.h"
+
+#include "mozilla/jetpack/JetpackActorCommon.h"
+#include "mozilla/jetpack/PJetpack.h"
+#include "mozilla/jetpack/PHandleParent.h"
+#include "mozilla/jetpack/PHandleChild.h"
+#include "mozilla/jetpack/Handle.h"
+
+#include "jsapi.h"
+#include "jstl.h"
+#include "jshashtable.h"
+
+using mozilla::jetpack::JetpackActorCommon;
+using mozilla::jetpack::PHandleParent;
+using mozilla::jetpack::HandleParent;
+using mozilla::jetpack::PHandleChild;
+using mozilla::jetpack::HandleChild;
+using mozilla::jetpack::KeyValue;
+using mozilla::jetpack::PrimVariant;
+using mozilla::jetpack::CompVariant;
+using mozilla::jetpack::Variant;
+
+class JetpackActorCommon::OpaqueSeenType
+{
+public:
+  typedef JSObject* KeyType;
+  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");
+  }
+
+  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;
+
+  bool add(KeyType obj, IdType* id) {
+    MapType::AddPtr ap = map.lookupForAdd(obj);
+    if (!ap) {
+      if (!rmap.AppendElement(obj) ||
+          !map.add(ap, obj, *id = rmap.Length()))
+        *id = kInvalidId;
+      return true;
+    }
+    *id = ap->value;
+    return false;
+  }
+
+  KeyType reverseLookup(IdType id) {
+    return rmap.SafeElementAt(id - 1, NULL);
+  }
+
+private:
+  MapType map;
+  nsAutoTArray<KeyType, 4> rmap;
+
+};
+
+bool
+JetpackActorCommon::jsval_to_PrimVariant(JSContext* cx, JSType type, jsval from,
+                                         PrimVariant* to)
+{
+  // A false return from this function just means the value couldn't be
+  // converted to a PrimVariant (i.e., it wasn't a primitive value).
+
+  switch (type) {
+  case JSTYPE_VOID:
+    *to = void_t();
+    return true;
+
+  case JSTYPE_NULL:
+    *to = null_t();
+    return true;
+
+  case JSTYPE_FUNCTION:
+    return false;
+
+  case JSTYPE_OBJECT: {
+    HandleParent* hp = HandleParent::FromJSVal(cx, from);
+    HandleChild* hc = HandleChild::FromJSVal(cx, from);
+    NS_ASSERTION(!hc || !hp, "Can't be both a parent and a child");
+    if (hp) {
+      *to = hp;
+      return true;
+    }
+    if (hc) {
+      *to = hc;
+      return true;
+    }
+    return false;
+  }
+
+  case JSTYPE_STRING:
+    *to = nsDependentString((PRUnichar*)JS_GetStringChars(JSVAL_TO_STRING(from)),
+                            JS_GetStringLength(JSVAL_TO_STRING(from)));
+    return true;
+
+  case JSTYPE_NUMBER:
+    if (JSVAL_IS_INT(from))
+      *to = JSVAL_TO_INT(from);
+    else if (JSVAL_IS_DOUBLE(from))
+      *to = *JSVAL_TO_DOUBLE(from);
+    else
+      return false;
+    return true;
+
+  case JSTYPE_BOOLEAN:
+    *to = !!JSVAL_TO_BOOLEAN(from);
+    return true;
+
+  case JSTYPE_XML:
+    return false;
+
+  default:
+    return false;
+  }
+}
+
+bool
+JetpackActorCommon::jsval_to_CompVariant(JSContext* cx, JSType type, jsval from,
+                                         CompVariant* to, OpaqueSeenType* seen)
+{
+  if (type != JSTYPE_OBJECT)
+    return false;
+
+  js::LazilyConstructed<OpaqueSeenType> lost;
+  if (!seen) {
+    lost.construct();
+    seen = lost.addr();
+    if (!seen->ok())
+      return false;
+  }
+
+  OpaqueSeenType::KeyType obj = JSVAL_TO_OBJECT(from);
+  OpaqueSeenType::IdType id;
+  if (!seen->add(obj, &id)) {
+    if (OpaqueSeenType::kInvalidId == id)
+      return false;
+    *to = CompVariant(id);
+    return true;
+  }
+
+  if (JS_IsArrayObject(cx, obj)) {
+    nsTArray<Variant> elems;
+    jsuint len;
+    if (!JS_GetArrayLength(cx, obj, &len) ||
+        !elems.SetCapacity(len))
+      return false;
+    for (jsuint i = 0; i < len; ++i) {
+      jsval val;
+      Variant* vp = elems.AppendElement();
+      if (!JS_GetElement(cx, obj, i, &val) ||
+          !jsval_to_Variant(cx, val, vp, seen))
+        *vp = void_t();
+    }
+    *to = elems;
+    return true;
+  }
+
+  js::AutoIdArray ida(cx, JS_Enumerate(cx, obj));
+  if (!ida)
+    return false;
+
+  nsTArray<KeyValue> kvs;
+  for (size_t i = 0; i < ida.length(); ++i) {
+    jsval val; // reused for both key and value
+    if (!JS_IdToValue(cx, ida[i], &val))
+      return false;
+    JSString* idStr = JS_ValueToString(cx, val);
+    if (!idStr)
+      return false;
+    if (!JS_GetPropertyById(cx, obj, ida[i], &val))
+      return false;
+    KeyValue kv;
+    // Silently drop properties that can't be converted.
+    if (jsval_to_Variant(cx, val, &kv.value(), seen)) {
+      kv.key() = nsDependentString((PRUnichar*)JS_GetStringChars(idStr),
+                                   JS_GetStringLength(idStr));
+      // If AppendElement fails, we lose this property, no big deal.
+      kvs.AppendElement(kv);
+    }
+  }
+  *to = kvs;
+
+  return true;
+}
+
+bool
+JetpackActorCommon::jsval_to_Variant(JSContext* cx, jsval from, Variant* to,
+                                     OpaqueSeenType* seen)
+{
+  JSType type = JS_TypeOfValue(cx, from);
+  if (JSVAL_IS_NULL(from))
+    type = JSTYPE_NULL;
+
+  PrimVariant pv;
+  if (jsval_to_PrimVariant(cx, type, from, &pv)) {
+    *to = pv;
+    return true;
+  }
+
+  CompVariant cv;
+  if (jsval_to_CompVariant(cx, type, from, &cv, seen)) {
+    *to = cv;
+    return true;
+  }
+
+  return false;
+}
+
+bool
+JetpackActorCommon::jsval_from_PrimVariant(JSContext* cx,
+                                           const PrimVariant& from,
+                                           jsval* to)
+{
+  switch (from.type()) {
+  case PrimVariant::Tvoid_t:
+    *to = JSVAL_VOID;
+    return true;
+
+  case PrimVariant::Tnull_t:
+    *to = JSVAL_NULL;
+    return true;
+
+  case PrimVariant::Tbool:
+    *to = from.get_bool() ? JSVAL_TRUE : JSVAL_FALSE;
+    return true;
+
+  case PrimVariant::Tint:
+    *to = INT_TO_JSVAL(from.get_int());
+    return true;
+
+  case PrimVariant::Tdouble:
+    return !!JS_NewDoubleValue(cx, from.get_double(), to);
+
+  case PrimVariant::TnsString: {
+    const nsString& str = from.get_nsString();
+    // TODO Use some sort of sharedstring/stringbuffer abstraction to
+    // exploit sharing opportunities more generally.
+    if (!str.Length()) {
+      *to = JS_GetEmptyStringValue(cx);
+      return true;
+    }
+    JSString* s =
+      JS_NewUCStringCopyN(cx, str.get(), str.Length());
+    if (!s)
+      return false;
+    *to = STRING_TO_JSVAL(s);
+    return true;
+  }
+
+  case PrimVariant::TPHandleParent: {
+    JSObject* hobj =
+      static_cast<const HandleParent*>(from.get_PHandleParent())->ToJSObject(cx);
+    if (!hobj)
+      return false;
+    *to = OBJECT_TO_JSVAL(hobj);
+    return true;
+  }    
+
+  case PrimVariant::TPHandleChild: {
+    JSObject* hobj =
+      static_cast<const HandleChild*>(from.get_PHandleChild())->ToJSObject(cx);
+    if (!hobj)
+      return false;
+    *to = OBJECT_TO_JSVAL(hobj);
+    return true;
+  }
+
+  default:
+    return false;
+  }
+}
+
+bool
+JetpackActorCommon::jsval_from_CompVariant(JSContext* cx,
+                                           const CompVariant& from,
+                                           jsval* to,
+                                           OpaqueSeenType* seen)
+{
+  js::LazilyConstructed<OpaqueSeenType> lost;
+  if (!seen) {
+    lost.construct();
+    seen = lost.addr();
+    if (!seen->ok())
+      return false;
+  }
+
+  JSObject* obj = NULL;
+
+  switch (from.type()) {
+  case CompVariant::TArrayOfKeyValue: {
+    if (!(obj = JS_NewObject(cx, NULL, NULL, NULL)))
+      return false;
+    js::AutoObjectRooter root(cx, obj);
+
+    OpaqueSeenType::IdType ignored;
+    if (!seen->add(obj, &ignored))
+      return false;
+
+    const nsTArray<KeyValue>& kvs = from.get_ArrayOfKeyValue();
+    for (PRUint32 i = 0; i < kvs.Length(); ++i) {
+      const KeyValue& kv = kvs.ElementAt(i);
+      js::AutoValueRooter toSet(cx);
+      if (!jsval_from_Variant(cx, kv.value(), toSet.addr(), seen) ||
+          !JS_SetUCProperty(cx, obj,
+                            kv.key().get(),
+                            kv.key().Length(),
+                            toSet.addr()))
+        return false;
+    }
+
+    break;
+  }
+
+  case CompVariant::TArrayOfVariant: {
+    const nsTArray<Variant>& vs = from.get_ArrayOfVariant();
+    nsAutoTArray<jsval, 8> jsvals;
+    jsval* elems = jsvals.AppendElements(vs.Length());
+    if (!elems)
+      return false;
+    for (PRUint32 i = 0; i < vs.Length(); ++i)
+      elems[i] = JSVAL_VOID;
+    js::AutoArrayRooter root(cx, vs.Length(), elems);
+
+    OpaqueSeenType::IdType ignored;
+    if (!seen->add(obj, &ignored))
+      return false;
+
+    for (PRUint32 i = 0; i < vs.Length(); ++i)
+      if (!jsval_from_Variant(cx, vs.ElementAt(i), elems + i, seen))
+        return false;
+
+    if (!(obj = JS_NewArrayObject(cx, vs.Length(), elems)))
+      return false;
+
+    break;
+  }
+
+  case CompVariant::Tsize_t:
+    if (!(obj = seen->reverseLookup(from.get_size_t())))
+      return false;
+    break;
+
+  default:
+    return false;
+  }
+
+  *to = OBJECT_TO_JSVAL(obj);
+  return true;
+}
+
+bool
+JetpackActorCommon::jsval_from_Variant(JSContext* cx, const Variant& from,
+                                       jsval* to, OpaqueSeenType* seen)
+{
+  switch (from.type()) {
+  case Variant::TPrimVariant:
+    return jsval_from_PrimVariant(cx, from, to);
+  case Variant::TCompVariant:
+    return jsval_from_CompVariant(cx, from, to, seen);
+  default:
+    return false;
+  }
+}
+
+bool
+JetpackActorCommon::RecvMessage(JSContext* cx,
+                                const nsString& messageName,
+                                const nsTArray<Variant>& data,
+                                nsTArray<Variant>* results)
+{
+  if (results)
+    results->Clear();
+
+  RecList* list;
+  if (!mReceivers.Get(messageName, &list))
+    return true;
+  nsAutoTArray<jsval, 4> snapshot;
+  list->copyTo(snapshot);
+  if (!snapshot.Length())
+    return true;
+  
+  nsAutoTArray<jsval, 4> args;
+  PRUint32 argc = data.Length() + 1;
+  jsval* argv = args.AppendElements(argc);
+  if (!argv)
+    return false;
+  for (PRUint32 i = 0; i < argc; ++i)
+    argv[i] = JSVAL_VOID;
+  js::AutoArrayRooter argvRooter(cx, argc, argv);
+
+  JSString* msgNameStr =
+    JS_NewUCStringCopyN(cx,
+                        messageName.get(),
+                        messageName.Length());
+  if (!msgNameStr)
+    return false;
+  argv[0] = STRING_TO_JSVAL(msgNameStr);
+
+  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);
+      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;
+    delete old;
+  }
+}
+
+void
+JetpackActorCommon::RecList::add(jsval v)
+{
+  RecNode* node = mHead, *tail = NULL;
+  while (node) {
+    if (node->value() == v)
+      return;
+    node = (tail = node)->down;
+  }
+  node = new RecNode(mCx, v);
+  if (tail)
+    tail->down = node;
+  else
+    mHead = node;
+}
+
+void
+JetpackActorCommon::RecList::remove(jsval v)
+{
+  while (mHead && mHead->value() == v) {
+    RecNode* old = mHead;
+    mHead = mHead->down;
+    delete old;
+  }
+  if (!mHead)
+    return;
+  RecNode* prev = mHead, *node = prev->down;
+  while (node) {
+    if (node->value() == v) {
+      prev->down = node->down;
+      delete node;
+    }
+    node = (prev = node)->down;
+  }
+}
+
+void
+JetpackActorCommon::RecList::copyTo(nsTArray<jsval>& dst) const
+{
+  dst.Clear();
+  for (RecNode* node = mHead; node; node = node->down)
+    dst.AppendElement(node->value());
+}
+
+nsresult
+JetpackActorCommon::RegisterReceiver(JSContext* cx,
+                                     const nsString& messageName,
+                                     jsval receiver)
+{
+  if (!JSVAL_IS_OBJECT(receiver) ||
+      !JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(receiver)))
+    return NS_ERROR_INVALID_ARG;
+
+  RecList* list;
+  if (!mReceivers.Get(messageName, &list)) {
+    list = new RecList(cx);
+    if (!list || !mReceivers.Put(messageName, list)) {
+      delete list;
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  list->add(receiver);
+
+  return NS_OK;
+}
+
+void
+JetpackActorCommon::UnregisterReceiver(const nsString& messageName,
+                                       jsval receiver)
+{
+  RecList* list;
+  if (!mReceivers.Get(messageName, &list))
+    return;
+  list->remove(receiver);
+}
new file mode 100644
--- /dev/null
+++ b/js/jetpack/JetpackActorCommon.h
@@ -0,0 +1,143 @@
+/* -*- 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 Firefox.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation <http://www.mozilla.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Newman <mozilla@benjamn.com> (original author)
+ *
+ * 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 ***** */
+
+#ifndef mozilla_jetpack_JetpackActorCommon_h
+#define mozilla_jetpack_JetpackActorCommon_h
+
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsAutoJSValHolder.h"
+
+struct JSContext;
+
+namespace mozilla {
+namespace jetpack {
+
+struct KeyValue;
+class PrimVariant;
+class CompVariant;
+class Variant;
+
+class JetpackActorCommon
+{
+public:
+
+  bool
+  RecvMessage(JSContext* cx,
+              const nsString& messageName,
+              const nsTArray<Variant>& data,
+              nsTArray<Variant>* results);
+
+  nsresult
+  RegisterReceiver(JSContext* cx,
+                   const nsString& messageName,
+                   jsval receiver);
+
+  void
+  UnregisterReceiver(const nsString& messageName,
+                     jsval receiver);
+
+  void
+  UnregisterReceivers(const nsString& messageName) {
+    mReceivers.Remove(messageName);
+  }
+
+  void ClearReceivers() {
+    mReceivers.Clear();
+  }
+
+  class OpaqueSeenType;
+  static bool jsval_to_Variant(JSContext* cx, jsval from, Variant* to,
+                               OpaqueSeenType* seen = NULL);
+  static bool jsval_from_Variant(JSContext* cx, const Variant& from, jsval* to,
+                                 OpaqueSeenType* seen = NULL);
+
+protected:
+
+  JetpackActorCommon() {
+    mReceivers.Init();
+    NS_ASSERTION(mReceivers.IsInitialized(),
+                 "Failed to initialize message receiver hash set");
+  }
+
+private:
+
+  static bool jsval_to_PrimVariant(JSContext* cx, JSType type, jsval from,
+                                   PrimVariant* to);
+  static bool jsval_to_CompVariant(JSContext* cx, JSType type, jsval from,
+                                   CompVariant* to, OpaqueSeenType* seen);
+
+  static bool jsval_from_PrimVariant(JSContext* cx, const PrimVariant& from,
+                                     jsval* to);
+  static bool jsval_from_CompVariant(JSContext* cx, const CompVariant& from,
+                                     jsval* to, OpaqueSeenType* seen);
+
+  // Don't want to be memcpy'ing nsAutoJSValHolders around, so we need a
+  // linked list of receivers.
+  class RecList
+  {
+    JSContext* mCx;
+    class RecNode
+    {
+      nsAutoJSValHolder mHolder;
+    public:
+      RecNode* down;
+      RecNode(JSContext* cx, jsval v) : down(NULL) {
+        mHolder.Hold(cx);
+        mHolder = v;
+      }
+      jsval value() { return mHolder; }
+    }* mHead;
+  public:
+    RecList(JSContext* cx) : mCx(cx), mHead(NULL) {}
+   ~RecList();
+    void add(jsval v);
+    void remove(jsval v);
+    void copyTo(nsTArray<jsval>& dst) const;
+  };
+
+  nsClassHashtable<nsStringHashKey, RecList> mReceivers;
+
+};
+
+} // namespace jetpack
+} // namespace mozilla
+
+#endif
--- a/js/jetpack/JetpackChild.cpp
+++ b/js/jetpack/JetpackChild.cpp
@@ -30,54 +30,395 @@
  * 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 "mozilla/jetpack/JetpackChild.h"
+#include "base/basictypes.h"
+#include "jscntxt.h"
 
-#include <stdio.h>
+#include "mozilla/jetpack/JetpackChild.h"
+#include "mozilla/jetpack/Handle.h"
+
+#include "jsarray.h"
 
 namespace mozilla {
 namespace jetpack {
 
 JetpackChild::JetpackChild()
 {
-  NS_ASSERTION(!gInstance, "Something terribly wrong here!");
-  gInstance = this;
 }
 
 JetpackChild::~JetpackChild()
 {
-  NS_ASSERTION(gInstance == this, "Something terribly wrong here!");
-  gInstance = nsnull;
 }
 
+#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_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
+};
+  
 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)))
+    return false;
+
+  {
+    JSAutoRequest request(mImplCx);
+    JS_SetContextPrivate(mImplCx, this);
+    JSObject* implGlobal =
+      JS_NewGlobalObject(mImplCx, const_cast<JSClass*>(&sGlobalClass));
+    if (!implGlobal ||
+        !JS_InitStandardClasses(mImplCx, implGlobal) ||
+        !JS_DefineProperties(mImplCx, implGlobal,
+                             const_cast<JSPropertySpec*>(sImplProperties)) ||
+        !JS_DefineFunctions(mImplCx, 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()
 {
+  JS_DestroyContext(mUserCx);
+  JS_DestroyContext(mImplCx);
+  JS_DestroyRuntime(mRuntime);
+  JS_ShutDown();
+}
+
+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;
+}
+
+bool
+JetpackChild::RecvLoadImplementation(const nsCString& code)
+{
+  return Evaluate(mImplCx, code);
+}
+
+bool
+JetpackChild::RecvLoadUserScript(const nsCString& code)
+{
+  return Evaluate(mUserCx, code);
+}
+
+PHandleChild*
+JetpackChild::AllocPHandle()
+{
+  return new HandleChild();
 }
 
 bool
-JetpackChild::RecvLoadImplementation(const nsCString& script)
+JetpackChild::DeallocPHandle(PHandleChild* actor)
 {
-  printf("Received LoadImplementation message: '%s'\n", script.get());
+  delete actor;
   return true;
 }
 
-JetpackChild* JetpackChild::gInstance;
+JetpackChild*
+JetpackChild::GetThis(JSContext* cx)
+{
+  JetpackChild* self =
+    static_cast<JetpackChild*>(JS_GetContextPrivate(cx));
+  JS_ASSERT(cx == self->mImplCx ||
+            cx == self->mUserCx);
+  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)
+{
+  if (argc < 1) {
+    JS_ReportError(cx, "Message requires a name, at least");
+    return JS_FALSE;
+  }
+
+  jsval* argv = JS_ARGV(cx, vp);
+
+  JSString* msgNameStr = JS_ValueToString(cx, argv[0]);
+  if (!msgNameStr) {
+    JS_ReportError(cx, "Could not convert value to string");
+    return JS_FALSE;
+  }
+
+  result->msgName.Assign((PRUnichar*)JS_GetStringChars(msgNameStr),
+                         JS_GetStringLength(msgNameStr));
+
+  result->data.Clear();
+
+  if (!result->data.SetCapacity(argc)) {
+    JS_ReportOutOfMemory(cx);
+    return JS_FALSE;
+  }
+
+  for (uintN i = 1; i < argc; ++i) {
+    Variant* vp = result->data.AppendElement();
+    if (!JetpackActorCommon::jsval_to_Variant(cx, argv[i], vp)) {
+      JS_ReportError(cx, "Invalid message argument at position %d", i);
+      return JS_FALSE;
+    }
+  }
+
+  return JS_TRUE;
+}
+
+JSBool
+JetpackChild::SendMessage(JSContext* cx, uintN argc, jsval* vp)
+{
+  MessageResult smr;
+  if (!MessageCommon(cx, argc, vp, &smr))
+    return JS_FALSE;
+
+  if (!GetThis(cx)->SendSendMessage(smr.msgName, smr.data)) {
+    JS_ReportError(cx, "Failed to sendMessage");
+    return JS_FALSE;
+  }
+
+  return JS_TRUE;
+}
+
+JSBool
+JetpackChild::CallMessage(JSContext* cx, uintN argc, jsval* vp)
+{
+  MessageResult smr;
+  if (!MessageCommon(cx, argc, vp, &smr))
+    return JS_FALSE;
+
+  nsTArray<Variant> results;
+  if (!GetThis(cx)->SendCallMessage(smr.msgName, smr.data, &results)) {
+    JS_ReportError(cx, "Failed to callMessage");
+    return JS_FALSE;
+  }
+
+  nsAutoTArray<jsval, 4> jsvals;
+  jsval* rvals = jsvals.AppendElements(results.Length());
+  if (!rvals) {
+    JS_ReportOutOfMemory(cx);
+    return JS_FALSE;
+  }
+  for (PRUint32 i = 0; i < results.Length(); ++i)
+    rvals[i] = JSVAL_VOID;
+  js::AutoArrayRooter root(cx, results.Length(), rvals);
+
+  for (PRUint32 i = 0; i < results.Length(); ++i)
+    if (!jsval_from_Variant(cx, results.ElementAt(i), rvals + i)) {
+      JS_ReportError(cx, "Invalid result from handler %d", i);
+      return JS_FALSE;
+    }
+
+  JSObject* arrObj = JS_NewArrayObject(cx, results.Length(), rvals);
+  if (!arrObj) {
+    JS_ReportOutOfMemory(cx);
+    return JS_FALSE;
+  }
+  JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(arrObj));
+
+  return JS_TRUE;
+}
+
+struct ReceiverResult
+{
+  nsString msgName;
+  jsval receiver;
+};
+
+static JSBool
+ReceiverCommon(JSContext* cx, uintN argc, jsval* vp,
+               const char* methodName, uintN arity,
+               ReceiverResult* result)
+{
+  if (argc != arity) {
+    JS_ReportError(cx, "%s requires exactly %d arguments", methodName, arity);
+    return JS_FALSE;
+  }
+
+  // Not currently possible, but think of the future.
+  if (arity < 1)
+    return JS_TRUE;
+
+  jsval* argv = JS_ARGV(cx, vp);
+
+  JSString* str = JS_ValueToString(cx, argv[0]);
+  if (!str) {
+    JS_ReportError(cx, "%s expects a stringifiable value as its first argument",
+                   methodName);
+    return JS_FALSE;
+  }
+
+  result->msgName.Assign((PRUnichar*)JS_GetStringChars(str),
+                         JS_GetStringLength(str));
+
+  if (arity < 2)
+    return JS_TRUE;
+
+  if (!JSVAL_IS_OBJECT(argv[1]) ||
+      !JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(argv[1])))
+  {
+    JS_ReportError(cx, "%s expects a function as its second argument",
+                   methodName);
+    return JS_FALSE;
+  }
+
+  // GC-safe because argv is rooted.
+  result->receiver = argv[1];
+
+  return JS_TRUE;
+}
+
+JSBool
+JetpackChild::RegisterReceiver(JSContext* cx, uintN argc, jsval* vp)
+{
+  ReceiverResult rr;
+  if (!ReceiverCommon(cx, argc, vp, "registerReceiver", 2, &rr))
+    return JS_FALSE;
+
+  JetpackActorCommon* actor = GetThis(cx);
+  nsresult rv = actor->RegisterReceiver(cx, rr.msgName, rr.receiver);
+  if (NS_FAILED(rv)) {
+    JS_ReportOutOfMemory(cx);
+    return JS_FALSE;
+  }
+
+  return JS_TRUE;
+}
+
+JSBool
+JetpackChild::UnregisterReceiver(JSContext* cx, uintN argc, jsval* vp)
+{
+  ReceiverResult rr;
+  if (!ReceiverCommon(cx, argc, vp, "unregisterReceiver", 2, &rr))
+    return JS_FALSE;
+
+  JetpackActorCommon* actor = GetThis(cx);
+  actor->UnregisterReceiver(rr.msgName, rr.receiver);
+  return JS_TRUE;
+}
+
+JSBool
+JetpackChild::UnregisterReceivers(JSContext* cx, uintN argc, jsval* vp)
+{
+  ReceiverResult rr;
+  if (!ReceiverCommon(cx, argc, vp, "unregisterReceivers", 1, &rr))
+    return JS_FALSE;
+
+  JetpackActorCommon* actor = GetThis(cx);
+  actor->UnregisterReceivers(rr.msgName);
+  return JS_TRUE;
+}
+
+JSBool
+JetpackChild::Wrap(JSContext* cx, uintN argc, jsval* vp)
+{
+  NS_NOTYETIMPLEMENTED("wrap not yet implemented (depends on bug 563010)");
+  return JS_FALSE;
+}
+
+JSBool
+JetpackChild::CreateHandle(JSContext* cx, uintN argc, jsval* vp)
+{
+  if (argc > 0) {
+    JS_ReportError(cx, "createHandle takes zero arguments");
+    return JS_FALSE;
+  }
+
+  HandleChild* handle;
+  JSObject* hobj;
+
+  PHandleChild* phc = GetThis(cx)->SendPHandleConstructor();
+  if (!(handle = static_cast<HandleChild*>(phc)) ||
+      !(hobj = handle->ToJSObject(cx))) {
+    JS_ReportError(cx, "Failed to construct Handle");
+    return JS_FALSE;
+  }
+
+  JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(hobj));
+
+  return JS_TRUE;
+}
 
 } // namespace jetpack
 } // namespace mozilla
--- a/js/jetpack/JetpackChild.h
+++ b/js/jetpack/JetpackChild.h
@@ -34,39 +34,70 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_jetpack_JetpackChild_h
 #define mozilla_jetpack_JetpackChild_h
 
 #include "mozilla/jetpack/PJetpackChild.h"
+#include "mozilla/jetpack/JetpackActorCommon.h"
+
+#include "nsTArray.h"
 
 namespace mozilla {
 namespace jetpack {
 
-class JetpackChild : public PJetpackChild
+class PHandleChild;
+
+class JetpackChild
+  : public PJetpackChild
+  , private JetpackActorCommon
 {
 public:
   JetpackChild();
   ~JetpackChild();
 
   static JetpackChild* current();
 
   bool Init(base::ProcessHandle aParentProcessHandle,
             MessageLoop* aIOLoop,
             IPC::Channel* aChannel);
 
   void CleanUp();
 
 protected:
-  NS_OVERRIDE virtual bool RecvLoadImplementation(const nsCString& script);
+  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 PHandleChild* AllocPHandle();
+  NS_OVERRIDE virtual bool DeallocPHandle(PHandleChild* actor);
 
 private:
-  static JetpackChild* gInstance;
+  JSRuntime* mRuntime;
+  JSContext *mImplCx, *mUserCx;
+
+  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 const JSClass sGlobalClass;
 
   DISALLOW_EVIL_CONSTRUCTORS(JetpackChild);
 };
 
 } // namespace jetpack
 } // namespace mozilla
 
 
--- a/js/jetpack/JetpackParent.cpp
+++ b/js/jetpack/JetpackParent.cpp
@@ -31,40 +31,210 @@
  * 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 "mozilla/jetpack/JetpackParent.h"
+#include "mozilla/jetpack/Handle.h"
+
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsIVariant.h"
+#include "nsIXPConnect.h"
 
 namespace mozilla {
 namespace jetpack {
 
-JetpackParent::JetpackParent()
+JetpackParent::JetpackParent(JSContext* cx)
   : mSubprocess(new JetpackProcessParent())
+  , mContext(cx)
 {
   mSubprocess->Launch();
   Open(mSubprocess->GetChannel(),
        mSubprocess->GetChildProcessHandle());
 }
 
 JetpackParent::~JetpackParent()
 {
   XRE_GetIOMessageLoop()
     ->PostTask(FROM_HERE, new DeleteTask<JetpackProcessParent>(mSubprocess));
 }
 
 NS_IMPL_ISUPPORTS1(JetpackParent, nsIJetpack)
 
-NS_IMETHODIMP
-JetpackParent::LoadImplementation(const nsAString& aURI)
+static nsresult
+ReadFromURI(const nsAString& aURI,
+            nsCString* content)
 {
-  // this is all wrong, load the URI and send the data, but for now...
-  if (!SendLoadImplementation(NS_ConvertUTF16toUTF8(aURI)))
+  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;
+  rv = xpc->GetCurrentNativeCallContext(&ncc);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSContext* cx;
+  rv = ncc->GetJSContext(&cx);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRUint32 argc;
+  rv = ncc->GetArgc(&argc);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  jsval* argv;
+  rv = ncc->GetArgvPtr(&argv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsTArray<Variant> data;
+  NS_ENSURE_TRUE(data.SetCapacity(argc), NS_ERROR_OUT_OF_MEMORY);
+
+  JSAutoRequest request(cx);
+
+  for (PRUint32 i = 1; i < argc; ++i)
+    if (!jsval_to_Variant(cx, argv[i], data.AppendElement()))
+      return NS_ERROR_INVALID_ARG;
+
+  if (!SendSendMessage(nsString(aMessageName), data))
     return NS_ERROR_FAILURE;
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+JetpackParent::RegisterReceiver(const nsAString& aMessageName,
+                                jsval aReceiver)
+{
+  return JetpackActorCommon::RegisterReceiver(mContext,
+                                              nsString(aMessageName),
+                                              aReceiver);
+}
+
+NS_IMETHODIMP
+JetpackParent::UnregisterReceiver(const nsAString& aMessageName,
+                                  jsval aReceiver)
+{
+  JetpackActorCommon::UnregisterReceiver(nsString(aMessageName),
+                                         aReceiver);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+JetpackParent::UnregisterReceivers(const nsAString& aMessageName)
+{
+  JetpackActorCommon::UnregisterReceivers(nsString(aMessageName));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+JetpackParent::LoadImplementation(const nsAString& aURI)
+{
+  nsCString code;
+  nsresult rv = ReadFromURI(aURI, &code);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!code.IsEmpty() &&
+      !SendLoadImplementation(code))
+    rv = NS_ERROR_FAILURE;
+
+  return rv;
+}
+
+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;
+}
+
+bool
+JetpackParent::RecvSendMessage(const nsString& messageName,
+                               const nsTArray<Variant>& data)
+{
+  JSAutoRequest request(mContext);
+  return JetpackActorCommon::RecvMessage(mContext, messageName, data, NULL);
+}
+
+bool
+JetpackParent::RecvCallMessage(const nsString& messageName,
+                               const nsTArray<Variant>& data,
+                               nsTArray<Variant>* results)
+{
+  JSAutoRequest request(mContext);
+  return JetpackActorCommon::RecvMessage(mContext, messageName, data, results);
+}
+
+NS_IMETHODIMP
+JetpackParent::CreateHandle(nsIVariant** aResult)
+{
+  HandleParent* handle =
+    static_cast<HandleParent*>(SendPHandleConstructor());
+  NS_ENSURE_TRUE(handle, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv;
+  nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSAutoRequest request(mContext);
+
+  JSObject* hobj = handle->ToJSObject(mContext);
+  if (!hobj)
+    return NS_ERROR_FAILURE;
+
+  return xpc->JSToVariant(mContext, OBJECT_TO_JSVAL(hobj), aResult);
+}
+
+PHandleParent*
+JetpackParent::AllocPHandle()
+{
+  return new HandleParent();
+}
+
+bool
+JetpackParent::DeallocPHandle(PHandleParent* actor)
+{
+  delete actor;
+  return true;
+}
+
 } // namespace jetpack
 } // namespace mozilla
--- a/js/jetpack/JetpackParent.h
+++ b/js/jetpack/JetpackParent.h
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Firefox.
  *
  * The Initial Developer of the Original Code is
  * the Mozilla Foundation <http://www.mozilla.org>.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
+ *   Ben Newman <mozilla@benjamn.com>
  *
  * 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
@@ -35,34 +36,53 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_jetpack_JetpackParent_h
 #define mozilla_jetpack_JetpackParent_h
 
 #include "mozilla/jetpack/PJetpackParent.h"
 #include "mozilla/jetpack/JetpackProcessParent.h"
+#include "mozilla/jetpack/JetpackActorCommon.h"
 #include "nsIJetpack.h"
 
+#include "nsTArray.h"
+
+struct JSContext;
+
 namespace mozilla {
 namespace jetpack {
 
+class PHandleParent;
+
 class JetpackParent
   : public PJetpackParent
   , public nsIJetpack
+  , private JetpackActorCommon
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIJETPACK
 
-  JetpackParent();
+  JetpackParent(JSContext* cx);
   ~JetpackParent();
 
+protected:
+  NS_OVERRIDE virtual bool RecvSendMessage(const nsString& messageName,
+                                           const nsTArray<Variant>& data);
+  NS_OVERRIDE virtual bool RecvCallMessage(const nsString& messageName,
+                                           const nsTArray<Variant>& data,
+                                           nsTArray<Variant>* results);
+
+  NS_OVERRIDE virtual PHandleParent* AllocPHandle();
+  NS_OVERRIDE virtual bool DeallocPHandle(PHandleParent* actor);
+
 private:
   JetpackProcessParent* mSubprocess;
+  JSContext* mContext;
 
   DISALLOW_EVIL_CONSTRUCTORS(JetpackParent);
 };
 
 } // namespace jetpack
 } // namespace mozilla
 
 #endif // mozilla_jetpack_JetpackParent_h
--- a/js/jetpack/JetpackService.cpp
+++ b/js/jetpack/JetpackService.cpp
@@ -38,27 +38,42 @@
 #include "base/basictypes.h"
 #include "mozilla/jetpack/JetpackService.h"
 
 #include "mozilla/jetpack/JetpackParent.h"
 #include "nsIJetpack.h"
 
 #include "nsIGenericFactory.h"
 
+#include "nsIXPConnect.h"
+
 namespace mozilla {
 namespace jetpack {
 
 NS_IMPL_ISUPPORTS1(JetpackService,
                    nsIJetpackService)
 
 NS_IMETHODIMP
 JetpackService::CreateJetpack(nsIJetpack** aResult)
 {
-  nsRefPtr<JetpackParent> j = new JetpackParent();
+  nsresult rv;
+  nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAXPCNativeCallContext* ncc = NULL;
+  rv = xpc->GetCurrentNativeCallContext(&ncc);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSContext* cx;
+  rv = ncc->GetJSContext(&cx);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<JetpackParent> j = new JetpackParent(cx);
   *aResult = j.forget().get();
+
   return NS_OK;
 }
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(JetpackService)
 
 } // namespace jetpack
 } // namespace mozilla
 
--- a/js/jetpack/Makefile.in
+++ b/js/jetpack/Makefile.in
@@ -56,21 +56,28 @@ XPIDLSRCS = \
 
 EXPORTS_NAMESPACES = mozilla/jetpack
 EXPORTS_mozilla/jetpack = \
   JetpackProcessChild.h \
   JetpackProcessParent.h \
   JetpackParent.h \
   JetpackChild.h \
   JetpackService.h \
+  JetpackActorCommon.h \
+  Handle.h \
   $(NULL)
 
 CPPSRCS = \
   JetpackParent.cpp \
   JetpackChild.cpp \
   JetpackProcessChild.cpp \
   JetpackProcessParent.cpp \
   JetpackService.cpp \
+  JetpackActorCommon.cpp \
   $(NULL)
 
+ifdef ENABLE_TESTS
+TOOL_DIRS += tests
+endif
+
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/js/jetpack/PHandle.ipdl
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sw=4 et tw=80:
+ *
+ * ***** 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.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Newman <mozilla@benjamn.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 PJetpack;
+
+namespace mozilla {
+namespace jetpack {
+
+async protocol PHandle
+{
+  manager PJetpack or PHandle;
+  manages PHandle;
+both:
+  PHandle();
+  __delete__();
+};
+
+} // namespace jetpack
+} // namespace mozilla
--- a/js/jetpack/PJetpack.ipdl
+++ b/js/jetpack/PJetpack.ipdl
@@ -30,19 +30,63 @@
  * 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 PHandle;
+
+using mozilla::void_t;
+using mozilla::null_t;
+
 namespace mozilla {
 namespace jetpack {
 
-async protocol PJetpack
-{
-child:
-  async LoadImplementation(nsCString script);
+struct KeyValue {
+  nsString key;
+  Variant value;
+};
+
+union PrimVariant {
+  void_t;
+  null_t;
+  bool;
+  int;
+  double;
+  nsString;
+  PHandle;
 };
 
-}
-}
+union CompVariant {
+  KeyValue[];
+  Variant[];
+  size_t; // reference
+};
+  
+union Variant {
+  PrimVariant;
+  CompVariant;
+};
+
+sync protocol PJetpack
+{
+  manages PHandle;
+both:
+  async SendMessage(nsString messageName,
+                    Variant[] data);
+  async PHandle();
+
+child:
+  async LoadImplementation(nsCString code);
+  async LoadUserScript(nsCString code);
+
+parent:
+  sync CallMessage(nsString messageName,
+                   Variant[] data)
+    returns (Variant[] results);
+
+};
+
+} // namespace jetpack
+} // namespace mozilla
--- a/js/jetpack/ipdl.mk
+++ b/js/jetpack/ipdl.mk
@@ -1,3 +1,4 @@
 IPDLSRCS = \
   PJetpack.ipdl \
+  PHandle.ipdl \
   $(NULL)
--- a/js/jetpack/nsIJetpack.idl
+++ b/js/jetpack/nsIJetpack.idl
@@ -32,13 +32,29 @@
  * 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 "nsISupports.idl"
 
-[scriptable, uuid(be12cc9f-75d4-4f2d-b062-f549ea13446a)]
+interface nsIVariant;
+
+[scriptable, uuid(563afdf8-5d2f-4bb2-9ac0-fb82461d2922)]
 interface nsIJetpack : nsISupports
 {
+  void sendMessage(in AString aMessageName
+                   /* [optional] in jsval v1,
+                      [optional] in jsval v2,
+                      ... */);
+
+  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);
+
+  nsIVariant createHandle();
 };
new file mode 100644
--- /dev/null
+++ b/js/jetpack/tests/Makefile.in
@@ -0,0 +1,50 @@
+# ***** 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.org code.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Ben Newman <mozilla@benjamn.com> (original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of 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 *****
+
+DEPTH     = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = test_jetpack
+
+XPCSHELL_TESTS = unit
+
+include $(topsrcdir)/config/config.mk
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/js/jetpack/tests/unit/handle_tests.js
@@ -0,0 +1,99 @@
+function run_handle_tests() {
+  test_sanity();
+  test_safe_iteration();
+  test_local_invalidation();
+  test_long_parent_chain(100);
+  test_invalid_creation();
+}
+
+function test_sanity() {
+  var parent = createHandle(),
+      child = parent.createHandle(),
+      grandchild = child.createHandle();
+
+  do_check_neq(child, parent);
+  do_check_eq(child.parent, parent);
+  do_check_eq(parent.parent, null);
+  do_check_eq(grandchild.parent.parent, parent);
+
+  do_check_true(child.isValid);
+  do_check_true(parent.isValid);
+
+  parent.invalidate();
+}
+
+function test_safe_iteration() {
+  var handle = createHandle(),
+      keys = [];
+  handle.foo = 42;
+  handle.self = handle;
+  for (var k in handle)
+    keys[keys.length] = k;
+  do_check_eq(keys.sort().join("~"),
+              "foo~self");
+  handle.invalidate();
+}
+
+function test_local_invalidation() {
+  var parent = createHandle(),
+      child = parent.createHandle();
+
+  do_check_true(child.invalidate());
+  do_check_false(child.isValid);
+  do_check_true(parent.isValid);
+
+  child = parent.createHandle();
+  do_check_true(child.isValid);
+
+  do_check_true(parent.invalidate());
+  do_check_false(parent.invalidate());
+  do_check_false(child.isValid);
+  do_check_false(parent.isValid);
+
+  parent = createHandle();
+  child = parent.createHandle();
+  child = child.createHandle();
+
+  var uncle = parent.createHandle(),
+      sibling = child.parent.createHandle();
+
+  do_check_eq(child.parent.parent, parent);
+  do_check_true(child.parent.isValid);
+
+  do_check_true(child.parent.invalidate());
+  do_check_false(child.isValid);
+  do_check_true(parent.isValid);
+
+  do_check_false(sibling.isValid);
+  do_check_true(uncle.isValid);
+
+  parent.invalidate();
+}
+
+function test_long_parent_chain(len) {
+  const ancestor = createHandle();
+  for (var handle = ancestor, i = 0; i < len; ++i)
+    handle = handle.createHandle();
+  const child = handle;
+
+  while (handle != ancestor)
+    handle = handle.parent;
+
+  do_check_true(child.isValid);
+  do_check_true(ancestor.invalidate());
+  do_check_false(child.isValid);
+}
+
+function test_invalid_creation() {
+  var parent = createHandle(),
+      child = parent.createHandle();
+
+  parent.invalidate();
+
+  do_check_eq(child.parent, null);
+
+  var threw = false;
+  try { child.createHandle(); }
+  catch (x) { threw = true; }
+  do_check_true(threw);
+}
new file mode 100644
--- /dev/null
+++ b/js/jetpack/tests/unit/impl.js
@@ -0,0 +1,43 @@
+function echo() {
+  sendMessage.apply(this, arguments);
+}
+
+registerReceiver("echo", echo);
+
+registerReceiver("callback",
+                 function(msgName, data, handle) {
+                   sendMessage("sendback",
+                               callMessage("callback", data)[0],
+                               handle);
+                 });
+
+registerReceiver("gimmeHandle",
+                 function(msgName) {
+                   sendMessage("recvHandle", "ok", createHandle());
+                 });
+
+registerReceiver("kthx",
+                 function(msgName, data, child) {
+                   sendMessage("recvHandleAgain", data + data, child.parent);
+                 });
+
+registerReceiver("echo2", echo);
+
+registerReceiver("multireturn begin",
+                 function() {
+                   var results = callMessage("multireturn");
+                   sendMessage.apply(null, ["multireturn check"].concat(results));
+                 });
+
+registerReceiver("testarray",
+                 function(msgName, array) {
+                   sendMessage("testarray", array.reverse());
+                 });
+
+registerReceiver("test primitive types", echo);
+
+registerReceiver("drop methods", echo);
+
+registerReceiver("exception coping", echo);
+
+registerReceiver("duplicate receivers", echo);
new file mode 100644
--- /dev/null
+++ b/js/jetpack/tests/unit/test_jetpack.js
@@ -0,0 +1,186 @@
+var jps = jps || Components.classes["@mozilla.org/jetpack/service;1"]
+  .getService(Components.interfaces.nsIJetpackService),
+  jetpack = jps.createJetpack();
+
+load("handle_tests.js");
+function createHandle() {
+  return jetpack.createHandle();
+}
+
+function run_test() {
+  run_handle_tests();
+
+  jetpack.loadImplementation("file://" + do_get_file("impl.js").path);
+
+  var circ1 = {},
+      circ2 = {},
+      circ3 = {},
+      ok = false;
+  ((circ1.next = circ2).next = circ3).next = circ1;
+  try {
+    jetpack.sendMessage("ignored", circ3, circ1);
+    ok = true;
+  } catch (x) {
+    do_check_false(x);
+  }
+  do_check_true(ok);
+
+  var echoHandle = jetpack.createHandle();
+  echoHandle.payload = { weight: 10 };
+  jetpack.registerReceiver("echo",
+                           function(msgName, data, handle) {
+                             do_check_eq(arguments.length, 3);
+                             do_check_eq(msgName, "echo");
+                             do_check_eq(data, "echo this");
+                             do_check_true(handle.isValid);
+                             do_check_eq(handle, echoHandle);
+                             do_check_eq(handle.payload.weight, 10);
+                             do_test_finished();
+                           });
+
+  jetpack.registerReceiver("callback",
+                           function(msgName, data) {
+                             do_check_eq(msgName, "callback");
+                             return "called back: " + data;
+                           });
+
+  var callbackHandle = echoHandle.createHandle();
+  jetpack.registerReceiver("sendback",
+                           function(msgName, data, handle) {
+                             do_check_eq(msgName, "sendback");
+                             do_check_eq(data, "called back: call me back");
+                             do_check_eq(handle, callbackHandle);
+                             do_test_finished();
+                           });
+
+  var obj;
+  jetpack.registerReceiver("recvHandle",
+                           function(msgName, data, handle) {
+                             handle.mark = obj = {};
+                             jetpack.sendMessage("kthx", data + data, handle.createHandle());
+                           });
+  jetpack.registerReceiver("recvHandleAgain",
+                           function(msgName, data, handle) {
+                             do_check_eq(data, "okokokok");
+                             do_check_eq(handle.mark, obj);
+                             do_test_finished();
+                           });
+  var obj1 = {
+    id: Math.random() + ""
+  }, obj2 = {
+    id: Math.random() + "",
+    obj: obj1
+  };
+  jetpack.registerReceiver("echo2",
+                           function(msgName, a, b) {
+                             do_check_neq(obj1, a);
+                             do_check_neq(obj2, b);
+                             do_check_eq(obj1.id, a.id);
+                             do_check_eq(obj2.id, b.id);
+                             do_check_eq(obj1.id, obj2.obj.id);
+                             do_test_finished();
+                           });
+
+  jetpack.registerReceiver("multireturn", function() { return obj1 });
+  jetpack.registerReceiver("multireturn", function() { return circ1 });
+  jetpack.registerReceiver("multireturn", function() { return obj2 });
+  jetpack.registerReceiver("multireturn check",
+                           function(msgName, rval1, rval2, rval3) {
+                             do_check_eq(rval1.id, obj1.id);
+                             do_check_eq(rval2.next.next.next, rval2);
+                             do_check_eq(rval3.id, obj2.id);
+                             do_check_eq(rval3.obj.id, obj1.id);
+                             do_test_finished();
+                           });
+
+  var testarray = [1, 1, 2, 3, 5, 8, 13];
+  jetpack.registerReceiver("testarray",
+                           function(msgName, reversed) {
+                             for (var i = 0; i < testarray.length; ++i)
+                               do_check_eq(testarray[i],
+                                           reversed[reversed.length - i - 1]);
+                             do_test_finished();
+                           });
+
+  var undefined;
+  jetpack.registerReceiver("test primitive types",
+                           function(msgName,
+                                    void_val, null_val,
+                                    bool_true, bool_false,
+                                    one, two, nine99,
+                                    one_quarter,
+                                    oyez_str)
+                           {
+                             do_check_true(void_val === undefined);
+                             do_check_true(null_val === null);
+                             do_check_true(bool_true === true);
+                             do_check_true(bool_false === false);
+                             do_check_eq(one, 1);
+                             do_check_eq(two, 2);
+                             do_check_eq(nine99, 999);
+                             do_check_eq(one_quarter, 0.25);
+                             do_check_eq(oyez_str, "oyez");
+
+                             do_test_finished();
+                           });
+
+  var drop = {
+    nested: {
+      method: function() { return this.value },
+      value: 42
+    }
+  };
+  jetpack.registerReceiver("drop methods",
+                           function(msgName, echoed) {
+                             do_check_true(!echoed.nested.method);
+                             do_check_eq(echoed.nested.value, 42);
+                             do_test_finished();
+                           });
+
+  var coped = "did not cope";
+  jetpack.registerReceiver("exception coping",
+                           function(msgName) { throw coped = "did cope" });
+  jetpack.registerReceiver("exception coping",
+                           function(msgName) {
+                             do_check_eq(coped, "did cope");
+                             do_test_finished();
+                           });
+
+  var calls = "";
+  function countCalls() { calls += "." }
+  jetpack.registerReceiver("duplicate receivers", countCalls);
+  jetpack.registerReceiver("duplicate receivers", countCalls);
+  jetpack.registerReceiver("duplicate receivers",
+                           function() { do_check_eq(calls, ".") });
+  jetpack.registerReceiver("duplicate receivers", countCalls);
+  jetpack.registerReceiver("duplicate receivers",
+                           function() {
+                             do_check_eq(calls, ".");
+                             jetpack.unregisterReceivers("duplicate receivers");
+                           });
+  jetpack.registerReceiver("duplicate receivers",
+                           function() { 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();
+  do_test_pending();
+
+  jetpack.sendMessage("echo", "echo this", echoHandle);
+  jetpack.sendMessage("callback", "call me back", callbackHandle);
+  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");
+}