Relanding bug 372769 with some cycle collection changes to fix leaks. r=mrbkap, sr=sicking, with r=sicking on the leak fixes.
authorbzbarsky@mit.edu
Fri, 28 Sep 2007 06:45:01 -0700
changeset 6392 6b821bbc782cf3be8f8dd78c8a3cbb58aef894e6
parent 6391 787afe8d47ea996bb080f6066b24ddeb9dfb4193
child 6393 2800a26495deb32fd90ba0cb0487743afa7b20b5
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap, sicking, with, sicking
bugs372769
milestone1.9a9pre
Relanding bug 372769 with some cycle collection changes to fix leaks. r=mrbkap, sr=sicking, with r=sicking on the leak fixes.
content/xbl/src/Makefile.in
content/xbl/src/nsXBLBinding.cpp
content/xbl/src/nsXBLBinding.h
content/xbl/src/nsXBLContentSink.cpp
content/xbl/src/nsXBLContentSink.h
content/xbl/src/nsXBLDocumentInfo.cpp
content/xbl/src/nsXBLProtoImpl.cpp
content/xbl/src/nsXBLProtoImpl.h
content/xbl/src/nsXBLProtoImplField.cpp
content/xbl/src/nsXBLProtoImplField.h
content/xbl/src/nsXBLPrototypeBinding.cpp
content/xbl/src/nsXBLPrototypeBinding.h
content/xbl/src/nsXBLResourceLoader.cpp
content/xbl/test/Makefile.in
dom/src/base/nsDOMClassInfo.cpp
dom/src/base/nsDOMClassInfo.h
--- a/content/xbl/src/Makefile.in
+++ b/content/xbl/src/Makefile.in
@@ -106,11 +106,12 @@ LOCAL_INCLUDES	= \
 		-I$(srcdir)/../../base/src \
 		-I$(srcdir)/../../html/base/src \
 		-I$(srcdir)/../../html/document/src \
 		-I$(srcdir)/../../xml/document/src \
 		-I$(srcdir)/../../xul/content/src \
 		-I$(srcdir)/../../xul/document/src \
 		-I$(srcdir)/../../events/src \
 		-I$(srcdir)/../../../layout/style \
+		-I$(srcdir)/../../../dom/src/base \
 		$(NULL)
 
 DEFINES += -D_IMPL_NS_LAYOUT
--- a/content/xbl/src/nsXBLBinding.cpp
+++ b/content/xbl/src/nsXBLBinding.cpp
@@ -97,37 +97,144 @@
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIEventListenerManager.h"
 #include "nsGUIEvent.h"
 
 #include "prprf.h"
 #include "nsNodeUtils.h"
 
+// Nasty hack.  Maybe we could move some of the classinfo utility methods
+// (e.g. WrapNative and ThrowJSException) over to nsContentUtils?
+#include "nsDOMClassInfo.h"
+#include "nsJSUtils.h"
+
 // Helper classes
 
 /***********************************************************************/
 //
 // The JS class for XBLBinding
 //
-PR_STATIC_CALLBACK(void)
+JS_STATIC_DLL_CALLBACK(void)
 XBLFinalize(JSContext *cx, JSObject *obj)
 {
+  nsIXBLDocumentInfo* docInfo =
+    static_cast<nsIXBLDocumentInfo*>(::JS_GetPrivate(cx, obj));
+  NS_RELEASE(docInfo);
+  
   nsXBLJSClass* c = static_cast<nsXBLJSClass*>(::JS_GetClass(cx, obj));
   c->Drop();
 }
 
+JS_STATIC_DLL_CALLBACK(JSBool)
+XBLResolve(JSContext *cx, JSObject *obj, jsval id, uintN flags,
+           JSObject **objp)
+{
+  // Note: if we get here, that means that the implementation for some binding
+  // was installed, which means that AllowScripts() tested true.  Hence no need
+  // to do checks like that here.
+  
+  // Default to not resolving things.
+  NS_ASSERTION(*objp, "Must have starting object");
+
+  JSObject* origObj = *objp;
+  *objp = NULL;
+
+  if (!JSVAL_IS_STRING(id)) {
+    return JS_TRUE;
+  }
+
+  nsDependentJSString fieldName(id);
+
+  jsval slotVal;
+  ::JS_GetReservedSlot(cx, obj, 0, &slotVal);
+  NS_ASSERTION(!JSVAL_IS_VOID(slotVal), "How did that happen?");
+    
+  nsXBLPrototypeBinding* protoBinding =
+    static_cast<nsXBLPrototypeBinding*>(JSVAL_TO_PRIVATE(slotVal));
+  NS_ASSERTION(protoBinding, "Must have prototype binding!");
+
+  nsXBLProtoImplField* field = protoBinding->FindField(fieldName);
+  if (!field) {
+    return JS_TRUE;
+  }
+
+  // We have this field.  Time to install it.  Get our node.
+  JSClass* nodeClass = ::JS_GetClass(cx, origObj);
+  if (!nodeClass) {
+    return JS_FALSE;
+  }
+  
+  if (~nodeClass->flags &
+      (JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS)) {
+    // Looks like whatever |origObj| is it's not our nsIContent.  It might well
+    // be the proto our binding installed, however, so just baul out quietly.
+    // Do NOT throw an exception here.
+    // We could make this stricter by checking the class maybe, but whatever
+    return JS_TRUE;
+  }
+
+  nsCOMPtr<nsIXPConnectWrappedNative> xpcWrapper =
+    do_QueryInterface(static_cast<nsISupports*>(::JS_GetPrivate(cx, origObj)));
+  if (!xpcWrapper) {
+    nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_UNEXPECTED);
+    return JS_FALSE;
+  }
+
+  nsCOMPtr<nsIContent> content = do_QueryWrappedNative(xpcWrapper);
+  if (!content) {
+    nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_UNEXPECTED);
+    return JS_FALSE;
+  }
+
+  // This mirrors code in nsXBLProtoImpl::InstallImplementation
+  nsIDocument* doc = content->GetOwnerDoc();
+  if (!doc) {
+    return JS_TRUE;
+  }
+
+  nsIScriptGlobalObject* global = doc->GetScriptGlobalObject();
+  if (!global) {
+    return JS_TRUE;
+  }
+
+  nsCOMPtr<nsIScriptContext> context = global->GetContext();
+  if (!context) {
+    return JS_TRUE;
+  }
+
+
+  // Now we either resolve or fail
+  *objp = origObj;
+  nsresult rv = field->InstallField(context, origObj,
+                                    protoBinding->DocURI());
+  if (NS_FAILED(rv)) {
+    if (!::JS_IsExceptionPending(cx)) {
+      nsDOMClassInfo::ThrowJSException(cx, rv);
+    }
+
+    return JS_FALSE;
+  }
+
+  return JS_TRUE;
+}
+
 nsXBLJSClass::nsXBLJSClass(const nsAFlatCString& aClassName)
 {
   memset(this, 0, sizeof(nsXBLJSClass));
   next = prev = static_cast<JSCList*>(this);
   name = ToNewCString(aClassName);
+  flags =
+    JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS |
+    JSCLASS_NEW_RESOLVE | JSCLASS_NEW_RESOLVE_GETS_START |
+    // Our one reserved slot holds the relevant nsXBLPrototypeBinding
+    JSCLASS_HAS_RESERVED_SLOTS(1);
   addProperty = delProperty = setProperty = getProperty = ::JS_PropertyStub;
   enumerate = ::JS_EnumerateStub;
-  resolve = ::JS_ResolveStub;
+  resolve = (JSResolveOp)XBLResolve;
   convert = ::JS_ConvertStub;
   finalize = XBLFinalize;
 }
 
 nsrefcnt
 nsXBLJSClass::Destroy()
 {
   NS_ASSERTION(next == prev && prev == static_cast<JSCList*>(this),
@@ -1029,16 +1136,17 @@ nsXBLBinding::WalkRules(nsIStyleRuleProc
 }
 
 // Internal helper methods ////////////////////////////////////////////////////////////////
 
 // static
 nsresult
 nsXBLBinding::DoInitJSClass(JSContext *cx, JSObject *global, JSObject *obj,
                             const nsAFlatCString& aClassName,
+                            nsXBLPrototypeBinding* aProtoBinding,
                             void **aClassObject)
 {
   // First ensure our JS class is initialized.
   jsval val;
   JSObject* proto;
 
   nsCAutoString className(aClassName);
   JSObject* parent_proto = nsnull;  // If we have an "obj" we can set this
@@ -1134,89 +1242,46 @@ nsXBLBinding::DoInitJSClass(JSContext *c
 
       (nsXBLService::gClassTable)->Remove(&key);
 
       c->Drop();
 
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
+    // Keep this proto binding alive while we're alive.  Do this first so that
+    // we can guarantee that in XBLFinalize this will be non-null.
+    nsIXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
+    ::JS_SetPrivate(cx, proto, docInfo);
+    NS_ADDREF(docInfo);
+
+    if (!::JS_SetReservedSlot(cx, proto, 0, PRIVATE_TO_JSVAL(aProtoBinding))) {
+      (nsXBLService::gClassTable)->Remove(&key);
+
+      // |c| will get dropped when |proto| is finalized
+
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
     *aClassObject = (void*)proto;
   }
   else {
     proto = JSVAL_TO_OBJECT(val);
   }
 
   if (obj) {
     // Set the prototype of our object to be the new class.
     if (!::JS_SetPrototype(cx, obj, proto)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   return NS_OK;
 }
 
-
-nsresult
-nsXBLBinding::InitClass(const nsCString& aClassName,
-                        nsIScriptContext* aContext, 
-                        nsIDocument* aDocument, void** aScriptObject,
-                        void** aClassObject)
-{
-  *aClassObject = nsnull;
-  *aScriptObject = nsnull;
-
-  nsresult rv;
-
-  // Obtain the bound element's current script object.
-  JSContext* cx = (JSContext*)aContext->GetNativeContext();
-
-  nsIDocument *ownerDoc = mBoundElement->GetOwnerDoc();
-  nsIScriptGlobalObject *sgo;
-
-  if (!ownerDoc || !(sgo = ownerDoc->GetScriptGlobalObject())) {
-    NS_ERROR("Can't find global object for bound content!");
-
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
-  rv = nsContentUtils::XPConnect()->WrapNative(cx, sgo->GetGlobalJSObject(),
-                                               mBoundElement,
-                                               NS_GET_IID(nsISupports),
-                                               getter_AddRefs(wrapper));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  JSObject* object = nsnull;
-  rv = wrapper->GetJSObject(&object);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  *aScriptObject = object;
-
-  // First ensure our JS class is initialized.
-
-  rv = DoInitJSClass(cx, sgo->GetGlobalJSObject(), object, aClassName,
-                     aClassObject);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Root mBoundElement so that it doesn't lose it's binding
-  nsIDocument* doc = mBoundElement->GetOwnerDoc();
-
-  if (doc) {
-    nsCOMPtr<nsIXPConnectWrappedNative> native_wrapper =
-      do_QueryInterface(wrapper);
-    if (native_wrapper) {
-      doc->AddReference(mBoundElement, native_wrapper);
-    }
-  }
-
-  return NS_OK;
-}
-
 PRBool
 nsXBLBinding::AllowScripts()
 {
   PRBool result;
   mPrototypeBinding->GetAllowScripts(&result);
   if (!result) {
     return result;
   }
@@ -1339,16 +1404,30 @@ nsXBLBinding*
 nsXBLBinding::GetFirstStyleBinding()
 {
   if (mIsStyleBinding)
     return this;
 
   return mNextBinding ? mNextBinding->GetFirstStyleBinding() : nsnull;
 }
 
+PRBool
+nsXBLBinding::ResolveAllFields(JSContext *cx, JSObject *obj) const
+{
+  if (!mPrototypeBinding->ResolveAllFields(cx, obj)) {
+    return PR_FALSE;
+  }
+
+  if (mNextBinding) {
+    return mNextBinding->ResolveAllFields(cx, obj);
+  }
+
+  return PR_TRUE;
+}
+
 void
 nsXBLBinding::MarkForDeath()
 {
   mMarkedForDeath = PR_TRUE;
   ExecuteDetachedHandler();
 }
 
 PRBool
--- a/content/xbl/src/nsXBLBinding.h
+++ b/content/xbl/src/nsXBLBinding.h
@@ -127,16 +127,20 @@ public:
   void ExecuteDetachedHandler();
   void UnhookEventHandlers();
 
   nsIAtom* GetBaseTag(PRInt32* aNameSpaceID);
   nsXBLBinding* GetFirstBindingWithConstructor();
   nsXBLBinding* RootBinding();
   nsXBLBinding* GetFirstStyleBinding();
 
+  // Resolve all the fields for this binding and all ancestor bindings on the
+  // object |obj|.  False return means a JS exception was set.
+  PRBool ResolveAllFields(JSContext *cx, JSObject *obj) const;
+
   // Get the list of insertion points for aParent. The nsInsertionPointList
   // is owned by the binding, you should not delete it.
   nsresult GetInsertionPointsFor(nsIContent* aParent,
                                  nsInsertionPointList** aResult);
 
   nsInsertionPointList* GetExistingInsertionPointsFor(nsIContent* aParent);
 
   nsIContent* GetInsertionPoint(nsIContent* aChild, PRUint32* aIndex);
@@ -150,26 +154,21 @@ public:
   void ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocument);
 
   void WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, void* aData);
 
   already_AddRefed<nsIDOMNodeList> GetAnonymousNodes();
 
   static nsresult DoInitJSClass(JSContext *cx, JSObject *global, JSObject *obj,
                                 const nsAFlatCString& aClassName,
+                                nsXBLPrototypeBinding* aProtoBinding,
                                 void **aClassObject);
 
   PRBool AllowScripts();  // XXX make const
 
-// Internal member functions
-protected:
-  nsresult InitClass(const nsCString& aClassName, nsIScriptContext* aContext,
-                     nsIDocument* aDocument, void** aScriptObject,
-                     void** aClassObject);
-
 // MEMBER VARIABLES
 protected:
   nsAutoRefCnt mRefCnt;
   nsXBLPrototypeBinding* mPrototypeBinding; // Weak, but we're holding a ref to the docinfo
   nsCOMPtr<nsIContent> mContent; // Strong. Our anonymous content stays around with us.
   nsRefPtr<nsXBLBinding> mNextBinding; // Strong. The derived binding owns the base class bindings.
   
   nsIContent* mBoundElement; // [WEAK] We have a reference, but we don't own it.
--- a/content/xbl/src/nsXBLContentSink.cpp
+++ b/content/xbl/src/nsXBLContentSink.cpp
@@ -78,22 +78,23 @@ NS_NewXBLContentSink(nsIXMLContentSink**
 
   return CallQueryInterface(it, aResult);
 }
 
 nsXBLContentSink::nsXBLContentSink()
   : mState(eXBL_InDocument),
     mSecondaryState(eXBL_None),
     mDocInfo(nsnull),
+    mIsChromeOrResource(PR_FALSE),
     mFoundFirstBinding(PR_FALSE),    
-    mIsChromeOrResource(PR_FALSE),
     mBinding(nsnull),
     mHandler(nsnull),
     mImplementation(nsnull),
     mImplMember(nsnull),
+    mImplField(nsnull),
     mProperty(nsnull),
     mMethod(nsnull),
     mField(nsnull)
 {
   mPrettyPrintXML = PR_FALSE;
 }
 
 nsXBLContentSink::~nsXBLContentSink()
@@ -258,16 +259,28 @@ nsXBLContentSink::AddMember(nsXBLProtoIm
   if (mImplMember)
     mImplMember->SetNext(aMember); // Already have a chain. Just append to the end.
   else
     mImplementation->SetMemberList(aMember); // We're the first member in the chain.
 
   mImplMember = aMember; // Adjust our pointer to point to the new last member in the chain.
 }
 
+void
+nsXBLContentSink::AddField(nsXBLProtoImplField* aField)
+{
+  // Add this field to our chain.
+  if (mImplField)
+    mImplField->SetNext(aField); // Already have a chain. Just append to the end.
+  else
+    mImplementation->SetFieldList(aField); // We're the first member in the chain.
+
+  mImplField = aField; // Adjust our pointer to point to the new last field in the chain.
+}
+
 NS_IMETHODIMP 
 nsXBLContentSink::HandleStartElement(const PRUnichar *aName, 
                                      const PRUnichar **aAtts, 
                                      PRUint32 aAttsCount, 
                                      PRInt32 aIndex, 
                                      PRUint32 aLineNumber)
 {
   nsresult rv = nsXMLContentSink::HandleStartElement(aName,aAtts,aAttsCount,aIndex,aLineNumber);
@@ -698,17 +711,18 @@ nsXBLContentSink::ConstructResource(cons
   }
 }
 
 void
 nsXBLContentSink::ConstructImplementation(const PRUnichar **aAtts)
 {
   mImplementation = nsnull;
   mImplMember = nsnull;
-      
+  mImplField = nsnull;
+  
   if (!mBinding)
     return;
 
   const PRUnichar* name = nsnull;
 
   nsCOMPtr<nsIAtom> prefix, localName;
   for (; *aAtts; aAtts += 2) {
     PRInt32 nameSpaceID;
@@ -772,17 +786,17 @@ nsXBLContentSink::ConstructField(const P
   }
 
   if (name) {
     // All of our pointers are now filled in. Construct our field with all of
     // these parameters.
     mField = new nsXBLProtoImplField(name, readonly);
     if (mField) {
       mField->SetLineNumber(aLineNumber);
-      AddMember(mField);
+      AddField(mField);
     }
   }
 }
 
 void
 nsXBLContentSink::ConstructProperty(const PRUnichar **aAtts)
 {
   const PRUnichar* name     = nsnull;
--- a/content/xbl/src/nsXBLContentSink.h
+++ b/content/xbl/src/nsXBLContentSink.h
@@ -152,27 +152,29 @@ protected:
                          const PRUnichar* aSourceText,
                          nsIScriptError *aError,
                          PRBool *_retval);
 
 protected:
   nsresult ReportUnexpectedElement(nsIAtom* aElementName, PRUint32 aLineNumber);
 
   void AddMember(nsXBLProtoImplMember* aMember);
+  void AddField(nsXBLProtoImplField* aField);
   
   XBLPrimaryState mState;
   XBLSecondaryState mSecondaryState;
   nsIXBLDocumentInfo* mDocInfo;
   PRPackedBool mIsChromeOrResource; // For bug #45989
   PRPackedBool mFoundFirstBinding;
 
   nsXBLPrototypeBinding* mBinding;
   nsXBLPrototypeHandler* mHandler; // current handler, owned by its PrototypeBinding
   nsXBLProtoImpl* mImplementation;
   nsXBLProtoImplMember* mImplMember;
+  nsXBLProtoImplField* mImplField;
   nsXBLProtoImplProperty* mProperty;
   nsXBLProtoImplMethod* mMethod;
   nsXBLProtoImplField* mField;
 };
 
 nsresult
 NS_NewXBLContentSink(nsIXMLContentSink** aResult,
                      nsIDocument* aDoc,
--- a/content/xbl/src/nsXBLDocumentInfo.cpp
+++ b/content/xbl/src/nsXBLDocumentInfo.cpp
@@ -404,16 +404,18 @@ nsXBLDocGlobalObject::SetNewArguments(ns
 // nsIScriptObjectPrincipal methods
 //
 
 nsIPrincipal*
 nsXBLDocGlobalObject::GetPrincipal()
 {
   nsresult rv = NS_OK;
   if (!mGlobalObjectOwner) {
+    // XXXbz this should really save the principal when
+    // ClearGlobalObjectOwner() happens.
     return nsnull;
   }
 
   nsCOMPtr<nsIXBLDocumentInfo> docInfo = do_QueryInterface(mGlobalObjectOwner, &rv);
   NS_ENSURE_SUCCESS(rv, nsnull);
 
   nsCOMPtr<nsIDocument> document;
   rv = docInfo->GetDocument(getter_AddRefs(document));
@@ -437,18 +439,29 @@ TraverseProtos(nsHashKey *aKey, void *aD
 {
   nsCycleCollectionTraversalCallback *cb = 
     static_cast<nsCycleCollectionTraversalCallback*>(aClosure);
   nsXBLPrototypeBinding *proto = static_cast<nsXBLPrototypeBinding*>(aData);
   proto->Traverse(*cb);
   return kHashEnumerateNext;
 }
 
+static PRIntn PR_CALLBACK
+UnlinkProtos(nsHashKey *aKey, void *aData, void* aClosure)
+{
+  nsXBLPrototypeBinding *proto = static_cast<nsXBLPrototypeBinding*>(aData);
+  proto->Unlink();
+  return kHashEnumerateNext;
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLDocumentInfo)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLDocumentInfo)
+  if (tmp->mBindingTable) {
+    tmp->mBindingTable->Enumerate(UnlinkProtos, nsnull);
+  }
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mGlobalObject)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLDocumentInfo)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument)
   if (tmp->mBindingTable) {
     tmp->mBindingTable->Enumerate(TraverseProtos, &cb);
   }
--- a/content/xbl/src/nsXBLProtoImpl.cpp
+++ b/content/xbl/src/nsXBLProtoImpl.cpp
@@ -42,25 +42,26 @@
 #include "nsContentUtils.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptGlobalObjectOwner.h"
 #include "nsIScriptContext.h"
 #include "nsIXPConnect.h"
 #include "nsIServiceManager.h"
 #include "nsIXBLDocumentInfo.h"
 #include "nsIDOMNode.h"
+#include "nsXBLPrototypeBinding.h"
 
 nsresult
 nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aBinding, nsIContent* aBoundElement)
 {
   // This function is called to install a concrete implementation on a bound element using
   // this prototype implementation as a guide.  The prototype implementation is compiled lazily,
   // so for the first bound element that needs a concrete implementation, we also build the
   // prototype implementation.
-  if (!mMembers)  // Constructor and destructor also live in mMembers
+  if (!mMembers && !mFields)  // Constructor and destructor also live in mMembers
     return NS_OK; // Nothing to do, so let's not waste time.
 
   // If the way this gets the script context changes, fix
   // nsXBLProtoImplAnonymousMethod::Execute
   nsIDocument* document = aBoundElement->GetOwnerDoc();
   if (!document) return NS_OK;
 
   nsIScriptGlobalObject *global = document->GetScriptGlobalObject();
@@ -210,16 +211,55 @@ nsXBLProtoImpl::Traverse(nsCycleCollecti
 
   nsXBLProtoImplMember *member;
   for (member = mMembers; member; member = member->GetNext()) {
     member->Traverse(cb);
   }
 }
 
 void
+nsXBLProtoImpl::Unlink()
+{
+  if (mClassObject) {
+    DestroyMembers(nsnull);
+  }
+}
+
+nsXBLProtoImplField*
+nsXBLProtoImpl::FindField(const nsString& aFieldName) const
+{
+  for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
+    if (aFieldName.Equals(f->GetName())) {
+      return f;
+    }
+  }
+
+  return nsnull;
+}
+
+PRBool
+nsXBLProtoImpl::ResolveAllFields(JSContext *cx, JSObject *obj) const
+{
+  for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
+    // Using OBJ_LOOKUP_PROPERTY is a pain, since what we have is a
+    // PRUnichar* for the property name.  Let's just use the public API and
+    // all.
+    nsDependentString name(f->GetName());
+    jsval dummy;
+    if (!::JS_LookupUCProperty(cx, obj,
+                               reinterpret_cast<const jschar*>(name.get()),
+                               name.Length(), &dummy)) {
+      return PR_FALSE;
+    }
+  }
+
+  return PR_TRUE;
+}
+
+void
 nsXBLProtoImpl::DestroyMembers(nsXBLProtoImplMember* aBrokenMember)
 {
   NS_ASSERTION(mClassObject, "This should never be called when there is no class object");
   PRBool compiled = PR_TRUE;
   for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
     if (curr == aBrokenMember) {
       compiled = PR_FALSE;
     }
--- a/content/xbl/src/nsXBLProtoImpl.h
+++ b/content/xbl/src/nsXBLProtoImpl.h
@@ -37,67 +37,90 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsXBLProtoImpl_h__
 #define nsXBLProtoImpl_h__
 
 #include "nsMemory.h"
 #include "nsXBLPrototypeHandler.h"
 #include "nsXBLProtoImplMember.h"
-#include "nsXBLPrototypeBinding.h"
+#include "nsXBLProtoImplField.h"
 
 class nsIXPConnectJSObjectHolder;
+class nsXBLPrototypeBinding;
+class nsXBLProtoImplAnonymousMethod;
 
 class nsXBLProtoImpl
 {
 public:
   nsXBLProtoImpl() 
     : mClassObject(nsnull),
       mMembers(nsnull),
+      mFields(nsnull),
       mConstructor(nsnull),
       mDestructor(nsnull)
   { 
     MOZ_COUNT_CTOR(nsXBLProtoImpl); 
   }
   ~nsXBLProtoImpl() 
   { 
     MOZ_COUNT_DTOR(nsXBLProtoImpl);
     // Note: the constructor and destructor are in mMembers, so we'll
     // clean them up automatically.
     for (nsXBLProtoImplMember* curr = mMembers; curr; curr=curr->GetNext())
       curr->Destroy(mClassObject != nsnull);
-    delete mMembers; 
+    delete mMembers;
+    delete mFields;
   }
   
   nsresult InstallImplementation(nsXBLPrototypeBinding* aBinding, nsIContent* aBoundElement);
   nsresult InitTargetObjects(nsXBLPrototypeBinding* aBinding, nsIScriptContext* aContext, 
                              nsIContent* aBoundElement, 
                              nsIXPConnectJSObjectHolder** aScriptObjectHolder,
                              void** aTargetClassObject);
   nsresult CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding);
 
-  void SetMemberList(nsXBLProtoImplMember* aMemberList) { delete mMembers; mMembers = aMemberList; }
+  void SetMemberList(nsXBLProtoImplMember* aMemberList)
+  {
+    delete mMembers;
+    mMembers = aMemberList;
+  }
+
+  void SetFieldList(nsXBLProtoImplField* aFieldList)
+  {
+    delete mFields;
+    mFields = aFieldList;
+  }
 
   void Traverse(nsCycleCollectionTraversalCallback &cb) const;
+  void Unlink();
+
+  nsXBLProtoImplField* FindField(const nsString& aFieldName) const;
+
+  // Resolve all the fields for this implementation on the object |obj| False
+  // return means a JS exception was set.
+  PRBool ResolveAllFields(JSContext *cx, JSObject *obj) const;
 
 protected:
   // Function to call if compilation of a member fails.  When this is called,
   // all members before aBrokenMember are compiled, compilation of
   // aBrokenMember failed, and members after aBrokenMember are uncompiled.
   // This function assumes that aBrokenMember is _not_ compiled.
   void DestroyMembers(nsXBLProtoImplMember* aBrokenMember);
   
 public:
   nsCString mClassName; // The name of the class. 
 
 protected:
   void* mClassObject;   // The class object for the binding. We'll use this to pre-compile properties 
                         // and methods for the binding.
 
   nsXBLProtoImplMember* mMembers; // The members of an implementation are chained in this singly-linked list.
+
+  nsXBLProtoImplField* mFields; // Our fields
   
 public:
   nsXBLProtoImplAnonymousMethod* mConstructor; // Our class constructor.
   nsXBLProtoImplAnonymousMethod* mDestructor;  // Our class destructor.
 };
 
 nsresult
 NS_NewXBLProtoImpl(nsXBLPrototypeBinding* aBinding, 
--- a/content/xbl/src/nsXBLProtoImplField.cpp
+++ b/content/xbl/src/nsXBLProtoImplField.cpp
@@ -41,42 +41,42 @@
 #include "jsapi.h"
 #include "nsIContent.h"
 #include "nsString.h"
 #include "nsUnicharUtils.h"
 #include "nsReadableUtils.h"
 #include "nsXBLProtoImplField.h"
 #include "nsIScriptContext.h"
 #include "nsContentUtils.h"
+#include "nsIURI.h"
 
 nsXBLProtoImplField::nsXBLProtoImplField(const PRUnichar* aName, const PRUnichar* aReadOnly)
-  : nsXBLProtoImplMember(aName),
+  : mNext(nsnull),
     mFieldText(nsnull),
     mFieldTextLength(0),
     mLineNumber(0)
 {
   MOZ_COUNT_CTOR(nsXBLProtoImplField);
+  mName = NS_strdup(aName);  // XXXbz make more sense to use a stringbuffer?
+  
   mJSAttributes = JSPROP_ENUMERATE;
   if (aReadOnly) {
     nsAutoString readOnly; readOnly.Assign(*aReadOnly);
     if (readOnly.LowerCaseEqualsLiteral("true"))
       mJSAttributes |= JSPROP_READONLY;
   }
 }
 
 nsXBLProtoImplField::~nsXBLProtoImplField()
 {
   MOZ_COUNT_DTOR(nsXBLProtoImplField);
   if (mFieldText)
     nsMemory::Free(mFieldText);
-}
-
-void
-nsXBLProtoImplField::Destroy(PRBool aIsCompiled)
-{
+  NS_Free(mName);
+  delete mNext;
 }
 
 void 
 nsXBLProtoImplField::AppendFieldText(const nsAString& aText)
 {
   if (mFieldText) {
     nsDependentString fieldTextStr(mFieldText, mFieldTextLength);
     nsAutoString newFieldText = fieldTextStr + aText;
@@ -87,72 +87,61 @@ nsXBLProtoImplField::AppendFieldText(con
   }
   else {
     mFieldText = ToNewUnicode(aText);
     mFieldTextLength = aText.Length();
   }
 }
 
 nsresult
-nsXBLProtoImplField::InstallMember(nsIScriptContext* aContext,
-                                   nsIContent* aBoundElement, 
-                                   void* aScriptObject,
-                                   void* aTargetClassObject,
-                                   const nsCString& aClassStr)
+nsXBLProtoImplField::InstallField(nsIScriptContext* aContext,
+                                  JSObject* aBoundNode,
+                                  nsIURI* aBindingDocURI) const
 {
-  if (mFieldTextLength == 0)
-    return NS_OK; // nothing to do.
+  NS_PRECONDITION(aBoundNode,
+                  "uh-oh, bound node should NOT be null or bad things will "
+                  "happen");
 
-  JSContext* cx = (JSContext*) aContext->GetNativeContext();
-  NS_ASSERTION(aScriptObject, "uh-oh, script Object should NOT be null or bad things will happen");
-  if (!aScriptObject)
-    return NS_ERROR_FAILURE;
-
-  nsCAutoString bindingURI(aClassStr);
-  PRInt32 hash = bindingURI.RFindChar('#');
-  if (hash != kNotFound)
-    bindingURI.Truncate(hash);
-  
-  // compile the literal string 
-  jsval result = JSVAL_NULL;
+  jsval result = JSVAL_VOID;
   
   // EvaluateStringWithValue and JS_DefineUCProperty can both trigger GC, so
   // protect |result| here.
   nsresult rv;
   nsAutoGCRoot root(&result, &rv);
   if (NS_FAILED(rv))
     return rv;
-  PRBool undefined;
-  // XXX Need a URI here!
-  nsCOMPtr<nsIScriptContext> context = aContext;
-  rv = context->EvaluateStringWithValue(nsDependentString(mFieldText,
-                                                          mFieldTextLength), 
-                                        aScriptObject,
-                                        nsnull, bindingURI.get(),
-                                        mLineNumber, nsnull,
-                                        (void*) &result, &undefined);
-  if (NS_FAILED(rv))
-    return rv;
 
-  if (!undefined) {
-    // Define the evaluated result as a JS property
-    nsDependentString name(mName);
-    JSAutoRequest ar(cx);
-    if (!::JS_DefineUCProperty(cx, static_cast<JSObject *>(aScriptObject),
-                               reinterpret_cast<const jschar*>(mName), 
-                               name.Length(), result, nsnull, nsnull, mJSAttributes))
-      return NS_ERROR_OUT_OF_MEMORY;
+  if (mFieldTextLength != 0) {
+    nsCAutoString uriSpec;
+    aBindingDocURI->GetSpec(uriSpec);
+  
+    // compile the literal string
+    // XXX Could we produce a better principal here?  Should be able
+    // to, really!
+    PRBool undefined;
+    nsCOMPtr<nsIScriptContext> context = aContext;
+    rv = context->EvaluateStringWithValue(nsDependentString(mFieldText,
+                                                            mFieldTextLength), 
+                                          aBoundNode,
+                                          nsnull, uriSpec.get(),
+                                          mLineNumber, nsnull,
+                                          (void*) &result, &undefined);
+    if (NS_FAILED(rv))
+      return rv;
+
+    if (undefined) {
+      result = JSVAL_VOID;
+    }
+  }
+
+  // Define the evaluated result as a JS property
+  nsDependentString name(mName);
+  JSContext* cx = (JSContext*) aContext->GetNativeContext();
+  JSAutoRequest ar(cx);
+  if (!::JS_DefineUCProperty(cx, aBoundNode,
+                             reinterpret_cast<const jschar*>(mName), 
+                             name.Length(), result, nsnull, nsnull,
+                             mJSAttributes)) {
+    return NS_ERROR_OUT_OF_MEMORY;
   }
   
   return NS_OK;
 }
-
-nsresult 
-nsXBLProtoImplField::CompileMember(nsIScriptContext* aContext, const nsCString& aClassStr,
-                                   void* aClassObject)
-{
-  return NS_OK;
-}
-
-void
-nsXBLProtoImplField::Traverse(nsCycleCollectionTraversalCallback &cb) const
-{
-}
--- a/content/xbl/src/nsXBLProtoImplField.h
+++ b/content/xbl/src/nsXBLProtoImplField.h
@@ -41,39 +41,40 @@
 
 #include "nsIAtom.h"
 #include "nsString.h"
 #include "jsapi.h"
 #include "nsIContent.h"
 #include "nsString.h"
 #include "nsXBLProtoImplMember.h"
 
-class nsXBLProtoImplField: public nsXBLProtoImplMember
+class nsIURI;
+
+class nsXBLProtoImplField
 {
 public:
   nsXBLProtoImplField(const PRUnichar* aName, const PRUnichar* aReadOnly);
-  virtual ~nsXBLProtoImplField();
-  virtual void Destroy(PRBool aIsCompiled);
+  ~nsXBLProtoImplField();
 
   void AppendFieldText(const nsAString& aText);
   void SetLineNumber(PRUint32 aLineNumber) {
     mLineNumber = aLineNumber;
   }
   
-  virtual nsresult InstallMember(nsIScriptContext* aContext,
-                                 nsIContent* aBoundElement, 
-                                 void* aScriptObject,
-                                 void* aTargetClassObject,
-                                 const nsCString& aClassStr);
-  virtual nsresult CompileMember(nsIScriptContext* aContext,
-                                 const nsCString& aClassStr,
-                                 void* aClassObject);
+  nsXBLProtoImplField* GetNext() const { return mNext; }
+  void SetNext(nsXBLProtoImplField* aNext) { mNext = aNext; }
 
-  virtual void Traverse(nsCycleCollectionTraversalCallback &cb) const;
+  nsresult InstallField(nsIScriptContext* aContext,
+                        JSObject* aBoundNode, nsIURI*
+                        aBindingDocURI) const;
+
+  const PRUnichar* GetName() const { return mName; }
 
 protected:
+  nsXBLProtoImplField* mNext;
+  PRUnichar* mName;
   PRUnichar* mFieldText;
   PRUint32 mFieldTextLength;
   PRUint32 mLineNumber;
   uintN mJSAttributes;
 };
 
 #endif // nsXBLProtoImplField_h__
--- a/content/xbl/src/nsXBLPrototypeBinding.cpp
+++ b/content/xbl/src/nsXBLPrototypeBinding.cpp
@@ -361,16 +361,35 @@ nsXBLPrototypeBinding::Traverse(nsCycleC
     cb.NoteXPCOMChild(mResources->mLoader);
   if (mInsertionPointTable)
     mInsertionPointTable->Enumerate(TraverseInsertionPoint, &cb);
   if (mInterfaceTable)
     mInterfaceTable->Enumerate(TraverseBinding, &cb);
 }
 
 void
+nsXBLPrototypeBinding::Unlink()
+{
+  mBinding = nsnull;
+  if (mImplementation)
+    mImplementation->Unlink();
+  if (mResources)
+    NS_IF_RELEASE(mResources->mLoader);
+
+  // I'm not sure whether it would be safer to just nuke the tables or to
+  // traverse them with unlinking functions...  or whether we even need to
+  // unlink them.  I think we need to at least clean up mInsertionPointTable
+  // becase it can hold strong refs to nodes in the binding document.
+  delete mInsertionPointTable;
+  mInsertionPointTable = nsnull;
+  delete mInterfaceTable;
+  mInterfaceTable = nsnull;
+}
+
+void
 nsXBLPrototypeBinding::Initialize()
 {
   nsIContent* content = GetImmediateChild(nsGkAtoms::content);
   if (content) {
     // Make sure to construct the attribute table first, since constructing the
     // insertion point table removes some of the subtrees, which makes them
     // unreachable by walking our DOM.
     ConstructAttributeTable(content);
@@ -817,17 +836,17 @@ nsXBLPrototypeBinding::InitClass(const n
                                  JSObject * aScriptObject,
                                  void ** aClassObject)
 {
   NS_ENSURE_ARG_POINTER(aClassObject); 
 
   *aClassObject = nsnull;
 
   return nsXBLBinding::DoInitJSClass(aContext, aGlobal, aScriptObject,
-                                     aClassName, aClassObject);
+                                     aClassName, this, aClassObject);
 }
 
 nsIContent*
 nsXBLPrototypeBinding::LocateInstance(nsIContent* aBoundElement,
                                       nsIContent* aTemplRoot,
                                       nsIContent* aCopyRoot, 
                                       nsIContent* aTemplChild)
 {
--- a/content/xbl/src/nsXBLPrototypeBinding.h
+++ b/content/xbl/src/nsXBLPrototypeBinding.h
@@ -45,25 +45,26 @@
 #include "nsXBLProtoImplMethod.h"
 #include "nsICSSStyleSheet.h"
 #include "nsICSSLoaderObserver.h"
 #include "nsWeakReference.h"
 #include "nsIContent.h"
 #include "nsHashtable.h"
 #include "nsIXBLDocumentInfo.h"
 #include "nsCOMArray.h"
+#include "nsXBLProtoImpl.h"
 
 class nsIAtom;
 class nsIDocument;
 class nsIScriptContext;
 class nsISupportsArray;
 class nsSupportsHashtable;
 class nsIXBLService;
 class nsFixedSizeAllocator;
-class nsXBLProtoImpl;
+class nsXBLProtoImplField;
 class nsXBLBinding;
 
 // *********************************************************************/
 // The XBLPrototypeBinding class
 
 // Instances of this class are owned by the nsXBLDocumentInfo object returned
 // by XBLDocumentInfo().  Consumers who want to refcount things should refcount
 // that.
@@ -89,16 +90,32 @@ public:
   nsXBLPrototypeHandler* GetPrototypeHandlers() { return mPrototypeHandler; }
   void SetPrototypeHandlers(nsXBLPrototypeHandler* aHandler) { mPrototypeHandler = aHandler; }
 
   nsXBLProtoImplAnonymousMethod* GetConstructor();
   nsresult SetConstructor(nsXBLProtoImplAnonymousMethod* aConstructor);
   nsXBLProtoImplAnonymousMethod* GetDestructor();
   nsresult SetDestructor(nsXBLProtoImplAnonymousMethod* aDestructor);
 
+  nsXBLProtoImplField* FindField(const nsString& aFieldName) const
+  {
+    return mImplementation ? mImplementation->FindField(aFieldName) : nsnull;
+  }
+
+  // Resolve all the fields for this binding on the object |obj|.
+  // False return means a JS exception was set.
+  PRBool ResolveAllFields(JSContext* cx, JSObject* obj) const
+  {
+    return !mImplementation || mImplementation->ResolveAllFields(cx, obj);
+  }
+
+  const nsCString& ClassName() const {
+    return mImplementation ? mImplementation->mClassName : EmptyCString();
+  }
+
   nsresult InitClass(const nsCString& aClassName, JSContext * aContext,
                      JSObject * aGlobal, JSObject * aScriptObject,
                      void ** aClassObject);
 
   nsresult ConstructInterfaceTable(const nsAString& aImpls);
   
   void SetImplementation(nsXBLProtoImpl* aImpl) { mImplementation = aImpl; }
   nsresult InstallImplementation(nsIContent* aBoundElement);
@@ -167,16 +184,17 @@ public:
   // binding.  It may well throw errors (eg on out-of-memory).  Do not confuse
   // this with the Initialize() method, which must be called after the
   // binding's handlers, properties, etc are all set.
   nsresult Init(const nsACString& aRef,
                 nsIXBLDocumentInfo* aInfo,
                 nsIContent* aElement);
 
   void Traverse(nsCycleCollectionTraversalCallback &cb) const;
+  void Unlink();
 
 // Static members
   static PRUint32 gRefCnt;
  
   static nsFixedSizeAllocator* kAttrPool;
 
 // Internal member functions.
 // XXXbz GetImmediateChild needs to be public to be called by SetAttrs,
--- a/content/xbl/src/nsXBLResourceLoader.cpp
+++ b/content/xbl/src/nsXBLResourceLoader.cpp
@@ -55,17 +55,19 @@
 #include "nsGkAtoms.h"
 #include "nsFrameManager.h"
 #include "nsStyleContext.h"
 #include "nsXBLPrototypeBinding.h"
 #include "nsCSSRuleProcessor.h"
 #include "nsContentUtils.h"
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLResourceLoader)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsXBLResourceLoader)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLResourceLoader)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mBoundElements)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLResourceLoader)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mBoundElements)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXBLResourceLoader)
   NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
--- a/content/xbl/test/Makefile.in
+++ b/content/xbl/test/Makefile.in
@@ -45,12 +45,13 @@ relativesrcdir  = content/xbl/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =	\
 		test_bug296375.xul \
 		test_bug366770.html \
 		test_bug371724.xhtml \
+		test_bug372769.xhtml \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
--- a/dom/src/base/nsDOMClassInfo.cpp
+++ b/dom/src/base/nsDOMClassInfo.cpp
@@ -472,17 +472,18 @@ static const char kDOMStringBundleURL[] 
   ~nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY)
 
 // We need to let JavaScript QI elements to interfaces that are not in
 // the classinfo since XBL can be used to dynamically implement new
 // unknown interfaces on elements, accessibility relies on this being
 // possible.
 
 #define ELEMENT_SCRIPTABLE_FLAGS                                              \
-  (NODE_SCRIPTABLE_FLAGS & ~nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY)
+  ((NODE_SCRIPTABLE_FLAGS & ~nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY) |   \
+   nsIXPCScriptable::WANT_ENUMERATE)
 
 #define EXTERNAL_OBJ_SCRIPTABLE_FLAGS                                         \
   (ELEMENT_SCRIPTABLE_FLAGS & ~nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY | \
    nsIXPCScriptable::WANT_GETPROPERTY |                                       \
    nsIXPCScriptable::WANT_SETPROPERTY |                                       \
    nsIXPCScriptable::WANT_CALL)
 
 #define DOCUMENT_SCRIPTABLE_FLAGS                                             \
@@ -6935,16 +6936,42 @@ nsElementSH::PostCreate(nsIXPConnectWrap
   
   if (binding) {
     binding->ExecuteAttachedHandler();
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsElementSH::Enumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+                       JSObject *obj, PRBool *_retval)
+{
+  // Make sure to not call the superclass here!
+  nsCOMPtr<nsIContent> content(do_QueryWrappedNative(wrapper));
+  NS_ENSURE_TRUE(content, NS_ERROR_UNEXPECTED);
+
+  nsIDocument* doc = content->GetOwnerDoc();
+  if (!doc) {
+    // Nothing else to do here
+    return NS_OK;
+  }
+
+  nsXBLBinding* binding = doc->BindingManager()->GetBinding(content);
+  if (!binding) {
+    // Nothing else to do here
+    return NS_OK;
+  }
+
+  *_retval = binding->ResolveAllFields(cx, obj);
+  
+  return NS_OK;
+}
+  
+
 // Generic array scriptable helper.
 
 NS_IMETHODIMP
 nsGenericArraySH::NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
                              JSObject *obj, jsval id, PRUint32 flags,
                              JSObject **objp, PRBool *_retval)
 {
   PRBool is_number = PR_FALSE;
--- a/dom/src/base/nsDOMClassInfo.h
+++ b/dom/src/base/nsDOMClassInfo.h
@@ -558,16 +558,18 @@ protected:
 
   virtual ~nsElementSH()
   {
   }
 
 public:
   NS_IMETHOD PostCreate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
                         JSObject *obj);
+  NS_IMETHOD Enumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+                       JSObject *obj, PRBool *_retval);
 
   static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
   {
     return new nsElementSH(aData);
   }
 };