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 id26799
push userphilringnalda@gmail.com
push dateSun, 18 May 2014 00:55:16 +0000
treeherdermozilla-central@00ef3a7d7aa7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs996785
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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;
         }
     }
 }