Bug 877762 - GC: Post-barrier cycle collector participants - 8 Convert JS::Object to use Heap<T> (XBL) r=bz r=sfink
authorJon Coppeard <jcoppeard@mozilla.com>
Tue, 18 Jun 2013 11:00:38 +0100
changeset 135459 047741328e5a5c47cd6d8be22e2eb9325a836812
parent 135458 f6d53e1cde69f62305640e59440aabc4a643bc17
child 135460 34f7cce42095e24fdd6c066d7fe6219dfc5e500f
push id24841
push userryanvm@gmail.com
push dateTue, 18 Jun 2013 23:04:53 +0000
treeherdermozilla-central@d2a7cfa34154 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, sfink
bugs877762
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 877762 - GC: Post-barrier cycle collector participants - 8 Convert JS::Object to use Heap<T> (XBL) r=bz r=sfink
content/xbl/src/nsXBLMaybeCompiled.h
content/xbl/src/nsXBLProtoImplMethod.cpp
content/xbl/src/nsXBLProtoImplMethod.h
content/xbl/src/nsXBLProtoImplProperty.cpp
content/xbl/src/nsXBLProtoImplProperty.h
new file mode 100644
--- /dev/null
+++ b/content/xbl/src/nsXBLMaybeCompiled.h
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLMaybeCompiled_h__
+#define nsXBLMaybeCompiled_h__
+
+#include "js/RootingAPI.h"
+
+/*
+ * A union containing either a pointer representing uncompiled source or a
+ * JSObject* representing the compiled result.  The class is templated on the
+ * source object type.
+ *
+ * The purpose of abstracting this as a separate class is to allow it to be
+ * wrapped in a JS::Heap<T> to correctly handle post-barriering of the JSObject
+ * pointer, when present.
+ */
+template <class UncompiledT>
+class nsXBLMaybeCompiled
+{
+public:
+  nsXBLMaybeCompiled() : mUncompiled(BIT_UNCOMPILED) {}
+
+  nsXBLMaybeCompiled(UncompiledT* uncompiled)
+    : mUncompiled(reinterpret_cast<uintptr_t>(uncompiled) | BIT_UNCOMPILED) {}
+
+  nsXBLMaybeCompiled(JSObject* compiled) : mCompiled(compiled) {}
+
+  bool IsCompiled() const
+  {
+    return !(mUncompiled & BIT_UNCOMPILED);
+  }
+
+  UncompiledT* GetUncompiled() const
+  {
+    MOZ_ASSERT(!IsCompiled(), "Attempt to get compiled function as uncompiled");
+    uintptr_t unmasked = mUncompiled & ~BIT_UNCOMPILED;
+    return reinterpret_cast<UncompiledT*>(unmasked);
+  }
+
+  JSObject* GetJSFunction() const
+  {
+    MOZ_ASSERT(IsCompiled(), "Attempt to get uncompiled function as compiled");
+    return mCompiled;
+  }
+
+private:
+  JSObject*& UnsafeGetJSFunction()
+  {
+    MOZ_ASSERT(IsCompiled(), "Attempt to get uncompiled function as compiled");
+    return mCompiled;
+  }
+
+  enum { BIT_UNCOMPILED = 1 << 0 };
+
+  union
+  {
+    // An pointer that represents the function before being compiled, with
+    // BIT_UNCOMPILED set.
+    uintptr_t mUncompiled;
+
+    // The JS object for the compiled result.
+    JSObject* mCompiled;
+  };
+
+  friend class js::RootMethods<nsXBLMaybeCompiled<UncompiledT> >;
+};
+
+/* Add support for JS::Heap<nsXBLMaybeCompiled>. */
+namespace js {
+
+template <class UncompiledT>
+struct RootMethods<nsXBLMaybeCompiled<UncompiledT> > : public RootMethods<JSObject *>
+{
+  typedef struct RootMethods<JSObject *> Base;
+
+  static nsXBLMaybeCompiled<UncompiledT> initial() { return nsXBLMaybeCompiled<UncompiledT>(); }
+
+  static bool poisoned(nsXBLMaybeCompiled<UncompiledT> function)
+  {
+    return function.IsCompiled() && Base::poisoned(function.GetJSFunction());
+  }
+
+  static bool needsPostBarrier(nsXBLMaybeCompiled<UncompiledT> function)
+  {
+    return function.IsCompiled() && Base::needsPostBarrier(function.GetJSFunction());
+  }
+
+#ifdef JSGC_GENERATIONAL
+  static void postBarrier(nsXBLMaybeCompiled<UncompiledT>* functionp)
+  {
+    Base::postBarrier(&functionp->UnsafeGetJSFunction());
+  }
+
+  static void relocate(nsXBLMaybeCompiled<UncompiledT>* functionp)
+  {
+    Base::relocate(&functionp->UnsafeGetJSFunction());
+  }
+#endif
+};
+
+template <class UncompiledT>
+class HeapBase<nsXBLMaybeCompiled<UncompiledT> >
+{
+  const JS::Heap<nsXBLMaybeCompiled<UncompiledT> >& wrapper() const {
+    return *static_cast<const JS::Heap<nsXBLMaybeCompiled<UncompiledT> >*>(this);
+  }
+
+  JS::Heap<nsXBLMaybeCompiled<UncompiledT> >& wrapper() {
+    return *static_cast<JS::Heap<nsXBLMaybeCompiled<UncompiledT> >*>(this);
+  }
+
+  const nsXBLMaybeCompiled<UncompiledT>* extract() const {
+    return wrapper().address();
+  }
+
+  nsXBLMaybeCompiled<UncompiledT>* extract() {
+    return wrapper().unsafeGet();
+  }
+
+public:
+  bool IsCompiled() const { return extract()->IsCompiled(); }
+  UncompiledT* GetUncompiled() const { return extract()->GetUncompiled(); }
+  JSObject* GetJSFunction() const { return extract()->GetJSFunction(); }
+
+  void SetUncompiled(UncompiledT* source) {
+    wrapper().set(nsXBLMaybeCompiled<UncompiledT>(source));
+  }
+
+  void SetJSFunction(JSObject* function) {
+    wrapper().set(nsXBLMaybeCompiled<UncompiledT>(function));
+  }
+
+  JS::Heap<JSObject*>& AsHeapObject()
+  {
+    MOZ_ASSERT(extract()->IsCompiled());
+    return *reinterpret_cast<JS::Heap<JSObject*>*>(this);
+  }
+};
+
+} /* namespace js */
+
+#endif // nsXBLMaybeCompiled_h__
--- a/content/xbl/src/nsXBLProtoImplMethod.cpp
+++ b/content/xbl/src/nsXBLProtoImplMethod.cpp
@@ -19,18 +19,18 @@
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "xpcpublic.h"
 #include "nsXBLPrototypeBinding.h"
 
 using namespace mozilla;
 
 nsXBLProtoImplMethod::nsXBLProtoImplMethod(const PRUnichar* aName) :
-  nsXBLProtoImplMember(aName), 
-  mUncompiledMethod(BIT_UNCOMPILED)
+  nsXBLProtoImplMember(aName),
+  mMethod()
 {
   MOZ_COUNT_CTOR(nsXBLProtoImplMethod);
 }
 
 nsXBLProtoImplMethod::~nsXBLProtoImplMethod()
 {
   MOZ_COUNT_DTOR(nsXBLProtoImplMethod);
 
@@ -103,22 +103,23 @@ nsXBLProtoImplMethod::InstallMember(JSCo
                   "Should not be installing an uncompiled method");
   MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx));
 
   JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject));
   JS::Rooted<JSObject*> scopeObject(aCx, xpc::GetXBLScope(aCx, globalObject));
   NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
 
   // now we want to reevaluate our property using aContext and the script object for this window...
-  if (mJSMethodObject) {
+  JS::Rooted<JSObject*> jsMethodObject(aCx, GetCompiledMethod());
+  if (jsMethodObject) {
     nsDependentString name(mName);
 
     // First, make the function in the compartment of the scope object.
     JSAutoCompartment ac(aCx, scopeObject);
-    JS::Rooted<JSObject*> method(aCx, ::JS_CloneFunctionObject(aCx, mJSMethodObject, scopeObject));
+    JS::Rooted<JSObject*> method(aCx, ::JS_CloneFunctionObject(aCx, jsMethodObject, scopeObject));
     if (!method) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     // Then, enter the content compartment, wrap the method pointer, and define
     // the wrapped version on the class object.
     JSAutoCompartment ac2(aCx, aTargetClassObject);
     if (!JS_WrapObject(aCx, method.address()))
@@ -144,27 +145,27 @@ nsXBLProtoImplMethod::CompileMember(nsIS
   NS_PRECONDITION(aClassObject,
                   "Must have class object to compile");
 
   nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod();
 
   // No parameters or body was supplied, so don't install method.
   if (!uncompiledMethod) {
     // Early return after which we consider ourselves compiled.
-    mJSMethodObject = nullptr;
+    SetCompiledMethod(nullptr);
 
     return NS_OK;
   }
 
   // Don't install method if no name was supplied.
   if (!mName) {
     delete uncompiledMethod;
 
     // Early return after which we consider ourselves compiled.
-    mJSMethodObject = nullptr;
+    SetCompiledMethod(nullptr);
 
     return NS_OK;
   }
 
   // We have a method.
   // Allocate an array for our arguments.
   int32_t paramCount = uncompiledMethod->GetParameterCount();
   char** args = nullptr;
@@ -214,73 +215,69 @@ nsXBLProtoImplMethod::CompileMember(nsIS
   // Destroy our uncompiled method and delete our arg list.
   delete uncompiledMethod;
   delete [] args;
   if (NS_FAILED(rv)) {
     SetUncompiledMethod(nullptr);
     return rv;
   }
 
-  mJSMethodObject = methodObject;
+  SetCompiledMethod(methodObject);
 
   return NS_OK;
 }
 
 void
 nsXBLProtoImplMethod::Trace(const TraceCallbacks& aCallbacks, void *aClosure)
 {
-  if (IsCompiled() && mJSMethodObject) {
-    aCallbacks.Trace(&mJSMethodObject, "mJSMethodObject", aClosure);
+  if (IsCompiled() && GetCompiledMethod()) {
+    aCallbacks.Trace(&mMethod.AsHeapObject(), "mMethod", aClosure);
   }
 }
 
 nsresult
 nsXBLProtoImplMethod::Read(nsIScriptContext* aContext,
                            nsIObjectInputStream* aStream)
 {
   JS::Rooted<JSObject*> methodObject(aContext->GetNativeContext());
   nsresult rv = XBL_DeserializeFunction(aContext, aStream, &methodObject);
   if (NS_FAILED(rv)) {
     SetUncompiledMethod(nullptr);
     return rv;
   }
 
-  mJSMethodObject = methodObject;
-
-#ifdef DEBUG
-  mIsCompiled = true;
-#endif
+  SetCompiledMethod(methodObject);
 
   return NS_OK;
 }
 
 nsresult
 nsXBLProtoImplMethod::Write(nsIScriptContext* aContext,
                             nsIObjectOutputStream* aStream)
 {
-  if (mJSMethodObject) {
+  MOZ_ASSERT(IsCompiled());
+  if (GetCompiledMethod()) {
     nsresult rv = aStream->Write8(XBLBinding_Serialize_Method);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = aStream->WriteWStringZ(mName);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    return XBL_SerializeFunction(aContext, aStream,
-                                 JS::Handle<JSObject*>::fromMarkedLocation(&mJSMethodObject));
+    return XBL_SerializeFunction(aContext, aStream, mMethod.AsHeapObject());
   }
 
   return NS_OK;
 }
 
 nsresult
 nsXBLProtoImplAnonymousMethod::Execute(nsIContent* aBoundElement)
 {
   NS_PRECONDITION(IsCompiled(), "Can't execute uncompiled method");
   
-  if (!mJSMethodObject) {
+  if (!GetCompiledMethod()) {
     // Nothing to do here
     return NS_OK;
   }
 
   // Get the script context the same way
   // nsXBLProtoImpl::InstallImplementation does.
   nsIDocument* document = aBoundElement->OwnerDoc();
 
@@ -322,17 +319,17 @@ nsXBLProtoImplAnonymousMethod::Execute(n
 
   JSAutoCompartment ac(cx, scopeObject);
   if (!JS_WrapObject(cx, thisObject.address()))
       return NS_ERROR_OUT_OF_MEMORY;
 
   // Clone the function object, using thisObject as the parent so "this" is in
   // the scope chain of the resulting function (for backwards compat to the
   // days when this was an event handler).
-  JS::Rooted<JSObject*> method(cx, ::JS_CloneFunctionObject(cx, mJSMethodObject, thisObject));
+  JS::Rooted<JSObject*> method(cx, ::JS_CloneFunctionObject(cx, GetCompiledMethod(), thisObject));
   if (!method)
     return NS_ERROR_OUT_OF_MEMORY;
 
   // Now call the method
 
   // Check whether it's OK to call the method.
   rv = nsContentUtils::GetSecurityManager()->CheckFunctionAccess(cx, method,
                                                                  thisObject);
@@ -359,19 +356,19 @@ nsXBLProtoImplAnonymousMethod::Execute(n
   return NS_OK;
 }
 
 nsresult
 nsXBLProtoImplAnonymousMethod::Write(nsIScriptContext* aContext,
                                      nsIObjectOutputStream* aStream,
                                      XBLBindingSerializeDetails aType)
 {
-  if (mJSMethodObject) {
+  MOZ_ASSERT(IsCompiled());
+  if (GetCompiledMethod()) {
     nsresult rv = aStream->Write8(aType);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = XBL_SerializeFunction(aContext, aStream,
-                               JS::Handle<JSObject*>::fromMarkedLocation(&mJSMethodObject));
+    rv = XBL_SerializeFunction(aContext, aStream, mMethod.AsHeapObject());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
--- a/content/xbl/src/nsXBLProtoImplMethod.h
+++ b/content/xbl/src/nsXBLProtoImplMethod.h
@@ -6,16 +6,17 @@
 #ifndef nsXBLProtoImplMethod_h__
 #define nsXBLProtoImplMethod_h__
 
 #include "mozilla/Attributes.h"
 #include "nsIAtom.h"
 #include "nsString.h"
 #include "jsapi.h"
 #include "nsString.h"
+#include "nsXBLMaybeCompiled.h"
 #include "nsXBLProtoImplMember.h"
 #include "nsXBLSerialize.h"
 
 class nsIContent;
 
 struct nsXBLParameter {
   nsXBLParameter* mNext;
   char* mName;
@@ -97,39 +98,41 @@ public:
 
   virtual void Trace(const TraceCallbacks& aCallbacks, void *aClosure) MOZ_OVERRIDE;
 
   nsresult Read(nsIScriptContext* aContext, nsIObjectInputStream* aStream);
   virtual nsresult Write(nsIScriptContext* aContext, nsIObjectOutputStream* aStream) MOZ_OVERRIDE;
 
   bool IsCompiled() const
   {
-    return !(mUncompiledMethod & BIT_UNCOMPILED);
+    return mMethod.IsCompiled();
   }
+
   void SetUncompiledMethod(nsXBLUncompiledMethod* aUncompiledMethod)
   {
-    mUncompiledMethod = uintptr_t(aUncompiledMethod) | BIT_UNCOMPILED;
+    mMethod.SetUncompiled(aUncompiledMethod);
   }
+
   nsXBLUncompiledMethod* GetUncompiledMethod() const
   {
-    uintptr_t unmasked = mUncompiledMethod & ~BIT_UNCOMPILED;
-    return reinterpret_cast<nsXBLUncompiledMethod*>(unmasked);
+    return mMethod.GetUncompiled();
   }
 
 protected:
-  enum { BIT_UNCOMPILED = 1 << 0 };
+  void SetCompiledMethod(JSObject* aCompiledMethod)
+  {
+    mMethod.SetJSFunction(aCompiledMethod);
+  }
 
-  union {
-    uintptr_t mUncompiledMethod; // An object that represents the method before being compiled.
-    JSObject* mJSMethodObject;    // The JS object for the method (after compilation)
-  };
+  JSObject* GetCompiledMethod() const
+  {
+    return mMethod.GetJSFunction();
+  }
 
-#ifdef DEBUG
-  bool mIsCompiled;
-#endif
+  JS::Heap<nsXBLMaybeCompiled<nsXBLUncompiledMethod> > mMethod;
 };
 
 class nsXBLProtoImplAnonymousMethod : public nsXBLProtoImplMethod {
 public:
   nsXBLProtoImplAnonymousMethod() :
     nsXBLProtoImplMethod(EmptyString().get())
   {}
   
--- a/content/xbl/src/nsXBLProtoImplProperty.cpp
+++ b/content/xbl/src/nsXBLProtoImplProperty.cpp
@@ -22,18 +22,16 @@
 using namespace mozilla;
 
 nsXBLProtoImplProperty::nsXBLProtoImplProperty(const PRUnichar* aName,
                                                const PRUnichar* aGetter, 
                                                const PRUnichar* aSetter,
                                                const PRUnichar* aReadOnly,
                                                uint32_t aLineNumber) :
   nsXBLProtoImplMember(aName), 
-  mGetterText(nullptr),
-  mSetterText(nullptr),
   mJSAttributes(JSPROP_ENUMERATE)
 #ifdef DEBUG
   , mIsCompiled(false)
 #endif
 {
   MOZ_COUNT_CTOR(nsXBLProtoImplProperty);
 
   if (aReadOnly) {
@@ -50,125 +48,112 @@ nsXBLProtoImplProperty::nsXBLProtoImplPr
     AppendSetterText(nsDependentString(aSetter));
     SetSetterLineNumber(aLineNumber);
   }
 }
 
 nsXBLProtoImplProperty::nsXBLProtoImplProperty(const PRUnichar* aName,
                                                const bool aIsReadOnly)
   : nsXBLProtoImplMember(aName),
-    mGetterText(nullptr),
-    mSetterText(nullptr),
     mJSAttributes(JSPROP_ENUMERATE)
 #ifdef DEBUG
   , mIsCompiled(false)
 #endif
 {
   MOZ_COUNT_CTOR(nsXBLProtoImplProperty);
 
   if (aIsReadOnly)
     mJSAttributes |= JSPROP_READONLY;
 }
 
 nsXBLProtoImplProperty::~nsXBLProtoImplProperty()
 {
   MOZ_COUNT_DTOR(nsXBLProtoImplProperty);
 
-  if (!(mJSAttributes & JSPROP_GETTER)) {
-    delete mGetterText;
+  if (!mGetter.IsCompiled()) {
+    delete mGetter.GetUncompiled();
   }
 
-  if (!(mJSAttributes & JSPROP_SETTER)) {
-    delete mSetterText;
+  if (!mSetter.IsCompiled()) {
+    delete mSetter.GetUncompiled();
+  }
+}
+
+void nsXBLProtoImplProperty::EnsureUncompiledText(PropertyOp& aPropertyOp)
+{
+  if (!aPropertyOp.GetUncompiled()) {
+    nsXBLTextWithLineNumber* text = new nsXBLTextWithLineNumber();
+    aPropertyOp.SetUncompiled(text);
   }
 }
 
 void 
 nsXBLProtoImplProperty::AppendGetterText(const nsAString& aText)
 {
   NS_PRECONDITION(!mIsCompiled,
                   "Must not be compiled when accessing getter text");
-  if (!mGetterText) {
-    mGetterText = new nsXBLTextWithLineNumber();
-    if (!mGetterText)
-      return;
-  }
-
-  mGetterText->AppendText(aText);
+  EnsureUncompiledText(mGetter);
+  mGetter.GetUncompiled()->AppendText(aText);
 }
 
 void 
 nsXBLProtoImplProperty::AppendSetterText(const nsAString& aText)
 {
   NS_PRECONDITION(!mIsCompiled,
                   "Must not be compiled when accessing setter text");
-  if (!mSetterText) {
-    mSetterText = new nsXBLTextWithLineNumber();
-    if (!mSetterText)
-      return;
-  }
-
-  mSetterText->AppendText(aText);
+  EnsureUncompiledText(mSetter);
+  mSetter.GetUncompiled()->AppendText(aText);
 }
 
 void
 nsXBLProtoImplProperty::SetGetterLineNumber(uint32_t aLineNumber)
 {
   NS_PRECONDITION(!mIsCompiled,
                   "Must not be compiled when accessing getter text");
-  if (!mGetterText) {
-    mGetterText = new nsXBLTextWithLineNumber();
-    if (!mGetterText)
-      return;
-  }
-
-  mGetterText->SetLineNumber(aLineNumber);
+  EnsureUncompiledText(mGetter);
+  mGetter.GetUncompiled()->SetLineNumber(aLineNumber);
 }
 
 void
 nsXBLProtoImplProperty::SetSetterLineNumber(uint32_t aLineNumber)
 {
   NS_PRECONDITION(!mIsCompiled,
                   "Must not be compiled when accessing setter text");
-  if (!mSetterText) {
-    mSetterText = new nsXBLTextWithLineNumber();
-    if (!mSetterText)
-      return;
-  }
-
-  mSetterText->SetLineNumber(aLineNumber);
+  EnsureUncompiledText(mSetter);
+  mSetter.GetUncompiled()->SetLineNumber(aLineNumber);
 }
 
 const char* gPropertyArgs[] = { "val" };
 
 nsresult
 nsXBLProtoImplProperty::InstallMember(JSContext *aCx,
                                       JS::Handle<JSObject*> aTargetClassObject)
 {
   NS_PRECONDITION(mIsCompiled,
                   "Should not be installing an uncompiled property");
+  MOZ_ASSERT(mGetter.IsCompiled() && mSetter.IsCompiled());
   MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx));
   JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject));
   JS::Rooted<JSObject*> scopeObject(aCx, xpc::GetXBLScope(aCx, globalObject));
   NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
 
   // now we want to reevaluate our property using aContext and the script object for this window...
-  if (mJSGetterObject || mJSSetterObject) {
+  if (mGetter.GetJSFunction() || mSetter.GetJSFunction()) {
     // First, enter the compartment of the scope object and clone the functions.
     JSAutoCompartment ac(aCx, scopeObject);
 
     JS::Rooted<JSObject*> getter(aCx, nullptr);
-    if (mJSGetterObject) {
-      if (!(getter = ::JS_CloneFunctionObject(aCx, mJSGetterObject, scopeObject)))
+    if (mGetter.GetJSFunction()) {
+      if (!(getter = ::JS_CloneFunctionObject(aCx, mGetter.GetJSFunction(), scopeObject)))
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
     JS::Rooted<JSObject*> setter(aCx, nullptr);
-    if (mJSSetterObject) {
-      if (!(setter = ::JS_CloneFunctionObject(aCx, mJSSetterObject, scopeObject)))
+    if (mSetter.GetJSFunction()) {
+      if (!(setter = ::JS_CloneFunctionObject(aCx, mSetter.GetJSFunction(), scopeObject)))
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
     // Now, enter the content compartment, wrap the getter/setter, and define
     // them on the class object.
     JSAutoCompartment ac2(aCx, aTargetClassObject);
     nsDependentString name(mName);
     if (!JS_WrapObject(aCx, getter.address()) ||
@@ -187,161 +172,164 @@ nsXBLProtoImplProperty::InstallMember(JS
 nsresult
 nsXBLProtoImplProperty::CompileMember(nsIScriptContext* aContext, const nsCString& aClassStr,
                                       JS::Handle<JSObject*> aClassObject)
 {
   NS_PRECONDITION(!mIsCompiled,
                   "Trying to compile an already-compiled property");
   NS_PRECONDITION(aClassObject,
                   "Must have class object to compile");
+  MOZ_ASSERT(!mGetter.IsCompiled() && !mSetter.IsCompiled());
 
   if (!mName)
     return NS_ERROR_FAILURE; // Without a valid name, we can't install the member.
 
   // We have a property.
   nsresult rv = NS_OK;
 
   nsAutoCString functionUri;
-  if (mGetterText || mSetterText) {
+  if (mGetter.GetUncompiled() || mSetter.GetUncompiled()) {
     functionUri = aClassStr;
     int32_t hash = functionUri.RFindChar('#');
     if (hash != kNotFound) {
       functionUri.Truncate(hash);
     }
   }
 
   bool deletedGetter = false;
-  if (mGetterText && mGetterText->GetText()) {
-    nsDependentString getter(mGetterText->GetText());
+  nsXBLTextWithLineNumber *getterText = mGetter.GetUncompiled();
+  if (getterText && getterText->GetText()) {
+    nsDependentString getter(getterText->GetText());
     if (!getter.IsEmpty()) {
       AutoPushJSContext cx(aContext->GetNativeContext());
       JSAutoCompartment ac(cx, aClassObject);
       JS::CompileOptions options(cx);
-      options.setFileAndLine(functionUri.get(), mGetterText->GetLineNumber())
+      options.setFileAndLine(functionUri.get(), getterText->GetLineNumber())
              .setVersion(JSVERSION_LATEST);
       nsCString name = NS_LITERAL_CSTRING("get_") + NS_ConvertUTF16toUTF8(mName);
       JS::RootedObject rootedNull(cx, nullptr); // See bug 781070.
       JS::RootedObject getterObject(cx);
       rv = nsJSUtils::CompileFunction(cx, rootedNull, options, name, 0, nullptr,
                                       getter, getterObject.address());
 
-      // Make sure we free mGetterText here before setting mJSGetterObject, since
-      // that'll overwrite mGetterText
-      delete mGetterText;
+      delete getterText;
       deletedGetter = true;
-      mJSGetterObject = getterObject;
+
+      mGetter.SetJSFunction(getterObject);
     
-      if (mJSGetterObject && NS_SUCCEEDED(rv)) {
+      if (mGetter.GetJSFunction() && NS_SUCCEEDED(rv)) {
         mJSAttributes |= JSPROP_GETTER | JSPROP_SHARED;
       }
       if (NS_FAILED(rv)) {
-        mJSGetterObject = nullptr;
+        mGetter.SetJSFunction(nullptr);
         mJSAttributes &= ~JSPROP_GETTER;
         /*chaining to return failure*/
       }
     }
   } // if getter is not empty
 
   if (!deletedGetter) {  // Empty getter
-    delete mGetterText;
-    mJSGetterObject = nullptr;
+    delete getterText;
+    mGetter.SetJSFunction(nullptr);
   }
   
   if (NS_FAILED(rv)) {
     // We failed to compile our getter.  So either we've set it to null, or
     // it's still set to the text object.  In either case, it's safe to return
     // the error here, since then we'll be cleaned up as uncompiled and that
     // will be ok.  Going on and compiling the setter and _then_ returning an
     // error, on the other hand, will try to clean up a compiled setter as
     // uncompiled and crash.
     return rv;
   }
 
   bool deletedSetter = false;
-  if (mSetterText && mSetterText->GetText()) {
-    nsDependentString setter(mSetterText->GetText());
+  nsXBLTextWithLineNumber *setterText = mSetter.GetUncompiled();
+  if (setterText && setterText->GetText()) {
+    nsDependentString setter(setterText->GetText());
     if (!setter.IsEmpty()) {
       AutoPushJSContext cx(aContext->GetNativeContext());
       JSAutoCompartment ac(cx, aClassObject);
       JS::CompileOptions options(cx);
-      options.setFileAndLine(functionUri.get(), mSetterText->GetLineNumber())
+      options.setFileAndLine(functionUri.get(), setterText->GetLineNumber())
              .setVersion(JSVERSION_LATEST);
       nsCString name = NS_LITERAL_CSTRING("set_") + NS_ConvertUTF16toUTF8(mName);
       JS::RootedObject rootedNull(cx, nullptr); // See bug 781070.
       JS::RootedObject setterObject(cx);
       rv = nsJSUtils::CompileFunction(cx, rootedNull, options, name, 1,
                                       gPropertyArgs, setter, setterObject.address());
 
-      // Make sure we free mSetterText here before setting mJSGetterObject, since
-      // that'll overwrite mSetterText
-      delete mSetterText;
+      delete setterText;
       deletedSetter = true;
-      mJSSetterObject = setterObject;
+      mSetter.SetJSFunction(setterObject);
 
-      if (mJSSetterObject && NS_SUCCEEDED(rv)) {
+      if (mSetter.GetJSFunction() && NS_SUCCEEDED(rv)) {
         mJSAttributes |= JSPROP_SETTER | JSPROP_SHARED;
       }
       if (NS_FAILED(rv)) {
-        mJSSetterObject = nullptr;
+        mSetter.SetJSFunction(nullptr);
         mJSAttributes &= ~JSPROP_SETTER;
         /*chaining to return failure*/
       }
     }
   } // if setter wasn't empty....
 
   if (!deletedSetter) {  // Empty setter
-    delete mSetterText;
-    mJSSetterObject = nullptr;
+    delete setterText;
+    mSetter.SetJSFunction(nullptr);
   }
 
 #ifdef DEBUG
   mIsCompiled = NS_SUCCEEDED(rv);
 #endif
 
   return rv;
 }
 
 void
 nsXBLProtoImplProperty::Trace(const TraceCallbacks& aCallbacks, void *aClosure)
 {
   if (mJSAttributes & JSPROP_GETTER) {
-    aCallbacks.Trace(&mJSGetterObject, "mJSGetterObject", aClosure);
+    aCallbacks.Trace(&mGetter.AsHeapObject(), "mGetter", aClosure);
   }
 
   if (mJSAttributes & JSPROP_SETTER) {
-    aCallbacks.Trace(&mJSSetterObject, "mJSSetterObject", aClosure);
+    aCallbacks.Trace(&mSetter.AsHeapObject(), "mSetter", aClosure);
   }
 }
 
 nsresult
 nsXBLProtoImplProperty::Read(nsIScriptContext* aContext,
                              nsIObjectInputStream* aStream,
                              XBLBindingSerializeDetails aType)
 {
+  MOZ_ASSERT(!mIsCompiled);
+  MOZ_ASSERT(!mGetter.GetUncompiled() && !mSetter.GetUncompiled());
+
   JSContext *cx = aContext->GetNativeContext();
 
+  JS::Rooted<JSObject*> getterObject(cx);
   if (aType == XBLBinding_Serialize_GetterProperty ||
       aType == XBLBinding_Serialize_GetterSetterProperty) {
-    JS::Rooted<JSObject*> getterObject(cx);
     nsresult rv = XBL_DeserializeFunction(aContext, aStream, &getterObject);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mJSGetterObject = getterObject;
     mJSAttributes |= JSPROP_GETTER | JSPROP_SHARED;
   }
-
+  mGetter.SetJSFunction(getterObject);
+  
+  JS::Rooted<JSObject*> setterObject(cx);
   if (aType == XBLBinding_Serialize_SetterProperty ||
       aType == XBLBinding_Serialize_GetterSetterProperty) {
-    JS::Rooted<JSObject*> setterObject(cx);
     nsresult rv = XBL_DeserializeFunction(aContext, aStream, &setterObject);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mJSSetterObject = setterObject;
     mJSAttributes |= JSPROP_SETTER | JSPROP_SHARED;
   }
+  mSetter.SetJSFunction(setterObject);
 
 #ifdef DEBUG
   mIsCompiled = true;
 #endif
 
   return NS_OK;
 }
 
@@ -365,21 +353,19 @@ nsXBLProtoImplProperty::Write(nsIScriptC
   }
 
   nsresult rv = aStream->Write8(type);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aStream->WriteWStringZ(mName);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mJSAttributes & JSPROP_GETTER) {
-    rv = XBL_SerializeFunction(aContext, aStream,
-      JS::Handle<JSObject*>::fromMarkedLocation(&mJSGetterObject));
+    rv = XBL_SerializeFunction(aContext, aStream, mGetter.AsHeapObject());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (mJSAttributes & JSPROP_SETTER) {
-    rv = XBL_SerializeFunction(aContext, aStream,
-      JS::Handle<JSObject*>::fromMarkedLocation(&mJSSetterObject));
+    rv = XBL_SerializeFunction(aContext, aStream, mSetter.AsHeapObject());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
--- a/content/xbl/src/nsXBLProtoImplProperty.h
+++ b/content/xbl/src/nsXBLProtoImplProperty.h
@@ -7,16 +7,17 @@
 #define nsXBLProtoImplProperty_h__
 
 #include "mozilla/Attributes.h"
 #include "nsIAtom.h"
 #include "nsString.h"
 #include "jsapi.h"
 #include "nsString.h"
 #include "nsXBLSerialize.h"
+#include "nsXBLMaybeCompiled.h"
 #include "nsXBLProtoImplMember.h"
 
 class nsXBLProtoImplProperty: public nsXBLProtoImplMember
 {
 public:
   nsXBLProtoImplProperty(const PRUnichar* aName,
                          const PRUnichar* aGetter, 
                          const PRUnichar* aSetter,
@@ -43,30 +44,26 @@ public:
 
   nsresult Read(nsIScriptContext* aContext,
                 nsIObjectInputStream* aStream,
                 XBLBindingSerializeDetails aType);
   virtual nsresult Write(nsIScriptContext* aContext,
                          nsIObjectOutputStream* aStream) MOZ_OVERRIDE;
 
 protected:
-  union {
-    // The raw text for the getter (prior to compilation).
-    nsXBLTextWithLineNumber* mGetterText;
-    // The JS object for the getter (after compilation)
-    JSObject *               mJSGetterObject;
-  };
+  typedef JS::Heap<nsXBLMaybeCompiled<nsXBLTextWithLineNumber> > PropertyOp;
+
+  void EnsureUncompiledText(PropertyOp& aPropertyOp);
 
-  union {
-    // The raw text for the setter (prior to compilation).
-    nsXBLTextWithLineNumber* mSetterText;
-    // The JS object for the setter (after compilation)
-    JSObject *               mJSSetterObject;
-  };
+  // The raw text for the getter, or the JS object (after compilation).
+  PropertyOp mGetter;
+
+  // The raw text for the setter, or the JS object (after compilation).
+  PropertyOp mSetter;
   
-  unsigned mJSAttributes;          // A flag for all our JS properties (getter/setter/readonly/shared/enum)
+  unsigned mJSAttributes;  // A flag for all our JS properties (getter/setter/readonly/shared/enum)
 
 #ifdef DEBUG
   bool mIsCompiled;
 #endif
 };
 
 #endif // nsXBLProtoImplProperty_h__