summaryrefslogtreecommitdiffstats
path: root/ipc/mscom
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /ipc/mscom
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/mscom')
-rw-r--r--ipc/mscom/Aggregation.h83
-rw-r--r--ipc/mscom/AgileReference.cpp223
-rw-r--r--ipc/mscom/AgileReference.h143
-rw-r--r--ipc/mscom/ApartmentRegion.h93
-rw-r--r--ipc/mscom/COMWrappers.cpp101
-rw-r--r--ipc/mscom/COMWrappers.h44
-rw-r--r--ipc/mscom/EnsureMTA.cpp250
-rw-r--r--ipc/mscom/EnsureMTA.h191
-rw-r--r--ipc/mscom/ProcessRuntime.cpp458
-rw-r--r--ipc/mscom/ProcessRuntime.h87
-rw-r--r--ipc/mscom/ProfilerMarkers.cpp236
-rw-r--r--ipc/mscom/ProfilerMarkers.h18
-rw-r--r--ipc/mscom/Utils.cpp404
-rw-r--r--ipc/mscom/Utils.h146
-rw-r--r--ipc/mscom/moz.build42
-rw-r--r--ipc/mscom/mozglue/ProcessRuntimeShared.cpp31
-rw-r--r--ipc/mscom/mozglue/ProcessRuntimeShared.h55
-rw-r--r--ipc/mscom/mozglue/moz.build15
18 files changed, 2620 insertions, 0 deletions
diff --git a/ipc/mscom/Aggregation.h b/ipc/mscom/Aggregation.h
new file mode 100644
index 0000000000..ca171e39cc
--- /dev/null
+++ b/ipc/mscom/Aggregation.h
@@ -0,0 +1,83 @@
+/* -*- 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_Aggregation_h
+#define mozilla_mscom_Aggregation_h
+
+#include "mozilla/Attributes.h"
+
+#include <stddef.h>
+#include <unknwn.h>
+
+namespace mozilla {
+namespace mscom {
+
+/**
+ * This is used for stabilizing a COM object's reference count during
+ * construction when that object aggregates other objects. Since the aggregated
+ * object(s) may AddRef() or Release(), we need to artifically boost the
+ * refcount to prevent premature destruction. Note that we increment/decrement
+ * instead of AddRef()/Release() in this class because we want to adjust the
+ * refcount without causing any other side effects (like object destruction).
+ */
+template <typename RefCntT>
+class MOZ_RAII StabilizedRefCount {
+ public:
+ explicit StabilizedRefCount(RefCntT& aRefCnt) : mRefCnt(aRefCnt) {
+ ++aRefCnt;
+ }
+
+ ~StabilizedRefCount() { --mRefCnt; }
+
+ StabilizedRefCount(const StabilizedRefCount&) = delete;
+ StabilizedRefCount(StabilizedRefCount&&) = delete;
+ StabilizedRefCount& operator=(const StabilizedRefCount&) = delete;
+ StabilizedRefCount& operator=(StabilizedRefCount&&) = delete;
+
+ private:
+ RefCntT& mRefCnt;
+};
+
+namespace detail {
+
+template <typename T>
+class InternalUnknown : public IUnknown {
+ public:
+ STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override {
+ return This()->InternalQueryInterface(aIid, aOutInterface);
+ }
+
+ STDMETHODIMP_(ULONG) AddRef() override { return This()->InternalAddRef(); }
+
+ STDMETHODIMP_(ULONG) Release() override { return This()->InternalRelease(); }
+
+ private:
+ T* This() {
+ return reinterpret_cast<T*>(reinterpret_cast<char*>(this) -
+ offsetof(T, mInternalUnknown));
+ }
+};
+
+} // namespace detail
+} // namespace mscom
+} // namespace mozilla
+
+#define DECLARE_AGGREGATABLE(Type) \
+ public: \
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override { \
+ return mOuter->QueryInterface(riid, ppv); \
+ } \
+ STDMETHODIMP_(ULONG) AddRef() override { return mOuter->AddRef(); } \
+ STDMETHODIMP_(ULONG) Release() override { return mOuter->Release(); } \
+ \
+ protected: \
+ STDMETHODIMP InternalQueryInterface(REFIID riid, void** ppv); \
+ STDMETHODIMP_(ULONG) InternalAddRef(); \
+ STDMETHODIMP_(ULONG) InternalRelease(); \
+ friend class mozilla::mscom::detail::InternalUnknown<Type>; \
+ mozilla::mscom::detail::InternalUnknown<Type> mInternalUnknown
+
+#endif // mozilla_mscom_Aggregation_h
diff --git a/ipc/mscom/AgileReference.cpp b/ipc/mscom/AgileReference.cpp
new file mode 100644
index 0000000000..7a1b94822d
--- /dev/null
+++ b/ipc/mscom/AgileReference.cpp
@@ -0,0 +1,223 @@
+/* -*- 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/. */
+
+#include "mozilla/mscom/AgileReference.h"
+
+#include <utility>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/mscom/Utils.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "nsDebug.h"
+# include "nsPrintfCString.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+#if NTDDI_VERSION < NTDDI_WINBLUE
+
+// Declarations from Windows SDK specific to Windows 8.1
+
+enum AgileReferenceOptions {
+ AGILEREFERENCE_DEFAULT = 0,
+ AGILEREFERENCE_DELAYEDMARSHAL = 1,
+};
+
+HRESULT WINAPI RoGetAgileReference(AgileReferenceOptions options, REFIID riid,
+ IUnknown* pUnk,
+ IAgileReference** ppAgileReference);
+
+#endif // NTDDI_VERSION < NTDDI_WINBLUE
+
+namespace mozilla {
+namespace mscom {
+namespace detail {
+
+GlobalInterfaceTableCookie::GlobalInterfaceTableCookie(IUnknown* aObject,
+ REFIID aIid,
+ HRESULT& aOutHResult)
+ : mCookie(0) {
+ IGlobalInterfaceTable* git = ObtainGit();
+ MOZ_ASSERT(git);
+ if (!git) {
+ aOutHResult = E_POINTER;
+ return;
+ }
+
+ aOutHResult = git->RegisterInterfaceInGlobal(aObject, aIid, &mCookie);
+ MOZ_ASSERT(SUCCEEDED(aOutHResult));
+}
+
+GlobalInterfaceTableCookie::~GlobalInterfaceTableCookie() {
+ IGlobalInterfaceTable* git = ObtainGit();
+ MOZ_ASSERT(git);
+ if (!git) {
+ return;
+ }
+
+ DebugOnly<HRESULT> hr = git->RevokeInterfaceFromGlobal(mCookie);
+#if defined(MOZILLA_INTERNAL_API)
+ NS_WARNING_ASSERTION(
+ SUCCEEDED(hr),
+ nsPrintfCString("IGlobalInterfaceTable::RevokeInterfaceFromGlobal failed "
+ "with HRESULT 0x%08lX",
+ ((HRESULT)hr))
+ .get());
+#else
+ MOZ_ASSERT(SUCCEEDED(hr));
+#endif // defined(MOZILLA_INTERNAL_API)
+ mCookie = 0;
+}
+
+HRESULT GlobalInterfaceTableCookie::GetInterface(REFIID aIid,
+ void** aOutInterface) const {
+ IGlobalInterfaceTable* git = ObtainGit();
+ MOZ_ASSERT(git);
+ if (!git) {
+ return E_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(IsValid());
+ return git->GetInterfaceFromGlobal(mCookie, aIid, aOutInterface);
+}
+
+/* static */
+IGlobalInterfaceTable* GlobalInterfaceTableCookie::ObtainGit() {
+ // Internally to COM, the Global Interface Table is a singleton, therefore we
+ // don't worry about holding onto this reference indefinitely.
+ static IGlobalInterfaceTable* sGit = []() -> IGlobalInterfaceTable* {
+ IGlobalInterfaceTable* result = nullptr;
+ DebugOnly<HRESULT> hr = ::CoCreateInstance(
+ CLSID_StdGlobalInterfaceTable, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IGlobalInterfaceTable, reinterpret_cast<void**>(&result));
+ MOZ_ASSERT(SUCCEEDED(hr));
+ return result;
+ }();
+
+ return sGit;
+}
+
+} // namespace detail
+
+AgileReference::AgileReference() : mIid(), mHResult(E_NOINTERFACE) {}
+
+AgileReference::AgileReference(REFIID aIid, IUnknown* aObject)
+ : mIid(aIid), mHResult(E_UNEXPECTED) {
+ AssignInternal(aObject);
+}
+
+AgileReference::AgileReference(AgileReference&& aOther)
+ : mIid(aOther.mIid),
+ mAgileRef(std::move(aOther.mAgileRef)),
+ mGitCookie(std::move(aOther.mGitCookie)),
+ mHResult(aOther.mHResult) {
+ aOther.mHResult = CO_E_RELEASED;
+}
+
+void AgileReference::Assign(REFIID aIid, IUnknown* aObject) {
+ Clear();
+ mIid = aIid;
+ AssignInternal(aObject);
+}
+
+void AgileReference::AssignInternal(IUnknown* aObject) {
+ // We expect mIid to already be set
+ DebugOnly<IID> zeroIid = {};
+ MOZ_ASSERT(mIid != zeroIid);
+
+ /*
+ * There are two possible techniques for creating agile references. Starting
+ * with Windows 8.1, we may use the RoGetAgileReference API, which is faster.
+ * If that API is not available, we fall back to using the Global Interface
+ * Table.
+ */
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::RoGetAgileReference)>
+ pRoGetAgileReference(L"ole32.dll", "RoGetAgileReference");
+
+ MOZ_ASSERT(aObject);
+
+ if (pRoGetAgileReference &&
+ SUCCEEDED(mHResult =
+ pRoGetAgileReference(AGILEREFERENCE_DEFAULT, mIid, aObject,
+ getter_AddRefs(mAgileRef)))) {
+ return;
+ }
+
+ mGitCookie = new detail::GlobalInterfaceTableCookie(aObject, mIid, mHResult);
+ MOZ_ASSERT(mGitCookie->IsValid());
+}
+
+AgileReference::~AgileReference() { Clear(); }
+
+void AgileReference::Clear() {
+ mIid = {};
+ mAgileRef = nullptr;
+ mGitCookie = nullptr;
+ mHResult = E_NOINTERFACE;
+}
+
+AgileReference& AgileReference::operator=(const AgileReference& aOther) {
+ Clear();
+ mIid = aOther.mIid;
+ mAgileRef = aOther.mAgileRef;
+ mGitCookie = aOther.mGitCookie;
+ mHResult = aOther.mHResult;
+ return *this;
+}
+
+AgileReference& AgileReference::operator=(AgileReference&& aOther) {
+ Clear();
+ mIid = aOther.mIid;
+ mAgileRef = std::move(aOther.mAgileRef);
+ mGitCookie = std::move(aOther.mGitCookie);
+ mHResult = aOther.mHResult;
+ aOther.mHResult = CO_E_RELEASED;
+ return *this;
+}
+
+HRESULT
+AgileReference::Resolve(REFIID aIid, void** aOutInterface) const {
+ MOZ_ASSERT(aOutInterface);
+ // This check is exclusive-OR; we should have one or the other, but not both
+ MOZ_ASSERT((mAgileRef || mGitCookie) && !(mAgileRef && mGitCookie));
+ MOZ_ASSERT(IsCOMInitializedOnCurrentThread());
+
+ if (!aOutInterface) {
+ return E_INVALIDARG;
+ }
+
+ *aOutInterface = nullptr;
+
+ if (mAgileRef) {
+ // IAgileReference lets you directly resolve the interface you want...
+ return mAgileRef->Resolve(aIid, aOutInterface);
+ }
+
+ if (!mGitCookie) {
+ return E_UNEXPECTED;
+ }
+
+ RefPtr<IUnknown> originalInterface;
+ HRESULT hr =
+ mGitCookie->GetInterface(mIid, getter_AddRefs(originalInterface));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (aIid == mIid) {
+ originalInterface.forget(aOutInterface);
+ return S_OK;
+ }
+
+ // ...Whereas the GIT requires us to obtain the same interface that we
+ // requested and then QI for the desired interface afterward.
+ return originalInterface->QueryInterface(aIid, aOutInterface);
+}
+
+} // namespace mscom
+} // namespace mozilla
diff --git a/ipc/mscom/AgileReference.h b/ipc/mscom/AgileReference.h
new file mode 100644
index 0000000000..d39e444494
--- /dev/null
+++ b/ipc/mscom/AgileReference.h
@@ -0,0 +1,143 @@
+/* -*- 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_AgileReference_h
+#define mozilla_mscom_AgileReference_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "nsISupportsImpl.h"
+
+#include <objidl.h>
+
+namespace mozilla {
+namespace mscom {
+namespace detail {
+
+class MOZ_HEAP_CLASS GlobalInterfaceTableCookie final {
+ public:
+ GlobalInterfaceTableCookie(IUnknown* aObject, REFIID aIid,
+ HRESULT& aOutHResult);
+
+ bool IsValid() const { return !!mCookie; }
+ HRESULT GetInterface(REFIID aIid, void** aOutInterface) const;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GlobalInterfaceTableCookie)
+
+ GlobalInterfaceTableCookie(const GlobalInterfaceTableCookie&) = delete;
+ GlobalInterfaceTableCookie(GlobalInterfaceTableCookie&&) = delete;
+
+ GlobalInterfaceTableCookie& operator=(const GlobalInterfaceTableCookie&) =
+ delete;
+ GlobalInterfaceTableCookie& operator=(GlobalInterfaceTableCookie&&) = delete;
+
+ private:
+ ~GlobalInterfaceTableCookie();
+
+ private:
+ DWORD mCookie;
+
+ private:
+ static IGlobalInterfaceTable* ObtainGit();
+};
+
+} // namespace detail
+
+/**
+ * This class encapsulates an "agile reference." These are references that
+ * allow you to pass COM interfaces between apartments. When you have an
+ * interface that you would like to pass between apartments, you wrap that
+ * interface in an AgileReference and pass the agile reference instead. Then
+ * you unwrap the interface by calling AgileReference::Resolve.
+ *
+ * Sample usage:
+ *
+ * // In the multithreaded apartment, foo is an IFoo*
+ * auto myAgileRef = MakeUnique<AgileReference>(IID_IFoo, foo);
+ *
+ * // myAgileRef is passed to our main thread, which runs in a single-threaded
+ * // apartment:
+ *
+ * RefPtr<IFoo> foo;
+ * HRESULT hr = myAgileRef->Resolve(IID_IFoo, getter_AddRefs(foo));
+ * // Now foo may be called from the main thread
+ */
+class AgileReference final {
+ public:
+ AgileReference();
+
+ template <typename InterfaceT>
+ explicit AgileReference(RefPtr<InterfaceT>& aObject)
+ : AgileReference(__uuidof(InterfaceT), aObject) {}
+
+ AgileReference(REFIID aIid, IUnknown* aObject);
+
+ AgileReference(const AgileReference& aOther) = default;
+ AgileReference(AgileReference&& aOther);
+
+ ~AgileReference();
+
+ explicit operator bool() const {
+ return mAgileRef || (mGitCookie && mGitCookie->IsValid());
+ }
+
+ HRESULT GetHResult() const { return mHResult; }
+
+ template <typename T>
+ void Assign(const RefPtr<T>& aOther) {
+ Assign(__uuidof(T), aOther);
+ }
+
+ template <typename T>
+ AgileReference& operator=(const RefPtr<T>& aOther) {
+ Assign(aOther);
+ return *this;
+ }
+
+ HRESULT Resolve(REFIID aIid, void** aOutInterface) const;
+
+ AgileReference& operator=(const AgileReference& aOther);
+ AgileReference& operator=(AgileReference&& aOther);
+
+ AgileReference& operator=(decltype(nullptr)) {
+ Clear();
+ return *this;
+ }
+
+ void Clear();
+
+ private:
+ void Assign(REFIID aIid, IUnknown* aObject);
+ void AssignInternal(IUnknown* aObject);
+
+ private:
+ IID mIid;
+ RefPtr<IAgileReference> mAgileRef;
+ RefPtr<detail::GlobalInterfaceTableCookie> mGitCookie;
+ HRESULT mHResult;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+template <typename T>
+RefPtr<T>::RefPtr(const mozilla::mscom::AgileReference& aAgileRef)
+ : mRawPtr(nullptr) {
+ (*this) = aAgileRef;
+}
+
+template <typename T>
+RefPtr<T>& RefPtr<T>::operator=(
+ const mozilla::mscom::AgileReference& aAgileRef) {
+ void* newRawPtr;
+ if (FAILED(aAgileRef.Resolve(__uuidof(T), &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+ return *this;
+}
+
+#endif // mozilla_mscom_AgileReference_h
diff --git a/ipc/mscom/ApartmentRegion.h b/ipc/mscom/ApartmentRegion.h
new file mode 100644
index 0000000000..41915710df
--- /dev/null
+++ b/ipc/mscom/ApartmentRegion.h
@@ -0,0 +1,93 @@
+/* -*- 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_ApartmentRegion_h
+#define mozilla_mscom_ApartmentRegion_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/mscom/COMWrappers.h"
+
+namespace mozilla {
+namespace mscom {
+
+class MOZ_NON_TEMPORARY_CLASS ApartmentRegion final {
+ public:
+ /**
+ * This constructor is to be used when we want to instantiate the object but
+ * we do not yet know which type of apartment we want. Call Init() to
+ * complete initialization.
+ */
+ constexpr ApartmentRegion() : mInitResult(CO_E_NOTINITIALIZED) {}
+
+ explicit ApartmentRegion(COINIT aAptType)
+ : mInitResult(wrapped::CoInitializeEx(nullptr, aAptType)) {
+ // If this fires then we're probably mixing apartments on the same thread
+ MOZ_ASSERT(IsValid());
+ }
+
+ ~ApartmentRegion() {
+ if (IsValid()) {
+ wrapped::CoUninitialize();
+ }
+ }
+
+ explicit operator bool() const { return IsValid(); }
+
+ bool IsValidOutermost() const { return mInitResult == S_OK; }
+
+ bool IsValid() const { return SUCCEEDED(mInitResult); }
+
+ bool Init(COINIT aAptType) {
+ MOZ_ASSERT(mInitResult == CO_E_NOTINITIALIZED);
+ mInitResult = wrapped::CoInitializeEx(nullptr, aAptType);
+ MOZ_ASSERT(IsValid());
+ return IsValid();
+ }
+
+ HRESULT
+ GetHResult() const { return mInitResult; }
+
+ private:
+ ApartmentRegion(const ApartmentRegion&) = delete;
+ ApartmentRegion& operator=(const ApartmentRegion&) = delete;
+ ApartmentRegion(ApartmentRegion&&) = delete;
+ ApartmentRegion& operator=(ApartmentRegion&&) = delete;
+
+ HRESULT mInitResult;
+};
+
+template <COINIT T>
+class MOZ_NON_TEMPORARY_CLASS ApartmentRegionT final {
+ public:
+ ApartmentRegionT() : mAptRgn(T) {}
+
+ ~ApartmentRegionT() = default;
+
+ explicit operator bool() const { return mAptRgn.IsValid(); }
+
+ bool IsValidOutermost() const { return mAptRgn.IsValidOutermost(); }
+
+ bool IsValid() const { return mAptRgn.IsValid(); }
+
+ HRESULT GetHResult() const { return mAptRgn.GetHResult(); }
+
+ private:
+ ApartmentRegionT(const ApartmentRegionT&) = delete;
+ ApartmentRegionT& operator=(const ApartmentRegionT&) = delete;
+ ApartmentRegionT(ApartmentRegionT&&) = delete;
+ ApartmentRegionT& operator=(ApartmentRegionT&&) = delete;
+
+ ApartmentRegion mAptRgn;
+};
+
+typedef ApartmentRegionT<COINIT_APARTMENTTHREADED> STARegion;
+typedef ApartmentRegionT<COINIT_MULTITHREADED> MTARegion;
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_ApartmentRegion_h
diff --git a/ipc/mscom/COMWrappers.cpp b/ipc/mscom/COMWrappers.cpp
new file mode 100644
index 0000000000..1bc48a4927
--- /dev/null
+++ b/ipc/mscom/COMWrappers.cpp
@@ -0,0 +1,101 @@
+/* -*- 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/. */
+
+#include "mozilla/mscom/COMWrappers.h"
+
+#include <objbase.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+
+namespace mozilla::mscom::wrapped {
+
+HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit) {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&::CoInitializeEx)>
+ pCoInitializeEx(L"combase.dll", "CoInitializeEx");
+ if (!pCoInitializeEx) {
+ return ::CoInitializeEx(pvReserved, dwCoInit);
+ }
+
+ return pCoInitializeEx(pvReserved, dwCoInit);
+}
+
+void CoUninitialize() {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&::CoUninitialize)>
+ pCoUninitialize(L"combase.dll", "CoUninitialize");
+ if (!pCoUninitialize) {
+ return ::CoUninitialize();
+ }
+
+ return pCoUninitialize();
+}
+
+HRESULT CoIncrementMTAUsage(CO_MTA_USAGE_COOKIE* pCookie) {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CoIncrementMTAUsage)>
+ pCoIncrementMTAUsage(L"combase.dll", "CoIncrementMTAUsage");
+ // This API is only available beginning with Windows 8.
+ if (!pCoIncrementMTAUsage) {
+ return E_NOTIMPL;
+ }
+
+ HRESULT hr = pCoIncrementMTAUsage(pCookie);
+ MOZ_ASSERT(SUCCEEDED(hr));
+ return hr;
+}
+
+HRESULT CoGetApartmentType(APTTYPE* pAptType, APTTYPEQUALIFIER* pAptQualifier) {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CoGetApartmentType)>
+ pCoGetApartmentType(L"combase.dll", "CoGetApartmentType");
+ if (!pCoGetApartmentType) {
+ return ::CoGetApartmentType(pAptType, pAptQualifier);
+ }
+
+ return pCoGetApartmentType(pAptType, pAptQualifier);
+}
+
+HRESULT CoInitializeSecurity(PSECURITY_DESCRIPTOR pSecDesc, LONG cAuthSvc,
+ SOLE_AUTHENTICATION_SERVICE* asAuthSvc,
+ void* pReserved1, DWORD dwAuthnLevel,
+ DWORD dwImpLevel, void* pAuthList,
+ DWORD dwCapabilities, void* pReserved3) {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CoInitializeSecurity)>
+ pCoInitializeSecurity(L"combase.dll", "CoInitializeSecurity");
+ if (!pCoInitializeSecurity) {
+ return ::CoInitializeSecurity(pSecDesc, cAuthSvc, asAuthSvc, pReserved1,
+ dwAuthnLevel, dwImpLevel, pAuthList,
+ dwCapabilities, pReserved3);
+ }
+
+ return pCoInitializeSecurity(pSecDesc, cAuthSvc, asAuthSvc, pReserved1,
+ dwAuthnLevel, dwImpLevel, pAuthList,
+ dwCapabilities, pReserved3);
+}
+
+HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter,
+ DWORD dwClsContext, REFIID riid, LPVOID* ppv) {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&::CoCreateInstance)>
+ pCoCreateInstance(L"combase.dll", "CoCreateInstance");
+ if (!pCoCreateInstance) {
+ return ::CoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv);
+ }
+
+ return pCoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv);
+}
+
+HRESULT CoCreateGuid(GUID* pguid) {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&::CoCreateGuid)>
+ pCoCreateGuid(L"combase.dll", "CoCreateGuid");
+ if (!pCoCreateGuid) {
+ return ::CoCreateGuid(pguid);
+ }
+
+ return pCoCreateGuid(pguid);
+}
+
+} // namespace mozilla::mscom::wrapped
diff --git a/ipc/mscom/COMWrappers.h b/ipc/mscom/COMWrappers.h
new file mode 100644
index 0000000000..38bef10749
--- /dev/null
+++ b/ipc/mscom/COMWrappers.h
@@ -0,0 +1,44 @@
+/* -*- 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_COMWrappers_h
+#define mozilla_mscom_COMWrappers_h
+
+#include <objbase.h>
+
+#if (NTDDI_VERSION < NTDDI_WIN8)
+// Win8+ API that we use very carefully
+DECLARE_HANDLE(CO_MTA_USAGE_COOKIE);
+HRESULT WINAPI CoIncrementMTAUsage(CO_MTA_USAGE_COOKIE* pCookie);
+#endif // (NTDDI_VERSION < NTDDI_WIN8)
+
+// A set of wrapped COM functions, so that we can dynamically link to the
+// functions in combase.dll on win8+. This prevents ole32.dll and many other
+// DLLs loading, which are not required when we have win32k locked down.
+namespace mozilla::mscom::wrapped {
+
+HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit);
+
+void CoUninitialize();
+
+HRESULT CoIncrementMTAUsage(CO_MTA_USAGE_COOKIE* pCookie);
+
+HRESULT CoGetApartmentType(APTTYPE* pAptType, APTTYPEQUALIFIER* pAptQualifier);
+
+HRESULT CoInitializeSecurity(PSECURITY_DESCRIPTOR pSecDesc, LONG cAuthSvc,
+ SOLE_AUTHENTICATION_SERVICE* asAuthSvc,
+ void* pReserved1, DWORD dwAuthnLevel,
+ DWORD dwImpLevel, void* pAuthList,
+ DWORD dwCapabilities, void* pReserved3);
+
+HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter,
+ DWORD dwClsContext, REFIID riid, LPVOID* ppv);
+
+HRESULT CoCreateGuid(GUID* pguid);
+
+} // namespace mozilla::mscom::wrapped
+
+#endif // mozilla_mscom_COMWrappers_h
diff --git a/ipc/mscom/EnsureMTA.cpp b/ipc/mscom/EnsureMTA.cpp
new file mode 100644
index 0000000000..0a84eeabf9
--- /dev/null
+++ b/ipc/mscom/EnsureMTA.cpp
@@ -0,0 +1,250 @@
+/* -*- 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/. */
+
+#include "mozilla/mscom/EnsureMTA.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/mscom/COMWrappers.h"
+#include "mozilla/mscom/ProcessRuntime.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticLocalPtr.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+
+#include "private/pprthred.h"
+
+namespace {
+
+class EnterMTARunnable : public mozilla::Runnable {
+ public:
+ EnterMTARunnable() : mozilla::Runnable("EnterMTARunnable") {}
+ NS_IMETHOD Run() override {
+ mozilla::DebugOnly<HRESULT> hr =
+ mozilla::mscom::wrapped::CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ MOZ_ASSERT(SUCCEEDED(hr));
+ return NS_OK;
+ }
+};
+
+class BackgroundMTAData {
+ public:
+ BackgroundMTAData() {
+ nsCOMPtr<nsIRunnable> runnable = new EnterMTARunnable();
+ mozilla::DebugOnly<nsresult> rv = NS_NewNamedThread(
+ "COM MTA", getter_AddRefs(mThread), runnable.forget());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_NewNamedThread failed");
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ ~BackgroundMTAData() {
+ if (mThread) {
+ mThread->Dispatch(
+ NS_NewRunnableFunction("BackgroundMTAData::~BackgroundMTAData",
+ &mozilla::mscom::wrapped::CoUninitialize),
+ NS_DISPATCH_NORMAL);
+ mThread->Shutdown();
+ }
+ }
+
+ nsCOMPtr<nsIThread> GetThread() const { return mThread; }
+
+ private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace mscom {
+
+EnsureMTA::EnsureMTA() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // It is possible that we're running so early that we might need to start
+ // the thread manager ourselves. We do this here to guarantee that we have
+ // the ability to start the persistent MTA thread at any moment beyond this
+ // point.
+ nsresult rv = nsThreadManager::get().Init();
+ // We intentionally don't check rv unless we need it when
+ // CoIncremementMTAUsage is unavailable.
+
+ // Calling this function initializes the MTA without needing to explicitly
+ // create a thread and call CoInitializeEx to do it.
+ // We don't retain the cookie because once we've incremented the MTA, we
+ // leave it that way for the lifetime of the process.
+ CO_MTA_USAGE_COOKIE mtaCookie = nullptr;
+ HRESULT hr = wrapped::CoIncrementMTAUsage(&mtaCookie);
+ if (SUCCEEDED(hr)) {
+ if (NS_SUCCEEDED(rv)) {
+ // Start the persistent MTA thread (mostly) asynchronously.
+ Unused << GetPersistentMTAThread();
+ }
+
+ return;
+ }
+
+ // In the fallback case, we simply initialize our persistent MTA thread.
+
+ // Make sure thread manager init succeeded before trying to initialize the
+ // persistent MTA thread.
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Before proceeding any further, pump a runnable through the persistent MTA
+ // thread to ensure that it is up and running and has finished initializing
+ // the multi-threaded apartment.
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ "EnsureMTA::EnsureMTA()",
+ []() { MOZ_RELEASE_ASSERT(IsCurrentThreadExplicitMTA()); }));
+ SyncDispatchToPersistentThread(runnable);
+}
+
+/* static */
+RefPtr<EnsureMTA::CreateInstanceAgileRefPromise>
+EnsureMTA::CreateInstanceInternal(REFCLSID aClsid, REFIID aIid) {
+ MOZ_ASSERT(IsCurrentThreadExplicitMTA());
+
+ RefPtr<IUnknown> iface;
+ HRESULT hr = wrapped::CoCreateInstance(aClsid, nullptr, CLSCTX_INPROC_SERVER,
+ aIid, getter_AddRefs(iface));
+ if (FAILED(hr)) {
+ return CreateInstanceAgileRefPromise::CreateAndReject(hr, __func__);
+ }
+
+ // We need to use the two argument constructor for AgileReference because our
+ // RefPtr is not parameterized on the specific interface being requested.
+ AgileReference agileRef(aIid, iface);
+ if (!agileRef) {
+ return CreateInstanceAgileRefPromise::CreateAndReject(agileRef.GetHResult(),
+ __func__);
+ }
+
+ return CreateInstanceAgileRefPromise::CreateAndResolve(std::move(agileRef),
+ __func__);
+}
+
+/* static */
+RefPtr<EnsureMTA::CreateInstanceAgileRefPromise> EnsureMTA::CreateInstance(
+ REFCLSID aClsid, REFIID aIid) {
+ MOZ_ASSERT(IsCOMInitializedOnCurrentThread());
+
+ const bool isClassOk = IsClassThreadAwareInprocServer(aClsid);
+ MOZ_ASSERT(isClassOk,
+ "mozilla::mscom::EnsureMTA::CreateInstance is not "
+ "safe/performant/necessary to use with this CLSID. This CLSID "
+ "either does not support creation from within a multithreaded "
+ "apartment, or it is not an in-process server.");
+ if (!isClassOk) {
+ return CreateInstanceAgileRefPromise::CreateAndReject(CO_E_NOT_SUPPORTED,
+ __func__);
+ }
+
+ if (IsCurrentThreadExplicitMTA()) {
+ // It's safe to immediately call CreateInstanceInternal
+ return CreateInstanceInternal(aClsid, aIid);
+ }
+
+ // aClsid and aIid are references. Make local copies that we can put into the
+ // lambda in case the sources of aClsid or aIid are not static data
+ CLSID localClsid = aClsid;
+ IID localIid = aIid;
+
+ auto invoker = [localClsid,
+ localIid]() -> RefPtr<CreateInstanceAgileRefPromise> {
+ return CreateInstanceInternal(localClsid, localIid);
+ };
+
+ nsCOMPtr<nsIThread> mtaThread(GetPersistentMTAThread());
+
+ return InvokeAsync(mtaThread, __func__, std::move(invoker));
+}
+
+/* static */
+nsCOMPtr<nsIThread> EnsureMTA::GetPersistentMTAThread() {
+ static StaticLocalAutoPtr<BackgroundMTAData> sMTAData(
+ []() -> BackgroundMTAData* {
+ BackgroundMTAData* bgData = new BackgroundMTAData();
+
+ auto setClearOnShutdown = [ptr = &sMTAData]() -> void {
+ ClearOnShutdown(ptr, ShutdownPhase::XPCOMShutdownThreads);
+ };
+
+ if (NS_IsMainThread()) {
+ setClearOnShutdown();
+ return bgData;
+ }
+
+ SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction("mscom::EnsureMTA::GetPersistentMTAThread",
+ std::move(setClearOnShutdown)));
+
+ return bgData;
+ }());
+
+ MOZ_ASSERT(sMTAData);
+
+ return sMTAData->GetThread();
+}
+
+/* static */
+void EnsureMTA::SyncDispatchToPersistentThread(nsIRunnable* aRunnable) {
+ nsCOMPtr<nsIThread> thread(GetPersistentMTAThread());
+ MOZ_ASSERT(thread);
+ if (!thread) {
+ return;
+ }
+
+ // Note that, due to APC dispatch, we might reenter this function while we
+ // wait on this event. We therefore need a unique event object for each
+ // entry into this function. If perf becomes an issue then we will want to
+ // maintain an array of events where the Nth event is unique to the Nth
+ // reentry.
+ nsAutoHandle event(::CreateEventW(nullptr, FALSE, FALSE, nullptr));
+ if (!event) {
+ return;
+ }
+
+ HANDLE eventHandle = event.get();
+ auto eventSetter = [&aRunnable, eventHandle]() -> void {
+ aRunnable->Run();
+ ::SetEvent(eventHandle);
+ };
+
+ nsresult rv = thread->Dispatch(
+ NS_NewRunnableFunction("mscom::EnsureMTA::SyncDispatchToPersistentThread",
+ std::move(eventSetter)),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ AUTO_PROFILER_THREAD_SLEEP;
+ DWORD waitResult;
+ while ((waitResult = ::WaitForSingleObjectEx(event, INFINITE, FALSE)) ==
+ WAIT_IO_COMPLETION) {
+ }
+ MOZ_ASSERT(waitResult == WAIT_OBJECT_0);
+}
+
+/**
+ * While this function currently appears to be redundant, it may become more
+ * sophisticated in the future. For example, we could optionally dispatch to an
+ * MTA context if we wanted to utilize the MTA thread pool.
+ */
+/* static */
+void EnsureMTA::SyncDispatch(nsCOMPtr<nsIRunnable>&& aRunnable, Option aOpt) {
+ SyncDispatchToPersistentThread(aRunnable);
+}
+
+} // namespace mscom
+} // namespace mozilla
diff --git a/ipc/mscom/EnsureMTA.h b/ipc/mscom/EnsureMTA.h
new file mode 100644
index 0000000000..5e410d4daa
--- /dev/null
+++ b/ipc/mscom/EnsureMTA.h
@@ -0,0 +1,191 @@
+/* -*- 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_EnsureMTA_h
+#define mozilla_mscom_EnsureMTA_h
+
+#include "MainThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/Unused.h"
+#include "mozilla/mscom/AgileReference.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsWindowsHelpers.h"
+
+#include <windows.h>
+
+namespace mozilla {
+namespace mscom {
+namespace detail {
+
+// Forward declarations
+template <typename T>
+struct MTADelete;
+
+template <typename T>
+struct MTARelease;
+
+template <typename T>
+struct MTAReleaseInChildProcess;
+
+struct PreservedStreamDeleter;
+
+} // namespace detail
+
+class ProcessRuntime;
+
+// This class is OK to use as a temporary on the stack.
+class MOZ_STACK_CLASS EnsureMTA final {
+ public:
+ enum class Option {
+ Default,
+ // Forcibly dispatch to the thread returned by GetPersistentMTAThread(),
+ // even if the current thread is already inside a MTA.
+ ForceDispatchToPersistentThread,
+ };
+
+ /**
+ * Synchronously run |aClosure| on a thread living in the COM multithreaded
+ * apartment. If the current thread lives inside the COM MTA, then it runs
+ * |aClosure| immediately unless |aOpt| ==
+ * Option::ForceDispatchToPersistentThread.
+ */
+ template <typename FuncT>
+ explicit EnsureMTA(FuncT&& aClosure, Option aOpt = Option::Default) {
+ if (aOpt != Option::ForceDispatchToPersistentThread &&
+ IsCurrentThreadMTA()) {
+ // We're already on the MTA, we can run aClosure directly
+ aClosure();
+ return;
+ }
+
+ // In this case we need to run aClosure on a background thread in the MTA
+ nsCOMPtr<nsIRunnable> runnable(
+ NS_NewRunnableFunction("EnsureMTA::EnsureMTA", std::move(aClosure)));
+ SyncDispatch(std::move(runnable), aOpt);
+ }
+
+ using CreateInstanceAgileRefPromise =
+ MozPromise<AgileReference, HRESULT, false>;
+
+ /**
+ * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
+ *
+ * Asynchronously instantiate a new COM object from a MTA thread, unless the
+ * current thread is already living inside the multithreaded apartment, in
+ * which case the object is immediately instantiated.
+ *
+ * This function only supports the most common configurations for creating
+ * a new object, so it only supports in-process servers. Furthermore, this
+ * function does not support aggregation (ie. the |pUnkOuter| parameter to
+ * CoCreateInstance).
+ *
+ * Given that attempting to instantiate an Apartment-threaded COM object
+ * inside the MTA results in a *loss* of performance, we assert when that
+ * situation arises.
+ *
+ * The resulting promise, once resolved, provides an AgileReference that may
+ * be passed between any COM-initialized thread in the current process.
+ *
+ * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
+ *
+ * WARNING:
+ * Some COM objects do not support creation in the multithreaded apartment,
+ * in which case this function is not available as an option. In this case,
+ * the promise will always be rejected. In debug builds we will assert.
+ *
+ * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
+ *
+ * WARNING:
+ * Any in-process COM objects whose interfaces accept HWNDs are probably
+ * *not* safe to instantiate in the multithreaded apartment! Even if this
+ * function succeeds when creating such an object, you *MUST NOT* do so, as
+ * these failures might not become apparent until your code is running out in
+ * the wild on the release channel!
+ *
+ * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
+ *
+ * WARNING:
+ * When you obtain an interface from the AgileReference, it may or may not be
+ * a proxy to the real object. This depends entirely on the implementation of
+ * the underlying class and the multithreading capabilities that the class
+ * declares to the COM runtime. If the interface is proxied, it might be
+ * expensive to invoke methods on that interface! *Always* test the
+ * performance of your method calls when calling interfaces that are resolved
+ * via this function!
+ *
+ * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
+ *
+ * (Despite this myriad of warnings, it is still *much* safer to use this
+ * function to asynchronously create COM objects than it is to roll your own!)
+ *
+ * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
+ */
+ static RefPtr<CreateInstanceAgileRefPromise> CreateInstance(REFCLSID aClsid,
+ REFIID aIid);
+
+ private:
+ static RefPtr<CreateInstanceAgileRefPromise> CreateInstanceInternal(
+ REFCLSID aClsid, REFIID aIid);
+
+ static nsCOMPtr<nsIThread> GetPersistentMTAThread();
+
+ static void SyncDispatch(nsCOMPtr<nsIRunnable>&& aRunnable, Option aOpt);
+ static void SyncDispatchToPersistentThread(nsIRunnable* aRunnable);
+
+ // The following function is private in order to force any consumers to be
+ // declared as friends of EnsureMTA. The intention is to prevent
+ // AsyncOperation from becoming some kind of free-for-all mechanism for
+ // asynchronously executing work on a background thread.
+ template <typename FuncT>
+ static void AsyncOperation(FuncT&& aClosure) {
+ if (IsCurrentThreadMTA()) {
+ aClosure();
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread(GetPersistentMTAThread());
+ MOZ_ASSERT(thread);
+ if (!thread) {
+ return;
+ }
+
+ DebugOnly<nsresult> rv = thread->Dispatch(
+ NS_NewRunnableFunction("mscom::EnsureMTA::AsyncOperation",
+ std::move(aClosure)),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ /**
+ * This constructor just ensures that the MTA is up and running. This should
+ * only be called by ProcessRuntime.
+ */
+ EnsureMTA();
+
+ friend class mozilla::mscom::ProcessRuntime;
+
+ template <typename T>
+ friend struct mozilla::mscom::detail::MTADelete;
+
+ template <typename T>
+ friend struct mozilla::mscom::detail::MTARelease;
+
+ template <typename T>
+ friend struct mozilla::mscom::detail::MTAReleaseInChildProcess;
+
+ friend struct mozilla::mscom::detail::PreservedStreamDeleter;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_EnsureMTA_h
diff --git a/ipc/mscom/ProcessRuntime.cpp b/ipc/mscom/ProcessRuntime.cpp
new file mode 100644
index 0000000000..f20b0658bb
--- /dev/null
+++ b/ipc/mscom/ProcessRuntime.cpp
@@ -0,0 +1,458 @@
+/* -*- 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/. */
+
+#include "mozilla/mscom/ProcessRuntime.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/mscom/COMWrappers.h"
+#include "mozilla/mscom/ProcessRuntimeShared.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WindowsProcessMitigations.h"
+#include "mozilla/WindowsVersion.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "mozilla/mscom/EnsureMTA.h"
+# if defined(MOZ_SANDBOX)
+# include "mozilla/sandboxTarget.h"
+# endif // defined(MOZ_SANDBOX)
+#endif // defined(MOZILLA_INTERNAL_API)
+
+#include <accctrl.h>
+#include <aclapi.h>
+#include <objbase.h>
+#include <objidl.h>
+
+// This API from oleaut32.dll is not declared in Windows SDK headers
+extern "C" void __cdecl SetOaNoCache(void);
+
+using namespace mozilla::mscom::detail;
+
+namespace mozilla {
+namespace mscom {
+
+#if defined(MOZILLA_INTERNAL_API)
+ProcessRuntime* ProcessRuntime::sInstance = nullptr;
+
+ProcessRuntime::ProcessRuntime() : ProcessRuntime(XRE_GetProcessType()) {}
+
+ProcessRuntime::ProcessRuntime(const GeckoProcessType aProcessType)
+ : ProcessRuntime(aProcessType == GeckoProcessType_Default
+ ? ProcessCategory::GeckoBrowserParent
+ : ProcessCategory::GeckoChild) {}
+#endif // defined(MOZILLA_INTERNAL_API)
+
+ProcessRuntime::ProcessRuntime(const ProcessCategory aProcessCategory)
+ : mInitResult(CO_E_NOTINITIALIZED), mProcessCategory(aProcessCategory) {
+#if defined(MOZILLA_INTERNAL_API)
+ MOZ_DIAGNOSTIC_ASSERT(!sInstance);
+ sInstance = this;
+
+ EnsureMTA();
+ /**
+ * From this point forward, all threads in this process are implicitly
+ * members of the multi-threaded apartment, with the following exceptions:
+ * 1. If any Win32 GUI APIs were called on the current thread prior to
+ * executing this constructor, then this thread has already been implicitly
+ * initialized as the process's main STA thread; or
+ * 2. A thread explicitly and successfully calls CoInitialize(Ex) to specify
+ * otherwise.
+ */
+
+ const bool isCurThreadImplicitMTA = IsCurrentThreadImplicitMTA();
+ // We only assert that the implicit MTA precondition holds when not running
+ // as the Gecko parent process.
+ MOZ_DIAGNOSTIC_ASSERT(aProcessCategory ==
+ ProcessCategory::GeckoBrowserParent ||
+ isCurThreadImplicitMTA);
+
+# if defined(MOZ_SANDBOX)
+ const bool isLockedDownChildProcess =
+ mProcessCategory == ProcessCategory::GeckoChild && IsWin32kLockedDown();
+ // If our process is running under Win32k lockdown, we cannot initialize
+ // COM with a single-threaded apartment. This is because STAs create a hidden
+ // window, which implicitly requires user32 and Win32k, which are blocked.
+ // Instead we start the multi-threaded apartment and conduct our process-wide
+ // COM initialization there.
+ if (isLockedDownChildProcess) {
+ // Make sure we're still running with the sandbox's privileged impersonation
+ // token.
+ HANDLE rawCurThreadImpToken;
+ if (!::OpenThreadToken(::GetCurrentThread(), TOKEN_DUPLICATE | TOKEN_QUERY,
+ FALSE, &rawCurThreadImpToken)) {
+ mInitResult = HRESULT_FROM_WIN32(::GetLastError());
+ return;
+ }
+ nsAutoHandle curThreadImpToken(rawCurThreadImpToken);
+
+ // Ensure that our current token is still an impersonation token (ie, we
+ // have not yet called RevertToSelf() on this thread).
+ DWORD len;
+ TOKEN_TYPE tokenType;
+ MOZ_RELEASE_ASSERT(
+ ::GetTokenInformation(rawCurThreadImpToken, TokenType, &tokenType,
+ sizeof(tokenType), &len) &&
+ len == sizeof(tokenType) && tokenType == TokenImpersonation);
+
+ // Ideally we want our current thread to be running implicitly inside the
+ // MTA, but if for some wacky reason we did not end up with that, we may
+ // compensate by completing initialization via EnsureMTA's persistent
+ // thread.
+ if (!isCurThreadImplicitMTA) {
+ InitUsingPersistentMTAThread(curThreadImpToken);
+ return;
+ }
+ }
+# endif // defined(MOZ_SANDBOX)
+#endif // defined(MOZILLA_INTERNAL_API)
+
+ mAptRegion.Init(GetDesiredApartmentType(mProcessCategory));
+
+ // It can happen that we are not the outermost COM initialization on this
+ // thread. In fact it should regularly be the case that the outermost
+ // initialization occurs from outside of XUL, before we show the skeleton UI,
+ // at which point we still need to run some things here from within XUL.
+ if (!mAptRegion.IsValidOutermost()) {
+ mInitResult = mAptRegion.GetHResult();
+#if defined(MOZILLA_INTERNAL_API)
+ MOZ_ASSERT(mProcessCategory == ProcessCategory::GeckoBrowserParent);
+ if (mProcessCategory != ProcessCategory::GeckoBrowserParent) {
+ // This is unexpected unless we're GeckoBrowserParent
+ return;
+ }
+
+ ProcessInitLock lock;
+
+ // Is another instance of ProcessRuntime responsible for the outer
+ // initialization?
+ const bool prevInit =
+ lock.GetInitState() == ProcessInitState::FullyInitialized;
+ MOZ_ASSERT(prevInit);
+ if (prevInit) {
+ PostInit();
+ }
+#endif // defined(MOZILLA_INTERNAL_API)
+ return;
+ }
+
+ InitInsideApartment();
+ if (FAILED(mInitResult)) {
+ return;
+ }
+
+#if defined(MOZILLA_INTERNAL_API)
+# if defined(MOZ_SANDBOX)
+ if (isLockedDownChildProcess) {
+ // In locked-down child processes, defer PostInit until priv drop
+ SandboxTarget::Instance()->RegisterSandboxStartCallback([self = this]() {
+ // Ensure that we're still live and the init was successful before
+ // calling PostInit()
+ if (self == sInstance && SUCCEEDED(self->mInitResult)) {
+ PostInit();
+ }
+ });
+ return;
+ }
+# endif // defined(MOZ_SANDBOX)
+
+ PostInit();
+#endif // defined(MOZILLA_INTERNAL_API)
+}
+
+#if defined(MOZILLA_INTERNAL_API)
+ProcessRuntime::~ProcessRuntime() {
+ MOZ_DIAGNOSTIC_ASSERT(sInstance == this);
+ sInstance = nullptr;
+}
+
+# if defined(MOZ_SANDBOX)
+void ProcessRuntime::InitUsingPersistentMTAThread(
+ const nsAutoHandle& aCurThreadToken) {
+ // Create an impersonation token based on the current thread's token
+ HANDLE rawMtaThreadImpToken = nullptr;
+ if (!::DuplicateToken(aCurThreadToken, SecurityImpersonation,
+ &rawMtaThreadImpToken)) {
+ mInitResult = HRESULT_FROM_WIN32(::GetLastError());
+ return;
+ }
+ nsAutoHandle mtaThreadImpToken(rawMtaThreadImpToken);
+
+ // Impersonate and initialize.
+ bool tokenSet = false;
+ EnsureMTA(
+ [this, rawMtaThreadImpToken, &tokenSet]() -> void {
+ if (!::SetThreadToken(nullptr, rawMtaThreadImpToken)) {
+ mInitResult = HRESULT_FROM_WIN32(::GetLastError());
+ return;
+ }
+
+ tokenSet = true;
+ InitInsideApartment();
+ },
+ EnsureMTA::Option::ForceDispatchToPersistentThread);
+
+ if (!tokenSet) {
+ return;
+ }
+
+ SandboxTarget::Instance()->RegisterSandboxStartCallback(
+ [self = this]() -> void {
+ EnsureMTA(
+ []() -> void {
+ // This is a security risk if it fails, so we release assert
+ MOZ_RELEASE_ASSERT(::RevertToSelf(),
+ "mscom::ProcessRuntime RevertToSelf failed");
+ },
+ EnsureMTA::Option::ForceDispatchToPersistentThread);
+
+ // Ensure that we're still live and the init was successful before
+ // calling PostInit()
+ if (self == sInstance && SUCCEEDED(self->mInitResult)) {
+ PostInit();
+ }
+ });
+}
+# endif // defined(MOZ_SANDBOX)
+#endif // defined(MOZILLA_INTERNAL_API)
+
+/* static */
+COINIT ProcessRuntime::GetDesiredApartmentType(
+ const ProcessRuntime::ProcessCategory aProcessCategory) {
+ switch (aProcessCategory) {
+ case ProcessCategory::GeckoBrowserParent:
+ return COINIT_APARTMENTTHREADED;
+ case ProcessCategory::GeckoChild:
+ if (!IsWin32kLockedDown()) {
+ // If Win32k is not locked down then we probably still need STA.
+ // We disable DDE since that is not usable from child processes.
+ return static_cast<COINIT>(COINIT_APARTMENTTHREADED |
+ COINIT_DISABLE_OLE1DDE);
+ }
+
+ [[fallthrough]];
+ default:
+ return COINIT_MULTITHREADED;
+ }
+}
+
+void ProcessRuntime::InitInsideApartment() {
+ ProcessInitLock lock;
+ const ProcessInitState prevInitState = lock.GetInitState();
+ if (prevInitState == ProcessInitState::FullyInitialized) {
+ // COM has already been initialized by a previous ProcessRuntime instance
+ mInitResult = S_OK;
+ return;
+ }
+
+ if (prevInitState < ProcessInitState::PartialSecurityInitialized) {
+ // We are required to initialize security prior to configuring global
+ // options.
+ mInitResult = InitializeSecurity(mProcessCategory);
+ MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(mInitResult));
+
+ // Even though this isn't great, we should try to proceed even when
+ // CoInitializeSecurity has previously been called: the additional settings
+ // we want to change are important enough that we don't want to skip them.
+ if (FAILED(mInitResult) && mInitResult != RPC_E_TOO_LATE) {
+ return;
+ }
+
+ lock.SetInitState(ProcessInitState::PartialSecurityInitialized);
+ }
+
+ if (prevInitState < ProcessInitState::PartialGlobalOptions) {
+ RefPtr<IGlobalOptions> globalOpts;
+ mInitResult = wrapped::CoCreateInstance(
+ CLSID_GlobalOptions, nullptr, CLSCTX_INPROC_SERVER, IID_IGlobalOptions,
+ getter_AddRefs(globalOpts));
+ MOZ_ASSERT(SUCCEEDED(mInitResult));
+ if (FAILED(mInitResult)) {
+ return;
+ }
+
+ // Disable COM's catch-all exception handler
+ mInitResult = globalOpts->Set(COMGLB_EXCEPTION_HANDLING,
+ COMGLB_EXCEPTION_DONOT_HANDLE_ANY);
+ MOZ_ASSERT(SUCCEEDED(mInitResult));
+ if (FAILED(mInitResult)) {
+ return;
+ }
+
+ lock.SetInitState(ProcessInitState::PartialGlobalOptions);
+ }
+
+ // Disable the BSTR cache (as it never invalidates, thus leaking memory)
+ // (This function is itself idempotent, so we do not concern ourselves with
+ // tracking whether or not we've already called it.)
+ ::SetOaNoCache();
+
+ lock.SetInitState(ProcessInitState::FullyInitialized);
+}
+
+#if defined(MOZILLA_INTERNAL_API)
+/**
+ * Guaranteed to run *after* the COM (and possible sandboxing) initialization
+ * has successfully completed and stabilized. This method MUST BE IDEMPOTENT!
+ */
+/* static */ void ProcessRuntime::PostInit() {
+ // Currently "roughed-in" but unused.
+}
+#endif // defined(MOZILLA_INTERNAL_API)
+
+/* static */
+DWORD
+ProcessRuntime::GetClientThreadId() {
+ DWORD callerTid;
+ HRESULT hr = ::CoGetCallerTID(&callerTid);
+ // Don't return callerTid unless the call succeeded and returned S_FALSE,
+ // indicating that the caller originates from a different process.
+ if (hr != S_FALSE) {
+ return 0;
+ }
+
+ return callerTid;
+}
+
+/* static */
+HRESULT
+ProcessRuntime::InitializeSecurity(const ProcessCategory aProcessCategory) {
+ HANDLE rawToken = nullptr;
+ BOOL ok = ::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &rawToken);
+ if (!ok) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+ nsAutoHandle token(rawToken);
+
+ DWORD len = 0;
+ ok = ::GetTokenInformation(token, TokenUser, nullptr, len, &len);
+ DWORD win32Error = ::GetLastError();
+ if (!ok && win32Error != ERROR_INSUFFICIENT_BUFFER) {
+ return HRESULT_FROM_WIN32(win32Error);
+ }
+
+ auto tokenUserBuf = MakeUnique<BYTE[]>(len);
+ TOKEN_USER& tokenUser = *reinterpret_cast<TOKEN_USER*>(tokenUserBuf.get());
+ ok = ::GetTokenInformation(token, TokenUser, tokenUserBuf.get(), len, &len);
+ if (!ok) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ len = 0;
+ ok = ::GetTokenInformation(token, TokenPrimaryGroup, nullptr, len, &len);
+ win32Error = ::GetLastError();
+ if (!ok && win32Error != ERROR_INSUFFICIENT_BUFFER) {
+ return HRESULT_FROM_WIN32(win32Error);
+ }
+
+ auto tokenPrimaryGroupBuf = MakeUnique<BYTE[]>(len);
+ TOKEN_PRIMARY_GROUP& tokenPrimaryGroup =
+ *reinterpret_cast<TOKEN_PRIMARY_GROUP*>(tokenPrimaryGroupBuf.get());
+ ok = ::GetTokenInformation(token, TokenPrimaryGroup,
+ tokenPrimaryGroupBuf.get(), len, &len);
+ if (!ok) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ SECURITY_DESCRIPTOR sd;
+ if (!::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ BYTE systemSid[SECURITY_MAX_SID_SIZE];
+ DWORD systemSidSize = sizeof(systemSid);
+ if (!::CreateWellKnownSid(WinLocalSystemSid, nullptr, systemSid,
+ &systemSidSize)) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ BYTE adminSid[SECURITY_MAX_SID_SIZE];
+ DWORD adminSidSize = sizeof(adminSid);
+ if (!::CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, adminSid,
+ &adminSidSize)) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ const bool allowAppContainers =
+ aProcessCategory == ProcessCategory::GeckoBrowserParent &&
+ IsWin8OrLater();
+
+ BYTE appContainersSid[SECURITY_MAX_SID_SIZE];
+ DWORD appContainersSidSize = sizeof(appContainersSid);
+ if (allowAppContainers) {
+ if (!::CreateWellKnownSid(WinBuiltinAnyPackageSid, nullptr,
+ appContainersSid, &appContainersSidSize)) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+ }
+
+ // Grant access to SYSTEM, Administrators, the user, and when running as the
+ // browser process on Windows 8+, all app containers.
+ const size_t kMaxInlineEntries = 4;
+ mozilla::Vector<EXPLICIT_ACCESS_W, kMaxInlineEntries> entries;
+
+ Unused << entries.append(EXPLICIT_ACCESS_W{
+ COM_RIGHTS_EXECUTE,
+ GRANT_ACCESS,
+ NO_INHERITANCE,
+ {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER,
+ reinterpret_cast<LPWSTR>(systemSid)}});
+
+ Unused << entries.append(EXPLICIT_ACCESS_W{
+ COM_RIGHTS_EXECUTE,
+ GRANT_ACCESS,
+ NO_INHERITANCE,
+ {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID,
+ TRUSTEE_IS_WELL_KNOWN_GROUP, reinterpret_cast<LPWSTR>(adminSid)}});
+
+ Unused << entries.append(EXPLICIT_ACCESS_W{
+ COM_RIGHTS_EXECUTE,
+ GRANT_ACCESS,
+ NO_INHERITANCE,
+ {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER,
+ reinterpret_cast<LPWSTR>(tokenUser.User.Sid)}});
+
+ if (allowAppContainers) {
+ Unused << entries.append(
+ EXPLICIT_ACCESS_W{COM_RIGHTS_EXECUTE,
+ GRANT_ACCESS,
+ NO_INHERITANCE,
+ {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID,
+ TRUSTEE_IS_WELL_KNOWN_GROUP,
+ reinterpret_cast<LPWSTR>(appContainersSid)}});
+ }
+
+ PACL rawDacl = nullptr;
+ win32Error =
+ ::SetEntriesInAclW(entries.length(), entries.begin(), nullptr, &rawDacl);
+ if (win32Error != ERROR_SUCCESS) {
+ return HRESULT_FROM_WIN32(win32Error);
+ }
+
+ UniquePtr<ACL, LocalFreeDeleter> dacl(rawDacl);
+
+ if (!::SetSecurityDescriptorDacl(&sd, TRUE, dacl.get(), FALSE)) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ if (!::SetSecurityDescriptorOwner(&sd, tokenUser.User.Sid, FALSE)) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ if (!::SetSecurityDescriptorGroup(&sd, tokenPrimaryGroup.PrimaryGroup,
+ FALSE)) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ return wrapped::CoInitializeSecurity(
+ &sd, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT,
+ RPC_C_IMP_LEVEL_IDENTIFY, nullptr, EOAC_NONE, nullptr);
+}
+
+} // namespace mscom
+} // namespace mozilla
diff --git a/ipc/mscom/ProcessRuntime.h b/ipc/mscom/ProcessRuntime.h
new file mode 100644
index 0000000000..568281f360
--- /dev/null
+++ b/ipc/mscom/ProcessRuntime.h
@@ -0,0 +1,87 @@
+/* -*- 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_ProcessRuntime_h
+#define mozilla_mscom_ProcessRuntime_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/mscom/ApartmentRegion.h"
+#include "nsWindowsHelpers.h"
+#if defined(MOZILLA_INTERNAL_API)
+# include "nsXULAppAPI.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+namespace mozilla {
+namespace mscom {
+
+class MOZ_NON_TEMPORARY_CLASS ProcessRuntime final {
+#if !defined(MOZILLA_INTERNAL_API)
+ public:
+#endif // defined(MOZILLA_INTERNAL_API)
+ enum class ProcessCategory {
+ GeckoBrowserParent,
+ // We give Launcher its own process category, but internally to this class
+ // it should be treated identically to GeckoBrowserParent.
+ Launcher = GeckoBrowserParent,
+ GeckoChild,
+ Service,
+ };
+
+ // This constructor is only public when compiled outside of XUL
+ explicit ProcessRuntime(const ProcessCategory aProcessCategory);
+
+ public:
+#if defined(MOZILLA_INTERNAL_API)
+ ProcessRuntime();
+ ~ProcessRuntime();
+#else
+ ~ProcessRuntime() = default;
+#endif // defined(MOZILLA_INTERNAL_API)
+
+ explicit operator bool() const { return SUCCEEDED(mInitResult); }
+ HRESULT GetHResult() const { return mInitResult; }
+
+ ProcessRuntime(const ProcessRuntime&) = delete;
+ ProcessRuntime(ProcessRuntime&&) = delete;
+ ProcessRuntime& operator=(const ProcessRuntime&) = delete;
+ ProcessRuntime& operator=(ProcessRuntime&&) = delete;
+
+ /**
+ * @return 0 if call is in-process or resolving the calling thread failed,
+ * otherwise contains the thread id of the calling thread.
+ */
+ static DWORD GetClientThreadId();
+
+ private:
+#if defined(MOZILLA_INTERNAL_API)
+ explicit ProcessRuntime(const GeckoProcessType aProcessType);
+# if defined(MOZ_SANDBOX)
+ void InitUsingPersistentMTAThread(const nsAutoHandle& aCurThreadToken);
+# endif // defined(MOZ_SANDBOX)
+#endif // defined(MOZILLA_INTERNAL_API)
+ void InitInsideApartment();
+
+#if defined(MOZILLA_INTERNAL_API)
+ static void PostInit();
+#endif // defined(MOZILLA_INTERNAL_API)
+ static HRESULT InitializeSecurity(const ProcessCategory aProcessCategory);
+ static COINIT GetDesiredApartmentType(const ProcessCategory aProcessCategory);
+
+ private:
+ HRESULT mInitResult;
+ const ProcessCategory mProcessCategory;
+ ApartmentRegion mAptRegion;
+
+ private:
+#if defined(MOZILLA_INTERNAL_API)
+ static ProcessRuntime* sInstance;
+#endif // defined(MOZILLA_INTERNAL_API)
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_ProcessRuntime_h
diff --git a/ipc/mscom/ProfilerMarkers.cpp b/ipc/mscom/ProfilerMarkers.cpp
new file mode 100644
index 0000000000..29d033723f
--- /dev/null
+++ b/ipc/mscom/ProfilerMarkers.cpp
@@ -0,0 +1,236 @@
+/* -*- 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/. */
+
+#include "ProfilerMarkers.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/Services.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+
+#include <objbase.h>
+#include <objidlbase.h>
+
+// {9DBE6B28-E5E7-4FDE-AF00-9404604E74DC}
+static const GUID GUID_MozProfilerMarkerExtension = {
+ 0x9dbe6b28, 0xe5e7, 0x4fde, {0xaf, 0x0, 0x94, 0x4, 0x60, 0x4e, 0x74, 0xdc}};
+
+namespace {
+
+class ProfilerMarkerChannelHook final : public IChannelHook {
+ ~ProfilerMarkerChannelHook() = default;
+
+ public:
+ ProfilerMarkerChannelHook() : mRefCnt(0) {}
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override;
+ STDMETHODIMP_(ULONG) AddRef() override;
+ STDMETHODIMP_(ULONG) Release() override;
+
+ /**
+ * IChannelHook exposes six methods: The Client* methods are called when
+ * a client is sending an IPC request, whereas the Server* methods are called
+ * when a server is receiving an IPC request.
+ *
+ * For our purposes, we only care about the client-side methods. The COM
+ * runtime invokes the methods in the following order:
+ * 1. ClientGetSize, where the hook specifies the size of its payload;
+ * 2. ClientFillBuffer, where the hook fills the channel's buffer with its
+ * payload information. NOTE: This method is only called when ClientGetSize
+ * specifies a non-zero payload size. For our purposes, since we are not
+ * sending a payload, this method will never be called!
+ * 3. ClientNotify, when the response has been received from the server.
+ *
+ * Since we want to use these hooks to record the beginning and end of a COM
+ * IPC call, we use ClientGetSize for logging the start, and ClientNotify for
+ * logging the end.
+ *
+ * Finally, our implementation responds to any request matching our extension
+ * ID, however we only care about main thread COM calls.
+ */
+
+ // IChannelHook
+ STDMETHODIMP_(void)
+ ClientGetSize(REFGUID aExtensionId, REFIID aIid,
+ ULONG* aOutDataSize) override;
+
+ // No-op (see the large comment above)
+ STDMETHODIMP_(void)
+ ClientFillBuffer(REFGUID aExtensionId, REFIID aIid, ULONG* aDataSize,
+ void* aDataBuf) override {}
+
+ STDMETHODIMP_(void)
+ ClientNotify(REFGUID aExtensionId, REFIID aIid, ULONG aDataSize,
+ void* aDataBuffer, DWORD aDataRep, HRESULT aFault) override;
+
+ // We don't care about the server-side notifications, so leave as no-ops.
+ STDMETHODIMP_(void)
+ ServerNotify(REFGUID aExtensionId, REFIID aIid, ULONG aDataSize,
+ void* aDataBuf, DWORD aDataRep) override {}
+
+ STDMETHODIMP_(void)
+ ServerGetSize(REFGUID aExtensionId, REFIID aIid, HRESULT aFault,
+ ULONG* aOutDataSize) override {}
+
+ STDMETHODIMP_(void)
+ ServerFillBuffer(REFGUID aExtensionId, REFIID aIid, ULONG* aDataSize,
+ void* aDataBuf, HRESULT aFault) override {}
+
+ private:
+ void BuildMarkerName(REFIID aIid, nsACString& aOutMarkerName);
+
+ private:
+ mozilla::Atomic<ULONG> mRefCnt;
+};
+
+HRESULT ProfilerMarkerChannelHook::QueryInterface(REFIID aIid,
+ void** aOutInterface) {
+ if (aIid == IID_IChannelHook || aIid == IID_IUnknown) {
+ RefPtr<IChannelHook> ptr(this);
+ ptr.forget(aOutInterface);
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+ULONG ProfilerMarkerChannelHook::AddRef() { return ++mRefCnt; }
+
+ULONG ProfilerMarkerChannelHook::Release() {
+ ULONG result = --mRefCnt;
+ if (!result) {
+ delete this;
+ }
+
+ return result;
+}
+
+void ProfilerMarkerChannelHook::BuildMarkerName(REFIID aIid,
+ nsACString& aOutMarkerName) {
+ aOutMarkerName.AssignLiteral("ORPC Call for ");
+
+ nsAutoCString iidStr;
+ mozilla::mscom::DiagnosticNameForIID(aIid, iidStr);
+ aOutMarkerName.Append(iidStr);
+}
+
+void ProfilerMarkerChannelHook::ClientGetSize(REFGUID aExtensionId, REFIID aIid,
+ ULONG* aOutDataSize) {
+ if (aExtensionId == GUID_MozProfilerMarkerExtension) {
+ if (NS_IsMainThread()) {
+ nsAutoCString markerName;
+ BuildMarkerName(aIid, markerName);
+ PROFILER_MARKER(markerName, IPC, mozilla::MarkerTiming::IntervalStart(),
+ Tracing, "MSCOM");
+ }
+
+ if (aOutDataSize) {
+ // We don't add any payload data to the channel
+ *aOutDataSize = 0UL;
+ }
+ }
+}
+
+void ProfilerMarkerChannelHook::ClientNotify(REFGUID aExtensionId, REFIID aIid,
+ ULONG aDataSize, void* aDataBuffer,
+ DWORD aDataRep, HRESULT aFault) {
+ if (NS_IsMainThread() && aExtensionId == GUID_MozProfilerMarkerExtension) {
+ nsAutoCString markerName;
+ BuildMarkerName(aIid, markerName);
+ PROFILER_MARKER(markerName, IPC, mozilla::MarkerTiming::IntervalEnd(),
+ Tracing, "MSCOM");
+ }
+}
+
+} // anonymous namespace
+
+static void RegisterChannelHook() {
+ RefPtr<ProfilerMarkerChannelHook> hook(new ProfilerMarkerChannelHook());
+ mozilla::DebugOnly<HRESULT> hr =
+ ::CoRegisterChannelHook(GUID_MozProfilerMarkerExtension, hook);
+ MOZ_ASSERT(SUCCEEDED(hr));
+}
+
+namespace {
+
+class ProfilerStartupObserver final : public nsIObserver {
+ ~ProfilerStartupObserver() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+};
+
+NS_IMPL_ISUPPORTS(ProfilerStartupObserver, nsIObserver)
+
+NS_IMETHODIMP ProfilerStartupObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (strcmp(aTopic, "profiler-started")) {
+ return NS_OK;
+ }
+
+ RegisterChannelHook();
+
+ // Once we've set the channel hook, we don't care about this notification
+ // anymore; our channel hook will remain set for the lifetime of the process.
+ nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
+ MOZ_ASSERT(!!obsServ);
+ if (!obsServ) {
+ return NS_OK;
+ }
+
+ obsServ->RemoveObserver(this, "profiler-started");
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace mscom {
+
+void InitProfilerMarkers() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ if (profiler_is_active()) {
+ // If the profiler is already running, we'll immediately register our
+ // channel hook.
+ RegisterChannelHook();
+ return;
+ }
+
+ // The profiler is not running yet. To avoid unnecessary invocations of the
+ // channel hook, we won't bother with installing it until the profiler starts.
+ // Set up an observer to watch for this.
+ nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
+ MOZ_ASSERT(!!obsServ);
+ if (!obsServ) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserver> obs(new ProfilerStartupObserver());
+ obsServ->AddObserver(obs, "profiler-started", false);
+}
+
+} // namespace mscom
+} // namespace mozilla
diff --git a/ipc/mscom/ProfilerMarkers.h b/ipc/mscom/ProfilerMarkers.h
new file mode 100644
index 0000000000..a21d5b375a
--- /dev/null
+++ b/ipc/mscom/ProfilerMarkers.h
@@ -0,0 +1,18 @@
+/* -*- 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_ProfilerMarkers_h
+#define mozilla_mscom_ProfilerMarkers_h
+
+namespace mozilla {
+namespace mscom {
+
+void InitProfilerMarkers();
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_ProfilerMarkers_h
diff --git a/ipc/mscom/Utils.cpp b/ipc/mscom/Utils.cpp
new file mode 100644
index 0000000000..a6aeebf12f
--- /dev/null
+++ b/ipc/mscom/Utils.cpp
@@ -0,0 +1,404 @@
+/* -*- 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/. */
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "MainThreadUtils.h"
+# include "mozilla/dom/ContentChild.h"
+#endif
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/mscom/COMWrappers.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/WindowsVersion.h"
+
+#include <objidl.h>
+#include <shlwapi.h>
+#include <winnt.h>
+
+#include <utility>
+
+#if defined(_MSC_VER)
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+#endif
+
+namespace mozilla {
+namespace mscom {
+
+bool IsCOMInitializedOnCurrentThread() {
+ APTTYPE aptType;
+ APTTYPEQUALIFIER aptTypeQualifier;
+ HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier);
+ return hr != CO_E_NOTINITIALIZED;
+}
+
+bool IsCurrentThreadMTA() {
+ APTTYPE aptType;
+ APTTYPEQUALIFIER aptTypeQualifier;
+ HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ return aptType == APTTYPE_MTA;
+}
+
+bool IsCurrentThreadExplicitMTA() {
+ APTTYPE aptType;
+ APTTYPEQUALIFIER aptTypeQualifier;
+ HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ return aptType == APTTYPE_MTA &&
+ aptTypeQualifier != APTTYPEQUALIFIER_IMPLICIT_MTA;
+}
+
+bool IsCurrentThreadImplicitMTA() {
+ APTTYPE aptType;
+ APTTYPEQUALIFIER aptTypeQualifier;
+ HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ return aptType == APTTYPE_MTA &&
+ aptTypeQualifier == APTTYPEQUALIFIER_IMPLICIT_MTA;
+}
+
+#if defined(MOZILLA_INTERNAL_API)
+bool IsCurrentThreadNonMainMTA() {
+ if (NS_IsMainThread()) {
+ return false;
+ }
+
+ return IsCurrentThreadMTA();
+}
+#endif // defined(MOZILLA_INTERNAL_API)
+
+bool IsProxy(IUnknown* aUnknown) {
+ if (!aUnknown) {
+ return false;
+ }
+
+ // Only proxies implement this interface, so if it is present then we must
+ // be dealing with a proxied object.
+ RefPtr<IClientSecurity> clientSecurity;
+ HRESULT hr = aUnknown->QueryInterface(IID_IClientSecurity,
+ (void**)getter_AddRefs(clientSecurity));
+ if (SUCCEEDED(hr) || hr == RPC_E_WRONG_THREAD) {
+ return true;
+ }
+ return false;
+}
+
+bool IsValidGUID(REFGUID aCheckGuid) {
+ // This function determines whether or not aCheckGuid conforms to RFC4122
+ // as it applies to Microsoft COM.
+
+ BYTE variant = aCheckGuid.Data4[0];
+ if (!(variant & 0x80)) {
+ // NCS Reserved
+ return false;
+ }
+ if ((variant & 0xE0) == 0xE0) {
+ // Reserved for future use
+ return false;
+ }
+ if ((variant & 0xC0) == 0xC0) {
+ // Microsoft Reserved.
+ return true;
+ }
+
+ BYTE version = HIBYTE(aCheckGuid.Data3) >> 4;
+ // Other versions are specified in RFC4122 but these are the two used by COM.
+ return version == 1 || version == 4;
+}
+
+uintptr_t GetContainingModuleHandle() {
+ HMODULE thisModule = nullptr;
+#if defined(_MSC_VER)
+ thisModule = reinterpret_cast<HMODULE>(&__ImageBase);
+#else
+ if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast<LPCTSTR>(&GetContainingModuleHandle),
+ &thisModule)) {
+ return 0;
+ }
+#endif
+ return reinterpret_cast<uintptr_t>(thisModule);
+}
+
+namespace detail {
+
+long BuildRegGuidPath(REFGUID aGuid, const GuidType aGuidType, wchar_t* aBuf,
+ const size_t aBufLen) {
+ constexpr wchar_t kClsid[] = L"CLSID\\";
+ constexpr wchar_t kAppid[] = L"AppID\\";
+ constexpr wchar_t kSubkeyBase[] = L"SOFTWARE\\Classes\\";
+
+ // We exclude null terminators in these length calculations because we include
+ // the stringified GUID's null terminator at the end. Since kClsid and kAppid
+ // have identical lengths, we just choose one to compute this length.
+ constexpr size_t kSubkeyBaseLen = mozilla::ArrayLength(kSubkeyBase) - 1;
+ constexpr size_t kSubkeyLen =
+ kSubkeyBaseLen + mozilla::ArrayLength(kClsid) - 1;
+ // Guid length as formatted for the registry (including curlies and dashes),
+ // but excluding null terminator.
+ constexpr size_t kGuidLen = kGuidRegFormatCharLenInclNul - 1;
+ constexpr size_t kExpectedPathLenInclNul = kSubkeyLen + kGuidLen + 1;
+
+ if (aBufLen < kExpectedPathLenInclNul) {
+ // Buffer is too short
+ return E_INVALIDARG;
+ }
+
+ if (wcscpy_s(aBuf, aBufLen, kSubkeyBase)) {
+ return E_INVALIDARG;
+ }
+
+ const wchar_t* strGuidType = aGuidType == GuidType::CLSID ? kClsid : kAppid;
+ if (wcscat_s(aBuf, aBufLen, strGuidType)) {
+ return E_INVALIDARG;
+ }
+
+ int guidConversionResult =
+ ::StringFromGUID2(aGuid, &aBuf[kSubkeyLen], aBufLen - kSubkeyLen);
+ if (!guidConversionResult) {
+ return E_INVALIDARG;
+ }
+
+ return S_OK;
+}
+
+} // namespace detail
+
+long CreateStream(const uint8_t* aInitBuf, const uint32_t aInitBufSize,
+ IStream** aOutStream) {
+ if (!aInitBufSize || !aOutStream) {
+ return E_INVALIDARG;
+ }
+
+ *aOutStream = nullptr;
+
+ HRESULT hr;
+ RefPtr<IStream> stream;
+
+ if (IsWin8OrLater()) {
+ // SHCreateMemStream is not safe for us to use until Windows 8. On older
+ // versions of Windows it is not thread-safe and it creates IStreams that do
+ // not support the full IStream API.
+
+ // If aInitBuf is null then initSize must be 0.
+ UINT initSize = aInitBuf ? aInitBufSize : 0;
+ stream = already_AddRefed<IStream>(::SHCreateMemStream(aInitBuf, initSize));
+ if (!stream) {
+ return E_OUTOFMEMORY;
+ }
+
+ if (!aInitBuf) {
+ // Now we'll set the required size
+ ULARGE_INTEGER newSize;
+ newSize.QuadPart = aInitBufSize;
+ hr = stream->SetSize(newSize);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ }
+ } else {
+ HGLOBAL hglobal = ::GlobalAlloc(GMEM_MOVEABLE, aInitBufSize);
+ if (!hglobal) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ // stream takes ownership of hglobal if this call is successful
+ hr = ::CreateStreamOnHGlobal(hglobal, TRUE, getter_AddRefs(stream));
+ if (FAILED(hr)) {
+ ::GlobalFree(hglobal);
+ return hr;
+ }
+
+ // The default stream size is derived from ::GlobalSize(hglobal), which due
+ // to rounding may be larger than aInitBufSize. We forcibly set the correct
+ // stream size here.
+ ULARGE_INTEGER streamSize;
+ streamSize.QuadPart = aInitBufSize;
+ hr = stream->SetSize(streamSize);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (aInitBuf) {
+ ULONG bytesWritten;
+ hr = stream->Write(aInitBuf, aInitBufSize, &bytesWritten);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (bytesWritten != aInitBufSize) {
+ return E_UNEXPECTED;
+ }
+ }
+ }
+
+ // Ensure that the stream is rewound
+ LARGE_INTEGER streamOffset;
+ streamOffset.QuadPart = 0LL;
+ hr = stream->Seek(streamOffset, STREAM_SEEK_SET, nullptr);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ stream.forget(aOutStream);
+ return S_OK;
+}
+
+#if defined(MOZILLA_INTERNAL_API)
+
+void GUIDToString(REFGUID aGuid, nsAString& aOutString) {
+ // This buffer length is long enough to hold a GUID string that is formatted
+ // to include curly braces and dashes.
+ const int kBufLenWithNul = 39;
+ aOutString.SetLength(kBufLenWithNul);
+ int result = StringFromGUID2(aGuid, char16ptr_t(aOutString.BeginWriting()),
+ kBufLenWithNul);
+ MOZ_ASSERT(result);
+ if (result) {
+ // Truncate the terminator
+ aOutString.SetLength(result - 1);
+ }
+}
+
+// Undocumented IIDs that are relevant for diagnostic purposes
+static const IID IID_ISCMLocalActivator = {
+ 0x00000136,
+ 0x0000,
+ 0x0000,
+ {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
+static const IID IID_IRundown = {
+ 0x00000134,
+ 0x0000,
+ 0x0000,
+ {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
+static const IID IID_IRemUnknown = {
+ 0x00000131,
+ 0x0000,
+ 0x0000,
+ {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
+static const IID IID_IRemUnknown2 = {
+ 0x00000143,
+ 0x0000,
+ 0x0000,
+ {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
+
+struct IIDToLiteralMapEntry {
+ constexpr IIDToLiteralMapEntry(REFIID aIid, nsLiteralCString&& aStr)
+ : mIid(aIid), mStr(std::forward<nsLiteralCString>(aStr)) {}
+
+ REFIID mIid;
+ const nsLiteralCString mStr;
+};
+
+/**
+ * Given the name of an interface, the IID_ENTRY macro generates a pair
+ * containing a reference to the interface ID and a stringified version of
+ * the interface name.
+ *
+ * For example:
+ *
+ * {IID_ENTRY(IUnknown)}
+ * is expanded to:
+ * {IID_IUnknown, "IUnknown"_ns}
+ *
+ */
+// clang-format off
+# define IID_ENTRY_STRINGIFY(iface) #iface##_ns
+# define IID_ENTRY(iface) IID_##iface, IID_ENTRY_STRINGIFY(iface)
+// clang-format on
+
+// Mapping of selected IIDs to friendly, human readable descriptions for each
+// interface.
+static constexpr IIDToLiteralMapEntry sIidDiagStrs[] = {
+ {IID_ENTRY(IUnknown)},
+ {IID_IRemUnknown, "cross-apartment IUnknown"_ns},
+ {IID_IRundown, "cross-apartment object management"_ns},
+ {IID_ISCMLocalActivator, "out-of-process object instantiation"_ns},
+ {IID_IRemUnknown2, "cross-apartment IUnknown"_ns}};
+
+# undef IID_ENTRY
+# undef IID_ENTRY_STRINGIFY
+
+void DiagnosticNameForIID(REFIID aIid, nsACString& aOutString) {
+ // If the IID matches something in sIidDiagStrs, output its string.
+ for (const auto& curEntry : sIidDiagStrs) {
+ if (curEntry.mIid == aIid) {
+ aOutString.Assign(curEntry.mStr);
+ return;
+ }
+ }
+
+ // Otherwise just convert the IID to string form and output that.
+ nsAutoString strIid;
+ GUIDToString(aIid, strIid);
+
+ aOutString.AssignLiteral("IID ");
+ AppendUTF16toUTF8(strIid, aOutString);
+}
+
+#else
+
+void GUIDToString(REFGUID aGuid,
+ wchar_t (&aOutBuf)[kGuidRegFormatCharLenInclNul]) {
+ DebugOnly<int> result =
+ ::StringFromGUID2(aGuid, aOutBuf, ArrayLength(aOutBuf));
+ MOZ_ASSERT(result);
+}
+
+#endif // defined(MOZILLA_INTERNAL_API)
+
+#if defined(MOZILLA_INTERNAL_API)
+bool IsClassThreadAwareInprocServer(REFCLSID aClsid) {
+ nsAutoString strClsid;
+ GUIDToString(aClsid, strClsid);
+
+ nsAutoString inprocServerSubkey(u"CLSID\\"_ns);
+ inprocServerSubkey.Append(strClsid);
+ inprocServerSubkey.Append(u"\\InprocServer32"_ns);
+
+ // Of the possible values, "Apartment" is the longest, so we'll make this
+ // buffer large enough to hold that one.
+ wchar_t threadingModelBuf[ArrayLength(L"Apartment")] = {};
+
+ DWORD numBytes = sizeof(threadingModelBuf);
+ LONG result = ::RegGetValueW(HKEY_CLASSES_ROOT, inprocServerSubkey.get(),
+ L"ThreadingModel", RRF_RT_REG_SZ, nullptr,
+ threadingModelBuf, &numBytes);
+ if (result != ERROR_SUCCESS) {
+ // This will also handle the case where the CLSID is not an inproc server.
+ return false;
+ }
+
+ DWORD numChars = numBytes / sizeof(wchar_t);
+ // numChars includes the null terminator
+ if (numChars <= 1) {
+ return false;
+ }
+
+ nsDependentString threadingModel(threadingModelBuf, numChars - 1);
+
+ // Ensure that the threading model is one of the known values that indicates
+ // that the class can operate natively (ie, no proxying) inside a MTA.
+ return threadingModel.LowerCaseEqualsLiteral("both") ||
+ threadingModel.LowerCaseEqualsLiteral("free") ||
+ threadingModel.LowerCaseEqualsLiteral("neutral");
+}
+#endif // defined(MOZILLA_INTERNAL_API)
+
+} // namespace mscom
+} // namespace mozilla
diff --git a/ipc/mscom/Utils.h b/ipc/mscom/Utils.h
new file mode 100644
index 0000000000..214b15044d
--- /dev/null
+++ b/ipc/mscom/Utils.h
@@ -0,0 +1,146 @@
+/* -*- 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_Utils_h
+#define mozilla_mscom_Utils_h
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "nsString.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+#include "mozilla/Attributes.h"
+#include <guiddef.h>
+#include <stdint.h>
+
+struct IStream;
+struct IUnknown;
+
+namespace mozilla {
+namespace mscom {
+namespace detail {
+
+enum class GuidType {
+ CLSID,
+ AppID,
+};
+
+long BuildRegGuidPath(REFGUID aGuid, const GuidType aGuidType, wchar_t* aBuf,
+ const size_t aBufLen);
+
+} // namespace detail
+
+bool IsCOMInitializedOnCurrentThread();
+bool IsCurrentThreadMTA();
+bool IsCurrentThreadExplicitMTA();
+bool IsCurrentThreadImplicitMTA();
+#if defined(MOZILLA_INTERNAL_API)
+bool IsCurrentThreadNonMainMTA();
+#endif // defined(MOZILLA_INTERNAL_API)
+bool IsProxy(IUnknown* aUnknown);
+bool IsValidGUID(REFGUID aCheckGuid);
+uintptr_t GetContainingModuleHandle();
+
+template <size_t N>
+inline long BuildAppidPath(REFGUID aAppId, wchar_t (&aPath)[N]) {
+ return detail::BuildRegGuidPath(aAppId, detail::GuidType::AppID, aPath, N);
+}
+
+template <size_t N>
+inline long BuildClsidPath(REFCLSID aClsid, wchar_t (&aPath)[N]) {
+ return detail::BuildRegGuidPath(aClsid, detail::GuidType::CLSID, aPath, N);
+}
+
+/**
+ * Given a buffer, create a new IStream object.
+ * @param aBuf Buffer containing data to initialize the stream. This parameter
+ * may be nullptr, causing the stream to be created with aBufLen
+ * bytes of uninitialized data.
+ * @param aBufLen Length of data in aBuf, or desired stream size if aBuf is
+ * nullptr.
+ * @param aOutStream Outparam to receive the newly created stream.
+ * @return HRESULT error code.
+ */
+long CreateStream(const uint8_t* aBuf, const uint32_t aBufLen,
+ IStream** aOutStream);
+
+/**
+ * Length of a stringified GUID as formatted for the registry, i.e. including
+ * curly-braces and dashes.
+ */
+constexpr size_t kGuidRegFormatCharLenInclNul = 39;
+
+#if defined(MOZILLA_INTERNAL_API)
+/**
+ * Checks the registry to see if |aClsid| is a thread-aware in-process server.
+ *
+ * In DCOM, an in-process server is a server that is implemented inside a DLL
+ * that is loaded into the client's process for execution. If |aClsid| declares
+ * itself to be a local server (that is, a server that resides in another
+ * process), this function returns false.
+ *
+ * For the server to be thread-aware, its registry entry must declare a
+ * ThreadingModel that is one of "Free", "Both", or "Neutral". If the threading
+ * model is "Apartment" or some other, invalid value, the class is treated as
+ * being single-threaded.
+ *
+ * NB: This function cannot check CLSIDs that were registered via manifests,
+ * as unfortunately there is not a documented API available to query for those.
+ * This should not be an issue for most CLSIDs that Gecko is interested in, as
+ * we typically instantiate system CLSIDs which are available in the registry.
+ *
+ * @param aClsid The CLSID of the COM class to be checked.
+ * @return true if the class meets the above criteria, otherwise false.
+ */
+bool IsClassThreadAwareInprocServer(REFCLSID aClsid);
+
+void GUIDToString(REFGUID aGuid, nsAString& aOutString);
+
+/**
+ * Converts an IID to a human-readable string for the purposes of diagnostic
+ * tools such as the profiler. For some special cases, we output a friendly
+ * string that describes the purpose of the interface. If no such description
+ * exists, we simply fall back to outputting the IID as a string formatted by
+ * GUIDToString().
+ */
+void DiagnosticNameForIID(REFIID aIid, nsACString& aOutString);
+#else
+void GUIDToString(REFGUID aGuid,
+ wchar_t (&aOutBuf)[kGuidRegFormatCharLenInclNul]);
+#endif // defined(MOZILLA_INTERNAL_API)
+
+/**
+ * Execute cleanup code when going out of scope if a condition is met.
+ * This is useful when, for example, particular cleanup needs to be performed
+ * whenever a call returns a failure HRESULT.
+ * Both the condition and cleanup code are provided as functions (usually
+ * lambdas).
+ */
+template <typename CondFnT, typename ExeFnT>
+class MOZ_RAII ExecuteWhen final {
+ public:
+ ExecuteWhen(CondFnT& aCondFn, ExeFnT& aExeFn)
+ : mCondFn(aCondFn), mExeFn(aExeFn) {}
+
+ ~ExecuteWhen() {
+ if (mCondFn()) {
+ mExeFn();
+ }
+ }
+
+ ExecuteWhen(const ExecuteWhen&) = delete;
+ ExecuteWhen(ExecuteWhen&&) = delete;
+ ExecuteWhen& operator=(const ExecuteWhen&) = delete;
+ ExecuteWhen& operator=(ExecuteWhen&&) = delete;
+
+ private:
+ CondFnT& mCondFn;
+ ExeFnT& mExeFn;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_Utils_h
diff --git a/ipc/mscom/moz.build b/ipc/mscom/moz.build
new file mode 100644
index 0000000000..e11b7314f4
--- /dev/null
+++ b/ipc/mscom/moz.build
@@ -0,0 +1,42 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.mscom += [
+ "Aggregation.h",
+ "AgileReference.h",
+ "ApartmentRegion.h",
+ "COMWrappers.h",
+ "EnsureMTA.h",
+ "ProcessRuntime.h",
+ "ProfilerMarkers.h",
+ "Utils.h",
+]
+
+DIRS += [
+ "mozglue",
+]
+
+UNIFIED_SOURCES += [
+ "AgileReference.cpp",
+ "COMWrappers.cpp",
+ "EnsureMTA.cpp",
+ "ProcessRuntime.cpp",
+ "ProfilerMarkers.cpp",
+ "Utils.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/xpcom/base",
+ "/xpcom/build",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "IPC: MSCOM")
+ SCHEDULES.exclusive = ["windows"]
diff --git a/ipc/mscom/mozglue/ProcessRuntimeShared.cpp b/ipc/mscom/mozglue/ProcessRuntimeShared.cpp
new file mode 100644
index 0000000000..ee4f6b221f
--- /dev/null
+++ b/ipc/mscom/mozglue/ProcessRuntimeShared.cpp
@@ -0,0 +1,31 @@
+/* -*- 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/. */
+
+#include "mozilla/mscom/ProcessRuntimeShared.h"
+
+#include "mozilla/glue/WinUtils.h"
+
+// We allow multiple ProcessRuntime instances to exist simultaneously (even
+// on separate threads), but only one should be doing the process-wide
+// initialization. These variables provide that mutual exclusion.
+static mozilla::glue::Win32SRWLock gLock;
+static mozilla::mscom::detail::ProcessInitState gProcessInitState =
+ mozilla::mscom::detail::ProcessInitState::Uninitialized;
+
+namespace mozilla {
+namespace mscom {
+namespace detail {
+
+MFBT_API ProcessInitState& BeginProcessRuntimeInit() {
+ gLock.LockExclusive();
+ return gProcessInitState;
+}
+
+MFBT_API void EndProcessRuntimeInit() { gLock.UnlockExclusive(); }
+
+} // namespace detail
+} // namespace mscom
+} // namespace mozilla
diff --git a/ipc/mscom/mozglue/ProcessRuntimeShared.h b/ipc/mscom/mozglue/ProcessRuntimeShared.h
new file mode 100644
index 0000000000..d5a58a7b92
--- /dev/null
+++ b/ipc/mscom/mozglue/ProcessRuntimeShared.h
@@ -0,0 +1,55 @@
+/* -*- 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_ProcessRuntimeShared_h
+#define mozilla_mscom_ProcessRuntimeShared_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Types.h"
+
+namespace mozilla {
+namespace mscom {
+namespace detail {
+
+enum class ProcessInitState : uint32_t {
+ Uninitialized = 0,
+ PartialSecurityInitialized,
+ PartialGlobalOptions,
+ FullyInitialized,
+};
+
+MFBT_API ProcessInitState& BeginProcessRuntimeInit();
+MFBT_API void EndProcessRuntimeInit();
+
+} // namespace detail
+
+class MOZ_RAII ProcessInitLock final {
+ public:
+ ProcessInitLock() : mInitState(detail::BeginProcessRuntimeInit()) {}
+
+ ~ProcessInitLock() { detail::EndProcessRuntimeInit(); }
+
+ detail::ProcessInitState GetInitState() const { return mInitState; }
+
+ void SetInitState(const detail::ProcessInitState aNewState) {
+ MOZ_DIAGNOSTIC_ASSERT(aNewState > mInitState);
+ mInitState = aNewState;
+ }
+
+ ProcessInitLock(const ProcessInitLock&) = delete;
+ ProcessInitLock(ProcessInitLock&&) = delete;
+ ProcessInitLock operator=(const ProcessInitLock&) = delete;
+ ProcessInitLock operator=(ProcessInitLock&&) = delete;
+
+ private:
+ detail::ProcessInitState& mInitState;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_ProcessRuntimeShared_h
diff --git a/ipc/mscom/mozglue/moz.build b/ipc/mscom/mozglue/moz.build
new file mode 100644
index 0000000000..f52a772324
--- /dev/null
+++ b/ipc/mscom/mozglue/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+FINAL_LIBRARY = "mozglue"
+
+EXPORTS.mozilla.mscom += [
+ "ProcessRuntimeShared.h",
+]
+
+UNIFIED_SOURCES += [
+ "ProcessRuntimeShared.cpp",
+]