Bug 1311212 - Add dead CPOW debugging facility (r=mrbkap)
authorBill McCloskey <billm@mozilla.com>
Tue, 18 Oct 2016 15:58:33 -0700
changeset 318614 99bf3401b43f4f621c91facce91f729688941317
parent 318613 d999217b98de228c954c01a6292b713f1162069d
child 318615 a0488e9c00247c438cd5de57f83b4ef28747d402
push id82972
push userwmccloskey@mozilla.com
push dateWed, 19 Oct 2016 23:26:49 +0000
treeherdermozilla-inbound@99bf3401b43f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs1311212
milestone52.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 1311212 - Add dead CPOW debugging facility (r=mrbkap)
dom/base/test/chrome/cpows_child.js
dom/base/test/chrome/cpows_parent.xul
js/ipc/CrossProcessObjectWrappers.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/xpconnect/src/XPCJSContext.cpp
--- a/dom/base/test/chrome/cpows_child.js
+++ b/dom/base/test/chrome/cpows_child.js
@@ -361,19 +361,22 @@ function unsafe_test(finish)
 function dead_test(finish)
 {
   if (!is_remote) {
     // Only run this test when running out-of-process.
     finish();
     return;
   }
 
+  let gcTrigger = function() {
+    // Force the GC to dead-ify the thing.
+    content.QueryInterface(Ci.nsIInterfaceRequestor)
+           .getInterface(Ci.nsIDOMWindowUtils)
+           .garbageCollect();
+  }
+
   {
     let thing = { value: "Gonna croak" };
-    sendAsyncMessage("cpows:dead", null, { thing });
+    sendAsyncMessage("cpows:dead", null, { thing, gcTrigger });
   }
-  // Force the GC to dead-ify the thing.
-  content.QueryInterface(Ci.nsIInterfaceRequestor)
-         .getInterface(Ci.nsIDOMWindowUtils)
-         .garbageCollect();
 
   addMessageListener("cpows:dead_done", finish);
 }
--- a/dom/base/test/chrome/cpows_parent.xul
+++ b/dom/base/test/chrome/cpows_parent.xul
@@ -413,24 +413,28 @@
       } catch (e if /unsafe CPOW usage forbidden/.test(String(e))) {
         ok(false, "cpow failed");
       }
       opener.wrappedJSObject.SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN);
       msg.target.messageManager.sendAsyncMessage("cpows:safe_done");
     }
 
     function recvDead(msg) {
-      try {
-        msg.objects.thing.value;
-        ok(false, "Should have been a dead CPOW");
-      } catch(e if /dead CPOW/.test(String(e))) {
-        ok(true, "Got the expected dead CPOW");
-        ok(e.stack, "The exception has a stack");
-      }
-      msg.target.messageManager.sendAsyncMessage("cpows:dead_done");
+      // Need to do this in a separate turn of the event loop.
+      setTimeout(() => {
+        msg.objects.gcTrigger();
+        try {
+          msg.objects.thing.value;
+          ok(false, "Should have been a dead CPOW");
+        } catch(e if /dead CPOW/.test(String(e))) {
+          ok(true, "Got the expected dead CPOW");
+          ok(e.stack, "The exception has a stack");
+        }
+        msg.target.messageManager.sendAsyncMessage("cpows:dead_done");
+      }, 0);
     }
 
     function run_tests(type) {
       info("Running tests: " + type);
       var node = document.getElementById('cpowbrowser_' + type);
 
       test_state = type;
       test_node = node;
--- a/js/ipc/CrossProcessObjectWrappers.h
+++ b/js/ipc/CrossProcessObjectWrappers.h
@@ -84,12 +84,15 @@ void
 ReleaseJavaScriptParent(PJavaScriptParent* parent);
 
 PJavaScriptChild*
 NewJavaScriptChild();
 
 void
 ReleaseJavaScriptChild(PJavaScriptChild* child);
 
+void
+AfterProcessTask();
+
 } // namespace jsipc
 } // namespace mozilla
 
 #endif // mozilla_jsipc_CrossProcessObjectWrappers_h__
--- a/js/ipc/JavaScriptChild.cpp
+++ b/js/ipc/JavaScriptChild.cpp
@@ -21,51 +21,72 @@ using namespace mozilla::jsipc;
 using mozilla::AutoSafeJSContext;
 
 static void
 UpdateChildWeakPointersBeforeSweepingZoneGroup(JSContext* cx, void* data)
 {
     static_cast<JavaScriptChild*>(data)->updateWeakPointers();
 }
 
+static void
+TraceChild(JSTracer* trc, void* data)
+{
+    static_cast<JavaScriptChild*>(data)->trace(trc);
+}
+
 JavaScriptChild::~JavaScriptChild()
 {
     JSContext* cx = dom::danger::GetJSContext();
     JS_RemoveWeakPointerZoneGroupCallback(cx, UpdateChildWeakPointersBeforeSweepingZoneGroup);
+    JS_RemoveExtraGCRootsTracer(cx, TraceChild, this);
 }
 
 bool
 JavaScriptChild::init()
 {
     if (!WrapperOwner::init())
         return false;
     if (!WrapperAnswer::init())
         return false;
 
     JSContext* cx = dom::danger::GetJSContext();
     JS_AddWeakPointerZoneGroupCallback(cx, UpdateChildWeakPointersBeforeSweepingZoneGroup, this);
+    JS_AddExtraGCRootsTracer(cx, TraceChild, this);
     return true;
 }
 
 void
+JavaScriptChild::trace(JSTracer* trc)
+{
+    objects_.trace(trc, strongReferenceObjIdMinimum_);
+}
+
+void
 JavaScriptChild::updateWeakPointers()
 {
     objects_.sweep();
     unwaivedObjectIds_.sweep();
     waivedObjectIds_.sweep();
 }
 
 JSObject*
 JavaScriptChild::scopeForTargetObjects()
 {
     // CPOWs from the parent need to point into the child's privileged junk
     // scope so that they can benefit from XrayWrappers in the child.
     return xpc::PrivilegedJunkScope();
 }
 
+bool
+JavaScriptChild::RecvDropTemporaryStrongReferences(const uint64_t& upToObjId)
+{
+    strongReferenceObjIdMinimum_ = upToObjId + 1;
+    return true;
+}
+
 PJavaScriptChild*
 mozilla::jsipc::NewJavaScriptChild()
 {
     JavaScriptChild* child = new JavaScriptChild();
     if (!child->init()) {
         delete child;
         return nullptr;
     }
--- a/js/ipc/JavaScriptChild.h
+++ b/js/ipc/JavaScriptChild.h
@@ -12,30 +12,39 @@
 #include "mozilla/jsipc/PJavaScriptChild.h"
 
 namespace mozilla {
 namespace jsipc {
 
 class JavaScriptChild : public JavaScriptBase<PJavaScriptChild>
 {
   public:
+    JavaScriptChild() : strongReferenceObjIdMinimum_(0) {}
     virtual ~JavaScriptChild();
 
     bool init();
+    void trace(JSTracer* trc);
     void updateWeakPointers();
 
     void drop(JSObject* obj);
 
     bool allowMessage(JSContext* cx) override { return true; }
 
   protected:
     virtual bool isParent() override { return false; }
     virtual JSObject* scopeForTargetObjects() override;
 
+    bool RecvDropTemporaryStrongReferences(const uint64_t& upToObjId) override;
+
   private:
     bool fail(JSContext* cx, ReturnStatus* rs);
     bool ok(ReturnStatus* rs);
+
+    // JavaScriptChild will keep strong references to JS objects that are
+    // referenced by the parent only if their ID is >=
+    // strongReferenceObjIdMinimum_.
+    uint64_t strongReferenceObjIdMinimum_;
 };
 
 } // namespace jsipc
 } // namespace mozilla
 
 #endif
--- a/js/ipc/JavaScriptParent.cpp
+++ b/js/ipc/JavaScriptParent.cpp
@@ -167,25 +167,27 @@ JavaScriptParent::scopeForTargetObjects(
 {
     // CPWOWs from the child need to point into the parent's unprivileged junk
     // scope so that a compromised child cannot compromise the parent. In
     // practice, this means that a child process can only (a) hold parent
     // objects alive and (b) invoke them if they are callable.
     return xpc::UnprivilegedJunkScope();
 }
 
-mozilla::ipc::IProtocol*
-JavaScriptParent::CloneProtocol(Channel* aChannel, ProtocolCloneContext* aCtx)
+void
+JavaScriptParent::afterProcessTask()
 {
-    ContentParent* contentParent = aCtx->GetContentParent();
-    nsAutoPtr<PJavaScriptParent> actor(contentParent->AllocPJavaScriptParent());
-    if (!actor || !contentParent->RecvPJavaScriptConstructor(actor)) {
-        return nullptr;
-    }
-    return actor.forget();
+    if (savedNextCPOWNumber_ == nextCPOWNumber_)
+        return;
+
+    savedNextCPOWNumber_ = nextCPOWNumber_;
+
+    MOZ_ASSERT(nextCPOWNumber_ > 0);
+    if (active())
+        Unused << SendDropTemporaryStrongReferences(nextCPOWNumber_ - 1);
 }
 
 PJavaScriptParent*
 mozilla::jsipc::NewJavaScriptParent()
 {
     JavaScriptParent* parent = new JavaScriptParent();
     if (!parent->init()) {
         delete parent;
@@ -194,8 +196,17 @@ mozilla::jsipc::NewJavaScriptParent()
     return parent;
 }
 
 void
 mozilla::jsipc::ReleaseJavaScriptParent(PJavaScriptParent* parent)
 {
     static_cast<JavaScriptParent*>(parent)->decref();
 }
+
+void
+mozilla::jsipc::AfterProcessTask()
+{
+    for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+        if (PJavaScriptParent* p = LoneManagedOrNullAsserts(cp->ManagedPJavaScriptParent()))
+            static_cast<JavaScriptParent*>(p)->afterProcessTask();
+    }
+}
--- a/js/ipc/JavaScriptParent.h
+++ b/js/ipc/JavaScriptParent.h
@@ -12,30 +12,32 @@
 #include "mozilla/jsipc/PJavaScriptParent.h"
 
 namespace mozilla {
 namespace jsipc {
 
 class JavaScriptParent : public JavaScriptBase<PJavaScriptParent>
 {
   public:
+    JavaScriptParent() : savedNextCPOWNumber_(1) {}
     virtual ~JavaScriptParent();
 
     bool init();
     void trace(JSTracer* trc);
 
     void drop(JSObject* obj);
 
     bool allowMessage(JSContext* cx) override;
-
-    mozilla::ipc::IProtocol*
-    CloneProtocol(Channel* aChannel, ProtocolCloneContext* aCtx) override;
+    void afterProcessTask();
 
   protected:
     virtual bool isParent() override { return true; }
     virtual JSObject* scopeForTargetObjects() override;
+
+  private:
+    uint64_t savedNextCPOWNumber_;
 };
 
 } // namespace jsipc
 } // namespace mozilla
 
 #endif // mozilla_jsipc_JavaScriptWrapper_h__
 
--- a/js/ipc/JavaScriptShared.cpp
+++ b/js/ipc/JavaScriptShared.cpp
@@ -28,20 +28,22 @@ bool
 IdToObjectMap::init()
 {
     if (table_.initialized())
         return true;
     return table_.init(32);
 }
 
 void
-IdToObjectMap::trace(JSTracer* trc)
+IdToObjectMap::trace(JSTracer* trc, uint64_t minimimId)
 {
-    for (Table::Range r(table_.all()); !r.empty(); r.popFront())
-        JS::TraceEdge(trc, &r.front().value(), "ipc-object");
+    for (Table::Range r(table_.all()); !r.empty(); r.popFront()) {
+        if (r.front().key().serialNumber() >= minimimId)
+            JS::TraceEdge(trc, &r.front().value(), "ipc-object");
+    }
 }
 
 void
 IdToObjectMap::sweep()
 {
     for (Table::Enum e(table_); !e.empty(); e.popFront()) {
         JS::Heap<JSObject*>* objp = &e.front().value();
         JS_UpdateWeakPointerAfterGC(objp);
@@ -140,17 +142,18 @@ ObjectToIdMap::clear()
 }
 
 bool JavaScriptShared::sLoggingInitialized;
 bool JavaScriptShared::sLoggingEnabled;
 bool JavaScriptShared::sStackLoggingEnabled;
 
 JavaScriptShared::JavaScriptShared()
   : refcount_(1),
-    nextSerialNumber_(1)
+    nextSerialNumber_(1),
+    nextCPOWNumber_(1)
 {
     if (!sLoggingInitialized) {
         sLoggingInitialized = true;
 
         if (PR_GetEnv("MOZ_CPOW_LOG")) {
             sLoggingEnabled = true;
             sStackLoggingEnabled = strstr(PR_GetEnv("MOZ_CPOW_LOG"), "stacks");
         } else {
--- a/js/ipc/JavaScriptShared.h
+++ b/js/ipc/JavaScriptShared.h
@@ -86,17 +86,17 @@ struct ObjectIdHasher
 class IdToObjectMap
 {
     typedef js::HashMap<ObjectId, JS::Heap<JSObject*>, ObjectIdHasher, js::SystemAllocPolicy> Table;
 
   public:
     IdToObjectMap();
 
     bool init();
-    void trace(JSTracer* trc);
+    void trace(JSTracer* trc, uint64_t minimumId = 0);
     void sweep();
 
     bool add(ObjectId id, JSObject* obj);
     JSObject* find(ObjectId id);
     void remove(ObjectId id);
 
     void clear();
     bool empty() const;
@@ -195,16 +195,20 @@ class JavaScriptShared : public CPOWMana
   protected:
     uintptr_t refcount_;
 
     IdToObjectMap objects_;
     IdToObjectMap cpows_;
 
     uint64_t nextSerialNumber_;
 
+    // nextCPOWNumber_ should be the value of nextSerialNumber_ in the other
+    // process. The next new CPOW we get should have this serial number.
+    uint64_t nextCPOWNumber_;
+
     // CPOW references can be weak, and any object we store in a map may be
     // GCed (at which point the CPOW will report itself "dead" to the owner).
     // This means that we don't want to store any js::Wrappers in the CPOW map,
     // because CPOW will die if the wrapper is GCed, even if the underlying
     // object is still alive.
     //
     // This presents a tricky situation for Xray waivers, since they're normally
     // represented as a special same-compartment wrapper. We have to strip them
--- a/js/ipc/PJavaScript.ipdl
+++ b/js/ipc/PJavaScript.ipdl
@@ -46,12 +46,15 @@ both:
     nested(inside_sync) sync RegExpToShared(uint64_t objId) returns (ReturnStatus rs, nsString source, uint32_t flags);
 
     nested(inside_sync) sync GetPropertyKeys(uint64_t objId, uint32_t flags) returns (ReturnStatus rs, JSIDVariant[] ids);
     nested(inside_sync) sync InstanceOf(uint64_t objId, JSIID iid) returns (ReturnStatus rs, bool instanceof);
     nested(inside_sync) sync DOMInstanceOf(uint64_t objId, int prototypeID, int depth) returns (ReturnStatus rs, bool instanceof);
 
 parent:
     async __delete__();
+
+child:
+    async DropTemporaryStrongReferences(uint64_t upToObjId);
 };
 
 }
 }
--- a/js/ipc/WrapperAnswer.cpp
+++ b/js/ipc/WrapperAnswer.cpp
@@ -22,16 +22,32 @@ using namespace mozilla::jsipc;
 // that we don't expect it to run script. For most of these traps that will only
 // happen if the target is a scripted proxy, which is probably something that we
 // don't want to support over CPOWs. When enough code is fixed up, the long-term
 // plan is to have the JS engine throw if it encounters script when it isn't
 // expecting it.
 using mozilla::dom::AutoJSAPI;
 using mozilla::dom::AutoEntryScript;
 
+static void
+MaybeForceDebugGC()
+{
+    static bool sEnvVarInitialized = false;
+    static bool sDebugGCs = false;
+
+    if (!sEnvVarInitialized)
+        sDebugGCs = !!PR_GetEnv("MOZ_DEBUG_DEAD_CPOWS");
+
+    if (sDebugGCs) {
+        JSContext* cx = nsXPConnect::GetContextInstance()->Context();
+        PrepareForFullGC(cx);
+        GCForReason(cx, GC_NORMAL, gcreason::COMPONENT_UTILS);
+    }
+}
+
 bool
 WrapperAnswer::fail(AutoJSAPI& jsapi, ReturnStatus* rs)
 {
     // By default, we set |undefined| unless we can get a more meaningful
     // exception.
     *rs = ReturnStatus(ReturnException(JSVariant(UndefinedVariant())));
 
     // Note we always return true from this function, since this propagates
@@ -80,16 +96,18 @@ WrapperAnswer::deadCPOW(AutoJSAPI& jsapi
     JS_ClearPendingException(cx);
     *rs = ReturnStatus(ReturnDeadCPOW());
     return true;
 }
 
 bool
 WrapperAnswer::RecvPreventExtensions(const ObjectId& objId, ReturnStatus* rs)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
         return deadCPOW(jsapi, rs);
@@ -111,16 +129,18 @@ EmptyDesc(PPropertyDescriptor* desc)
     desc->getter() = 0;
     desc->setter() = 0;
 }
 
 bool
 WrapperAnswer::RecvGetPropertyDescriptor(const ObjectId& objId, const JSIDVariant& idVar,
                                          ReturnStatus* rs, PPropertyDescriptor* out)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
     EmptyDesc(out);
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
@@ -141,16 +161,18 @@ WrapperAnswer::RecvGetPropertyDescriptor
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvGetOwnPropertyDescriptor(const ObjectId& objId, const JSIDVariant& idVar,
                                             ReturnStatus* rs, PPropertyDescriptor* out)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
     EmptyDesc(out);
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
@@ -171,16 +193,18 @@ WrapperAnswer::RecvGetOwnPropertyDescrip
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvDefineProperty(const ObjectId& objId, const JSIDVariant& idVar,
                                   const PPropertyDescriptor& descriptor, ReturnStatus* rs)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
         return deadCPOW(jsapi, rs);
@@ -199,16 +223,18 @@ WrapperAnswer::RecvDefineProperty(const 
     if (!JS_DefinePropertyById(cx, obj, id, desc, success))
         return fail(jsapi, rs);
     return ok(rs, success);
 }
 
 bool
 WrapperAnswer::RecvDelete(const ObjectId& objId, const JSIDVariant& idVar, ReturnStatus* rs)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
         return deadCPOW(jsapi, rs);
@@ -224,16 +250,18 @@ WrapperAnswer::RecvDelete(const ObjectId
         return fail(jsapi, rs);
     return ok(rs, success);
 }
 
 bool
 WrapperAnswer::RecvHas(const ObjectId& objId, const JSIDVariant& idVar, ReturnStatus* rs,
                        bool* foundp)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
     *foundp = false;
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
@@ -249,16 +277,18 @@ WrapperAnswer::RecvHas(const ObjectId& o
         return fail(jsapi, rs);
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvHasOwn(const ObjectId& objId, const JSIDVariant& idVar, ReturnStatus* rs,
                           bool* foundp)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
     *foundp = false;
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
@@ -274,16 +304,18 @@ WrapperAnswer::RecvHasOwn(const ObjectId
         return fail(jsapi, rs);
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvGet(const ObjectId& objId, const JSVariant& receiverVar,
                        const JSIDVariant& idVar, ReturnStatus* rs, JSVariant* result)
 {
+    MaybeForceDebugGC();
+
     // We may run scripted getters.
     AutoEntryScript aes(scopeForTargetObjects(),
                         "Cross-Process Object Wrapper 'get'");
     JSContext* cx = aes.cx();
 
     // The outparam will be written to the buffer, so it must be set even if
     // the parent won't read it.
     *result = UndefinedVariant();
@@ -311,16 +343,18 @@ WrapperAnswer::RecvGet(const ObjectId& o
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvSet(const ObjectId& objId, const JSIDVariant& idVar, const JSVariant& value,
                        const JSVariant& receiverVar, ReturnStatus* rs)
 {
+    MaybeForceDebugGC();
+
     // We may run scripted setters.
     AutoEntryScript aes(scopeForTargetObjects(),
                         "Cross-Process Object Wrapper 'set'");
     JSContext* cx = aes.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
         return deadCPOW(aes, rs);
@@ -344,16 +378,18 @@ WrapperAnswer::RecvSet(const ObjectId& o
         return fail(aes, rs);
 
     return ok(rs, result);
 }
 
 bool
 WrapperAnswer::RecvIsExtensible(const ObjectId& objId, ReturnStatus* rs, bool* result)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
     *result = false;
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
@@ -372,16 +408,18 @@ WrapperAnswer::RecvIsExtensible(const Ob
 bool
 WrapperAnswer::RecvCallOrConstruct(const ObjectId& objId,
                                    InfallibleTArray<JSParam>&& argv,
                                    const bool& construct,
                                    ReturnStatus* rs,
                                    JSVariant* result,
                                    nsTArray<JSParam>* outparams)
 {
+    MaybeForceDebugGC();
+
     AutoEntryScript aes(scopeForTargetObjects(),
                         "Cross-Process Object Wrapper call/construct");
     JSContext* cx = aes.cx();
 
     // The outparam will be written to the buffer, so it must be set even if
     // the parent won't read it.
     *result = UndefinedVariant();
 
@@ -470,16 +508,18 @@ WrapperAnswer::RecvCallOrConstruct(const
     LOG("%s.call(%s) = %s", ReceiverObj(objId), argv, OutVariant(*result));
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvHasInstance(const ObjectId& objId, const JSVariant& vVar, ReturnStatus* rs, bool* bp)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
         return deadCPOW(jsapi, rs);
@@ -495,16 +535,18 @@ WrapperAnswer::RecvHasInstance(const Obj
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvGetBuiltinClass(const ObjectId& objId, ReturnStatus* rs,
                                    uint32_t* classValue)
 {
+    MaybeForceDebugGC();
+
     *classValue = uint32_t(js::ESClass::Other);
 
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
@@ -520,16 +562,18 @@ WrapperAnswer::RecvGetBuiltinClass(const
     *classValue = uint32_t(cls);
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvIsArray(const ObjectId& objId, ReturnStatus* rs,
                            uint32_t* ans)
 {
+    MaybeForceDebugGC();
+
     *ans = uint32_t(IsArrayAnswer::NotArray);
 
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
@@ -544,16 +588,18 @@ WrapperAnswer::RecvIsArray(const ObjectI
 
     *ans = uint32_t(answer);
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvClassName(const ObjectId& objId, nsCString* name)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj) {
         // This is very unfortunate, but we have no choice.
@@ -565,16 +611,18 @@ WrapperAnswer::RecvClassName(const Objec
 
     *name = js::ObjectClassName(cx, obj);
     return true;
 }
 
 bool
 WrapperAnswer::RecvGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result)
 {
+    MaybeForceDebugGC();
+
     *result = NullVariant();
 
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
@@ -592,16 +640,18 @@ WrapperAnswer::RecvGetPrototype(const Ob
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvGetPrototypeIfOrdinary(const ObjectId& objId, ReturnStatus* rs, bool* isOrdinary,
                                           ObjectOrNullVariant* result)
 {
+    MaybeForceDebugGC();
+
     *result = NullVariant();
     *isOrdinary = false;
 
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
@@ -620,16 +670,18 @@ WrapperAnswer::RecvGetPrototypeIfOrdinar
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvRegExpToShared(const ObjectId& objId, ReturnStatus* rs,
                                   nsString* source, uint32_t* flags)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
         return deadCPOW(jsapi, rs);
@@ -646,16 +698,18 @@ WrapperAnswer::RecvRegExpToShared(const 
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvGetPropertyKeys(const ObjectId& objId, const uint32_t& flags,
                                    ReturnStatus* rs, nsTArray<JSIDVariant>* ids)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
         return deadCPOW(jsapi, rs);
@@ -676,16 +730,18 @@ WrapperAnswer::RecvGetPropertyKeys(const
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvInstanceOf(const ObjectId& objId, const JSIID& iid, ReturnStatus* rs,
                               bool* instanceof)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     *instanceof = false;
 
     RootedObject obj(cx, findObjectById(cx, objId));
@@ -703,16 +759,18 @@ WrapperAnswer::RecvInstanceOf(const Obje
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvDOMInstanceOf(const ObjectId& objId, const int& prototypeID,
                                  const int& depth, ReturnStatus* rs, bool* instanceof)
 {
+    MaybeForceDebugGC();
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
     *instanceof = false;
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -1198,16 +1198,18 @@ WrapperOwner::fromRemoteObjectVariant(JS
                              nullptr,
                              options);
         if (!obj)
             return nullptr;
 
         if (!cpows_.add(objId, obj))
             return nullptr;
 
+        nextCPOWNumber_ = objId.serialNumber() + 1;
+
         // Incref once we know the decref will be called.
         incref();
 
         AuxCPOWData* aux = new AuxCPOWData(objId,
                                            objVar.isCallable(),
                                            objVar.isConstructor(),
                                            objVar.isDOMObject(),
                                            objVar.objectTag());
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -39,16 +39,17 @@
 #include "nsScriptLoader.h"
 #include "jsapi.h"
 #include "jsprf.h"
 #include "js/MemoryMetrics.h"
 #include "mozilla/dom/GeneratedAtomList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/WindowBinding.h"
+#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "mozilla/Unused.h"
 #include "AccessCheck.h"
 #include "nsGlobalWindow.h"
@@ -3607,16 +3608,18 @@ XPCJSContext::AfterProcessTask(uint32_t 
     MOZ_ASSERT(NS_IsMainThread());
     nsJSContext::MaybePokeCC();
 
     CycleCollectedJSContext::AfterProcessTask(aNewRecursionDepth);
 
     // Now that we are certain that the event is complete,
     // we can flush any ongoing performance measurement.
     js::FlushPerformanceMonitoring(Get()->Context());
+
+    mozilla::jsipc::AfterProcessTask();
 }
 
 /***************************************************************************/
 
 void
 XPCJSContext::DebugDump(int16_t depth)
 {
 #ifdef DEBUG