diff --git a/dom/plugins/PluginModuleChild.cpp b/dom/plugins/PluginModuleChild.cpp
--- a/dom/plugins/PluginModuleChild.cpp
+++ b/dom/plugins/PluginModuleChild.cpp
@@ -40,16 +40,17 @@
 #include "mozilla/plugins/PluginModuleChild.h"
 
 #ifdef OS_LINUX
 #include <gtk/gtk.h>
 #endif
 
 #include "nsILocalFile.h"
 
+#include "pratom.h"
 #include "nsDebug.h"
 #include "nsCOMPtr.h"
 #include "nsPluginsDir.h"
 
 #include "mozilla/plugins/PluginInstanceChild.h"
 #include "mozilla/plugins/StreamNotifyChild.h"
 #include "mozilla/plugins/BrowserStreamChild.h"
 #include "mozilla/plugins/PluginStreamChild.h"
@@ -233,23 +234,52 @@ PluginModuleChild::GetActorForNPObject(N
     AssertPluginThread();
     NS_ASSERTION(mObjectMap.IsInitialized(), "Not initialized!");
     NS_ASSERTION(aObject, "Null pointer!");
     PluginScriptableObjectChild* actor;
     return mObjectMap.Get(aObject, &actor) ? actor : nsnull;
 }
 
 #ifdef DEBUG
+namespace {
+
+struct SearchInfo {
+  PluginScriptableObjectChild* target;
+  bool found;
+};
+
+PLDHashOperator
+ActorSearch(const void* aKey,
+            PluginScriptableObjectChild* aData,
+            void* aUserData)
+{
+  SearchInfo* info = reinterpret_cast<SearchInfo*>(aUserData);
+  NS_ASSERTION(info->target && ! info->found, "Bad info ptr!");
+
+  if (aData == info->target) {
+    info->found = true;
+    return PL_DHASH_STOP;
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+} // anonymous namespace
+
 bool
-PluginModuleChild::NPObjectIsRegistered(NPObject* aObject)
+PluginModuleChild::NPObjectIsRegisteredForActor(
+                                            PluginScriptableObjectChild* aActor)
 {
     AssertPluginThread();
     NS_ASSERTION(mObjectMap.IsInitialized(), "Not initialized!");
-    NS_ASSERTION(aObject, "Null pointer!");
-    return mObjectMap.Get(aObject, nsnull);
+    NS_ASSERTION(aActor, "Null actor!");
+
+    SearchInfo info = { aActor, false };
+    mObjectMap.EnumerateRead(ActorSearch, &info);
+    return info.found;
 }
 #endif
 
 //-----------------------------------------------------------------------------
 // FIXME/cjones: just getting this out of the way for the moment ...
 
 // FIXME
 typedef void (*PluginThreadCallback)(void*);
@@ -897,43 +927,49 @@ _createobject(NPP aNPP,
     }
     else {
         newObject = reinterpret_cast<NPObject*>(_memalloc(sizeof(NPObject)));
     }
 
     if (newObject) {
         newObject->_class = aClass;
         newObject->referenceCount = 1;
+        NS_LOG_ADDREF(newObject, 1, "ChildNPObject", sizeof(NPObject));
     }
     return newObject;
 }
 
 NPObject* NP_CALLBACK
 _retainobject(NPObject* aNPObj)
 {
     AssertPluginThread();
 
 #ifdef DEBUG
     printf("[PluginModuleChild] %s: object %p, refcnt %d\n", __FUNCTION__,
            aNPObj, aNPObj->referenceCount + 1);
 #endif
-    ++aNPObj->referenceCount;
+    int32_t refCnt = PR_AtomicIncrement((PRInt32*)&aNPObj->referenceCount);
+    NS_LOG_ADDREF(aNPObj, refCnt, "ChildNPObject", sizeof(NPObject));
+
     return aNPObj;
 }
 
 void NP_CALLBACK
 _releaseobject(NPObject* aNPObj)
 {
     AssertPluginThread();
 
 #ifdef DEBUG
     printf("[PluginModuleChild] %s: object %p, refcnt %d\n", __FUNCTION__,
            aNPObj, aNPObj->referenceCount - 1);
 #endif
-    if (--aNPObj->referenceCount == 0) {
+    int32_t refCnt = PR_AtomicDecrement((PRInt32*)&aNPObj->referenceCount);
+    NS_LOG_RELEASE(aNPObj, refCnt, "ChildNPObject");
+
+    if (refCnt == 0) {
         if (aNPObj->_class && aNPObj->_class->deallocate) {
             aNPObj->_class->deallocate(aNPObj);
         } else {
             _memfree(aNPObj);
         }
     }
     return;
 }
diff --git a/dom/plugins/PluginModuleChild.h b/dom/plugins/PluginModuleChild.h
--- a/dom/plugins/PluginModuleChild.h
+++ b/dom/plugins/PluginModuleChild.h
@@ -144,17 +144,17 @@ public:
     bool RegisterNPObject(NPObject* aObject,
                           PluginScriptableObjectChild* aActor);
 
     void UnregisterNPObject(NPObject* aObject);
 
     PluginScriptableObjectChild* GetActorForNPObject(NPObject* aObject);
 
 #ifdef DEBUG
-    bool NPObjectIsRegistered(NPObject* aObject);
+    bool NPObjectIsRegisteredForActor(PluginScriptableObjectChild* aActor);
 #endif
 
 private:
     bool InitGraphics();
 
     std::string mPluginFilename;
     PRLibrary* mLibrary;
 
diff --git a/dom/plugins/PluginScriptableObjectChild.cpp b/dom/plugins/PluginScriptableObjectChild.cpp
--- a/dom/plugins/PluginScriptableObjectChild.cpp
+++ b/dom/plugins/PluginScriptableObjectChild.cpp
@@ -631,36 +631,39 @@ const NPClass PluginScriptableObjectChil
   PluginScriptableObjectChild::ScriptableEnumerate,
   PluginScriptableObjectChild::ScriptableConstruct
 };
 
 PluginScriptableObjectChild::PluginScriptableObjectChild()
 : mInstance(nsnull),
   mObject(nsnull)
 {
+  MOZ_COUNT_CTOR(PluginScriptableObjectChild);
   AssertPluginThread();
 }
 
 PluginScriptableObjectChild::~PluginScriptableObjectChild()
 {
+  MOZ_COUNT_DTOR(PluginScriptableObjectChild);
   AssertPluginThread();
 
   if (mObject) {
     if (mObject->_class == GetClass()) {
       if (!static_cast<ChildNPObject*>(mObject)->invalidated) {
         NS_WARNING("This should have happened already!");
         ScriptableInvalidate(mObject);
       }
     }
     else {
       PluginModuleChild::sBrowserFuncs.releaseobject(mObject);
     }
-    NS_ASSERTION(!PluginModuleChild::current()->NPObjectIsRegistered(mObject),
-                 "NPObject still registered!");
   }
+  NS_ASSERTION(!PluginModuleChild::current()->
+               NPObjectIsRegisteredForActor(this),
+               "NPObjects still registered for this actor!");
 }
 
 void
 PluginScriptableObjectChild::Initialize(PluginInstanceChild* aInstance,
                                         NPObject* aObject)
 {
   AssertPluginThread();
 
diff --git a/dom/plugins/PluginScriptableObjectParent.cpp b/dom/plugins/PluginScriptableObjectParent.cpp
--- a/dom/plugins/PluginScriptableObjectParent.cpp
+++ b/dom/plugins/PluginScriptableObjectParent.cpp
@@ -747,20 +747,22 @@ const NPClass PluginScriptableObjectPare
   PluginScriptableObjectParent::ScriptableEnumerate,
   PluginScriptableObjectParent::ScriptableConstruct
 };
 
 PluginScriptableObjectParent::PluginScriptableObjectParent()
 : mInstance(nsnull),
   mObject(nsnull)
 {
+  MOZ_COUNT_CTOR(PluginScriptableObjectParent);
 }
 
 PluginScriptableObjectParent::~PluginScriptableObjectParent()
 {
+  MOZ_COUNT_DTOR(PluginScriptableObjectParent);
   if (mObject) {
     if (mObject->_class == GetClass()) {
       if (!static_cast<ParentNPObject*>(mObject)->invalidated) {
         ScriptableInvalidate(mObject);
       }
     }
     else {
       mInstance->GetNPNIface()->releaseobject(mObject);
@@ -795,19 +797,16 @@ PluginScriptableObjectParent::Initialize
   mObject = aObject;
 }
 
 bool
 PluginScriptableObjectParent::AnswerInvalidate()
 {
   if (mObject) {
     NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
-    if (mObject->_class && mObject->_class->invalidate) {
-      mObject->_class->invalidate(mObject);
-    }
     const NPNetscapeFuncs* npn = GetNetscapeFuncs(GetInstance());
     if (npn) {
       npn->releaseobject(mObject);
     }
     mObject = nsnull;
   }
   return true;
 }
diff --git a/ipc/glue/AsyncChannel.cpp b/ipc/glue/AsyncChannel.cpp
--- a/ipc/glue/AsyncChannel.cpp
+++ b/ipc/glue/AsyncChannel.cpp
@@ -50,16 +50,38 @@ struct RunnableMethodTraits<mozilla::ipc
 {
     static void RetainCallee(mozilla::ipc::AsyncChannel* obj) { }
     static void ReleaseCallee(mozilla::ipc::AsyncChannel* obj) { }
 };
 
 namespace mozilla {
 namespace ipc {
 
+AsyncChannel::AsyncChannel(AsyncListener* aListener)
+  : mTransport(0),
+    mListener(aListener),
+    mChannelState(ChannelClosed),
+    mMutex("mozilla.ipc.AsyncChannel.mMutex"),
+    mCvar(mMutex, "mozilla.ipc.AsyncChannel.mCvar"),
+    mIOLoop(),
+    mWorkerLoop()
+{
+    MOZ_COUNT_CTOR(AsyncChannel);
+}
+
+AsyncChannel::~AsyncChannel()
+{
+    MOZ_COUNT_DTOR(AsyncChannel);
+    if (!mChild && mTransport)
+        Close();
+    // we only hold a weak ref to the transport, which is "owned"
+    // by GeckoChildProcess/GeckoThread
+    mTransport = 0;
+}
+
 bool
 AsyncChannel::Open(Transport* aTransport, MessageLoop* aIOLoop)
 {
     NS_PRECONDITION(!mTransport, "Open() called > once");
     NS_PRECONDITION(aTransport, "need transport layer");
 
     // FIXME need to check for valid channel
 
diff --git a/ipc/glue/AsyncChannel.h b/ipc/glue/AsyncChannel.h
--- a/ipc/glue/AsyncChannel.h
+++ b/ipc/glue/AsyncChannel.h
@@ -85,35 +85,18 @@ public:
     class /*NS_INTERFACE_CLASS*/ AsyncListener: protected HasResultCodes
     {
     public:
         virtual ~AsyncListener() { }
         virtual Result OnMessageReceived(const Message& aMessage) = 0;
     };
 
 public:
-    AsyncChannel(AsyncListener* aListener) :
-        mTransport(0),
-        mListener(aListener),
-        mChannelState(ChannelClosed),
-        mMutex("mozilla.ipc.AsyncChannel.mMutex"),
-        mCvar(mMutex, "mozilla.ipc.AsyncChannel.mCvar"),
-        mIOLoop(),
-        mWorkerLoop()
-    {
-    }
-
-    virtual ~AsyncChannel()
-    {
-        if (!mChild && mTransport)
-            Close();
-        // we only hold a weak ref to the transport, which is "owned"
-        // by GeckoChildProcess/GeckoThread
-        mTransport = 0;
-    }
+    AsyncChannel(AsyncListener* aListener);
+    virtual ~AsyncChannel();
 
     // Open  from the perspective of the transport layer; the underlying
     // socketpair/pipe should already be created.
     //
     // Returns true iff the transport layer was successfully connected,
     // i.e., mChannelState == ChannelConnected.
     bool Open(Transport* aTransport, MessageLoop* aIOLoop=0);
     
diff --git a/ipc/glue/RPCChannel.cpp b/ipc/glue/RPCChannel.cpp
--- a/ipc/glue/RPCChannel.cpp
+++ b/ipc/glue/RPCChannel.cpp
@@ -56,16 +56,34 @@ struct RunnableMethodTraits<mozilla::ipc
 {
     static void RetainCallee(mozilla::ipc::RPCChannel* obj) { }
     static void ReleaseCallee(mozilla::ipc::RPCChannel* obj) { }
 };
 
 namespace mozilla {
 namespace ipc {
 
+RPCChannel::RPCChannel(RPCListener* aListener,
+                       RacyRPCPolicy aPolicy)
+  : SyncChannel(aListener),
+    mPending(),
+    mStack(),
+    mDeferred(),
+    mRemoteStackDepthGuess(0),
+    mRacePolicy(aPolicy)
+{
+    MOZ_COUNT_CTOR(RPCChannel);
+}
+
+RPCChannel::~RPCChannel()
+{
+    MOZ_COUNT_DTOR(RPCChannel);
+    // FIXME/cjones: impl
+}
+
 bool
 RPCChannel::Call(Message* msg, Message* reply)
 {
     AssertWorkerThread();
     RPC_ASSERT(!ProcessingSyncMessage(),
                "violation of sync handler invariant");
     RPC_ASSERT(msg->is_rpc(), "can only Call() RPC messages here");
 
diff --git a/ipc/glue/RPCChannel.h b/ipc/glue/RPCChannel.h
--- a/ipc/glue/RPCChannel.h
+++ b/ipc/glue/RPCChannel.h
@@ -66,30 +66,19 @@ public:
 
     // What happens if RPC calls race?
     enum RacyRPCPolicy {
         RRPError,
         RRPChildWins,
         RRPParentWins
     };
 
-    RPCChannel(RPCListener* aListener, RacyRPCPolicy aPolicy=RRPChildWins) :
-        SyncChannel(aListener),
-        mPending(),
-        mStack(),
-        mDeferred(),
-        mRemoteStackDepthGuess(0),
-        mRacePolicy(aPolicy)
-    {
-    }
+    RPCChannel(RPCListener* aListener, RacyRPCPolicy aPolicy=RRPChildWins);
 
-    virtual ~RPCChannel()
-    {
-        // FIXME/cjones: impl
-    }
+    virtual ~RPCChannel();
 
     // Make an RPC to the other side of the channel
     bool Call(Message* msg, Message* reply);
 
     // Override the SyncChannel handler so we can dispatch RPC
     // messages.  Called on the IO thread only.
     NS_OVERRIDE virtual void OnMessageReceived(const Message& msg);
     NS_OVERRIDE virtual void OnChannelError();
diff --git a/ipc/glue/SyncChannel.cpp b/ipc/glue/SyncChannel.cpp
--- a/ipc/glue/SyncChannel.cpp
+++ b/ipc/glue/SyncChannel.cpp
@@ -55,16 +55,34 @@ struct RunnableMethodTraits<mozilla::ipc
 {
     static void RetainCallee(mozilla::ipc::SyncChannel* obj) { }
     static void ReleaseCallee(mozilla::ipc::SyncChannel* obj) { }
 };
 
 namespace mozilla {
 namespace ipc {
 
+SyncChannel::SyncChannel(SyncListener* aListener)
+  : AsyncChannel(aListener),
+    mPendingReply(0),
+    mProcessingSyncMessage(false)
+#ifdef OS_WIN
+  , mUIThreadId(0)
+  , mEventLoopDepth(0)
+#endif
+{
+  MOZ_COUNT_CTOR(SyncChannel);
+}
+
+SyncChannel::~SyncChannel()
+{
+    MOZ_COUNT_DTOR(SyncChannel);
+    // FIXME/cjones: impl
+}
+
 bool
 SyncChannel::Send(Message* msg, Message* reply)
 {
     AssertWorkerThread();
     NS_ABORT_IF_FALSE(!ProcessingSyncMessage(),
                       "violation of sync handler invariant");
     NS_ABORT_IF_FALSE(msg->is_sync(), "can only Send() sync messages here");
 
diff --git a/ipc/glue/SyncChannel.h b/ipc/glue/SyncChannel.h
--- a/ipc/glue/SyncChannel.h
+++ b/ipc/glue/SyncChannel.h
@@ -57,31 +57,18 @@ public:
     {
     public:
         virtual ~SyncListener() { }
         virtual Result OnMessageReceived(const Message& aMessage) = 0;
         virtual Result OnMessageReceived(const Message& aMessage,
                                          Message*& aReply) = 0;
     };
 
-    SyncChannel(SyncListener* aListener) :
-        AsyncChannel(aListener),
-        mPendingReply(0),
-        mProcessingSyncMessage(false)
-#ifdef OS_WIN
-      , mUIThreadId(0)
-      , mEventLoopDepth(0)
-#endif
-    {
-    }
-
-    virtual ~SyncChannel()
-    {
-        // FIXME/cjones: impl
-    }
+    SyncChannel(SyncListener* aListener);
+    virtual ~SyncChannel();
 
     bool Send(Message* msg) {
         return AsyncChannel::Send(msg);
     }
 
     // Synchronously send |msg| (i.e., wait for |reply|)
     bool Send(Message* msg, Message* reply);
 
diff --git a/modules/plugin/base/src/nsJSNPRuntime.cpp b/modules/plugin/base/src/nsJSNPRuntime.cpp
--- a/modules/plugin/base/src/nsJSNPRuntime.cpp
+++ b/modules/plugin/base/src/nsJSNPRuntime.cpp
@@ -483,21 +483,24 @@ ReportExceptionIfPending(JSContext *cx)
 
   return JS_FALSE;
 }
 
 
 nsJSObjWrapper::nsJSObjWrapper(NPP npp)
   : nsJSObjWrapperKey(nsnull, npp)
 {
+  MOZ_COUNT_CTOR(nsJSObjWrapper);
   OnWrapperCreated();
 }
 
 nsJSObjWrapper::~nsJSObjWrapper()
 {
+  MOZ_COUNT_DTOR(nsJSObjWrapper);
+
   // Invalidate first, since it relies on sJSRuntime and sJSObjWrappers.
   NP_Invalidate(this);
 
   OnWrapperDestroyed();
 }
 
 // static
 NPObject *
@@ -1851,16 +1854,23 @@ NPObjWrapperPluginDestroyedCallback(PLDH
     table->ops = nsnull;
 
     NPObject *npobj = entry->mNPObj;
 
     if (npobj->_class && npobj->_class->invalidate) {
       npobj->_class->invalidate(npobj);
     }
 
+#ifdef NS_BUILD_REFCNT_LOGGING
+    while (npobj->referenceCount) {
+      --npobj->referenceCount;
+      NS_LOG_RELEASE(npobj, npobj->referenceCount, "BrowserNPObject");
+    }
+#endif
+
     // Force deallocation of plugin objects since the plugin they came
     // from is being torn down.
     if (npobj->_class && npobj->_class->deallocate) {
       npobj->_class->deallocate(npobj);
     } else {
       PR_Free(npobj);
     }
 
diff --git a/modules/plugin/base/src/nsNPAPIPlugin.cpp b/modules/plugin/base/src/nsNPAPIPlugin.cpp
--- a/modules/plugin/base/src/nsNPAPIPlugin.cpp
+++ b/modules/plugin/base/src/nsNPAPIPlugin.cpp
@@ -1394,47 +1394,50 @@ _createobject(NPP npp, NPClass* aClass)
     npobj = aClass->allocate(npp, aClass);
   } else {
     npobj = (NPObject *)PR_Malloc(sizeof(NPObject));
   }
 
   if (npobj) {
     npobj->_class = aClass;
     npobj->referenceCount = 1;
+    NS_LOG_ADDREF(npobj, 1, "BrowserNPObject", sizeof(NPObject));
   }
 
   NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY,
                  ("Created NPObject %p, NPClass %p\n", npobj, aClass));
 
   return npobj;
 }
 
 NPObject* NP_CALLBACK
 _retainobject(NPObject* npobj)
 {
   if (!NS_IsMainThread()) {
     NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_retainobject called from the wrong thread\n"));
   }
   if (npobj) {
-    PR_AtomicIncrement((PRInt32*)&npobj->referenceCount);
+    int32_t refCnt = PR_AtomicIncrement((PRInt32*)&npobj->referenceCount);
+    NS_LOG_ADDREF(npobj, refCnt, "BrowserNPObject", sizeof(NPObject));
   }
 
   return npobj;
 }
 
 void NP_CALLBACK
 _releaseobject(NPObject* npobj)
 {
   if (!NS_IsMainThread()) {
     NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_releaseobject called from the wrong thread\n"));
   }
   if (!npobj)
     return;
 
   int32_t refCnt = PR_AtomicDecrement((PRInt32*)&npobj->referenceCount);
+  NS_LOG_RELEASE(npobj, refCnt, "BrowserNPObject");
 
   if (refCnt == 0) {
     nsNPObjWrapper::OnDestroy(npobj);
 
     NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY,
                    ("Deleting NPObject %p, refcount hit 0\n", npobj));
 
     if (npobj->_class && npobj->_class->deallocate) {
diff --git a/modules/plugin/test/testplugin/nptest.cpp b/modules/plugin/test/testplugin/nptest.cpp
--- a/modules/plugin/test/testplugin/nptest.cpp
+++ b/modules/plugin/test/testplugin/nptest.cpp
@@ -473,17 +473,16 @@ NPP_New(NPMIMEType pluginType, NPP insta
   instance->pdata = instanceData;
 
   TestNPObject* scriptableObject = (TestNPObject*)NPN_CreateObject(instance, &sNPClass);
   if (!scriptableObject) {
     printf("NPN_CreateObject failed to create an object, can't create a plugin instance\n");
     free(instanceData);
     return NPERR_GENERIC_ERROR;
   }
-  NPN_RetainObject(scriptableObject);
   scriptableObject->npp = instance;
   scriptableObject->drawMode = DM_DEFAULT;
   scriptableObject->drawColor = 0;
   instanceData->scriptableObject = scriptableObject;
 
   instanceData->instanceCountWatchGeneration = sCurrentInstanceCountWatchGeneration;
   
   bool requestWindow = false;
