the rest of the fix for bug 66950 to add nsISupportsWeakReference support of xpconnect wrapped JSObjects. r=rogerl sr=brendan

This commit is contained in:
jband%netscape.com 2001-02-02 10:01:56 +00:00
parent 9dd0a41319
commit bd58ec8cea
4 changed files with 207 additions and 34 deletions

View File

@ -55,6 +55,84 @@ const char* XPCJSRuntime::mStrings[] = {
/***************************************************************************/
// GCCallback calls are chained
static JSGCCallback gOldJSGCCallback;
// data holder class for the enumerator callback below
struct JSDyingJSObjectData
{
JSContext* cx;
nsVoidArray* array;
};
JS_STATIC_DLL_CALLBACK(intN)
WrappedJSDyingJSObjectFinder(JSHashEntry *he, intN i, void *arg)
{
JSDyingJSObjectData* data = (JSDyingJSObjectData*) arg;
nsXPCWrappedJS* wrapper = (nsXPCWrappedJS*)he->value;
NS_ASSERTION(wrapper, "found a null JS wrapper!");
// walk the wrapper chain and find any whose JSObject is to be finalized
while(wrapper)
{
if(wrapper->IsSubjectToFinalization())
{
if(JS_IsAboutToBeFinalized(data->cx, wrapper->GetJSObject()))
data->array->AppendElement(wrapper);
}
wrapper = wrapper->GetNextWrapper();
}
return HT_ENUMERATE_NEXT;
}
// static
JSBool XPCJSRuntime::GCCallback(JSContext *cx, JSGCStatus status)
{
if(status == JSGC_MARK_END || status == JSGC_END)
{
XPCJSRuntime* self = nsXPConnect::GetRuntime();
if(self)
{
nsVoidArray* array = &self->mWrappedJSToReleaseArray;
if(status == JSGC_MARK_END)
{
nsAutoLock lock(self->mMapLock); // lock the wrapper map
JSDyingJSObjectData data = {cx, array};
// Add any wrappers whose JSObjects are to be finalized to
// this array. Note that this is a nsVoidArray because
// we do not want to be changing the refcount of these wrappers.
// We add them to the array now and Release the array members
// later to avoid the posibility of doing any JS GCThing
// allocations during the gc cycle.
self->mWrappedJSMap->Enumerate(WrappedJSDyingJSObjectFinder,
&data);
}
else // status == JSGC_END
{
// Release all the members whose JSObjects are now known
// to be dead.
for(PRInt32 i = array->Count() - 1; i >= 0; i--)
{
nsXPCWrappedJS* wrapper =
NS_REINTERPRET_CAST(nsXPCWrappedJS*,
array->ElementAt(i));
NS_RELEASE(wrapper);
}
array->Clear();
}
}
}
// always chain to old GCCallback if non-null.
return gOldJSGCCallback ? gOldJSGCCallback(cx, status) : JS_TRUE;
}
/***************************************************************************/
#ifdef XPC_CHECK_WRAPPERS_AT_SHUTDOWN
JS_STATIC_DLL_CALLBACK(JSHashNumber)
hash_root(const void *key)
@ -158,7 +236,8 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect,
mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_SIZE)),
mWrappedJSClassMap(IID2WrappedJSClassMap::newMap(XPC_JS_CLASS_MAP_SIZE)),
mWrappedNativeClassMap(IID2WrappedNativeClassMap::newMap(XPC_NATIVE_CLASS_MAP_SIZE)),
mMapLock(PR_NewLock())
mMapLock(PR_NewLock()),
mWrappedJSToReleaseArray()
{
#ifdef XPC_CHECK_WRAPPERS_AT_SHUTDOWN
@ -177,6 +256,10 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect,
mJSRuntimeService->GetRuntime(&mJSRuntime);
}
NS_ASSERTION(!gOldJSGCCallback, "XPCJSRuntime created more than once");
if(mJSRuntime)
gOldJSGCCallback = JS_SetGCCallbackRT(mJSRuntime, GCCallback);
// Install a JavaScript 'debugger' keyword handler in debug builds only
#ifdef DEBUG
if(mJSRuntime)

View File

@ -54,6 +54,7 @@
#include "nsIXPCScriptable.h"
#include "nsIXPCSecurityManager.h"
#include "nsIJSRuntimeService.h"
#include "nsWeakReference.h"
#include "nsCOMPtr.h"
#include "nsIModule.h"
#include "nsAutoLock.h"
@ -73,10 +74,12 @@
#include "prlong.h"
#include "prmem.h"
#include "prenv.h"
#include "nsReadableUtils.h"
#include "nsIJSContextStack.h"
#include "prthread.h"
#include "nsDeque.h"
#include "nsVoidArray.h"
#ifdef XPCONNECT_STANDALONE
#include <math.h>
@ -252,6 +255,8 @@ public:
return mStrings[index];
}
static JSBool GCCallback(JSContext *cx, JSGCStatus status);
void DebugDump(PRInt16 depth);
~XPCJSRuntime();
@ -291,6 +296,7 @@ private:
IID2WrappedJSClassMap* mWrappedJSClassMap;
IID2WrappedNativeClassMap* mWrappedNativeClassMap;
PRLock* mMapLock;
nsVoidArray mWrappedJSToReleaseArray;
};
/***************************************************************************/
@ -689,7 +695,7 @@ public:
static nsXPCWrappedJSClass* GetNewOrUsedClass(XPCJSRuntime* rt,
REFNSIID aIID);
REFNSIID GetIID() const {return mIID;}
XPCJSRuntime* GetJSRuntime() const {return mRuntime;}
XPCJSRuntime* GetRuntime() const {return mRuntime;}
nsIInterfaceInfo* GetInterfaceInfo() const {return mInfo;}
const char* GetInterfaceName();
@ -758,7 +764,9 @@ private:
/*************************/
class nsXPCWrappedJS : public nsXPTCStubBase, public nsIXPConnectWrappedJS
class nsXPCWrappedJS : public nsXPTCStubBase,
public nsIXPConnectWrappedJS,
public nsSupportsWeakReference
{
public:
NS_DECL_ISUPPORTS
@ -787,12 +795,21 @@ public:
nsXPCWrappedJSClass* GetClass() const {return mClass;}
REFNSIID GetIID() const {return GetClass()->GetIID();}
nsXPCWrappedJS* GetRootWrapper() const {return mRoot;}
nsXPCWrappedJS* GetNextWrapper() const {return mNext;}
nsXPCWrappedJS* Find(REFNSIID aIID);
nsXPCWrappedJS* FindInherited(REFNSIID aIID);
JSBool IsValid() const {return mJSObj != nsnull;}
void SystemIsBeingShutDown(JSRuntime* rt);
// This is used by XPCJSRuntime::GCCallback to find wrappers that no
// longer root their JSObject and are only still alive because they
// were being used via nsSupportsWeakReference at the time when their
// last (outside) reference was released. Wrappers that fit into that
// category are only deleted when we see that their cooresponding JSObject
// is to be finalized.
JSBool IsSubjectToFinalization() const {return IsValid() && mRefCnt == 1;}
JSBool IsAggregatedToNative() const {return mRoot->mOuter != nsnull;}
nsISupports* GetAggregatedNativeObject() const {return mRoot->mOuter;}
@ -1350,7 +1367,7 @@ private:
void FillCache(JSContext *cx, JSObject *obj,
nsIXPConnectWrappedNative *wrapper,
nsIXPCScriptable *arbitrary);
JSBool NeedToFillCache(JSObject* obj) const {return obj != mObj;}
private:

View File

@ -94,7 +94,24 @@ nsXPCWrappedJS::QueryInterface(REFNSIID aIID, void** aInstancePtr)
}
// do chained ref counting
// Refcounting is now similar to that used in the chained (pre-flattening)
// wrappednative system.
//
// We are now holding an extra refcount for nsISupportsWeakReference support.
//
// Non-root wrappers remove themselves from the chain in their destructors.
// We root the JSObject as the refcount transitions from 1->2. And we unroot
// the JSObject when the refcount transitions from 2->1.
//
// When the transition from 2->1 is made and no one holds a weak ref to the
// (aggregated) object then we decrement the refcount again to 0 (and
// destruct) . However, if a weak ref is held at the 2->1 transition, then we
// leave the refcount at 1 to indicate that state. This leaves the JSObject
// no longer rooted by us and (as far as we know) subject to possible
// collection. Code in XPCJSRuntime watches for JS gc to happen and will do
// the final release on wrappers whose JSObjects get finalized. Note that
// even after tranistioning to this refcount-of-one state callers might do
// an addref and cause us to re-root the JSObject and continue on more normally.
nsrefcnt
nsXPCWrappedJS::AddRef(void)
@ -102,8 +119,15 @@ nsXPCWrappedJS::AddRef(void)
NS_PRECONDITION(mRoot, "bad root");
nsrefcnt cnt = (nsrefcnt) PR_AtomicIncrement((PRInt32*)&mRefCnt);
NS_LOG_ADDREF(this, cnt, "nsXPCWrappedJS", sizeof(*this));
if(1 == cnt && mRoot && mRoot != this)
NS_ADDREF(mRoot);
if(2 == cnt && IsValid())
{
AutoPushCompatibleJSContext
autoContext(mClass->GetRuntime()->GetJSRuntime());
JSContext* cx = autoContext.GetJSContext();
if(cx)
JS_AddNamedRoot(cx, &mJSObj, "nsXPCWrappedJS::mJSObj");
}
return cnt;
}
@ -118,20 +142,30 @@ nsXPCWrappedJS::Release(void)
NS_ASSERTION(IsValid(), "post xpconnect shutdown call of nsXPCWrappedJS::Release");
#endif
do_decrement:
nsrefcnt cnt = (nsrefcnt) PR_AtomicDecrement((PRInt32*)&mRefCnt);
NS_LOG_RELEASE(this, cnt, "nsXPCWrappedJS");
if(0 == cnt)
{
if(mRoot == this)
{
NS_DELETEXPCOM(this); // cascaded delete
}
else
{
mRoot->Release();
}
NS_DELETEXPCOM(this); // also unlinks us from chain
return 0;
}
if(1 == cnt)
{
if(IsValid())
{
XPCJSRuntime* rt = mClass->GetRuntime();
if(rt)
JS_RemoveRootRT(rt->GetJSRuntime(), &mJSObj);
}
// If we are not being used from a weak reference, then this extra
// ref is not needed and we can let ourself be deleted.
if(!mRoot->HasWeakReferences())
goto do_decrement;
}
return cnt;
}
@ -275,41 +309,60 @@ nsXPCWrappedJS::nsXPCWrappedJS(XPCContext* xpcc,
#endif
NS_INIT_REFCNT();
// intensionally do double addref - see Release().
NS_ADDREF_THIS();
NS_ADDREF_THIS();
NS_ADDREF(aClass);
NS_IF_ADDREF(mOuter);
NS_ASSERTION(xpcc && xpcc->GetJSContext(), "bad context");
JS_AddNamedRoot(xpcc->GetJSContext(), &mJSObj,
"nsXPCWrappedJS::mJSObj");
if(mRoot != this)
NS_ADDREF(mRoot);
}
nsXPCWrappedJS::~nsXPCWrappedJS()
{
NS_PRECONDITION(0 == mRefCnt, "refcounting error");
// Any destructors called after shutdown are just going to leak stuff.
if(IsValid())
if(mRoot != this)
{
// unlink this wrapper
nsXPCWrappedJS* cur = mRoot;
while(1)
{
if(cur->mNext == this)
{
cur->mNext = mNext;
break;
}
cur = cur->mNext;
NS_ASSERTION(cur, "failed to find wrapper in its own chain");
}
// let the root go
NS_RELEASE(mRoot);
}
else
{
NS_ASSERTION(!mNext, "root wrapper with non-empty chain being deleted");
// Let the nsWeakReference object (if present) know of our demise.
ClearWeakReferences();
// remove this root wrapper from the map
XPCJSRuntime* rt = nsXPConnect::GetRuntime();
if(rt)
{
if(mRoot == this)
JSObject2WrappedJSMap* map = rt->GetWrappedJSMap();
if(map)
{
JSObject2WrappedJSMap* map = rt->GetWrappedJSMap();
if(map)
{
nsAutoLock lock(rt->GetMapLock());
map->Remove(this);
}
nsAutoLock lock(rt->GetMapLock());
map->Remove(this);
}
JS_RemoveRootRT(rt->GetJSRuntime(), &mJSObj);
}
NS_IF_RELEASE(mClass);
// XXX Should this moved out of the 'if' block?
// XXX OR... Should this called in SystemIsBeingShutDown?
NS_IF_RELEASE(mOuter);
}
if(mNext)
NS_DELETEXPCOM(mNext); // cascaded delete
NS_IF_RELEASE(mClass);
NS_IF_RELEASE(mOuter);
}
nsXPCWrappedJS*

View File

@ -291,6 +291,26 @@ nsXPCWrappedJSClass::DelegatedQueryInterface(nsXPCWrappedJS* self,
return NS_OK;
}
// We support nsISupportsWeakReference iff the root wrapped JSObject
// claims to support it in its QueryInterface implementation.
if(aIID.Equals(NS_GET_IID(nsISupportsWeakReference)))
{
// We only want to expose one implementation from our aggregate.
nsXPCWrappedJS* root = self->GetRootWrapper();
// Fail if JSObject doesn't claim support for nsISupportsWeakReference
if(!root->IsValid() ||
!CallQueryInterfaceOnJSObject(root->GetJSObject(), aIID))
{
*aInstancePtr = nsnull;
return NS_NOINTERFACE;
}
NS_ADDREF(root);
*aInstancePtr = (void*) NS_STATIC_CAST(nsISupportsWeakReference*,root);
return NS_OK;
}
nsXPCWrappedJS* sibling;
// Checks for any existing wrapper explicitly constructed for this iid.