Bug 996785 - Bidirectional CPOWs (r=mrbkap)
authorBill McCloskey <wmccloskey@mozilla.com>
Fri, 16 May 2014 16:40:37 -0700
changeset 183651 9055e91c6154d4372c1fb498cbc51da20cca505b
parent 183650 9bc4f004e7f004bd728ce1a26b15b9c47183dfaf
child 183652 a141144b6c99a87d255fe9e7753611ec3755d280
push idunknown
push userunknown
push dateunknown
reviewersmrbkap
bugs996785
milestone32.0a1
Bug 996785 - Bidirectional CPOWs (r=mrbkap)
dom/ipc/ContentChild.cpp
dom/ipc/ContentParent.cpp
js/ipc/JavaScriptBase.h
js/ipc/JavaScriptChild.cpp
js/ipc/JavaScriptChild.h
js/ipc/JavaScriptParent.cpp
js/ipc/JavaScriptParent.h
js/ipc/JavaScriptShared.cpp
js/ipc/JavaScriptShared.h
js/ipc/PJavaScript.ipdl
js/ipc/WrapperAnswer.cpp
js/ipc/WrapperOwner.cpp
js/ipc/WrapperOwner.h
js/src/jsapi.cpp
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -894,17 +894,17 @@ ContentChild::AllocPJavaScriptChild()
         return nullptr;
     }
     return child;
 }
 
 bool
 ContentChild::DeallocPJavaScriptChild(PJavaScriptChild *child)
 {
-    delete child;
+    static_cast<mozilla::jsipc::JavaScriptChild *>(child)->decref();
     return true;
 }
 
 PBrowserChild*
 ContentChild::AllocPBrowserChild(const IPCTabContext& aContext,
                                  const uint32_t& aChromeFlags)
 {
     // We'll happily accept any kind of IPCTabContext here; we don't need to
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -79,16 +79,17 @@
 #include "nsIClipboard.h"
 #include "nsICycleCollectorListener.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "mozilla/dom/WakeLock.h"
 #include "nsIDOMWindow.h"
 #include "nsIExternalProtocolService.h"
 #include "nsIGfxInfo.h"
 #include "nsIIdleService.h"
+#include "nsIJSRuntimeService.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMozBrowserFrame.h"
 #include "nsIMutable.h"
 #include "nsIObserverService.h"
 #include "nsIPresShell.h"
 #include "nsIRemoteBlob.h"
 #include "nsIScriptError.h"
@@ -2350,17 +2351,25 @@ ContentParent::RecvGetXPCOMProcessAttrib
 
     return true;
 }
 
 mozilla::jsipc::PJavaScriptParent *
 ContentParent::AllocPJavaScriptParent()
 {
     MOZ_ASSERT(!ManagedPJavaScriptParent().Length());
-    mozilla::jsipc::JavaScriptParent *parent = new mozilla::jsipc::JavaScriptParent();
+
+    nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
+    NS_ENSURE_TRUE(svc, nullptr);
+
+    JSRuntime *rt;
+    svc->GetRuntime(&rt);
+    NS_ENSURE_TRUE(svc, nullptr);
+
+    mozilla::jsipc::JavaScriptParent *parent = new mozilla::jsipc::JavaScriptParent(rt);
     if (!parent->init()) {
         delete parent;
         return nullptr;
     }
     return parent;
 }
 
 bool
--- a/js/ipc/JavaScriptBase.h
+++ b/js/ipc/JavaScriptBase.h
@@ -17,16 +17,23 @@ namespace mozilla {
 namespace jsipc {
 
 template<class Base>
 class JavaScriptBase : public WrapperOwner, public WrapperAnswer, public Base
 {
     typedef WrapperAnswer Answer;
 
   public:
+    JavaScriptBase(JSRuntime *rt)
+      : JavaScriptShared(rt),
+        WrapperOwner(rt),
+        WrapperAnswer(rt)
+    {}
+    virtual ~JavaScriptBase() {}
+
     virtual void ActorDestroy(WrapperOwner::ActorDestroyReason why) {
         WrapperOwner::ActorDestroy(why);
     }
 
     /*** IPC handlers ***/
 
     bool AnswerPreventExtensions(const ObjectId &objId, ReturnStatus *rs) {
         return Answer::AnswerPreventExtensions(objId, rs);
@@ -96,18 +103,25 @@ class JavaScriptBase : public WrapperOwn
                           ReturnStatus *rs, bool *instanceof) {
         return Answer::AnswerInstanceOf(objId, iid, rs, instanceof);
     }
     bool AnswerDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
                              ReturnStatus *rs, bool *instanceof) {
         return Answer::AnswerDOMInstanceOf(objId, prototypeID, depth, rs, instanceof);
     }
 
+    bool RecvDropObject(const ObjectId &objId) {
+        return Answer::RecvDropObject(objId);
+    }
+
     /*** Dummy call handlers ***/
 
+    bool SendDropObject(const ObjectId &objId) {
+        return Base::SendDropObject(objId);
+    }
     bool CallPreventExtensions(const ObjectId &objId, ReturnStatus *rs) {
         return Base::CallPreventExtensions(objId, rs);
     }
     bool CallGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
                                      ReturnStatus *rs,
                                      PPropertyDescriptor *out) {
         return Base::CallGetPropertyDescriptor(objId, id, rs, out);
     }
@@ -170,14 +184,23 @@ class JavaScriptBase : public WrapperOwn
     bool CallInstanceOf(const ObjectId &objId, const JSIID &iid,
                         ReturnStatus *rs, bool *instanceof) {
         return Base::CallInstanceOf(objId, iid, rs, instanceof);
     }
     bool CallDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
                            ReturnStatus *rs, bool *instanceof) {
         return Base::CallDOMInstanceOf(objId, prototypeID, depth, rs, instanceof);
     }
+
+    /* The following code is needed to suppress a bogus MSVC warning (C4250). */
+
+    virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp) {
+        return WrapperOwner::toObjectVariant(cx, obj, objVarp);
+    }
+    virtual JSObject *fromObjectVariant(JSContext *cx, ObjectVariant objVar) {
+        return WrapperOwner::fromObjectVariant(cx, objVar);
+    }
 };
 
 } // namespace jsipc
 } // namespace mozilla
 
 #endif
--- a/js/ipc/JavaScriptChild.cpp
+++ b/js/ipc/JavaScriptChild.cpp
@@ -14,99 +14,45 @@
 #include "nsCxPusher.h"
 
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 
 using mozilla::AutoSafeJSContext;
 
-JavaScriptChild::JavaScriptChild(JSRuntime *rt)
-  : lastId_(0),
-    rt_(rt)
+static void
+FinalizeChild(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartment, void *data)
 {
+    if (status == JSFINALIZE_GROUP_START) {
+        static_cast<JavaScriptChild *>(data)->finalize(fop);
+    }
 }
 
-static void
-Trace(JSTracer *trc, void *data)
+JavaScriptChild::JavaScriptChild(JSRuntime *rt)
+  : JavaScriptShared(rt),
+    JavaScriptBase<PJavaScriptChild>(rt)
 {
-    reinterpret_cast<JavaScriptChild *>(data)->trace(trc);
 }
 
 JavaScriptChild::~JavaScriptChild()
 {
-    JS_RemoveExtraGCRootsTracer(rt_, Trace, this);
-}
-
-void
-JavaScriptChild::trace(JSTracer *trc)
-{
-    objects_.trace(trc);
-    cpows_.trace(trc);
-    ids_.trace(trc);
+    JS_RemoveFinalizeCallback(rt_, FinalizeChild);
 }
 
 bool
 JavaScriptChild::init()
 {
     if (!WrapperOwner::init())
         return false;
     if (!WrapperAnswer::init())
         return false;
 
-    if (!ids_.init())
-        return false;
-
-    JS_AddExtraGCRootsTracer(rt_, Trace, this);
-    return true;
-}
-
-bool
-JavaScriptChild::RecvDropObject(const ObjectId &objId)
-{
-    JSObject *obj = findObjectById(objId);
-    if (obj) {
-        ids_.remove(obj);
-        objects_.remove(objId);
-    }
+    JS_AddFinalizeCallback(rt_, FinalizeChild, this);
     return true;
 }
 
-bool
-JavaScriptChild::toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp)
+void
+JavaScriptChild::finalize(JSFreeOp *fop)
 {
-    JS_ASSERT(obj);
-
-    ObjectId id = ids_.find(obj);
-    if (id) {
-        *objVarp = RemoteObject(id);
-        return true;
-    }
-
-    id = ++lastId_;
-    if (id > MAX_CPOW_IDS) {
-        JS_ReportError(cx, "CPOW id limit reached");
-        return false;
-    }
-
-    id <<= OBJECT_EXTRA_BITS;
-    if (JS_ObjectIsCallable(cx, obj))
-        id |= OBJECT_IS_CALLABLE;
-
-    if (!objects_.add(id, obj))
-        return false;
-    if (!ids_.add(cx, obj, id))
-        return false;
-
-    *objVarp = RemoteObject(id);
-    return true;
+    objects_.finalize(fop);
+    objectIds_.finalize(fop);
 }
-
-JSObject *
-JavaScriptChild::fromObjectVariant(JSContext *cx, ObjectVariant objVar)
-{
-    JS_ASSERT(objVar.type() == ObjectVariant::TLocalObject);
-    ObjectId id = objVar.get_LocalObject().id();
-    JSObject *obj = findObjectById(id);
-    MOZ_ASSERT(obj);
-    return obj;
-}
-
--- a/js/ipc/JavaScriptChild.h
+++ b/js/ipc/JavaScriptChild.h
@@ -13,34 +13,24 @@
 
 namespace mozilla {
 namespace jsipc {
 
 class JavaScriptChild : public JavaScriptBase<PJavaScriptChild>
 {
   public:
     JavaScriptChild(JSRuntime *rt);
-    ~JavaScriptChild();
+    virtual ~JavaScriptChild();
 
     bool init();
-    void trace(JSTracer *trc);
+    void finalize(JSFreeOp *fop);
 
-    bool RecvDropObject(const ObjectId &objId) MOZ_OVERRIDE;
-
-    virtual void drop(JSObject *obj) { MOZ_CRASH(); }
+    void drop(JSObject *obj);
 
   private:
-    virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp);
-    virtual JSObject *fromObjectVariant(JSContext *cx, ObjectVariant objVar);
-
     bool fail(JSContext *cx, ReturnStatus *rs);
     bool ok(ReturnStatus *rs);
-
-  private:
-    ObjectId lastId_;
-    JSRuntime *rt_;
-    ObjectToIdMap ids_;
 };
 
 } // mozilla
 } // jsipc
 
 #endif
--- a/js/ipc/JavaScriptParent.cpp
+++ b/js/ipc/JavaScriptParent.cpp
@@ -16,112 +16,48 @@
 #include "mozilla/Casting.h"
 
 using namespace js;
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 using namespace mozilla::dom;
 
-JavaScriptParent::JavaScriptParent()
-  : refcount_(1)
+static void
+TraceParent(JSTracer *trc, void *data)
+{
+    static_cast<JavaScriptParent *>(data)->trace(trc);
+}
+
+JavaScriptParent::JavaScriptParent(JSRuntime *rt)
+  : JavaScriptShared(rt),
+    JavaScriptBase<PJavaScriptParent>(rt)
 {
 }
 
-void
-JavaScriptParent::drop(JSObject *obj)
+JavaScriptParent::~JavaScriptParent()
 {
-    ObjectId objId = idOf(obj);
-
-    cpows_.remove(objId);
-    if (active() && !SendDropObject(objId))
-        (void)0;
-    decref();
+    JS_RemoveExtraGCRootsTracer(rt_, TraceParent, this);
 }
 
 bool
 JavaScriptParent::init()
 {
     if (!WrapperOwner::init())
         return false;
 
-    return true;
-}
-
-bool
-JavaScriptParent::toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp)
-{
-    JS_ASSERT(obj);
-    obj = js::CheckedUnwrap(obj, false);
-    if (!obj || !IsCPOW(obj)) {
-        JS_ReportError(cx, "cannot ipc non-cpow object");
-        return false;
-    }
-
-    *objVarp = LocalObject(idOf(obj));
+    JS_AddExtraGCRootsTracer(rt_, TraceParent, this);
     return true;
 }
 
-JSObject *
-JavaScriptParent::fromObjectVariant(JSContext *cx, ObjectVariant objVar)
+void
+JavaScriptParent::trace(JSTracer *trc)
 {
-    JS_ASSERT(objVar.type() == ObjectVariant::TRemoteObject);
-    ObjectId objId = objVar.get_RemoteObject().id();
-
-    RootedObject obj(cx, findCPOWById(objId));
-    if (obj) {
-        if (!JS_WrapObject(cx, &obj))
-            return nullptr;
-        return obj;
-    }
-
-    if (objId > MAX_CPOW_IDS) {
-        JS_ReportError(cx, "unusable CPOW id");
-        return nullptr;
-    }
-
-    bool callable = !!(objId & OBJECT_IS_CALLABLE);
-
-    RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
-
-    RootedValue v(cx, UndefinedValue());
-    ProxyOptions options;
-    options.selectDefaultClass(callable);
-    obj = NewProxyObject(cx,
-                         ProxyHandler(),
-                         v,
-                         nullptr,
-                         global,
-                         options);
-    if (!obj)
-        return nullptr;
-
-    if (!cpows_.add(objId, obj))
-        return nullptr;
-
-    // Incref once we know the decref will be called.
-    incref();
-
-    SetProxyExtra(obj, 0, PrivateValue(this));
-    SetProxyExtra(obj, 1, DoubleValue(BitwiseCast<double>(objId)));
-    return obj;
-}
-
-void
-JavaScriptParent::decref()
-{
-    refcount_--;
-    if (!refcount_)
-        delete this;
-}
-
-void
-JavaScriptParent::incref()
-{
-    refcount_++;
+    if (active())
+        objects_.trace(trc);
 }
 
 mozilla::ipc::IProtocol*
 JavaScriptParent::CloneProtocol(Channel* aChannel, ProtocolCloneContext* aCtx)
 {
     ContentParent *contentParent = aCtx->GetContentParent();
     nsAutoPtr<PJavaScriptParent> actor(contentParent->AllocPJavaScriptParent());
     if (!actor || !contentParent->RecvPJavaScriptConstructor(actor)) {
--- a/js/ipc/JavaScriptParent.h
+++ b/js/ipc/JavaScriptParent.h
@@ -12,33 +12,25 @@
 #include "mozilla/jsipc/PJavaScriptParent.h"
 
 namespace mozilla {
 namespace jsipc {
 
 class JavaScriptParent : public JavaScriptBase<PJavaScriptParent>
 {
   public:
-    JavaScriptParent();
+    JavaScriptParent(JSRuntime *rt);
+    virtual ~JavaScriptParent();
 
     bool init();
-
-    void decref();
-    void incref();
+    void trace(JSTracer *trc);
 
     void drop(JSObject *obj);
 
     mozilla::ipc::IProtocol*
     CloneProtocol(Channel* aChannel, ProtocolCloneContext* aCtx) MOZ_OVERRIDE;
-
-  private:
-    virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp);
-    virtual JSObject *fromObjectVariant(JSContext *cx, ObjectVariant objVar);
-
-  private:
-    uintptr_t refcount_;
 };
 
 } // jsipc
 } // mozilla
 
 #endif // mozilla_jsipc_JavaScriptWrapper_h__
 
--- a/js/ipc/JavaScriptShared.cpp
+++ b/js/ipc/JavaScriptShared.cpp
@@ -33,16 +33,28 @@ IdToObjectMap::trace(JSTracer *trc)
 {
     for (Table::Range r(table_.all()); !r.empty(); r.popFront()) {
         DebugOnly<JSObject *> prior = r.front().value().get();
         JS_CallHeapObjectTracer(trc, &r.front().value(), "ipc-object");
         MOZ_ASSERT(r.front().value() == prior);
     }
 }
 
+void
+IdToObjectMap::finalize(JSFreeOp *fop)
+{
+    for (Table::Enum e(table_); !e.empty(); e.popFront()) {
+        DebugOnly<JSObject *> prior = e.front().value().get();
+        if (JS_IsAboutToBeFinalized(&e.front().value()))
+            e.removeFront();
+        else
+            MOZ_ASSERT(e.front().value() == prior);
+    }
+}
+
 JSObject *
 IdToObjectMap::find(ObjectId id)
 {
     Table::Ptr p = table_.lookup(id);
     if (!p)
         return nullptr;
     return p->value();
 }
@@ -78,22 +90,24 @@ ObjectToIdMap::init()
     if (table_)
         return true;
 
     table_ = new Table(SystemAllocPolicy());
     return table_ && table_->init(32);
 }
 
 void
-ObjectToIdMap::trace(JSTracer *trc)
+ObjectToIdMap::finalize(JSFreeOp *fop)
 {
-    for (Table::Range r(table_->all()); !r.empty(); r.popFront()) {
-        JSObject *obj = r.front().key();
-        JS_CallObjectTracer(trc, &obj, "ipc-id");
-        MOZ_ASSERT(obj == r.front().key());
+    for (Table::Enum e(*table_); !e.empty(); e.popFront()) {
+        JSObject *obj = e.front().key();
+        if (JS_IsAboutToBeFinalizedUnbarriered(&obj))
+            e.removeFront();
+        else
+            MOZ_ASSERT(obj == e.front().key());
     }
 }
 
 ObjectId
 ObjectToIdMap::find(JSObject *obj)
 {
     Table::Ptr p = table_->lookup(obj);
     if (!p)
@@ -124,26 +138,50 @@ ObjectToIdMap::keyMarkCallback(JSTracer 
 }
 
 void
 ObjectToIdMap::remove(JSObject *obj)
 {
     table_->remove(obj);
 }
 
+JavaScriptShared::JavaScriptShared(JSRuntime *rt)
+  : rt_(rt),
+    refcount_(1),
+    lastId_(0)
+{
+}
+
 bool
 JavaScriptShared::init()
 {
     if (!objects_.init())
         return false;
     if (!cpows_.init())
         return false;
+    if (!objectIds_.init())
+        return false;
+
     return true;
 }
 
+void
+JavaScriptShared::decref()
+{
+    refcount_--;
+    if (!refcount_)
+        delete this;
+}
+
+void
+JavaScriptShared::incref()
+{
+    refcount_++;
+}
+
 bool
 JavaScriptShared::convertIdToGeckoString(JSContext *cx, JS::HandleId id, nsString *to)
 {
     RootedValue idval(cx);
     if (!JS_IdToValue(cx, id, &idval))
         return false;
 
     RootedString str(cx, ToString(cx, idval));
@@ -313,16 +351,22 @@ JavaScriptShared::ConvertID(const JSIID 
     to->m3[2] = from.m3_2();
     to->m3[3] = from.m3_3();
     to->m3[4] = from.m3_4();
     to->m3[5] = from.m3_5();
     to->m3[6] = from.m3_6();
     to->m3[7] = from.m3_7();
 }
 
+void
+JavaScriptShared::ReportNonexistentObject(JSContext *cx)
+{
+    JS_ReportError(cx, "operation not possible on dead CPOW");
+}
+
 static const uint64_t DefaultPropertyOp = 1;
 static const uint64_t GetterOnlyPropertyStub = 2;
 static const uint64_t UnknownPropertyOp = 3;
 
 bool
 JavaScriptShared::fromDescriptor(JSContext *cx, Handle<JSPropertyDescriptor> desc,
                                  PPropertyDescriptor *out)
 {
--- a/js/ipc/JavaScriptShared.h
+++ b/js/ipc/JavaScriptShared.h
@@ -43,16 +43,17 @@ class IdToObjectMap
 
     typedef js::HashMap<ObjectId, JS::Heap<JSObject *>, TableKeyHasher, js::SystemAllocPolicy> Table;
 
   public:
     IdToObjectMap();
 
     bool init();
     void trace(JSTracer *trc);
+    void finalize(JSFreeOp *fop);
 
     bool add(ObjectId id, JSObject *obj);
     JSObject *find(ObjectId id);
     void remove(ObjectId id);
 
   private:
     Table table_;
 };
@@ -63,33 +64,39 @@ class ObjectToIdMap
     typedef js::PointerHasher<JSObject *, 3> Hasher;
     typedef js::HashMap<JSObject *, ObjectId, Hasher, js::SystemAllocPolicy> Table;
 
   public:
     ObjectToIdMap();
     ~ObjectToIdMap();
 
     bool init();
-    void trace(JSTracer *trc);
+    void finalize(JSFreeOp *fop);
 
     bool add(JSContext *cx, JSObject *obj, ObjectId id);
     ObjectId find(JSObject *obj);
     void remove(JSObject *obj);
 
   private:
     static void keyMarkCallback(JSTracer *trc, JSObject *key, void *data);
 
     Table *table_;
 };
 
 class JavaScriptShared
 {
   public:
+    JavaScriptShared(JSRuntime *rt);
+    virtual ~JavaScriptShared() {}
+
     bool init();
 
+    void decref();
+    void incref();
+
     static const uint32_t OBJECT_EXTRA_BITS  = 1;
     static const uint32_t OBJECT_IS_CALLABLE = (1 << 0);
 
     bool Unwrap(JSContext *cx, const InfallibleTArray<CpowEntry> &aCpows, JS::MutableHandleObject objp);
     bool Wrap(JSContext *cx, JS::HandleObject aObj, InfallibleTArray<CpowEntry> *outCpows);
 
   protected:
     bool toVariant(JSContext *cx, JS::HandleValue from, JSVariant *to);
@@ -104,26 +111,40 @@ class JavaScriptShared
     bool convertGeckoStringToId(JSContext *cx, const nsString &from, JS::MutableHandleId id);
 
     virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp) = 0;
     virtual JSObject *fromObjectVariant(JSContext *cx, ObjectVariant objVar) = 0;
 
     static void ConvertID(const nsID &from, JSIID *to);
     static void ConvertID(const JSIID &from, nsID *to);
 
+    void ReportNonexistentObject(JSContext *cx);
+
     JSObject *findCPOWById(uint32_t objId) {
         return cpows_.find(objId);
     }
     JSObject *findObjectById(uint32_t objId) {
         return objects_.find(objId);
     }
+    JSObject *findObjectById(JSContext *cx, uint32_t objId) {
+        if (JSObject *result = objects_.find(objId))
+            return result;
+        ReportNonexistentObject(cx);
+        return nullptr;
+    }
 
   protected:
+    JSRuntime *rt_;
+    uintptr_t refcount_;
+
     IdToObjectMap objects_;
     IdToObjectMap cpows_;
+
+    ObjectId lastId_;
+    ObjectToIdMap objectIds_;
 };
 
 // Use 47 at most, to be safe, since jsval privates are encoded as doubles.
 static const uint64_t MAX_CPOW_IDS = (uint64_t(1) << 47) - 1;
 
 } // namespace jsipc
 } // namespace mozilla
 
--- a/js/ipc/PJavaScript.ipdl
+++ b/js/ipc/PJavaScript.ipdl
@@ -13,21 +13,20 @@ using struct mozilla::void_t from "ipc/I
 
 namespace mozilla {
 namespace jsipc {
 
 intr protocol PJavaScript
 {
     manager PContent;
 
-child:
-    // The parent process no longer holds any references to the child object.
+both:
+    // Sent when a CPOW has been finalized and table entries can be freed up.
     async DropObject(uint64_t objId);
 
-both:
     // These roughly map to the ProxyHandler hooks that CPOWs need.
     rpc PreventExtensions(uint64_t objId) returns (ReturnStatus rs);
     rpc GetPropertyDescriptor(uint64_t objId, nsString id) returns (ReturnStatus rs, PPropertyDescriptor result);
     rpc GetOwnPropertyDescriptor(uint64_t objId, nsString id) returns (ReturnStatus rs, PPropertyDescriptor result);
     rpc DefineProperty(uint64_t objId, nsString id, PPropertyDescriptor descriptor) returns (ReturnStatus rs);
     rpc Delete(uint64_t objId, nsString id) returns (ReturnStatus rs, bool successful);
 
     rpc Has(uint64_t objId, nsString id) returns (ReturnStatus rs, bool has);
--- a/js/ipc/WrapperAnswer.cpp
+++ b/js/ipc/WrapperAnswer.cpp
@@ -58,19 +58,19 @@ WrapperAnswer::ok(ReturnStatus *rs)
 }
 
 bool
 WrapperAnswer::AnswerPreventExtensions(const ObjectId &objId, ReturnStatus *rs)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
     if (!JS_PreventExtensions(cx, obj))
         return fail(cx, rs);
 
     return ok(rs);
 }
 
@@ -88,19 +88,19 @@ bool
 WrapperAnswer::AnswerGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
 					   ReturnStatus *rs, PPropertyDescriptor *out)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     EmptyDesc(out);
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     RootedId internedId(cx);
     if (!convertGeckoStringToId(cx, id, &internedId))
         return fail(cx, rs);
 
     Rooted<JSPropertyDescriptor> desc(cx);
@@ -120,19 +120,19 @@ bool
 WrapperAnswer::AnswerGetOwnPropertyDescriptor(const ObjectId &objId, const nsString &id,
 					      ReturnStatus *rs, PPropertyDescriptor *out)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     EmptyDesc(out);
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     RootedId internedId(cx);
     if (!convertGeckoStringToId(cx, id, &internedId))
         return fail(cx, rs);
 
     Rooted<JSPropertyDescriptor> desc(cx);
@@ -150,29 +150,29 @@ WrapperAnswer::AnswerGetOwnPropertyDescr
 
 bool
 WrapperAnswer::AnswerDefineProperty(const ObjectId &objId, const nsString &id,
 				    const PPropertyDescriptor &descriptor, ReturnStatus *rs)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     RootedId internedId(cx);
     if (!convertGeckoStringToId(cx, id, &internedId))
         return fail(cx, rs);
 
     Rooted<JSPropertyDescriptor> desc(cx);
     if (!toDescriptor(cx, descriptor, &desc))
-        return false;
+        return fail(cx, rs);
 
     if (!js::CheckDefineProperty(cx, obj, internedId, desc.value(), desc.attributes(),
                                  desc.getter(), desc.setter()))
     {
         return fail(cx, rs);
     }
 
     if (!JS_DefinePropertyById(cx, obj, internedId, desc.value(), desc.attributes(),
@@ -188,19 +188,19 @@ bool
 WrapperAnswer::AnswerDelete(const ObjectId &objId, const nsString &id, ReturnStatus *rs,
 			    bool *success)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     *success = false;
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     RootedId internedId(cx);
     if (!convertGeckoStringToId(cx, id, &internedId))
         return fail(cx, rs);
 
     if (!JS_DeletePropertyById2(cx, obj, internedId, success))
@@ -212,19 +212,19 @@ WrapperAnswer::AnswerDelete(const Object
 bool
 WrapperAnswer::AnswerHas(const ObjectId &objId, const nsString &id, ReturnStatus *rs, bool *bp)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     *bp = false;
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     RootedId internedId(cx);
     if (!convertGeckoStringToId(cx, id, &internedId))
         return fail(cx, rs);
 
     bool found;
@@ -238,19 +238,19 @@ WrapperAnswer::AnswerHas(const ObjectId 
 bool
 WrapperAnswer::AnswerHasOwn(const ObjectId &objId, const nsString &id, ReturnStatus *rs, bool *bp)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     *bp = false;
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     RootedId internedId(cx);
     if (!convertGeckoStringToId(cx, id, &internedId))
         return fail(cx, rs);
 
     Rooted<JSPropertyDescriptor> desc(cx);
@@ -267,23 +267,23 @@ WrapperAnswer::AnswerGet(const ObjectId 
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     // The outparam will be written to the buffer, so it must be set even if
     // the parent won't read it.
     *result = UndefinedVariant();
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
-    RootedObject receiver(cx, findObjectById(receiverId));
+    RootedObject receiver(cx, findObjectById(cx, receiverId));
     if (!receiver)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     RootedId internedId(cx);
     if (!convertGeckoStringToId(cx, id, &internedId))
         return fail(cx, rs);
 
     JS::RootedValue val(cx);
@@ -303,23 +303,23 @@ WrapperAnswer::AnswerSet(const ObjectId 
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     // The outparam will be written to the buffer, so it must be set even if
     // the parent won't read it.
     *result = UndefinedVariant();
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
-    RootedObject receiver(cx, findObjectById(receiverId));
+    RootedObject receiver(cx, findObjectById(cx, receiverId));
     if (!receiver)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     RootedId internedId(cx);
     if (!convertGeckoStringToId(cx, id, &internedId))
         return fail(cx, rs);
 
     MOZ_ASSERT(obj == receiver);
@@ -340,19 +340,19 @@ WrapperAnswer::AnswerSet(const ObjectId 
 bool
 WrapperAnswer::AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs, bool *result)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     *result = false;
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     bool extensible;
     if (!JS_IsExtensible(cx, obj, &extensible))
         return fail(cx, rs);
 
     *result = !!extensible;
@@ -365,19 +365,19 @@ WrapperAnswer::AnswerCall(const ObjectId
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     // The outparam will be written to the buffer, so it must be set even if
     // the parent won't read it.
     *result = UndefinedVariant();
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     MOZ_ASSERT(argv.Length() >= 2);
 
     RootedValue objv(cx);
     if (!fromVariant(cx, argv[0], &objv))
         return fail(cx, rs);
@@ -458,52 +458,57 @@ WrapperAnswer::AnswerCall(const ObjectId
 
 bool
 WrapperAnswer::AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
 				   bool *result)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
-    RootedObject obj(cx, findObjectById(objId));
-    if (!obj)
-        return false;
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj) {
+        // This is very unfortunate, but we have no choice.
+        *result = false;
+        return true;
+    }
 
     JSAutoCompartment comp(cx, obj);
 
     *result = js_ObjectClassIs(cx, obj, (js::ESClassValue)classValue);
     return true;
 }
 
 bool
 WrapperAnswer::AnswerClassName(const ObjectId &objId, nsString *name)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
-    RootedObject obj(cx, findObjectById(objId));
-    if (!obj)
-        return false;
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj) {
+        // This is very unfortunate, but we have no choice.
+        return "<dead CPOW>";
+    }
 
     JSAutoCompartment comp(cx, obj);
 
     *name = NS_ConvertASCIItoUTF16(js_ObjectClassName(cx, obj));
     return true;
 }
 
 bool
 WrapperAnswer::AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
 				      ReturnStatus *rs, nsTArray<nsString> *names)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     AutoIdVector props(cx);
     if (!js::GetPropertyNames(cx, obj, flags, &props))
         return fail(cx, rs);
 
     for (size_t i = 0; i < props.length(); i++) {
@@ -521,19 +526,19 @@ bool
 WrapperAnswer::AnswerInstanceOf(const ObjectId &objId, const JSIID &iid, ReturnStatus *rs,
 				bool *instanceof)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     *instanceof = false;
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     nsID nsiid;
     ConvertID(iid, &nsiid);
 
     nsresult rv = xpc::HasInstance(cx, obj, &nsiid, instanceof);
     if (rv != NS_OK)
@@ -547,19 +552,19 @@ WrapperAnswer::AnswerDOMInstanceOf(const
 				   const int &depth,
 				   ReturnStatus *rs, bool *instanceof)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     *instanceof = false;
 
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
-        return false;
+        return fail(cx, rs);
 
     JSAutoCompartment comp(cx, obj);
 
     bool tmp;
     if (!mozilla::dom::InterfaceHasInstance(cx, prototypeID, depth, obj, &tmp))
         return fail(cx, rs);
     *instanceof = tmp;
 
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -1,27 +1,29 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=4 sw=4 et tw=80:
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WrapperOwner.h"
+#include "mozilla/unused.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "jsfriendapi.h"
 #include "xpcprivate.h"
 
 using namespace js;
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 
-WrapperOwner::WrapperOwner()
-  : inactive_(false)
+WrapperOwner::WrapperOwner(JSRuntime *rt)
+  : JavaScriptShared(rt),
+    inactive_(false)
 {
 }
 
 static inline WrapperOwner *
 OwnerOf(JSObject *obj)
 {
     MOZ_ASSERT(IsCPOW(obj));
     return reinterpret_cast<WrapperOwner *>(GetProxyExtra(obj, 0).toPrivate());
@@ -81,22 +83,16 @@ class CPOWProxyHandler : public BaseProx
     virtual const char* className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual void finalize(JSFreeOp *fop, JSObject *proxy) MOZ_OVERRIDE;
 
     static CPOWProxyHandler singleton;
 };
 
 CPOWProxyHandler CPOWProxyHandler::singleton;
 
-/* static */ BaseProxyHandler *
-WrapperOwner::ProxyHandler()
-{
-    return &CPOWProxyHandler::singleton;
-}
-
 #define FORWARD(call, args)                                             \
     WrapperOwner *owner = OwnerOf(proxy);                               \
     if (!owner->active()) {                                             \
         JS_ReportError(cx, "cannot use a CPOW whose process is gone");  \
         return false;                                                   \
     }                                                                   \
     return owner->call args;
 
@@ -499,16 +495,27 @@ WrapperOwner::className(JSContext *cx, H
 }
 
 void
 CPOWProxyHandler::finalize(JSFreeOp *fop, JSObject *proxy)
 {
     OwnerOf(proxy)->drop(proxy);
 }
 
+void
+WrapperOwner::drop(JSObject *obj)
+{
+    ObjectId objId = idOf(obj);
+
+    cpows_.remove(objId);
+    if (active())
+        unused << SendDropObject(objId);
+    decref();
+}
+
 bool
 WrapperOwner::init()
 {
     if (!JavaScriptShared::init())
         return false;
 
     return true;
 }
@@ -617,8 +624,109 @@ WrapperOwner::ok(JSContext *cx, const Re
 
     RootedValue exn(cx);
     if (!fromVariant(cx, status.get_ReturnException().exn(), &exn))
         return false;
 
     JS_SetPendingException(cx, exn);
     return false;
 }
+
+bool
+WrapperOwner::toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp)
+{
+    JS_ASSERT(obj);
+    JSObject *unwrapped = js::CheckedUnwrap(obj, false);
+    if (unwrapped && IsCPOW(unwrapped) && OwnerOf(unwrapped) == this) {
+        *objVarp = LocalObject(idOf(unwrapped));
+        return true;
+    }
+
+    ObjectId id = objectIds_.find(obj);
+    if (id) {
+        *objVarp = RemoteObject(id);
+        return true;
+    }
+
+    id = ++lastId_;
+    if (id > MAX_CPOW_IDS) {
+        JS_ReportError(cx, "CPOW id limit reached");
+        return false;
+    }
+
+    id <<= OBJECT_EXTRA_BITS;
+    if (JS_ObjectIsCallable(cx, obj))
+        id |= OBJECT_IS_CALLABLE;
+
+    if (!objects_.add(id, obj))
+        return false;
+    if (!objectIds_.add(cx, obj, id))
+        return false;
+
+    *objVarp = RemoteObject(id);
+    return true;
+}
+
+JSObject *
+WrapperOwner::fromObjectVariant(JSContext *cx, ObjectVariant objVar)
+{
+    if (objVar.type() == ObjectVariant::TRemoteObject) {
+        return fromRemoteObjectVariant(cx, objVar.get_RemoteObject());
+    } else {
+        return fromLocalObjectVariant(cx, objVar.get_LocalObject());
+    }
+}
+
+JSObject *
+WrapperOwner::fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar)
+{
+    ObjectId objId = objVar.id();
+
+    RootedObject obj(cx, findCPOWById(objId));
+    if (obj) {
+        if (!JS_WrapObject(cx, &obj))
+            return nullptr;
+        return obj;
+    }
+
+    if (objId > MAX_CPOW_IDS) {
+        JS_ReportError(cx, "unusable CPOW id");
+        return nullptr;
+    }
+
+    bool callable = !!(objId & OBJECT_IS_CALLABLE);
+
+    RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+
+    RootedValue v(cx, UndefinedValue());
+    ProxyOptions options;
+    options.selectDefaultClass(callable);
+    obj = NewProxyObject(cx,
+                         &CPOWProxyHandler::singleton,
+                         v,
+                         nullptr,
+                         global,
+                         options);
+    if (!obj)
+        return nullptr;
+
+    if (!cpows_.add(objId, obj))
+        return nullptr;
+
+    // Incref once we know the decref will be called.
+    incref();
+
+    SetProxyExtra(obj, 0, PrivateValue(this));
+    SetProxyExtra(obj, 1, DoubleValue(BitwiseCast<double>(objId)));
+    return obj;
+}
+
+JSObject *
+WrapperOwner::fromLocalObjectVariant(JSContext *cx, LocalObject objVar)
+{
+    ObjectId id = objVar.id();
+    Rooted<JSObject*> obj(cx, findObjectById(cx, id));
+    if (!obj)
+        return nullptr;
+    if (!JS_WrapObject(cx, &obj))
+        return nullptr;
+    return obj;
+}
--- a/js/ipc/WrapperOwner.h
+++ b/js/ipc/WrapperOwner.h
@@ -12,31 +12,27 @@
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "js/Class.h"
 
 #ifdef XP_WIN
 #undef GetClassName
 #undef GetClassInfo
 #endif
 
-namespace js {
-class BaseProxyHandler;
-} // js
-
 namespace mozilla {
 namespace jsipc {
 
 class WrapperOwner : public virtual JavaScriptShared
 {
   public:
     typedef mozilla::ipc::IProtocolManager<
                        mozilla::ipc::IProtocol>::ActorDestroyReason
            ActorDestroyReason;
 
-    WrapperOwner();
+    WrapperOwner(JSRuntime *rt);
     bool init();
 
     // Fundamental proxy traps. These are required.
     // (The traps should be in the same order like js/src/jsproxy.h)
     bool preventExtensions(JSContext *cx, JS::HandleObject proxy);
     bool getPropertyDescriptor(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
                                JS::MutableHandle<JSPropertyDescriptor> desc);
     bool getOwnPropertyDescriptor(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
@@ -68,39 +64,43 @@ class WrapperOwner : public virtual Java
     /*
      * Check that |obj| is a DOM wrapper whose prototype chain contains
      * |prototypeID| at depth |depth|.
      */
     bool domInstanceOf(JSContext *cx, JSObject *obj, int prototypeID, int depth, bool *bp);
 
     bool active() { return !inactive_; }
 
-    virtual void drop(JSObject *obj) = 0;
+    void drop(JSObject *obj);
 
     virtual void ActorDestroy(ActorDestroyReason why);
 
+    virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp);
+    virtual JSObject *fromObjectVariant(JSContext *cx, ObjectVariant objVar);
+    JSObject *fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar);
+    JSObject *fromLocalObjectVariant(JSContext *cx, LocalObject objVar);
+
   protected:
     ObjectId idOf(JSObject *obj);
 
-    static js::BaseProxyHandler *ProxyHandler();
-
   private:
     bool getPropertyNames(JSContext *cx, JS::HandleObject proxy, uint32_t flags,
                           JS::AutoIdVector &props);
 
     // Catastrophic IPC failure.
     bool ipcfail(JSContext *cx);
 
     // Check whether a return status is okay, and if not, propagate its error.
     bool ok(JSContext *cx, const ReturnStatus &status);
 
     bool inactive_;
 
     /*** Dummy call handlers ***/
   public:
+    virtual bool SendDropObject(const ObjectId &objId) = 0;
     virtual bool CallPreventExtensions(const ObjectId &objId, ReturnStatus *rs) = 0;
     virtual bool CallGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
                                            ReturnStatus *rs,
                                            PPropertyDescriptor *out) = 0;
     virtual bool CallGetOwnPropertyDescriptor(const ObjectId &objId,
                                               const nsString &id,
                                               ReturnStatus *rs,
                                               PPropertyDescriptor *out) = 0;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1620,17 +1620,16 @@ JS_AddExtraGCRootsTracer(JSRuntime *rt, 
 {
     AssertHeapIsIdle(rt);
     return !!rt->gc.blackRootTracers.append(Callback<JSTraceDataOp>(traceOp, data));
 }
 
 JS_PUBLIC_API(void)
 JS_RemoveExtraGCRootsTracer(JSRuntime *rt, JSTraceDataOp traceOp, void *data)
 {
-    AssertHeapIsIdle(rt);
     for (size_t i = 0; i < rt->gc.blackRootTracers.length(); i++) {
         Callback<JSTraceDataOp> *e = &rt->gc.blackRootTracers[i];
         if (e->op == traceOp && e->data == data) {
             rt->gc.blackRootTracers.erase(e);
             break;
         }
     }
 }