From b864716baa8f89d86ce1020dbda209268186a54c Mon Sep 17 00:00:00 2001 From: "brendan%mozilla.org" Date: Sat, 1 Jul 2000 02:36:18 +0000 Subject: [PATCH] Reference-count XBL JSClass structs, and LRU-cache unreferenced ones up to some quota, to avoid bloat and shutdown crashes due to unavoidable manual delete/last-use misorder (42530, r=hyatt). --- content/xbl/src/nsXBLBinding.cpp | 156 ++++++++++++++++---------- content/xbl/src/nsXBLEventHandler.cpp | 4 +- content/xbl/src/nsXBLService.cpp | 33 ++++-- content/xbl/src/nsXBLService.h | 39 +++++-- layout/xbl/src/nsXBLBinding.cpp | 156 ++++++++++++++++---------- layout/xbl/src/nsXBLEventHandler.cpp | 4 +- layout/xbl/src/nsXBLService.cpp | 33 ++++-- layout/xbl/src/nsXBLService.h | 39 +++++-- 8 files changed, 304 insertions(+), 160 deletions(-) diff --git a/content/xbl/src/nsXBLBinding.cpp b/content/xbl/src/nsXBLBinding.cpp index 2e5a16abfb5f..9f7d327c78f9 100644 --- a/content/xbl/src/nsXBLBinding.cpp +++ b/content/xbl/src/nsXBLBinding.cpp @@ -19,7 +19,7 @@ * * Original Author: David W. Hyatt (hyatt@netscape.com) * - * Contributor(s): + * Contributor(s): Brendan Eich (brendan@mozilla.org) */ #include "nsCOMPtr.h" @@ -118,16 +118,43 @@ NS_IMPL_ISUPPORTS1(nsXBLAttributeEntry, nsIXBLAttributeEntry) PR_STATIC_CALLBACK(void) XBLFinalize(JSContext *cx, JSObject *obj) { - nsJSUtils::nsGenericFinalize(cx, obj); + nsXBLJSClass* c = NS_STATIC_CAST(nsXBLJSClass*, ::JS_GetClass(cx, obj)); + c->Drop(); } -// -// XULElement constructor -// -PR_STATIC_CALLBACK(JSBool) -XBLBindingCtor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +nsXBLJSClass::nsXBLJSClass(const nsCString& aClassName) { - return JS_FALSE; + memset(this, 0, sizeof(nsXBLJSClass)); + next = prev = NS_STATIC_CAST(JSCList*, this); + name = nsXPIDLCString::Copy(aClassName); + addProperty = delProperty = setProperty = getProperty = ::JS_PropertyStub; + enumerate = ::JS_EnumerateStub; + resolve = ::JS_ResolveStub; + convert = ::JS_ConvertStub; + finalize = XBLFinalize; +} + +nsrefcnt +nsXBLJSClass::Destroy() +{ + NS_ASSERTION(next == prev && prev == NS_STATIC_CAST(JSCList*, this), + "referenced nsXBLJSClass is on LRU list already!?"); + + if (nsXBLService::gClassLRUListLength >= nsXBLService::gClassLRUListQuota) { + // Over LRU list quota, just unhash and delete this class. + if (nsXBLService::gClassTable) { + nsStringKey key(name); + (nsXBLService::gClassTable)->Remove(&key); + } + delete this; + } else { + // Put this most-recently-used class on end of the LRU-sorted freelist. + JSCList* mru = NS_STATIC_CAST(JSCList*, this); + JS_APPEND_LINK(mru, &nsXBLService::gClassLRUList); + nsXBLService::gClassLRUListLength++; + } + + return 0; } // Static initialization @@ -216,10 +243,10 @@ NS_IMPL_ISUPPORTS1(nsXBLBinding, nsIXBLBinding) // Constructors/Destructors nsXBLBinding::nsXBLBinding(void) -: mAttributeTable(nsnull), - mInsertionPointTable(nsnull), +: mFirstHandler(nsnull), mIsStyleBinding(PR_TRUE), - mFirstHandler(nsnull) + mAttributeTable(nsnull), + mInsertionPointTable(nsnull) { NS_INIT_REFCNT(); gRefCnt++; @@ -955,7 +982,7 @@ nsXBLBinding::AttributeChanged(nsIAtom* aAttribute, PRInt32 aNameSpaceID, PRBool if (!aRemoveFlag) { // Construct a new text node and insert it. nsAutoString value; - nsresult result = mBoundElement->GetAttribute(aNameSpaceID, aAttribute, value); + mBoundElement->GetAttribute(aNameSpaceID, aAttribute, value); if (!value.IsEmpty()) { nsCOMPtr textNode; nsCOMPtr doc; @@ -1018,9 +1045,9 @@ nsXBLBinding::ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocumen // XXX Sanity check to make sure our class name matches // Pull ourselves out of the proto chain. JSContext* jscontext = (JSContext*)context->GetNativeContext(); - JSObject* ourProto = JS_GetPrototype(jscontext, scriptObject); - JSObject* grandProto = JS_GetPrototype(jscontext, ourProto); - JS_SetPrototype(jscontext, scriptObject, grandProto); + JSObject* ourProto = ::JS_GetPrototype(jscontext, scriptObject); + JSObject* grandProto = ::JS_GetPrototype(jscontext, ourProto); + ::JS_SetPrototype(jscontext, scriptObject, grandProto); } } } @@ -1065,69 +1092,84 @@ nsXBLBinding::InitClass(const nsCString& aClassName, nsIScriptContext* aContext, // First ensure our JS class is initialized. JSContext* jscontext = (JSContext*)aContext->GetNativeContext(); - JSObject* proto = nsnull; - JSObject* constructor = nsnull; - JSObject* parent_proto = nsnull; - JSObject* global = JS_GetGlobalObject(jscontext); + JSObject* global = ::JS_GetGlobalObject(jscontext); jsval vp; + JSObject* proto; - if ((PR_TRUE != JS_LookupProperty(jscontext, global, aClassName, &vp)) || - !JSVAL_IS_OBJECT(vp) || - ((constructor = JSVAL_TO_OBJECT(vp)) == nsnull) || - (PR_TRUE != JS_LookupProperty(jscontext, JSVAL_TO_OBJECT(vp), "prototype", &vp)) || - !JSVAL_IS_OBJECT(vp)) { + if ((! ::JS_LookupProperty(jscontext, global, aClassName, &vp)) || + JSVAL_IS_PRIMITIVE(vp)) { // We need to initialize the class. - JSClass* c; + nsXBLJSClass* c; void* classObject; nsStringKey key(aClassName); classObject = (nsXBLService::gClassTable)->Get(&key); - if (classObject) - c = (JSClass*)classObject; - else { - // We need to create a struct for this class. - c = new JSClass; - memset(c, 0, sizeof(JSClass)); - c->name = nsXPIDLCString::Copy(aClassName); - c->flags = JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS; - c->addProperty = c->delProperty = c->setProperty = c->getProperty = JS_PropertyStub; - c->enumerate = JS_EnumerateStub; - c->resolve = JS_ResolveStub; - c->convert = JS_ConvertStub; - c->finalize = XBLFinalize; + if (classObject) { + c = NS_STATIC_CAST(nsXBLJSClass*, classObject); + + // If c is on the LRU list (i.e., not linked to itself), remove it now! + JSCList* link = NS_STATIC_CAST(JSCList*, c); + if (c->next != link) { + JS_REMOVE_AND_INIT_LINK(link); + nsXBLService::gClassLRUListLength--; + } + } else { + if (JS_CLIST_IS_EMPTY(&nsXBLService::gClassLRUList)) { + // We need to create a struct for this class. + c = new nsXBLJSClass(aClassName); + if (!c) + return NS_ERROR_OUT_OF_MEMORY; + } else { + // Pull the least recently used class struct off the list. + JSCList* lru = (nsXBLService::gClassLRUList).next; + JS_REMOVE_AND_INIT_LINK(lru); + nsXBLService::gClassLRUListLength--; + + // Remove any mapping from the old name to the class struct. + c = NS_STATIC_CAST(nsXBLJSClass*, lru); + nsStringKey oldKey(c->name); + (nsXBLService::gClassTable)->Remove(&oldKey); + + // Change the class name and we're done. + nsMemory::Free(c->name); + c->name = nsXPIDLCString::Copy(aClassName); + } // Add c to our table. (nsXBLService::gClassTable)->Put(&key, (void*)c); } - // Retrieve the current prototype for the JS object. - parent_proto = JS_GetPrototype(jscontext, object); - proto = JS_InitClass(jscontext, // context - global, // global object - parent_proto, // parent proto - c, // JSClass - XBLBindingCtor, // JSNative ctor - 0, // ctor args - nsnull, // proto props - nsnull, // proto funcs - nsnull, // ctor props (static) - nsnull); // ctor funcs (static) - if (nsnull == proto) { - return NS_ERROR_FAILURE; + // Retrieve the current prototype of the JS object. + JSObject* parent_proto = ::JS_GetPrototype(jscontext, object); + + // Make a new object prototyped by parent_proto and parented by global. + proto = ::JS_InitClass(jscontext, // context + global, // global object + parent_proto, // parent proto + c, // JSClass + NULL, // JSNative ctor + 0, // ctor args + nsnull, // proto props + nsnull, // proto funcs + nsnull, // ctor props (static) + nsnull); // ctor funcs (static) + if (!proto) { + (nsXBLService::gClassTable)->Remove(&key); + delete c; + return NS_ERROR_OUT_OF_MEMORY; } + // The prototype holds a strong reference to its class struct. + c->Hold(); *aClassObject = (void*)proto; } - else if ((nsnull != constructor) && JSVAL_IS_OBJECT(vp)) { - proto = JSVAL_TO_OBJECT(vp); - } else { - return NS_ERROR_FAILURE; + proto = JSVAL_TO_OBJECT(vp); } // Set the prototype of our object to be the new class. - JS_SetPrototype(jscontext, object, proto); + ::JS_SetPrototype(jscontext, object, proto); return NS_OK; } diff --git a/content/xbl/src/nsXBLEventHandler.cpp b/content/xbl/src/nsXBLEventHandler.cpp index d473bafdebdd..2f16def91aa8 100644 --- a/content/xbl/src/nsXBLEventHandler.cpp +++ b/content/xbl/src/nsXBLEventHandler.cpp @@ -19,7 +19,7 @@ * * Original Author: David W. Hyatt (hyatt@netscape.com) * - * Contributor(s): + * Contributor(s): Brendan Eich (brendan@mozilla.org) */ #include "nsCOMPtr.h" @@ -674,7 +674,7 @@ nsXBLEventHandler::RemoveEventHandlers() PRBool mouse = nsXBLBinding::IsMouseHandler(type); PRBool key = nsXBLBinding::IsKeyHandler(type); PRBool focus = nsXBLBinding::IsFocusHandler(type); - PRBool xul = nsXBLBinding::IsXULHandler(type); + // XXX not used: PRBool xul = nsXBLBinding::IsXULHandler(type); PRBool scroll = nsXBLBinding::IsScrollHandler(type); // Remove the event listener. diff --git a/content/xbl/src/nsXBLService.cpp b/content/xbl/src/nsXBLService.cpp index 9863e0196bfe..90dac3de0c07 100644 --- a/content/xbl/src/nsXBLService.cpp +++ b/content/xbl/src/nsXBLService.cpp @@ -19,7 +19,7 @@ * * Original Author: David W. Hyatt (hyatt@netscape.com) * - * Contributor(s): + * Contributor(s): Brendan Eich (brendan@mozilla.org) */ #include "nsCOMPtr.h" @@ -122,6 +122,10 @@ nsINameSpaceManager* nsXBLService::gNameSpaceManager = nsnull; nsHashtable* nsXBLService::gClassTable = nsnull; +JSCList nsXBLService::gClassLRUList = JS_INIT_STATIC_CLIST(&nsXBLService::gClassLRUList); +PRUint32 nsXBLService::gClassLRUListLength = 0; +PRUint32 nsXBLService::gClassLRUListQuota = 64; + nsIAtom* nsXBLService::kExtendsAtom = nsnull; nsIAtom* nsXBLService::kHasChildrenAtom = nsnull; nsIAtom* nsXBLService::kURIAtom = nsnull; @@ -175,14 +179,6 @@ nsXBLService::nsXBLService(void) } } -static PRBool PR_CALLBACK DeleteClasses(nsHashKey* aKey, void* aValue, void* closure) -{ - JSClass* c = (JSClass*)aValue; - nsMemory::Free(c->name); - delete c; - return PR_TRUE; // return PR_TRUE to continue for nsHashtable enumerator -} - nsXBLService::~nsXBLService(void) { gRefCnt--; @@ -197,10 +193,23 @@ nsXBLService::~nsXBLService(void) NS_RELEASE(kHasChildrenAtom); NS_RELEASE(kURIAtom); - // Walk the hashtable and delete the JSClasses - if (gClassTable) - gClassTable->Enumerate(DeleteClasses); + // Walk the LRU list removing and deleting the nsXBLJSClasses. + while (!JS_CLIST_IS_EMPTY(&gClassLRUList)) { + JSCList* lru = gClassLRUList.next; + JS_REMOVE_AND_INIT_LINK(lru); + nsXBLJSClass* c = NS_STATIC_CAST(nsXBLJSClass*, lru); + delete c; + } + + // Any straggling nsXBLJSClass instances held by unfinalized JS objects + // created for bindings will be deleted when those objects are finalized + // (and not put on gClassLRUList, because length >= quota). + gClassLRUListLength = gClassLRUListQuota = 0; + + // At this point, the only hash table entries should be for referenced + // XBL class structs held by unfinalized JS binding objects. delete gClassTable; + gClassTable = nsnull; } } diff --git a/content/xbl/src/nsXBLService.h b/content/xbl/src/nsXBLService.h index 81036cc6d5f9..0b7f783f8304 100644 --- a/content/xbl/src/nsXBLService.h +++ b/content/xbl/src/nsXBLService.h @@ -19,13 +19,16 @@ * * Original Author: David W. Hyatt (hyatt@netscape.com) * - * Contributor(s): + * Contributor(s): Brendan Eich (brendan@mozilla.org) */ ////////////////////////////////////////////////////////////////////////////////////////// #include "nsIXBLService.h" +#include "jsapi.h" // nsXBLJSClass derives from JSClass +#include "jsclist.h" // nsXBLJSClass derives from JSCList + class nsIXBLBinding; class nsINameSpaceManager; class nsIContent; @@ -36,7 +39,7 @@ class nsIURI; class nsSupportsHashtable; class nsHashtable; -class nsXBLService: public nsIXBLService +class nsXBLService : public nsIXBLService { NS_DECL_ISUPPORTS @@ -52,10 +55,10 @@ class nsXBLService: public nsIXBLService // For a given element, returns a flat list of all the anonymous children that need // frames built. - NS_IMETHOD GetContentList(nsIContent* aContent, nsISupportsArray** aResult, nsIContent** aChildElement, + NS_IMETHOD GetContentList(nsIContent* aContent, nsISupportsArray** aResult, nsIContent** aChildElement, PRBool* aMultipleInsertionPoints); - // Gets the object's base class type. + // Gets the object's base class type. NS_IMETHOD ResolveTag(nsIContent* aContent, PRInt32* aNameSpaceID, nsIAtom** aResult); NS_IMETHOD AllowScripts(nsIContent* aContent, PRBool* aAllowScripts); @@ -78,8 +81,8 @@ public: NS_IMETHOD StripWhitespaceNodes(nsIContent* aContent); // MEMBER VARIABLES -public: - static nsSupportsHashtable* mBindingTable; // This is a table of all the bindings files +public: + static nsSupportsHashtable* mBindingTable; // This is a table of all the bindings files // we have loaded // during this session. static nsSupportsHashtable* mScriptAccessTable; // Can the doc's bindings access scripts @@ -89,12 +92,30 @@ public: static PRUint32 gRefCnt; // A count of XBLservice instances. static PRBool gDisableChromeCache; - - static nsHashtable* gClassTable; // A table of JSClass objects. + + static nsHashtable* gClassTable; // A table of nsXBLJSClass objects. + + static JSCList gClassLRUList; // LRU list of cached classes. + static PRUint32 gClassLRUListLength; // Number of classes on LRU list. + static PRUint32 gClassLRUListQuota; // Quota on class LRU list. // XBL Atoms - static nsIAtom* kExtendsAtom; + static nsIAtom* kExtendsAtom; static nsIAtom* kHasChildrenAtom; static nsIAtom* kURIAtom; }; +class nsXBLJSClass : public JSCList, public JSClass +{ +private: + nsrefcnt mRefCnt; + nsrefcnt Destroy(); + +public: + nsXBLJSClass(const nsCString& aClassName); + ~nsXBLJSClass() { nsMemory::Free(name); } + + nsrefcnt Hold() { return ++mRefCnt; } + nsrefcnt Drop() { return --mRefCnt ? mRefCnt : Destroy(); } +}; + diff --git a/layout/xbl/src/nsXBLBinding.cpp b/layout/xbl/src/nsXBLBinding.cpp index 2e5a16abfb5f..9f7d327c78f9 100644 --- a/layout/xbl/src/nsXBLBinding.cpp +++ b/layout/xbl/src/nsXBLBinding.cpp @@ -19,7 +19,7 @@ * * Original Author: David W. Hyatt (hyatt@netscape.com) * - * Contributor(s): + * Contributor(s): Brendan Eich (brendan@mozilla.org) */ #include "nsCOMPtr.h" @@ -118,16 +118,43 @@ NS_IMPL_ISUPPORTS1(nsXBLAttributeEntry, nsIXBLAttributeEntry) PR_STATIC_CALLBACK(void) XBLFinalize(JSContext *cx, JSObject *obj) { - nsJSUtils::nsGenericFinalize(cx, obj); + nsXBLJSClass* c = NS_STATIC_CAST(nsXBLJSClass*, ::JS_GetClass(cx, obj)); + c->Drop(); } -// -// XULElement constructor -// -PR_STATIC_CALLBACK(JSBool) -XBLBindingCtor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +nsXBLJSClass::nsXBLJSClass(const nsCString& aClassName) { - return JS_FALSE; + memset(this, 0, sizeof(nsXBLJSClass)); + next = prev = NS_STATIC_CAST(JSCList*, this); + name = nsXPIDLCString::Copy(aClassName); + addProperty = delProperty = setProperty = getProperty = ::JS_PropertyStub; + enumerate = ::JS_EnumerateStub; + resolve = ::JS_ResolveStub; + convert = ::JS_ConvertStub; + finalize = XBLFinalize; +} + +nsrefcnt +nsXBLJSClass::Destroy() +{ + NS_ASSERTION(next == prev && prev == NS_STATIC_CAST(JSCList*, this), + "referenced nsXBLJSClass is on LRU list already!?"); + + if (nsXBLService::gClassLRUListLength >= nsXBLService::gClassLRUListQuota) { + // Over LRU list quota, just unhash and delete this class. + if (nsXBLService::gClassTable) { + nsStringKey key(name); + (nsXBLService::gClassTable)->Remove(&key); + } + delete this; + } else { + // Put this most-recently-used class on end of the LRU-sorted freelist. + JSCList* mru = NS_STATIC_CAST(JSCList*, this); + JS_APPEND_LINK(mru, &nsXBLService::gClassLRUList); + nsXBLService::gClassLRUListLength++; + } + + return 0; } // Static initialization @@ -216,10 +243,10 @@ NS_IMPL_ISUPPORTS1(nsXBLBinding, nsIXBLBinding) // Constructors/Destructors nsXBLBinding::nsXBLBinding(void) -: mAttributeTable(nsnull), - mInsertionPointTable(nsnull), +: mFirstHandler(nsnull), mIsStyleBinding(PR_TRUE), - mFirstHandler(nsnull) + mAttributeTable(nsnull), + mInsertionPointTable(nsnull) { NS_INIT_REFCNT(); gRefCnt++; @@ -955,7 +982,7 @@ nsXBLBinding::AttributeChanged(nsIAtom* aAttribute, PRInt32 aNameSpaceID, PRBool if (!aRemoveFlag) { // Construct a new text node and insert it. nsAutoString value; - nsresult result = mBoundElement->GetAttribute(aNameSpaceID, aAttribute, value); + mBoundElement->GetAttribute(aNameSpaceID, aAttribute, value); if (!value.IsEmpty()) { nsCOMPtr textNode; nsCOMPtr doc; @@ -1018,9 +1045,9 @@ nsXBLBinding::ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocumen // XXX Sanity check to make sure our class name matches // Pull ourselves out of the proto chain. JSContext* jscontext = (JSContext*)context->GetNativeContext(); - JSObject* ourProto = JS_GetPrototype(jscontext, scriptObject); - JSObject* grandProto = JS_GetPrototype(jscontext, ourProto); - JS_SetPrototype(jscontext, scriptObject, grandProto); + JSObject* ourProto = ::JS_GetPrototype(jscontext, scriptObject); + JSObject* grandProto = ::JS_GetPrototype(jscontext, ourProto); + ::JS_SetPrototype(jscontext, scriptObject, grandProto); } } } @@ -1065,69 +1092,84 @@ nsXBLBinding::InitClass(const nsCString& aClassName, nsIScriptContext* aContext, // First ensure our JS class is initialized. JSContext* jscontext = (JSContext*)aContext->GetNativeContext(); - JSObject* proto = nsnull; - JSObject* constructor = nsnull; - JSObject* parent_proto = nsnull; - JSObject* global = JS_GetGlobalObject(jscontext); + JSObject* global = ::JS_GetGlobalObject(jscontext); jsval vp; + JSObject* proto; - if ((PR_TRUE != JS_LookupProperty(jscontext, global, aClassName, &vp)) || - !JSVAL_IS_OBJECT(vp) || - ((constructor = JSVAL_TO_OBJECT(vp)) == nsnull) || - (PR_TRUE != JS_LookupProperty(jscontext, JSVAL_TO_OBJECT(vp), "prototype", &vp)) || - !JSVAL_IS_OBJECT(vp)) { + if ((! ::JS_LookupProperty(jscontext, global, aClassName, &vp)) || + JSVAL_IS_PRIMITIVE(vp)) { // We need to initialize the class. - JSClass* c; + nsXBLJSClass* c; void* classObject; nsStringKey key(aClassName); classObject = (nsXBLService::gClassTable)->Get(&key); - if (classObject) - c = (JSClass*)classObject; - else { - // We need to create a struct for this class. - c = new JSClass; - memset(c, 0, sizeof(JSClass)); - c->name = nsXPIDLCString::Copy(aClassName); - c->flags = JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS; - c->addProperty = c->delProperty = c->setProperty = c->getProperty = JS_PropertyStub; - c->enumerate = JS_EnumerateStub; - c->resolve = JS_ResolveStub; - c->convert = JS_ConvertStub; - c->finalize = XBLFinalize; + if (classObject) { + c = NS_STATIC_CAST(nsXBLJSClass*, classObject); + + // If c is on the LRU list (i.e., not linked to itself), remove it now! + JSCList* link = NS_STATIC_CAST(JSCList*, c); + if (c->next != link) { + JS_REMOVE_AND_INIT_LINK(link); + nsXBLService::gClassLRUListLength--; + } + } else { + if (JS_CLIST_IS_EMPTY(&nsXBLService::gClassLRUList)) { + // We need to create a struct for this class. + c = new nsXBLJSClass(aClassName); + if (!c) + return NS_ERROR_OUT_OF_MEMORY; + } else { + // Pull the least recently used class struct off the list. + JSCList* lru = (nsXBLService::gClassLRUList).next; + JS_REMOVE_AND_INIT_LINK(lru); + nsXBLService::gClassLRUListLength--; + + // Remove any mapping from the old name to the class struct. + c = NS_STATIC_CAST(nsXBLJSClass*, lru); + nsStringKey oldKey(c->name); + (nsXBLService::gClassTable)->Remove(&oldKey); + + // Change the class name and we're done. + nsMemory::Free(c->name); + c->name = nsXPIDLCString::Copy(aClassName); + } // Add c to our table. (nsXBLService::gClassTable)->Put(&key, (void*)c); } - // Retrieve the current prototype for the JS object. - parent_proto = JS_GetPrototype(jscontext, object); - proto = JS_InitClass(jscontext, // context - global, // global object - parent_proto, // parent proto - c, // JSClass - XBLBindingCtor, // JSNative ctor - 0, // ctor args - nsnull, // proto props - nsnull, // proto funcs - nsnull, // ctor props (static) - nsnull); // ctor funcs (static) - if (nsnull == proto) { - return NS_ERROR_FAILURE; + // Retrieve the current prototype of the JS object. + JSObject* parent_proto = ::JS_GetPrototype(jscontext, object); + + // Make a new object prototyped by parent_proto and parented by global. + proto = ::JS_InitClass(jscontext, // context + global, // global object + parent_proto, // parent proto + c, // JSClass + NULL, // JSNative ctor + 0, // ctor args + nsnull, // proto props + nsnull, // proto funcs + nsnull, // ctor props (static) + nsnull); // ctor funcs (static) + if (!proto) { + (nsXBLService::gClassTable)->Remove(&key); + delete c; + return NS_ERROR_OUT_OF_MEMORY; } + // The prototype holds a strong reference to its class struct. + c->Hold(); *aClassObject = (void*)proto; } - else if ((nsnull != constructor) && JSVAL_IS_OBJECT(vp)) { - proto = JSVAL_TO_OBJECT(vp); - } else { - return NS_ERROR_FAILURE; + proto = JSVAL_TO_OBJECT(vp); } // Set the prototype of our object to be the new class. - JS_SetPrototype(jscontext, object, proto); + ::JS_SetPrototype(jscontext, object, proto); return NS_OK; } diff --git a/layout/xbl/src/nsXBLEventHandler.cpp b/layout/xbl/src/nsXBLEventHandler.cpp index d473bafdebdd..2f16def91aa8 100644 --- a/layout/xbl/src/nsXBLEventHandler.cpp +++ b/layout/xbl/src/nsXBLEventHandler.cpp @@ -19,7 +19,7 @@ * * Original Author: David W. Hyatt (hyatt@netscape.com) * - * Contributor(s): + * Contributor(s): Brendan Eich (brendan@mozilla.org) */ #include "nsCOMPtr.h" @@ -674,7 +674,7 @@ nsXBLEventHandler::RemoveEventHandlers() PRBool mouse = nsXBLBinding::IsMouseHandler(type); PRBool key = nsXBLBinding::IsKeyHandler(type); PRBool focus = nsXBLBinding::IsFocusHandler(type); - PRBool xul = nsXBLBinding::IsXULHandler(type); + // XXX not used: PRBool xul = nsXBLBinding::IsXULHandler(type); PRBool scroll = nsXBLBinding::IsScrollHandler(type); // Remove the event listener. diff --git a/layout/xbl/src/nsXBLService.cpp b/layout/xbl/src/nsXBLService.cpp index 9863e0196bfe..90dac3de0c07 100644 --- a/layout/xbl/src/nsXBLService.cpp +++ b/layout/xbl/src/nsXBLService.cpp @@ -19,7 +19,7 @@ * * Original Author: David W. Hyatt (hyatt@netscape.com) * - * Contributor(s): + * Contributor(s): Brendan Eich (brendan@mozilla.org) */ #include "nsCOMPtr.h" @@ -122,6 +122,10 @@ nsINameSpaceManager* nsXBLService::gNameSpaceManager = nsnull; nsHashtable* nsXBLService::gClassTable = nsnull; +JSCList nsXBLService::gClassLRUList = JS_INIT_STATIC_CLIST(&nsXBLService::gClassLRUList); +PRUint32 nsXBLService::gClassLRUListLength = 0; +PRUint32 nsXBLService::gClassLRUListQuota = 64; + nsIAtom* nsXBLService::kExtendsAtom = nsnull; nsIAtom* nsXBLService::kHasChildrenAtom = nsnull; nsIAtom* nsXBLService::kURIAtom = nsnull; @@ -175,14 +179,6 @@ nsXBLService::nsXBLService(void) } } -static PRBool PR_CALLBACK DeleteClasses(nsHashKey* aKey, void* aValue, void* closure) -{ - JSClass* c = (JSClass*)aValue; - nsMemory::Free(c->name); - delete c; - return PR_TRUE; // return PR_TRUE to continue for nsHashtable enumerator -} - nsXBLService::~nsXBLService(void) { gRefCnt--; @@ -197,10 +193,23 @@ nsXBLService::~nsXBLService(void) NS_RELEASE(kHasChildrenAtom); NS_RELEASE(kURIAtom); - // Walk the hashtable and delete the JSClasses - if (gClassTable) - gClassTable->Enumerate(DeleteClasses); + // Walk the LRU list removing and deleting the nsXBLJSClasses. + while (!JS_CLIST_IS_EMPTY(&gClassLRUList)) { + JSCList* lru = gClassLRUList.next; + JS_REMOVE_AND_INIT_LINK(lru); + nsXBLJSClass* c = NS_STATIC_CAST(nsXBLJSClass*, lru); + delete c; + } + + // Any straggling nsXBLJSClass instances held by unfinalized JS objects + // created for bindings will be deleted when those objects are finalized + // (and not put on gClassLRUList, because length >= quota). + gClassLRUListLength = gClassLRUListQuota = 0; + + // At this point, the only hash table entries should be for referenced + // XBL class structs held by unfinalized JS binding objects. delete gClassTable; + gClassTable = nsnull; } } diff --git a/layout/xbl/src/nsXBLService.h b/layout/xbl/src/nsXBLService.h index 81036cc6d5f9..0b7f783f8304 100644 --- a/layout/xbl/src/nsXBLService.h +++ b/layout/xbl/src/nsXBLService.h @@ -19,13 +19,16 @@ * * Original Author: David W. Hyatt (hyatt@netscape.com) * - * Contributor(s): + * Contributor(s): Brendan Eich (brendan@mozilla.org) */ ////////////////////////////////////////////////////////////////////////////////////////// #include "nsIXBLService.h" +#include "jsapi.h" // nsXBLJSClass derives from JSClass +#include "jsclist.h" // nsXBLJSClass derives from JSCList + class nsIXBLBinding; class nsINameSpaceManager; class nsIContent; @@ -36,7 +39,7 @@ class nsIURI; class nsSupportsHashtable; class nsHashtable; -class nsXBLService: public nsIXBLService +class nsXBLService : public nsIXBLService { NS_DECL_ISUPPORTS @@ -52,10 +55,10 @@ class nsXBLService: public nsIXBLService // For a given element, returns a flat list of all the anonymous children that need // frames built. - NS_IMETHOD GetContentList(nsIContent* aContent, nsISupportsArray** aResult, nsIContent** aChildElement, + NS_IMETHOD GetContentList(nsIContent* aContent, nsISupportsArray** aResult, nsIContent** aChildElement, PRBool* aMultipleInsertionPoints); - // Gets the object's base class type. + // Gets the object's base class type. NS_IMETHOD ResolveTag(nsIContent* aContent, PRInt32* aNameSpaceID, nsIAtom** aResult); NS_IMETHOD AllowScripts(nsIContent* aContent, PRBool* aAllowScripts); @@ -78,8 +81,8 @@ public: NS_IMETHOD StripWhitespaceNodes(nsIContent* aContent); // MEMBER VARIABLES -public: - static nsSupportsHashtable* mBindingTable; // This is a table of all the bindings files +public: + static nsSupportsHashtable* mBindingTable; // This is a table of all the bindings files // we have loaded // during this session. static nsSupportsHashtable* mScriptAccessTable; // Can the doc's bindings access scripts @@ -89,12 +92,30 @@ public: static PRUint32 gRefCnt; // A count of XBLservice instances. static PRBool gDisableChromeCache; - - static nsHashtable* gClassTable; // A table of JSClass objects. + + static nsHashtable* gClassTable; // A table of nsXBLJSClass objects. + + static JSCList gClassLRUList; // LRU list of cached classes. + static PRUint32 gClassLRUListLength; // Number of classes on LRU list. + static PRUint32 gClassLRUListQuota; // Quota on class LRU list. // XBL Atoms - static nsIAtom* kExtendsAtom; + static nsIAtom* kExtendsAtom; static nsIAtom* kHasChildrenAtom; static nsIAtom* kURIAtom; }; +class nsXBLJSClass : public JSCList, public JSClass +{ +private: + nsrefcnt mRefCnt; + nsrefcnt Destroy(); + +public: + nsXBLJSClass(const nsCString& aClassName); + ~nsXBLJSClass() { nsMemory::Free(name); } + + nsrefcnt Hold() { return ++mRefCnt; } + nsrefcnt Drop() { return --mRefCnt ? mRefCnt : Destroy(); } +}; +