Bug 838686 part 1. Add a helper class that can store a WebIDL callback or an XPCOM interface. r=peterv
☠☠ backed out by 215273993b1f ☠ ☠
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 26 Feb 2013 15:10:15 -0500
changeset 123056 a4763990a983c9e29a3cf49de18abecbbecba75d
parent 123055 b0a1b994278e4ca332e11302be355a8f56f913de
child 123057 1c851a5bbc9fe9782f0cf347730b1650ca119276
push id23639
push userbzbarsky@mozilla.com
push dateTue, 26 Feb 2013 20:12:46 +0000
treeherdermozilla-inbound@1c851a5bbc9f [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