summaryrefslogtreecommitdiffstats
path: root/ipc/mscom
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /ipc/mscom
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
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.cpp64
-rw-r--r--ipc/mscom/AgileReference.h143
-rw-r--r--ipc/mscom/ApartmentRegion.h106
-rw-r--r--ipc/mscom/COMWrappers.cpp101
-rw-r--r--ipc/mscom/COMWrappers.h38
-rw-r--r--ipc/mscom/EnsureMTA.cpp189
-rw-r--r--ipc/mscom/EnsureMTA.h129
-rw-r--r--ipc/mscom/ProcessRuntime.cpp486
-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.cpp324
-rw-r--r--ipc/mscom/Utils.h123
-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, 2270 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..76fe773b0a
--- /dev/null
+++ b/ipc/mscom/AgileReference.cpp
@@ -0,0 +1,64 @@
+/* -*- 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/mscom/Utils.h"
+
+#if defined(__MINGW32__)
+
+// 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);
+
+// Unfortunately, at time of writing, MinGW doesn't know how to statically link
+// to RoGetAgileReference. On these builds only, we substitute a runtime-linked
+// function pointer.
+
+# include "mozilla/DynamicallyLinkedFunctionPtr.h"
+
+static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::RoGetAgileReference)>
+ pRoGetAgileReference(L"ole32.dll", "RoGetAgileReference");
+
+# define RoGetAgileReference pRoGetAgileReference
+
+#endif // defined(__MINGW32__)
+
+namespace mozilla::mscom::detail {
+
+HRESULT AgileReference_CreateImpl(RefPtr<IAgileReference>& aRefPtr, REFIID riid,
+ IUnknown* aObject) {
+ MOZ_ASSERT(aObject);
+ MOZ_ASSERT(IsCOMInitializedOnCurrentThread());
+ return ::RoGetAgileReference(AGILEREFERENCE_DEFAULT, riid, aObject,
+ getter_AddRefs(aRefPtr));
+}
+
+HRESULT AgileReference_ResolveImpl(RefPtr<IAgileReference> const& aRefPtr,
+ REFIID riid, void** aOutInterface) {
+ MOZ_ASSERT(aRefPtr);
+ MOZ_ASSERT(aOutInterface);
+ MOZ_ASSERT(IsCOMInitializedOnCurrentThread());
+
+ if (!aRefPtr || !aOutInterface) {
+ return E_INVALIDARG;
+ }
+
+ *aOutInterface = nullptr;
+ return aRefPtr->Resolve(riid, aOutInterface);
+}
+
+} // namespace mozilla::mscom::detail
diff --git a/ipc/mscom/AgileReference.h b/ipc/mscom/AgileReference.h
new file mode 100644
index 0000000000..384c391ac7
--- /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 "mozilla/Result.h"
+#include "mozilla/Unused.h"
+#include "nsDebug.h"
+#include "nsISupportsImpl.h"
+
+#include <objidl.h>
+
+namespace mozilla::mscom {
+
+namespace detail {
+// Detemplatized implementation details of `AgileReference`.
+HRESULT AgileReference_CreateImpl(RefPtr<IAgileReference>&, REFIID, IUnknown*);
+HRESULT AgileReference_ResolveImpl(RefPtr<IAgileReference> const&, REFIID,
+ void**);
+} // 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 that instead. Then you can "unwrap" the interface by
+ * calling Resolve(), which will return a proxy object implementing the same
+ * interface.
+ *
+ * Sample usage:
+ *
+ * ```
+ * // From a non-main thread, where `foo` is an `IFoo*` or `RefPtr<IFoo>`:
+ * auto myAgileRef = AgileReference(foo);
+ * NS_DispatchToMainThread([mar = std::move(myAgileRef)] {
+ * RefPtr<IFoo> foo = mar.Resolve();
+ * // Now methods may be invoked on `foo`
+ * });
+ * ```
+ */
+template <typename InterfaceT>
+class AgileReference final {
+ static_assert(
+ std::is_base_of_v<IUnknown, InterfaceT>,
+ "template parameter of AgileReference must be a COM interface type");
+
+ public:
+ AgileReference() = default;
+ ~AgileReference() = default;
+
+ AgileReference(const AgileReference& aOther) = default;
+ AgileReference(AgileReference&& aOther) noexcept = default;
+
+ AgileReference& operator=(const AgileReference& aOther) = default;
+ AgileReference& operator=(AgileReference&& aOther) noexcept = default;
+
+ AgileReference& operator=(std::nullptr_t) {
+ mAgileRef = nullptr;
+ return *this;
+ }
+
+ // Create a new AgileReference from an existing COM object.
+ //
+ // These constructors do not provide the HRESULT on failure. If that's
+ // desired, use `AgileReference::Create()`, below.
+ explicit AgileReference(InterfaceT* aObject) {
+ HRESULT const hr = detail::AgileReference_CreateImpl(
+ mAgileRef, __uuidof(InterfaceT), aObject);
+ Unused << NS_WARN_IF(FAILED(hr));
+ }
+ explicit AgileReference(RefPtr<InterfaceT> const& aObject)
+ : AgileReference(aObject.get()) {}
+
+ // Create a new AgileReference from an existing COM object, or alternatively,
+ // return the HRESULT explaining why one couldn't be created.
+ //
+ // A convenience wrapper `MakeAgileReference()` which infers `InterfaceT` from
+ // the RefPtr's concrete type is provided below.
+ static Result<AgileReference<InterfaceT>, HRESULT> Create(
+ RefPtr<InterfaceT> const& aObject) {
+ AgileReference ret;
+ HRESULT const hr = detail::AgileReference_CreateImpl(
+ ret.mAgileRef, __uuidof(InterfaceT), aObject.get());
+ if (FAILED(hr)) {
+ return Err(hr);
+ }
+ return ret;
+ }
+
+ explicit operator bool() const { return !!mAgileRef; }
+
+ // Common case: resolve directly to the originally-specified interface-type.
+ RefPtr<InterfaceT> Resolve() const {
+ auto res = ResolveAs<InterfaceT>();
+ if (res.isErr()) return nullptr;
+ return res.unwrap();
+ }
+
+ // Uncommon cases: resolve directly to a different interface type, and/or
+ // provide IAgileReference::Resolve()'s HRESULT.
+ //
+ // When used in other COM apartments, `IAgileInterface::Resolve()` returns a
+ // proxy object which (at time of writing) is not documented to provide any
+ // interface other than the one for which it was instantiated. (Calling
+ // `QueryInterface` _might_ work, but isn't explicitly guaranteed.)
+ //
+ template <typename OtherInterface = InterfaceT>
+ Result<RefPtr<OtherInterface>, HRESULT> ResolveAs() const {
+ RefPtr<OtherInterface> p;
+ auto const hr = ResolveRaw(__uuidof(OtherInterface), getter_AddRefs(p));
+ if (FAILED(hr)) {
+ return Err(hr);
+ }
+ return p;
+ }
+
+ // Raw version of Resolve/ResolveAs. Rarely, if ever, preferable to the
+ // statically-typed versions.
+ HRESULT ResolveRaw(REFIID aIid, void** aOutInterface) const {
+ return detail::AgileReference_ResolveImpl(mAgileRef, aIid, aOutInterface);
+ }
+
+ private:
+ RefPtr<IAgileReference> mAgileRef;
+};
+
+// Attempt to create an AgileReference from a refcounted interface pointer,
+// providing the HRESULT as a secondary return-value.
+template <typename InterfaceT>
+inline Result<AgileReference<InterfaceT>, HRESULT> MakeAgileReference(
+ RefPtr<InterfaceT> const& aObj) {
+ return AgileReference<InterfaceT>::Create(aObj);
+}
+
+} // namespace mozilla::mscom
+
+#endif // mozilla_mscom_AgileReference_h
diff --git a/ipc/mscom/ApartmentRegion.h b/ipc/mscom/ApartmentRegion.h
new file mode 100644
index 0000000000..ea942b95b5
--- /dev/null
+++ b/ipc/mscom/ApartmentRegion.h
@@ -0,0 +1,106 @@
+/* -*- 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::mscom {
+
+// This runtime-dynamic apartment class is used in ProcessRuntime.cpp, to
+// initialize the process's main thread. Do not use it in new contexts if at all
+// possible; instead, prefer ApartmentRegionT, below.
+//
+// For backwards compatibility, this class does not yet automatically disable
+// OLE1/DDE, although there is believed to be no code relying on it.
+//
+// (TODO: phase out all uses of CoInitialize without `COINIT_DISABLE_OLE1DDE`?)
+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; }
+
+ ApartmentRegion(const ApartmentRegion&) = delete;
+ ApartmentRegion& operator=(const ApartmentRegion&) = delete;
+ ApartmentRegion(ApartmentRegion&&) = delete;
+ ApartmentRegion& operator=(ApartmentRegion&&) = delete;
+
+ private:
+ HRESULT mInitResult;
+};
+
+template <COINIT AptType, bool UseOLE1 = false>
+class MOZ_NON_TEMPORARY_CLASS ApartmentRegionT final {
+ static COINIT ActualType() {
+ static_assert(
+ !((AptType & COINIT_DISABLE_OLE1DDE) == 0 && UseOLE1),
+ "only one of `UseOLE1` and `COINIT_DISABLE_OLE1DDE` permitted");
+ if (UseOLE1) return AptType;
+ return static_cast<COINIT>(AptType | COINIT_DISABLE_OLE1DDE);
+ }
+
+ public:
+ ApartmentRegionT() : mAptRgn(ActualType()) {}
+
+ ~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(); }
+
+ ApartmentRegionT(const ApartmentRegionT&) = delete;
+ ApartmentRegionT& operator=(const ApartmentRegionT&) = delete;
+ ApartmentRegionT(ApartmentRegionT&&) = delete;
+ ApartmentRegionT& operator=(ApartmentRegionT&&) = delete;
+
+ private:
+ ApartmentRegion mAptRgn;
+};
+
+using STARegion = ApartmentRegionT<COINIT_APARTMENTTHREADED>;
+using MTARegion = ApartmentRegionT<COINIT_MULTITHREADED>;
+
+} // namespace mozilla::mscom
+
+#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..4550d4d347
--- /dev/null
+++ b/ipc/mscom/COMWrappers.h
@@ -0,0 +1,38 @@
+/* -*- 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>
+
+// 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..49d8446fa2
--- /dev/null
+++ b/ipc/mscom/EnsureMTA.cpp
@@ -0,0 +1,189 @@
+/* -*- 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 */
+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(
+ 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..662192c476
--- /dev/null
+++ b/ipc/mscom/EnsureMTA.h
@@ -0,0 +1,129 @@
+/* -*- 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);
+ }
+
+ private:
+ 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..ae4e32247c
--- /dev/null
+++ b/ipc/mscom/ProcessRuntime.cpp
@@ -0,0 +1,486 @@
+/* -*- 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"
+
+#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 allowAllNonRestrictedAppContainers =
+ aProcessCategory == ProcessCategory::GeckoBrowserParent;
+
+ BYTE appContainersSid[SECURITY_MAX_SID_SIZE];
+ DWORD appContainersSidSize = sizeof(appContainersSid);
+ if (allowAllNonRestrictedAppContainers) {
+ if (!::CreateWellKnownSid(WinBuiltinAnyPackageSid, nullptr,
+ appContainersSid, &appContainersSidSize)) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+ }
+
+ UniquePtr<BYTE[]> tokenAppContainerInfBuf;
+ len = 0;
+ ::GetTokenInformation(token, TokenAppContainerSid, nullptr, len, &len);
+ if (len) {
+ tokenAppContainerInfBuf = MakeUnique<BYTE[]>(len);
+ ok = ::GetTokenInformation(token, TokenAppContainerSid,
+ tokenAppContainerInfBuf.get(), len, &len);
+ if (!ok) {
+ // Don't fail if we get an error retrieving an app container SID.
+ tokenAppContainerInfBuf = nullptr;
+ }
+ }
+
+ // Grant access to SYSTEM, Administrators, the user, our app container (if in
+ // one) and when running as the browser process on Windows 8+, all non
+ // restricted app containers.
+ const size_t kMaxInlineEntries = 5;
+ 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 (allowAllNonRestrictedAppContainers) {
+ 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)}});
+ }
+
+ if (tokenAppContainerInfBuf) {
+ TOKEN_APPCONTAINER_INFORMATION& tokenAppContainerInf =
+ *reinterpret_cast<TOKEN_APPCONTAINER_INFORMATION*>(
+ tokenAppContainerInfBuf.get());
+
+ // TokenAppContainer will be null if we are not in an app container.
+ if (tokenAppContainerInf.TokenAppContainer) {
+ 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>(tokenAppContainerInf.TokenAppContainer)}});
+ }
+ }
+
+ 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..d93e012587
--- /dev/null
+++ b/ipc/mscom/Utils.cpp
@@ -0,0 +1,324 @@
+/* -*- 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 <objidl.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 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;
+ }
+ }
+
+ // 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)
+
+} // namespace mscom
+} // namespace mozilla
diff --git a/ipc/mscom/Utils.h b/ipc/mscom/Utils.h
new file mode 100644
index 0000000000..aabf5f5891
--- /dev/null
+++ b/ipc/mscom/Utils.h
@@ -0,0 +1,123 @@
+/* -*- 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)
+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",
+]