Bug 1263224: Add support for thread-safe weak references for COM-based objects; r?jimm
MozReview-Commit-ID: JxVMyDhNPs4
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/WeakRef.cpp
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#define INITGUID
+#include "mozilla/mscom/WeakRef.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+#include "nsWindowsHelpers.h"
+
+namespace mozilla {
+namespace mscom {
+
+WeakReferenceSupport::WeakReferenceSupport(Flags aFlags)
+ : mRefCnt(1)
+ , mFlags(aFlags)
+{
+ ::InitializeCriticalSectionAndSpinCount(&mCS, 4000);
+}
+
+WeakReferenceSupport::~WeakReferenceSupport()
+{
+ MOZ_ASSERT(mWeakRefs.IsEmpty());
+ ::DeleteCriticalSection(&mCS);
+}
+
+HRESULT
+WeakReferenceSupport::QueryInterface(REFIID riid, void** ppv)
+{
+ AutoCriticalSection lock(&mCS);
+ RefPtr<IUnknown> punk;
+ if (!ppv) {
+ return E_INVALIDARG;
+ }
+ *ppv = nullptr;
+
+ // Raise the refcount for stabilization purposes during aggregation
+ RefPtr<IUnknown> kungFuDeathGrip(static_cast<IUnknown*>(this));
+
+ if (riid == IID_IUnknown || riid == IID_IWeakReferenceSource) {
+ punk = static_cast<IUnknown*>(this);
+ } else {
+ HRESULT hr = ThreadSafeQueryInterface(riid, getter_AddRefs(punk));
+ if (FAILED(hr)) {
+ return hr;
+ }
+ }
+
+ if (!punk) {
+ return E_NOINTERFACE;
+ }
+
+ punk.forget(ppv);
+ return S_OK;
+}
+
+ULONG
+WeakReferenceSupport::AddRef()
+{
+ AutoCriticalSection lock(&mCS);
+ return ++mRefCnt;
+}
+
+ULONG
+WeakReferenceSupport::Release()
+{
+ ULONG newRefCnt;
+ { // Scope for lock
+ AutoCriticalSection lock(&mCS);
+ newRefCnt = --mRefCnt;
+ if (newRefCnt == 0) {
+ ClearWeakRefs();
+ }
+ }
+ if (newRefCnt == 0) {
+ if (mFlags != Flags::eDestroyOnMainThread || NS_IsMainThread()) {
+ delete this;
+ } else {
+ // It is possible for the last Release() call to happen off-main-thread.
+ // If so, we need to dispatch an event to delete ourselves.
+ mozilla::DebugOnly<nsresult> rv =
+ NS_DispatchToMainThread(NS_NewRunnableFunction([this]() -> void
+ {
+ delete this;
+ }));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ return newRefCnt;
+}
+
+void
+WeakReferenceSupport::ClearWeakRefs()
+{
+ for (uint32_t i = 0, len = mWeakRefs.Length(); i < len; ++i) {
+ mWeakRefs[i]->Clear();
+ mWeakRefs[i]->Release();
+ }
+ mWeakRefs.Clear();
+}
+
+HRESULT
+WeakReferenceSupport::GetWeakReference(IWeakReference** aOutWeakRef)
+{
+ if (!aOutWeakRef) {
+ return E_INVALIDARG;
+ }
+ *aOutWeakRef = nullptr;
+
+ AutoCriticalSection lock(&mCS);
+ RefPtr<WeakRef> weakRef = MakeAndAddRef<WeakRef>(this);
+
+ HRESULT hr = weakRef->QueryInterface(IID_IWeakReference, (void**)aOutWeakRef);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ mWeakRefs.AppendElement(weakRef.get());
+ weakRef->AddRef();
+ return S_OK;
+}
+
+WeakRef::WeakRef(WeakReferenceSupport* aSupport)
+ : mRefCnt(1)
+ , mMutex("mozilla::mscom::WeakRef::mMutex")
+ , mSupport(aSupport)
+{
+ MOZ_ASSERT(aSupport);
+}
+
+HRESULT
+WeakRef::QueryInterface(REFIID riid, void** ppv)
+{
+ IUnknown* punk = nullptr;
+ if (!ppv) {
+ return E_INVALIDARG;
+ }
+
+ if (riid == IID_IUnknown || riid == IID_IWeakReference) {
+ punk = static_cast<IUnknown*>(this);
+ }
+
+ *ppv = punk;
+ if (!punk) {
+ return E_NOINTERFACE;
+ }
+
+ punk->AddRef();
+ return S_OK;
+}
+
+ULONG
+WeakRef::AddRef()
+{
+ return (ULONG) InterlockedIncrement((LONG*)&mRefCnt);
+}
+
+ULONG
+WeakRef::Release()
+{
+ ULONG newRefCnt = (ULONG) InterlockedDecrement((LONG*)&mRefCnt);
+ if (newRefCnt == 0) {
+ delete this;
+ }
+ return newRefCnt;
+}
+
+HRESULT
+WeakRef::Resolve(REFIID aIid, void** aOutStrongReference)
+{
+ MutexAutoLock lock(mMutex);
+ if (!mSupport) {
+ return E_FAIL;
+ }
+ return mSupport->QueryInterface(aIid, aOutStrongReference);
+}
+
+void
+WeakRef::Clear()
+{
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mSupport);
+ mSupport = nullptr;
+}
+
+} // namespace mscom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/WeakRef.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 mozilla_mscom_WeakRef_h
+#define mozilla_mscom_WeakRef_h
+
+#include <guiddef.h>
+#include <Unknwn.h>
+
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+
+/**
+ * Thread-safe weak references for COM that works pre-Windows 8 and do not
+ * require WinRT.
+ */
+
+namespace mozilla {
+namespace mscom {
+
+// {F841AEFA-064C-49A4-B73D-EBD14A90F012}
+DEFINE_GUID(IID_IWeakReference,
+0xf841aefa, 0x64c, 0x49a4, 0xb7, 0x3d, 0xeb, 0xd1, 0x4a, 0x90, 0xf0, 0x12);
+
+struct IWeakReference : public IUnknown
+{
+ virtual STDMETHODIMP Resolve(REFIID aIid, void** aOutStringReference) = 0;
+};
+
+// {87611F0C-9BBB-4F78-9D43-CAC5AD432CA1}
+DEFINE_GUID(IID_IWeakReferenceSource,
+0x87611f0c, 0x9bbb, 0x4f78, 0x9d, 0x43, 0xca, 0xc5, 0xad, 0x43, 0x2c, 0xa1);
+
+struct IWeakReferenceSource : public IUnknown
+{
+ virtual STDMETHODIMP GetWeakReference(IWeakReference** aOutWeakRef) = 0;
+};
+
+class WeakRef;
+
+class WeakReferenceSupport : public IWeakReferenceSource
+{
+public:
+ enum class Flags
+ {
+ eNone = 0,
+ eDestroyOnMainThread = 1
+ };
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
+ STDMETHODIMP_(ULONG) AddRef() override;
+ STDMETHODIMP_(ULONG) Release() override;
+
+ // IWeakReferenceSource
+ STDMETHODIMP GetWeakReference(IWeakReference** aOutWeakRef) override;
+
+protected:
+ explicit WeakReferenceSupport(Flags aFlags);
+ virtual ~WeakReferenceSupport();
+
+ virtual HRESULT ThreadSafeQueryInterface(REFIID aIid,
+ IUnknown** aOutInterface) = 0;
+
+private:
+ void ClearWeakRefs();
+
+private:
+ // Using a raw CRITICAL_SECTION here because it can be reentered
+ CRITICAL_SECTION mCS;
+ ULONG mRefCnt;
+ nsTArray<WeakRef*> mWeakRefs;
+ Flags mFlags;
+};
+
+class WeakRef : public IWeakReference
+{
+public:
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
+ STDMETHODIMP_(ULONG) AddRef() override;
+ STDMETHODIMP_(ULONG) Release() override;
+
+ // IWeakReference
+ STDMETHODIMP Resolve(REFIID aIid, void** aOutStrongReference) override;
+
+ explicit WeakRef(WeakReferenceSupport* aSupport);
+
+ void Clear();
+
+private:
+ ULONG mRefCnt;
+ mozilla::Mutex mMutex; // Protects mSupport
+ WeakReferenceSupport* mSupport;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_WeakRef_h
+
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -10,21 +10,23 @@ EXPORTS.mozilla.mscom += [
'EnsureMTA.h',
'InterceptorLog.h',
'MainThreadInvoker.h',
'MainThreadRuntime.h',
'ProxyStream.h',
'Ptr.h',
'Registration.h',
'Utils.h',
+ 'WeakRef.h',
]
SOURCES += [
'Registration.cpp',
'Utils.cpp',
+ 'WeakRef.cpp',
]
UNIFIED_SOURCES += [
'EnsureMTA.cpp',
'InterceptorLog.cpp',
'MainThreadInvoker.cpp',
'MainThreadRuntime.cpp',
'ProxyStream.cpp',