Bug 990290 - Store class objects in a weak map off the XBL global. r=bz
authorBobby Holley <bobbyholley@gmail.com>
Fri, 11 Apr 2014 07:59:47 -0700
changeset 190132 e212a0c2a3cb64f082cb729b9dcb67fdbbe504f7
parent 190131 b7e93a0b9b47e33dc426a88e77c0e2caa19ffe89
child 190133 0fe126aa32df021e05ee07feef7d6b0b2f5a509c
push idunknown
push userunknown
push dateunknown
reviewersbz
bugs990290
milestone31.0a1
Bug 990290 - Store class objects in a weak map off the XBL global. r=bz Note that we simultaneously rip out all of the crazy lifetime management for the dynamic JSClasses here (it would be nice to do that in a separate patch, but it's all kind of tied up together). With this patch, we simply have one dynamic JSClass per class object, which is deleted in the finalizer. In the next patch, we remove dynamic JSClasses entirely.
dom/xbl/nsXBLBinding.cpp
dom/xbl/nsXBLBinding.h
dom/xbl/nsXBLProtoImpl.cpp
dom/xbl/nsXBLService.cpp
dom/xbl/nsXBLService.h
dom/xbl/test/file_bug821850.xhtml
--- a/dom/xbl/nsXBLBinding.cpp
+++ b/dom/xbl/nsXBLBinding.cpp
@@ -72,35 +72,30 @@ using namespace mozilla::dom;
 static void
 XBLFinalize(JSFreeOp *fop, JSObject *obj)
 {
   nsXBLDocumentInfo* docInfo =
     static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(obj));
   nsContentUtils::DeferredFinalize(docInfo);
 
   nsXBLJSClass* c = nsXBLJSClass::fromJSClass(::JS_GetClass(obj));
-  c->Drop();
+  delete c;
 }
 
 static bool
 XBLEnumerate(JSContext *cx, JS::Handle<JSObject*> obj)
 {
   nsXBLPrototypeBinding* protoBinding =
     static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate());
   MOZ_ASSERT(protoBinding);
 
   return protoBinding->ResolveAllFields(cx, obj);
 }
 
-uint64_t nsXBLJSClass::sIdCount = 0;
-
-nsXBLJSClass::nsXBLJSClass(const nsAFlatCString& aClassName,
-                           const nsCString& aKey)
-  : mRefCnt(0)
-  , mKey(aKey)
+nsXBLJSClass::nsXBLJSClass(const nsAFlatCString& aClassName)
 {
   memset(static_cast<JSClass*>(this), 0, sizeof(JSClass));
   name = ToNewCString(aClassName);
   flags =
     JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS |
     JSCLASS_NEW_RESOLVE |
     // Our one reserved slot holds the relevant nsXBLPrototypeBinding
     JSCLASS_HAS_RESERVED_SLOTS(1);
@@ -116,29 +111,19 @@ nsXBLJSClass::nsXBLJSClass(const nsAFlat
 bool
 nsXBLJSClass::IsXBLJSClass(const JSClass* aClass)
 {
   return aClass->finalize == XBLFinalize;
 }
 
 nsXBLJSClass::~nsXBLJSClass()
 {
-  if (nsXBLService::gClassTable) {
-    nsXBLService::gClassTable->Remove(mKey);
-    mKey.Truncate();
-  }
   nsMemory::Free((void*) name);
 }
 
-nsXBLJSClass*
-nsXBLService::getClass(const nsCString& k)
-{
-  return nsXBLService::gClassTable->Get(k);
-}
-
 // Implementation /////////////////////////////////////////////////////////////////
 
 // Constructors/Destructors
 nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding)
   : mMarkedForDeath(false)
   , mUsingXBLScope(false)
   , mPrototypeBinding(aBinding)
 {
@@ -904,139 +889,200 @@ nsXBLBinding::WalkRules(nsIStyleRuleProc
 
   nsIStyleRuleProcessor *rules = mPrototypeBinding->GetRuleProcessor();
   if (rules)
     (*aFunc)(rules, aData);
 }
 
 // Internal helper methods ////////////////////////////////////////////////////////////////
 
+// Get or create a WeakMap object on a given XBL-hosting global.
+//
+// The scheme is as follows. XBL-hosting globals (either privileged content
+// Windows or XBL scopes) get two lazily-defined WeakMap properties. Each
+// WeakMap is keyed by the grand-proto - i.e. the original prototype of the
+// content before it was bound, and the prototype of the class object that we
+// splice in. The values in the WeakMap are simple dictionary-style objects,
+// mapping from XBL class names to class objects.
+static JSObject*
+GetOrCreateClassObjectMap(JSContext *cx, JS::Handle<JSObject*> scope, const char *mapName)
+{
+  AssertSameCompartment(cx, scope);
+  MOZ_ASSERT(JS_IsGlobalObject(scope));
+  MOZ_ASSERT(scope == xpc::GetXBLScopeOrGlobal(cx, scope));
+
+  // First, see if the map is already defined.
+  JS::Rooted<JSPropertyDescriptor> desc(cx);
+  if (!JS_GetOwnPropertyDescriptor(cx, scope, mapName, 0, &desc)) {
+    return nullptr;
+  }
+  if (desc.object() && desc.value().isObject() &&
+      JS::IsWeakMapObject(&desc.value().toObject())) {
+    return &desc.value().toObject();
+  }
+
+  // It's not there. Create and define it.
+  JS::Rooted<JSObject*> map(cx, JS::NewWeakMapObject(cx));
+  if (!map || !JS_DefineProperty(cx, scope, mapName,
+                                 JS::ObjectValue(*map),
+                                 JS_PropertyStub, JS_StrictPropertyStub,
+                                 JSPROP_PERMANENT | JSPROP_READONLY))
+  {
+    return nullptr;
+  }
+  return map;
+}
+
+static JSObject*
+GetOrCreateMapEntryForPrototype(JSContext *cx, JS::Handle<JSObject*> proto)
+{
+  AssertSameCompartment(cx, proto);
+  // We want to hang our class objects off the XBL scope. But since we also
+  // hoist anonymous content into the XBL scope, this creates the potential for
+  // tricky collisions, since we can simultaneously  have a bound in-content
+  // node with grand-proto HTMLDivElement and a bound anonymous node whose
+  // grand-proto is the XBL scope's cross-compartment wrapper to HTMLDivElement.
+  // Since we have to wrap the WeakMap keys into its scope, this distinction
+  // would be lost if we don't do something about it.
+  //
+  // So we define two maps - one class objects that live in content (prototyped
+  // to content prototypes), and the other for class objects that live in the
+  // XBL scope (prototyped to cross-compartment-wrapped content prototypes).
+  const char* name = xpc::IsInXBLScope(proto) ? "__ContentClassObjectMap__"
+                                              : "__XBLClassObjectMap__";
+
+  // Now, enter the XBL scope, since that's where we need to operate, and wrap
+  // the proto accordingly.
+  JS::Rooted<JSObject*> scope(cx, xpc::GetXBLScopeOrGlobal(cx, proto));
+  JS::Rooted<JSObject*> wrappedProto(cx, proto);
+  JSAutoCompartment ac(cx, scope);
+  if (!JS_WrapObject(cx, &wrappedProto)) {
+    return nullptr;
+  }
+
+  // Grab the appropriate WeakMap.
+  JS::Rooted<JSObject*> map(cx, GetOrCreateClassObjectMap(cx, scope, name));
+  if (!map) {
+    return nullptr;
+  }
+
+  // See if we already have a map entry for that prototype.
+  JS::Rooted<JS::Value> val(cx);
+  if (!JS::GetWeakMapEntry(cx, map, wrappedProto, &val)) {
+    return nullptr;
+  }
+  if (val.isObject()) {
+    return &val.toObject();
+  }
+
+  // We don't have an entry. Create one and stick it in the map.
+  JS::Rooted<JSObject*> entry(cx);
+  entry = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scope);
+  if (!entry) {
+    return nullptr;
+  }
+  JS::Rooted<JS::Value> entryVal(cx, JS::ObjectValue(*entry));
+  if (!JS::SetWeakMapEntry(cx, map, wrappedProto, entryVal)) {
+    return nullptr;
+  }
+  return entry;
+}
+
 // static
 nsresult
 nsXBLBinding::DoInitJSClass(JSContext *cx,
                             JS::Handle<JSObject*> obj,
                             const nsAFlatCString& aClassName,
                             nsXBLPrototypeBinding* aProtoBinding,
                             JS::MutableHandle<JSObject*> aClassObject,
                             bool* aNew)
 {
   MOZ_ASSERT(obj);
 
-  // First ensure our JS class is initialized.
-  nsAutoCString className(aClassName);
-  nsAutoCString xblKey(aClassName);
-
   // Note that, now that NAC reflectors are created in the XBL scope, the
   // reflector is not necessarily same-compartment with the document. So we'll
   // end up creating a separate instance of the oddly-named XBL class object
   // and defining it as a property on the XBL scope's global. This works fine,
   // but we need to make sure never to assume that the the reflector and
   // prototype are same-compartment with the bound document.
-  JS::RootedObject global(cx, js::GetGlobalForObjectCrossCompartment(obj));
-  JSAutoCompartment ac(cx, global);
+  JS::Rooted<JSObject*> global(cx, js::GetGlobalForObjectCrossCompartment(obj));
+  JS::Rooted<JSObject*> xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, global));
 
-  JS::Rooted<JSObject*> parent_proto(cx, nullptr);
-  nsXBLJSClass* c = nullptr;
-
-  // Retrieve the current prototype of obj.
+  JS::Rooted<JSObject*> parent_proto(cx);
   if (!JS_GetPrototype(cx, obj, &parent_proto)) {
     return NS_ERROR_FAILURE;
   }
+
+  // Get the map entry for the parent prototype. In the one-off case that the
+  // parent prototype is null, we somewhat hackily just use the WeakMap itself
+  // as a property holder.
+  JS::Rooted<JSObject*> holder(cx);
   if (parent_proto) {
-    // We need to create a unique classname based on aClassName and
-    // id.  Append a space (an invalid URI character) to ensure that
-    // we don't have accidental collisions with the case when parent_proto is
-    // null and aClassName ends in some bizarre numbers (yeah, it's unlikely).
-    JS::Rooted<jsid> parent_proto_id(cx);
-    if (!::JS_GetObjectId(cx, parent_proto, &parent_proto_id)) {
-      // Probably OOM
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    // One space, maybe "0x", at most 16 chars (on a 64-bit system) of long,
-    // and a null-terminator (which PR_snprintf ensures is there even if the
-    // string representation of what we're printing does not fit in the buffer
-    // provided).
-    char buf[20];
-    if (sizeof(jsid) == 4) {
-      PR_snprintf(buf, sizeof(buf), " %lx", parent_proto_id.get());
-    } else {
-      MOZ_ASSERT(sizeof(jsid) == 8);
-      PR_snprintf(buf, sizeof(buf), " %llx", parent_proto_id.get());
-    }
-    xblKey.Append(buf);
-
-    c = nsXBLService::getClass(xblKey);
-    if (c) {
-      className.Assign(c->name);
-    } else {
-      char buf[20];
-      PR_snprintf(buf, sizeof(buf), " %llx", nsXBLJSClass::NewId());
-      className.Append(buf);
-    }
+    holder = GetOrCreateMapEntryForPrototype(cx, parent_proto);
+  } else {
+    JSAutoCompartment innerAC(cx, xblScope);
+    holder = GetOrCreateClassObjectMap(cx, xblScope, "__ContentClassObjectMap__");
   }
+  js::AssertSameCompartment(holder, xblScope);
+  JSAutoCompartment ac(cx, holder);
 
+  // Look up the class on the property holder. The only properties on the
+  // holder should be class objects. If we don't find the class object, we need
+  // to create and define it.
   JS::Rooted<JSObject*> proto(cx);
-  JS::Rooted<JS::Value> val(cx);
-
-  if (!::JS_LookupPropertyWithFlags(cx, global, className.get(), 0, &val))
+  JS::Rooted<JSPropertyDescriptor> desc(cx);
+  if (!JS_GetOwnPropertyDescriptor(cx, holder, aClassName.get(), 0, &desc)) {
     return NS_ERROR_OUT_OF_MEMORY;
-
-  if (val.isObject() && nsXBLJSClass::IsXBLJSClass(JS_GetClass(&val.toObject()))) {
-    *aNew = false;
-    proto = &val.toObject();
+  }
+  *aNew = !desc.object();
+  if (desc.object()) {
+    proto = &desc.value().toObject();
+    MOZ_ASSERT(nsXBLJSClass::IsXBLJSClass(JS_GetClass(js::UncheckedUnwrap(proto))));
   } else {
-    // We need to initialize the class.
-    *aNew = true;
 
-    if (!c) {
-      c = nsXBLService::getClass(xblKey);
-    }
-    if (!c) {
-      // We need to create a struct for this class.
-      c = new nsXBLJSClass(className, xblKey);
-
-      // Add c to our table.
-      nsXBLService::gClassTable->Put(xblKey, c);
-    }
-
-    // The prototype holds a strong reference to its class struct.
-    c->Hold();
-
+    // We need to create the prototype. First, enter the compartment where it's
+    // going to live, and create it.
+    JSAutoCompartment ac2(cx, global);
+    nsXBLJSClass* c = new nsXBLJSClass(aClassName);
     proto = JS_NewObjectWithGivenProto(cx, c, parent_proto, global);
-    if (!proto || !JS_DefineProperty(cx, global, c->name, JS::ObjectValue(*proto),
-                                     JS_PropertyStub, JS_StrictPropertyStub, 0))
-    {
-      nsXBLService::gClassTable->Remove(xblKey);
-      c->Drop();
+    if (!proto) {
+      delete c;
       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.
     // Note that we can't just store aProtoBinding in the private and
     // addref/release the nsXBLDocumentInfo through it, because cycle
     // collection doesn't seem to work right if the private is not an
     // nsISupports.
     nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
     ::JS_SetPrivate(proto, docInfo);
     NS_ADDREF(docInfo);
-
-    ::JS_SetReservedSlot(proto, 0, PRIVATE_TO_JSVAL(aProtoBinding));
-  }
+    JS_SetReservedSlot(proto, 0, PRIVATE_TO_JSVAL(aProtoBinding));
 
-  aClassObject.set(proto);
-
-  if (obj) {
-    // Set the prototype of our object to be the new class.
-    if (!::JS_SetPrototype(cx, obj, proto)) {
-      return NS_ERROR_FAILURE;
+    // Next, enter the compartment of the property holder, wrap the proto, and
+    // stick it on.
+    JSAutoCompartment ac3(cx, holder);
+    if (!JS_WrapObject(cx, &proto) ||
+        !JS_DefineProperty(cx, holder, aClassName.get(), JS::ObjectValue(*proto),
+                           JS_PropertyStub, JS_StrictPropertyStub,
+                           JSPROP_READONLY | JSPROP_PERMANENT))
+    {
+      return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
+  // Whew. We have the proto. Wrap it back into the compartment of |obj|,
+  // splice it in, and return it.
+  JSAutoCompartment ac4(cx, obj);
+  if (!JS_WrapObject(cx, &proto) || !JS_SetPrototype(cx, obj, proto)) {
+    return NS_ERROR_FAILURE;
+  }
+  aClassObject.set(proto);
   return NS_OK;
 }
 
 bool
 nsXBLBinding::AllowScripts()
 {
   return mBoundElement && mPrototypeBinding->GetAllowScripts();
 }
@@ -1114,30 +1160,32 @@ nsXBLBinding::LookupMember(JSContext* aC
 }
 
 bool
 nsXBLBinding::LookupMemberInternal(JSContext* aCx, nsString& aName,
                                    JS::Handle<jsid> aNameAsId,
                                    JS::MutableHandle<JSPropertyDescriptor> aDesc,
                                    JS::Handle<JSObject*> aXBLScope)
 {
-  // First, see if we have a JSClass. If we don't, it means that this binding
-  // doesn't have a class object, and thus doesn't have any members. Skip it.
-  if (!mJSClass) {
+  // First, see if we have an implementation. If we don't, it means that this
+  // binding doesn't have a class object, and thus doesn't have any members.
+  // Skip it.
+  if (!PrototypeBinding()->HasImplementation()) {
     if (!mNextBinding) {
       return true;
     }
     return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId,
                                               aDesc, aXBLScope);
   }
 
   // Find our class object. It's in a protected scope and permanent just in case,
   // so should be there no matter what.
   JS::Rooted<JS::Value> classObject(aCx);
-  if (!JS_GetProperty(aCx, aXBLScope, mJSClass->name, &classObject)) {
+  if (!JS_GetProperty(aCx, aXBLScope, PrototypeBinding()->ClassName().get(),
+                      &classObject)) {
     return false;
   }
 
   // The bound element may have been adoped by a document and have a different
   // wrapper (and different xbl scope) than when the binding was applied, in
   // this case getting the class object will fail. Behave as if the class
   // object did not exist.
   if (classObject.isUndefined()) {
--- a/dom/xbl/nsXBLBinding.h
+++ b/dom/xbl/nsXBLBinding.h
@@ -63,21 +63,16 @@ public:
   nsXBLBinding* GetBindingWithContent();
 
   nsXBLBinding* GetBaseBinding() const { return mNextBinding; }
   void SetBaseBinding(nsXBLBinding *aBinding);
 
   nsIContent* GetBoundElement() { return mBoundElement; }
   void SetBoundElement(nsIContent *aElement);
 
-  void SetJSClass(nsXBLJSClass *aClass) {
-    MOZ_ASSERT(!mJSClass && aClass);
-    mJSClass = aClass;
-  }
-
   /*
    * Does a lookup for a method or attribute provided by one of the bindings'
    * prototype implementation. If found, |desc| will be set up appropriately,
    * and wrapped into cx->compartment.
    *
    * May only be called when XBL code is being run in a separate scope, because
    * otherwise we don't have untainted data with which to do a proper lookup.
    */
@@ -165,19 +160,16 @@ public:
 protected:
 
   bool mMarkedForDeath;
   bool mUsingXBLScope;
 
   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.
-  nsRefPtr<nsXBLJSClass> mJSClass; // Strong. The class object also holds a strong reference,
-                                   // which might be somewhat redundant, but be safe to avoid
-                                   // worrying about edge cases.
 
   nsIContent* mBoundElement; // [WEAK] We have a reference, but we don't own it.
 
   // The <xbl:children> elements that we found in our <xbl:content> when we
   // processed this binding. The default insertion point has no includes
   // attribute and all other insertion points must have at least one includes
   // attribute. These points must be up-to-date with respect to their parent's
   // children, even if their parent has another binding attached to it,
--- a/dom/xbl/nsXBLProtoImpl.cpp
+++ b/dom/xbl/nsXBLProtoImpl.cpp
@@ -57,19 +57,16 @@ nsXBLProtoImpl::InstallImplementation(ns
   bool targetObjectIsNew = false;
   nsresult rv = InitTargetObjects(aPrototypeBinding,
                                   aBinding->GetBoundElement(),
                                   &targetClassObject,
                                   &targetObjectIsNew);
   NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects
   MOZ_ASSERT(targetClassObject);
 
-  // Stash a strong reference to the JSClass in the binding.
-  aBinding->SetJSClass(nsXBLJSClass::fromJSClass(JS_GetClass(targetClassObject)));
-
   // If the prototype already existed, we don't need to install anything. return early.
   if (!targetObjectIsNew)
     return NS_OK;
 
   // We want to define the canonical set of members in a safe place. If we're
   // using a separate XBL scope, we want to define them there first (so that
   // they'll be available for Xray lookups, among other things), and then copy
   // the properties to the content-side prototype as needed. We don't need to
@@ -178,16 +175,17 @@ nsXBLProtoImpl::InitTargetObjects(nsXBLP
   bool defineOnGlobal = dom::XULElementBinding::ConstructorEnabled(cx, global);
   dom::XULElementBinding::GetConstructorObject(cx, global, defineOnGlobal);
 
   rv = nsContentUtils::WrapNative(cx, aBoundElement, &v,
                                   /* aAllowWrapping = */ false);
   NS_ENSURE_SUCCESS(rv, rv);
 
   JS::Rooted<JSObject*> value(cx, &v.toObject());
+  JSAutoCompartment ac2(cx, value);
 
   // All of the above code was just obtaining the bound element's script object and its immediate
   // concrete base class.  We need to alter the object so that our concrete class is interposed
   // between the object and its base class.  We become the new base class of the object, and the
   // object's old base class becomes the new class' base class.
   rv = aBinding->InitClass(mClassName, cx, value, aTargetClassObject, aTargetIsNew);
   if (NS_FAILED(rv)) {
     return rv;
--- a/dom/xbl/nsXBLService.cpp
+++ b/dom/xbl/nsXBLService.cpp
@@ -363,42 +363,34 @@ nsXBLStreamListener::HandleEvent(nsIDOME
   return rv;
 }
 
 // Implementation /////////////////////////////////////////////////////////////////
 
 // Static member variable initialization
 bool nsXBLService::gAllowDataURIs = false;
 
-nsXBLService::ClassTable* nsXBLService::gClassTable = nullptr;
-
 // Implement our nsISupports methods
 NS_IMPL_ISUPPORTS1(nsXBLService, nsISupportsWeakReference)
 
 void
 nsXBLService::Init()
 {
   gInstance = new nsXBLService();
   NS_ADDREF(gInstance);
 }
 
 // Constructors/Destructors
 nsXBLService::nsXBLService(void)
 {
-  gClassTable = new ClassTable();
-
   Preferences::AddBoolVarCache(&gAllowDataURIs, "layout.debug.enable_data_xbl");
 }
 
 nsXBLService::~nsXBLService(void)
 {
-  // At this point, the only hash table entries should be for referenced
-  // XBL class structs held by unfinalized JS binding objects.
-  delete gClassTable;
-  gClassTable = nullptr;
 }
 
 // static
 bool
 nsXBLService::IsChromeOrResourceURI(nsIURI* aURI)
 {
   bool isChrome = false;
   bool isResource = false;
--- a/dom/xbl/nsXBLService.h
+++ b/dom/xbl/nsXBLService.h
@@ -109,63 +109,40 @@ protected:
   nsresult GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
                       bool aPeekFlag, nsIPrincipal* aOriginPrincipal,
                       bool* aIsReady, nsXBLBinding** aResult,
                       nsTArray<nsIURI*>& aDontExtendURIs);
 
 // MEMBER VARIABLES
 public:
   static bool gDisableChromeCache;
-
-  typedef nsDataHashtable<nsCStringHashKey, nsXBLJSClass*> ClassTable;
-  static ClassTable* gClassTable;           // A table of nsXBLJSClass objects.
-
   static bool     gAllowDataURIs;            // Whether we should allow data
                                              // urls in -moz-binding. Needed for
                                              // testing.
-
-  // Look up the class by key in gClassTable.
-  static nsXBLJSClass *getClass(const nsCString &key);
 };
 
 class nsXBLJSClass : public JSClass
 {
-private:
-  nsrefcnt mRefCnt;
-  nsCString mKey;
-  static uint64_t sIdCount;
-
 public:
-  nsXBLJSClass(const nsAFlatCString& aClassName, const nsCString& aKey);
+  nsXBLJSClass(const nsAFlatCString& aClassName);
   ~nsXBLJSClass();
 
-  static uint64_t NewId() { return ++sIdCount; }
-
-  nsCString& Key() { return mKey; }
-  void SetKey(const nsCString& aKey) { mKey = aKey; }
-
-  nsrefcnt Hold() { return ++mRefCnt; }
-  nsrefcnt Drop() { nsrefcnt curr = --mRefCnt; if (!curr) delete this; return curr; }
-  nsrefcnt AddRef() { return Hold(); }
-  nsrefcnt Release() { return Drop(); }
-
   static bool IsXBLJSClass(const JSClass* aClass);
 
   // Downcast from a pointer to const JSClass to a pointer to non-const
   // nsXBLJSClass.
   //
   // The const_cast is safe because nsXBLJSClass instances are never actually
   // const. It's necessary because we pass pointers to nsXBLJSClass to code
   // which uses pointers to const JSClass, and returns them back to us that
   // way, and we need to convert them back to pointers to non-const
   // nsXBLJSClass so that we can modify the reference count and add them to
   // the gClassLRUList list.
   static nsXBLJSClass*
   fromJSClass(const JSClass* c)
   {
     MOZ_ASSERT(IsXBLJSClass(c));
     nsXBLJSClass* x = const_cast<nsXBLJSClass*>(static_cast<const nsXBLJSClass*>(c));
-    MOZ_ASSERT(nsXBLService::getClass(x->mKey) == x);
     return x;
   }
 };
 
 #endif
--- a/dom/xbl/test/file_bug821850.xhtml
+++ b/dom/xbl/test/file_bug821850.xhtml
@@ -193,28 +193,16 @@ https://bugzilla.mozilla.org/show_bug.cg
         checkAllowed(function() { obj[propName] = function() {} }, desc + ": assign (again)");
     }
 
     // Make sure content can do whatever it wants with the prototype.
     checkMayTamper(proto, 'method', "XBL Proto Method");
     checkMayTamper(proto, 'prop', "XBL Proto Prop");
     checkMayTamper(proto, 'primitiveField', "XBL Field Accessor");
 
-    // As above, check that content can do what it wants with the prototype's
-    // property on the global.
-    var protoName, count = 0;
-    for (var k of Object.getOwnPropertyNames(window)) {
-      if (!/testBinding/.exec(k))
-        continue;
-      count++;
-      protoName = k;
-    }
-    is(count, 1, "Should be exactly one prototype object");
-    checkMayTamper(window, protoName, "XBL prototype prop on window");
-
     // Tamper with the derived object. This doesn't affect the XBL scope thanks
     // to Xrays.
     bound.method = function() { return "heh"; };
     Object.defineProperty(bound, 'method', {value: function() { return "hah" }});
     Object.defineProperty(bound, 'prop', {value: "redefined"});
     bound.primitiveField = 321;
 
     // We need a chrome window to create trusted events. This isn't really doable