Bug 838686 part 1. Add a helper class that can store a WebIDL callback or an XPCOM interface. r=peterv
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 26 Feb 2013 15:10:15 -0500
changeset 123129 ac3b7681470c502d3fe0c0be29385233f8d5e942
parent 123128 672ce8b62c32bd111aeecc0aa50f0e6012e29820
child 123130 a3aaa9067e143f785b4762ad4ecb2699580a9010
push id24372
push useremorley@mozilla.com
push dateWed, 27 Feb 2013 13:22:59 +0000
treeherdermozilla-central@0a91da5f5eab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs838686
milestone22.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 838686 part 1. Add a helper class that can store a WebIDL callback or an XPCOM interface. r=peterv
dom/bindings/CallbackObject.cpp
dom/bindings/CallbackObject.h
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -7,16 +7,17 @@
 #include "mozilla/dom/CallbackObject.h"
 #include "jsfriendapi.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIXPConnect.h"
 #include "nsIScriptContext.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSUtils.h"
 #include "nsIScriptSecurityManager.h"
+#include "xpcprivate.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
@@ -176,10 +177,44 @@ CallbackObject::CallSetup::~CallSetup()
   // Popping an nsCxPusher is safe even if it never got pushed.
   mCxPusher.Pop();
 
   if (mCtx) {
     mCtx->ScriptEvaluated(true);
   }
 }
 
+already_AddRefed<nsISupports>
+CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback,
+                                          const nsIID& aIID)
+{
+  if (!aCallback) {
+    return nullptr;
+  }
+
+  JSObject* callback = aCallback->Callback();
+
+  SafeAutoJSContext cx;
+  JSAutoCompartment ac(cx, callback);
+  XPCCallContext ccx(NATIVE_CALLER, cx);
+  if (!ccx.IsValid()) {
+    return nullptr;
+  }
+
+  nsRefPtr<nsXPCWrappedJS> wrappedJS;
+  nsresult rv =
+    nsXPCWrappedJS::GetNewOrUsed(ccx, callback, aIID,
+                                 nullptr, getter_AddRefs(wrappedJS));
+  if (NS_FAILED(rv) || !wrappedJS) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsISupports> retval;
+  rv = wrappedJS->QueryInterface(aIID, getter_AddRefs(retval));
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  return retval.forget();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -76,16 +76,29 @@ public:
   }
 
   JSObject* Callback() const
   {
     xpc_UnmarkGrayObject(mCallback);
     return mCallback;
   }
 
+  /*
+   * This getter does not change the color of the JSObject meaning that the
+   * object returned is not guaranteed to be kept alive past the next CC.
+   *
+   * This should only be called if you are certain that the return value won't
+   * be passed into a JS API function and that it won't be stored without being
+   * rooted (or otherwise signaling the stored value to the CC).
+   */
+  JSObject* CallbackPreserveColor() const
+  {
+    return mCallback;
+  }
+
   enum ExceptionHandling {
     eReportExceptions,
     eRethrowExceptions
   };
 
 protected:
   explicit CallbackObject(CallbackObject* aCallbackFunction)
     : mCallback(aCallbackFunction->mCallback)
@@ -159,12 +172,210 @@ protected:
     // An ErrorResult to possibly re-throw exceptions on and whether
     // we should re-throw them.
     ErrorResult& mErrorResult;
     const ExceptionHandling mExceptionHandling;
     uint32_t mSavedJSContextOptions;
   };
 };
 
+template<class WebIDLCallbackT, class XPCOMCallbackT>
+class CallbackObjectHolder;
+
+template<class T, class U>
+inline void ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField);
+
+class CallbackObjectHolderBase
+{
+protected:
+  // Returns null on all failures
+  already_AddRefed<nsISupports> ToXPCOMCallback(CallbackObject* aCallback,
+                                                const nsIID& aIID);
+};
+
+template<class WebIDLCallbackT, class XPCOMCallbackT>
+class CallbackObjectHolder : CallbackObjectHolderBase
+{
+  /**
+   * A class which stores either a WebIDLCallbackT* or an XPCOMCallbackT*.  Both
+   * types must inherit from nsISupports.  The pointer that's stored can be
+   * null.
+   *
+   * When storing a WebIDLCallbackT*, mPtrBits is set to the pointer value.
+   * When storing an XPCOMCallbackT*, mPtrBits is the pointer value with low bit
+   * set.
+   */
+public:
+  explicit CallbackObjectHolder(WebIDLCallbackT* aCallback)
+    : mPtrBits(reinterpret_cast<uintptr_t>(aCallback))
+  {
+    NS_IF_ADDREF(aCallback);
+  }
+
+  explicit CallbackObjectHolder(XPCOMCallbackT* aCallback)
+    : mPtrBits(reinterpret_cast<uintptr_t>(aCallback) | XPCOMCallbackFlag)
+  {
+    NS_IF_ADDREF(aCallback);
+  }
+
+  explicit CallbackObjectHolder(const CallbackObjectHolder& aOther)
+    : mPtrBits(aOther.mPtrBits)
+  {
+    NS_IF_ADDREF(GetISupports());
+  }
+
+  CallbackObjectHolder()
+    : mPtrBits(0)
+  {}
+
+  ~CallbackObjectHolder()
+  {
+    UnlinkSelf();
+  }
+
+  nsISupports* GetISupports() const
+  {
+    return reinterpret_cast<nsISupports*>(mPtrBits & ~XPCOMCallbackFlag);
+  }
+
+  // Even if HasWebIDLCallback returns true, GetWebIDLCallback() might still
+  // return null.
+  bool HasWebIDLCallback() const
+  {
+    return !(mPtrBits & XPCOMCallbackFlag);
+  }
+
+  WebIDLCallbackT* GetWebIDLCallback() const
+  {
+    MOZ_ASSERT(HasWebIDLCallback());
+    return reinterpret_cast<WebIDLCallbackT*>(mPtrBits);
+  }
+
+  XPCOMCallbackT* GetXPCOMCallback() const
+  {
+    MOZ_ASSERT(!HasWebIDLCallback());
+    return reinterpret_cast<XPCOMCallbackT*>(mPtrBits & ~XPCOMCallbackFlag);
+  }
+
+  bool operator==(WebIDLCallbackT* aOtherCallback) const
+  {
+    if (!aOtherCallback) {
+      // If other is null, then we must be null to be equal.
+      return !GetISupports();
+    }
+
+    if (!HasWebIDLCallback() || !GetWebIDLCallback()) {
+      // If other is non-null, then we can't be equal if we have a
+      // non-WebIDL callback or a null callback.
+      return false;
+    }
+
+    JSObject* thisObj =
+      js::UnwrapObject(GetWebIDLCallback()->CallbackPreserveColor());
+    JSObject* otherObj =
+      js::UnwrapObject(aOtherCallback->CallbackPreserveColor());
+    return thisObj == otherObj;
+  }
+
+  bool operator==(XPCOMCallbackT* aOtherCallback) const
+  {
+    return (!aOtherCallback && !GetISupports()) ||
+      (!HasWebIDLCallback() && GetXPCOMCallback() == aOtherCallback);
+  }
+
+  bool operator==(const CallbackObjectHolder& aOtherCallback) const
+  {
+    if (aOtherCallback.HasWebIDLCallback()) {
+      return *this == aOtherCallback.GetWebIDLCallback();
+    }
+
+    return *this == aOtherCallback.GetXPCOMCallback();
+  }
+
+  // Try to return an XPCOMCallbackT version of this object.
+  already_AddRefed<XPCOMCallbackT> ToXPCOMCallback()
+  {
+    if (!HasWebIDLCallback()) {
+      nsRefPtr<XPCOMCallbackT> callback = GetXPCOMCallback();
+      return callback.forget();
+    }
+
+    nsCOMPtr<nsISupports> supp =
+      CallbackObjectHolderBase::ToXPCOMCallback(GetWebIDLCallback(),
+                                                NS_GET_TEMPLATE_IID(XPCOMCallbackT));
+    // ToXPCOMCallback already did the right QI for us.
+    return static_cast<XPCOMCallbackT*>(supp.forget().get());
+  }
+
+  // Try to return a WebIDLCallbackT version of this object.
+  already_AddRefed<WebIDLCallbackT> ToWebIDLCallback()
+  {
+    if (HasWebIDLCallback()) {
+      nsRefPtr<WebIDLCallbackT> callback = GetWebIDLCallback();
+      return callback.forget();
+    }
+
+    XPCOMCallbackT* callback = GetXPCOMCallback();
+    if (!callback) {
+      return nullptr;
+    }
+
+    nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS = do_QueryInterface(callback);
+    if (!wrappedJS) {
+      return nullptr;
+    }
+
+    JSObject* obj;
+    if (NS_FAILED(wrappedJS->GetJSObject(&obj)) || !obj) {
+      return nullptr;
+    }
+
+    SafeAutoJSContext cx;
+    JSAutoCompartment ac(cx, obj);
+
+    bool inited;
+    nsRefPtr<WebIDLCallbackT> newCallback =
+      new WebIDLCallbackT(cx, nullptr, obj, &inited);
+    if (!inited) {
+      return nullptr;
+    }
+    return newCallback.forget();
+  }
+
+private:
+  static const uintptr_t XPCOMCallbackFlag = 1u;
+
+  friend inline void
+  ImplCycleCollectionUnlink<WebIDLCallbackT,
+                            XPCOMCallbackT>(CallbackObjectHolder& aField);
+
+  void UnlinkSelf()
+  {
+    // NS_IF_RELEASE because we might have been unlinked before
+    nsISupports* ptr = GetISupports();
+    NS_IF_RELEASE(ptr);
+    mPtrBits = 0;
+  }
+
+  uintptr_t mPtrBits;
+};
+
+template<class T, class U>
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+                            CallbackObjectHolder<T, U>& aField,
+                            const char* aName,
+                            uint32_t aFlags = 0)
+{
+  CycleCollectionNoteChild(aCallback, aField.GetISupports(), aName, aFlags);
+}
+
+template<class T, class U>
+inline void
+ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField)
+{
+  aField.UnlinkSelf();
+}
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_CallbackObject_h