зеркало из https://github.com/mozilla/gecko-dev.git
698 строки
20 KiB
C++
698 строки
20 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#define INITGUID
|
|
|
|
#include "mozilla/mscom/MainThreadHandoff.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/ThreadLocal.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/mscom/AgileReference.h"
|
|
#include "mozilla/mscom/InterceptorLog.h"
|
|
#include "mozilla/mscom/Registration.h"
|
|
#include "mozilla/mscom/Utils.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
using mozilla::DebugOnly;
|
|
using mozilla::Unused;
|
|
using mozilla::mscom::AgileReference;
|
|
|
|
namespace {
|
|
|
|
class MOZ_NON_TEMPORARY_CLASS InParamWalker : private ICallFrameWalker {
|
|
public:
|
|
InParamWalker() : mPreHandoff(true) {}
|
|
|
|
void SetHandoffDone() {
|
|
mPreHandoff = false;
|
|
mAgileRefsItr = mAgileRefs.begin();
|
|
}
|
|
|
|
HRESULT Walk(ICallFrame* aFrame) {
|
|
MOZ_ASSERT(aFrame);
|
|
if (!aFrame) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
return aFrame->WalkFrame(CALLFRAME_WALK_IN, this);
|
|
}
|
|
|
|
private:
|
|
// IUnknown
|
|
STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override {
|
|
if (!aOutInterface) {
|
|
return E_INVALIDARG;
|
|
}
|
|
*aOutInterface = nullptr;
|
|
|
|
if (aIid == IID_IUnknown || aIid == IID_ICallFrameWalker) {
|
|
*aOutInterface = static_cast<ICallFrameWalker*>(this);
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) AddRef() override { return 2; }
|
|
|
|
STDMETHODIMP_(ULONG) Release() override { return 1; }
|
|
|
|
// ICallFrameWalker
|
|
STDMETHODIMP OnWalkInterface(REFIID aIid, PVOID* aInterface, BOOL aIn,
|
|
BOOL aOut) override {
|
|
MOZ_ASSERT(aIn);
|
|
if (!aIn) {
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
IUnknown* origInterface = static_cast<IUnknown*>(*aInterface);
|
|
if (!origInterface) {
|
|
// Nothing to do
|
|
return S_OK;
|
|
}
|
|
|
|
if (mPreHandoff) {
|
|
mAgileRefs.AppendElement(AgileReference(aIid, origInterface));
|
|
return S_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(mAgileRefsItr != mAgileRefs.end());
|
|
if (mAgileRefsItr == mAgileRefs.end()) {
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
HRESULT hr = mAgileRefsItr->Resolve(aIid, aInterface);
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
if (SUCCEEDED(hr)) {
|
|
++mAgileRefsItr;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
InParamWalker(const InParamWalker&) = delete;
|
|
InParamWalker(InParamWalker&&) = delete;
|
|
InParamWalker& operator=(const InParamWalker&) = delete;
|
|
InParamWalker& operator=(InParamWalker&&) = delete;
|
|
|
|
private:
|
|
bool mPreHandoff;
|
|
AutoTArray<AgileReference, 1> mAgileRefs;
|
|
nsTArray<AgileReference>::iterator mAgileRefsItr;
|
|
};
|
|
|
|
class HandoffRunnable : public mozilla::Runnable {
|
|
public:
|
|
explicit HandoffRunnable(ICallFrame* aCallFrame, IUnknown* aTargetInterface)
|
|
: Runnable("HandoffRunnable"),
|
|
mCallFrame(aCallFrame),
|
|
mTargetInterface(aTargetInterface),
|
|
mResult(E_UNEXPECTED) {
|
|
DebugOnly<HRESULT> hr = mInParamWalker.Walk(aCallFrame);
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
}
|
|
|
|
NS_IMETHOD Run() override {
|
|
mInParamWalker.SetHandoffDone();
|
|
// We declare hr a DebugOnly because if mInParamWalker.Walk() fails, then
|
|
// mCallFrame->Invoke will fail anyway.
|
|
DebugOnly<HRESULT> hr = mInParamWalker.Walk(mCallFrame);
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
mResult = mCallFrame->Invoke(mTargetInterface);
|
|
return NS_OK;
|
|
}
|
|
|
|
HRESULT GetResult() const { return mResult; }
|
|
|
|
private:
|
|
ICallFrame* mCallFrame;
|
|
InParamWalker mInParamWalker;
|
|
IUnknown* mTargetInterface;
|
|
HRESULT mResult;
|
|
};
|
|
|
|
class MOZ_RAII SavedCallFrame final {
|
|
public:
|
|
explicit SavedCallFrame(mozilla::NotNull<ICallFrame*> aFrame)
|
|
: mCallFrame(aFrame) {
|
|
static const bool sIsInit = tlsFrame.init();
|
|
MOZ_ASSERT(sIsInit);
|
|
MOZ_ASSERT(!tlsFrame.get());
|
|
tlsFrame.set(this);
|
|
Unused << sIsInit;
|
|
}
|
|
|
|
~SavedCallFrame() {
|
|
MOZ_ASSERT(tlsFrame.get());
|
|
tlsFrame.set(nullptr);
|
|
}
|
|
|
|
HRESULT GetIidAndMethod(mozilla::NotNull<IID*> aIid,
|
|
mozilla::NotNull<ULONG*> aMethod) const {
|
|
return mCallFrame->GetIIDAndMethod(aIid, aMethod);
|
|
}
|
|
|
|
static const SavedCallFrame& Get() {
|
|
SavedCallFrame* saved = tlsFrame.get();
|
|
MOZ_ASSERT(saved);
|
|
|
|
return *saved;
|
|
}
|
|
|
|
SavedCallFrame(const SavedCallFrame&) = delete;
|
|
SavedCallFrame(SavedCallFrame&&) = delete;
|
|
SavedCallFrame& operator=(const SavedCallFrame&) = delete;
|
|
SavedCallFrame& operator=(SavedCallFrame&&) = delete;
|
|
|
|
private:
|
|
ICallFrame* mCallFrame;
|
|
|
|
private:
|
|
static MOZ_THREAD_LOCAL(SavedCallFrame*) tlsFrame;
|
|
};
|
|
|
|
MOZ_THREAD_LOCAL(SavedCallFrame*) SavedCallFrame::tlsFrame;
|
|
|
|
class MOZ_RAII LogEvent final {
|
|
public:
|
|
LogEvent() : mCallStart(mozilla::TimeStamp::Now()) {}
|
|
|
|
~LogEvent() {
|
|
if (mCapturedFrame.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
mozilla::TimeStamp callEnd(mozilla::TimeStamp::Now());
|
|
mozilla::TimeDuration totalTime(callEnd - mCallStart);
|
|
mozilla::TimeDuration overhead(totalTime - mGeckoDuration -
|
|
mCaptureDuration);
|
|
|
|
mozilla::mscom::InterceptorLog::Event(mCapturedFrame, overhead,
|
|
mGeckoDuration);
|
|
}
|
|
|
|
void CaptureFrame(ICallFrame* aFrame, IUnknown* aTarget,
|
|
const mozilla::TimeDuration& aGeckoDuration) {
|
|
mozilla::TimeStamp captureStart(mozilla::TimeStamp::Now());
|
|
|
|
mozilla::mscom::InterceptorLog::CaptureFrame(aFrame, aTarget,
|
|
mCapturedFrame);
|
|
mGeckoDuration = aGeckoDuration;
|
|
|
|
mozilla::TimeStamp captureEnd(mozilla::TimeStamp::Now());
|
|
|
|
// Make sure that the time we spent in CaptureFrame isn't charged against
|
|
// overall overhead
|
|
mCaptureDuration = captureEnd - captureStart;
|
|
}
|
|
|
|
LogEvent(const LogEvent&) = delete;
|
|
LogEvent(LogEvent&&) = delete;
|
|
LogEvent& operator=(const LogEvent&) = delete;
|
|
LogEvent& operator=(LogEvent&&) = delete;
|
|
|
|
private:
|
|
mozilla::TimeStamp mCallStart;
|
|
mozilla::TimeDuration mGeckoDuration;
|
|
mozilla::TimeDuration mCaptureDuration;
|
|
nsAutoCString mCapturedFrame;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace mozilla {
|
|
namespace mscom {
|
|
|
|
/* static */
|
|
HRESULT MainThreadHandoff::Create(IHandlerProvider* aHandlerProvider,
|
|
IInterceptorSink** aOutput) {
|
|
RefPtr<MainThreadHandoff> handoff(new MainThreadHandoff(aHandlerProvider));
|
|
return handoff->QueryInterface(IID_IInterceptorSink, (void**)aOutput);
|
|
}
|
|
|
|
MainThreadHandoff::MainThreadHandoff(IHandlerProvider* aHandlerProvider)
|
|
: mRefCnt(0), mHandlerProvider(aHandlerProvider) {}
|
|
|
|
MainThreadHandoff::~MainThreadHandoff() { MOZ_ASSERT(NS_IsMainThread()); }
|
|
|
|
HRESULT
|
|
MainThreadHandoff::QueryInterface(REFIID riid, void** ppv) {
|
|
IUnknown* punk = nullptr;
|
|
if (!ppv) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (riid == IID_IUnknown || riid == IID_ICallFrameEvents ||
|
|
riid == IID_IInterceptorSink || riid == IID_IMainThreadHandoff) {
|
|
punk = static_cast<IMainThreadHandoff*>(this);
|
|
} else if (riid == IID_ICallFrameWalker) {
|
|
punk = static_cast<ICallFrameWalker*>(this);
|
|
}
|
|
|
|
*ppv = punk;
|
|
if (!punk) {
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
punk->AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
ULONG
|
|
MainThreadHandoff::AddRef() {
|
|
return (ULONG)InterlockedIncrement((LONG*)&mRefCnt);
|
|
}
|
|
|
|
ULONG
|
|
MainThreadHandoff::Release() {
|
|
ULONG newRefCnt = (ULONG)InterlockedDecrement((LONG*)&mRefCnt);
|
|
if (newRefCnt == 0) {
|
|
// It is possible for the last Release() call to happen off-main-thread.
|
|
// If so, we need to dispatch an event to delete ourselves.
|
|
if (NS_IsMainThread()) {
|
|
delete this;
|
|
} else {
|
|
// We need to delete this object on the main thread, but we aren't on the
|
|
// main thread right now, so we send a reference to ourselves to the main
|
|
// thread to be re-released there.
|
|
RefPtr<MainThreadHandoff> self = this;
|
|
NS_ReleaseOnMainThread("MainThreadHandoff", self.forget());
|
|
}
|
|
}
|
|
return newRefCnt;
|
|
}
|
|
|
|
HRESULT
|
|
MainThreadHandoff::FixIServiceProvider(ICallFrame* aFrame) {
|
|
MOZ_ASSERT(aFrame);
|
|
|
|
CALLFRAMEPARAMINFO iidOutParamInfo;
|
|
HRESULT hr = aFrame->GetParamInfo(1, &iidOutParamInfo);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
VARIANT varIfaceOut;
|
|
hr = aFrame->GetParam(2, &varIfaceOut);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
MOZ_ASSERT(varIfaceOut.vt == (VT_UNKNOWN | VT_BYREF));
|
|
if (varIfaceOut.vt != (VT_UNKNOWN | VT_BYREF)) {
|
|
return DISP_E_BADVARTYPE;
|
|
}
|
|
|
|
IID** iidOutParam =
|
|
reinterpret_cast<IID**>(static_cast<BYTE*>(aFrame->GetStackLocation()) +
|
|
iidOutParamInfo.stackOffset);
|
|
|
|
return OnWalkInterface(**iidOutParam,
|
|
reinterpret_cast<void**>(varIfaceOut.ppunkVal), FALSE,
|
|
TRUE);
|
|
}
|
|
|
|
HRESULT
|
|
MainThreadHandoff::OnCall(ICallFrame* aFrame) {
|
|
LogEvent logEvent;
|
|
|
|
// (1) Get info about the method call
|
|
HRESULT hr;
|
|
IID iid;
|
|
ULONG method;
|
|
hr = aFrame->GetIIDAndMethod(&iid, &method);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
RefPtr<IInterceptor> interceptor;
|
|
hr = mInterceptor->Resolve(IID_IInterceptor,
|
|
(void**)getter_AddRefs(interceptor));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
InterceptorTargetPtr<IUnknown> targetInterface;
|
|
hr = interceptor->GetTargetForIID(iid, targetInterface);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// (2) Execute the method call synchronously on the main thread
|
|
RefPtr<HandoffRunnable> handoffInfo(
|
|
new HandoffRunnable(aFrame, targetInterface.get()));
|
|
MainThreadInvoker invoker;
|
|
if (!invoker.Invoke(do_AddRef(handoffInfo))) {
|
|
MOZ_ASSERT(false);
|
|
return E_UNEXPECTED;
|
|
}
|
|
hr = handoffInfo->GetResult();
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// (3) Capture *before* wrapping outputs so that the log will contain pointers
|
|
// to the true target interface, not the wrapped ones.
|
|
logEvent.CaptureFrame(aFrame, targetInterface.get(), invoker.GetDuration());
|
|
|
|
// (4) Scan the function call for outparams that contain interface pointers.
|
|
// Those will need to be wrapped with MainThreadHandoff so that they too will
|
|
// be exeuted on the main thread.
|
|
|
|
hr = aFrame->GetReturnValue();
|
|
if (FAILED(hr)) {
|
|
// If the call resulted in an error then there's not going to be anything
|
|
// that needs to be wrapped.
|
|
return S_OK;
|
|
}
|
|
|
|
if (iid == IID_IServiceProvider) {
|
|
// The only possible method index for IID_IServiceProvider is for
|
|
// QueryService at index 3; its other methods are inherited from IUnknown
|
|
// and are not processed here.
|
|
MOZ_ASSERT(method == 3);
|
|
// (5) If our interface is IServiceProvider, we need to manually ensure
|
|
// that the correct IID is provided for the interface outparam in
|
|
// IServiceProvider::QueryService.
|
|
hr = FixIServiceProvider(aFrame);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
} else if (const ArrayData* arrayData = FindArrayData(iid, method)) {
|
|
// (6) Unfortunately ICallFrame::WalkFrame does not correctly handle array
|
|
// outparams. Instead, we find out whether anybody has called
|
|
// mscom::RegisterArrayData to supply array parameter information and use it
|
|
// if available. This is a terrible hack, but it works for the short term.
|
|
// In the longer term we want to be able to use COM proxy/stub metadata to
|
|
// resolve array information for us.
|
|
hr = FixArrayElements(aFrame, *arrayData);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
} else {
|
|
SavedCallFrame savedFrame(WrapNotNull(aFrame));
|
|
|
|
// (7) Scan the outputs looking for any outparam interfaces that need
|
|
// wrapping. NB: WalkFrame does not correctly handle array outparams. It
|
|
// processes the first element of an array but not the remaining elements
|
|
// (if any).
|
|
hr = aFrame->WalkFrame(CALLFRAME_WALK_OUT, this);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static PVOID ResolveArrayPtr(VARIANT& aVariant) {
|
|
if (!(aVariant.vt & VT_BYREF)) {
|
|
return nullptr;
|
|
}
|
|
return aVariant.byref;
|
|
}
|
|
|
|
static PVOID* ResolveInterfacePtr(PVOID aArrayPtr, VARTYPE aVartype,
|
|
LONG aIndex) {
|
|
if (aVartype != (VT_VARIANT | VT_BYREF)) {
|
|
IUnknown** ifaceArray = reinterpret_cast<IUnknown**>(aArrayPtr);
|
|
return reinterpret_cast<PVOID*>(&ifaceArray[aIndex]);
|
|
}
|
|
VARIANT* variantArray = reinterpret_cast<VARIANT*>(aArrayPtr);
|
|
VARIANT& element = variantArray[aIndex];
|
|
return &element.byref;
|
|
}
|
|
|
|
HRESULT
|
|
MainThreadHandoff::FixArrayElements(ICallFrame* aFrame,
|
|
const ArrayData& aArrayData) {
|
|
// Extract the array length
|
|
VARIANT paramVal;
|
|
VariantInit(¶mVal);
|
|
HRESULT hr = aFrame->GetParam(aArrayData.mLengthParamIndex, ¶mVal);
|
|
MOZ_ASSERT(SUCCEEDED(hr) && (paramVal.vt == (VT_I4 | VT_BYREF) ||
|
|
paramVal.vt == (VT_UI4 | VT_BYREF)));
|
|
if (FAILED(hr) || (paramVal.vt != (VT_I4 | VT_BYREF) &&
|
|
paramVal.vt != (VT_UI4 | VT_BYREF))) {
|
|
return hr;
|
|
}
|
|
|
|
const LONG arrayLength = *(paramVal.plVal);
|
|
if (!arrayLength) {
|
|
// Nothing to do
|
|
return S_OK;
|
|
}
|
|
|
|
// Extract the array parameter
|
|
VariantInit(¶mVal);
|
|
PVOID arrayPtr = nullptr;
|
|
hr = aFrame->GetParam(aArrayData.mArrayParamIndex, ¶mVal);
|
|
if (hr == DISP_E_BADVARTYPE) {
|
|
// ICallFrame::GetParam is not able to coerce the param into a VARIANT.
|
|
// That's ok, we can try to do it ourselves.
|
|
CALLFRAMEPARAMINFO paramInfo;
|
|
hr = aFrame->GetParamInfo(aArrayData.mArrayParamIndex, ¶mInfo);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
PVOID stackBase = aFrame->GetStackLocation();
|
|
if (aArrayData.mFlag == ArrayData::Flag::eAllocatedByServer) {
|
|
// In order for the server to allocate the array's buffer and store it in
|
|
// an outparam, the parameter must be typed as Type***. Since the base
|
|
// of the array is Type*, we must dereference twice.
|
|
arrayPtr = **reinterpret_cast<PVOID**>(
|
|
reinterpret_cast<PBYTE>(stackBase) + paramInfo.stackOffset);
|
|
} else {
|
|
// We dereference because we need to obtain the value of a parameter
|
|
// from a stack offset. This pointer is the base of the array.
|
|
arrayPtr = *reinterpret_cast<PVOID*>(reinterpret_cast<PBYTE>(stackBase) +
|
|
paramInfo.stackOffset);
|
|
}
|
|
} else if (FAILED(hr)) {
|
|
return hr;
|
|
} else {
|
|
arrayPtr = ResolveArrayPtr(paramVal);
|
|
}
|
|
|
|
MOZ_ASSERT(arrayPtr);
|
|
if (!arrayPtr) {
|
|
return DISP_E_BADVARTYPE;
|
|
}
|
|
|
|
// We walk the elements of the array and invoke OnWalkInterface to wrap each
|
|
// one, just as ICallFrame::WalkFrame would do.
|
|
for (LONG index = 0; index < arrayLength; ++index) {
|
|
hr = OnWalkInterface(aArrayData.mArrayParamIid,
|
|
ResolveInterfacePtr(arrayPtr, paramVal.vt, index),
|
|
FALSE, TRUE);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
MainThreadHandoff::SetInterceptor(IWeakReference* aInterceptor) {
|
|
mInterceptor = aInterceptor;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
MainThreadHandoff::GetHandler(NotNull<CLSID*> aHandlerClsid) {
|
|
if (!mHandlerProvider) {
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
return mHandlerProvider->GetHandler(aHandlerClsid);
|
|
}
|
|
|
|
HRESULT
|
|
MainThreadHandoff::GetHandlerPayloadSize(NotNull<IInterceptor*> aInterceptor,
|
|
NotNull<DWORD*> aOutPayloadSize) {
|
|
if (!mHandlerProvider) {
|
|
return E_NOTIMPL;
|
|
}
|
|
return mHandlerProvider->GetHandlerPayloadSize(aInterceptor, aOutPayloadSize);
|
|
}
|
|
|
|
HRESULT
|
|
MainThreadHandoff::WriteHandlerPayload(NotNull<IInterceptor*> aInterceptor,
|
|
NotNull<IStream*> aStream) {
|
|
if (!mHandlerProvider) {
|
|
return E_NOTIMPL;
|
|
}
|
|
return mHandlerProvider->WriteHandlerPayload(aInterceptor, aStream);
|
|
}
|
|
|
|
REFIID
|
|
MainThreadHandoff::MarshalAs(REFIID aIid) {
|
|
if (!mHandlerProvider) {
|
|
return aIid;
|
|
}
|
|
return mHandlerProvider->MarshalAs(aIid);
|
|
}
|
|
|
|
HRESULT
|
|
MainThreadHandoff::DisconnectHandlerRemotes() {
|
|
if (!mHandlerProvider) {
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
return mHandlerProvider->DisconnectHandlerRemotes();
|
|
}
|
|
|
|
HRESULT
|
|
MainThreadHandoff::IsInterfaceMaybeSupported(REFIID aIid) {
|
|
if (!mHandlerProvider) {
|
|
return S_OK;
|
|
}
|
|
return mHandlerProvider->IsInterfaceMaybeSupported(aIid);
|
|
}
|
|
|
|
HRESULT
|
|
MainThreadHandoff::OnWalkInterface(REFIID aIid, PVOID* aInterface,
|
|
BOOL aIsInParam, BOOL aIsOutParam) {
|
|
MOZ_ASSERT(aInterface && aIsOutParam);
|
|
if (!aInterface || !aIsOutParam) {
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// Adopt aInterface for the time being. We can't touch its refcount off
|
|
// the main thread, so we'll use STAUniquePtr so that we can safely
|
|
// Release() it if necessary.
|
|
STAUniquePtr<IUnknown> origInterface(static_cast<IUnknown*>(*aInterface));
|
|
*aInterface = nullptr;
|
|
|
|
if (!origInterface) {
|
|
// Nothing to wrap.
|
|
return S_OK;
|
|
}
|
|
|
|
// First make sure that aInterface isn't a proxy - we don't want to wrap
|
|
// those.
|
|
if (IsProxy(origInterface.get())) {
|
|
*aInterface = origInterface.release();
|
|
return S_OK;
|
|
}
|
|
|
|
RefPtr<IInterceptor> interceptor;
|
|
HRESULT hr = mInterceptor->Resolve(IID_IInterceptor,
|
|
(void**)getter_AddRefs(interceptor));
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// Now make sure that origInterface isn't referring to the same IUnknown
|
|
// as an interface that we are already managing. We can determine this by
|
|
// querying (NOT casting!) both objects for IUnknown and then comparing the
|
|
// resulting pointers.
|
|
InterceptorTargetPtr<IUnknown> existingTarget;
|
|
hr = interceptor->GetTargetForIID(aIid, existingTarget);
|
|
if (SUCCEEDED(hr)) {
|
|
// We'll start by checking the raw pointers. If they are equal, then the
|
|
// objects are equal. OTOH, if they differ, we must compare their
|
|
// IUnknown pointers to know for sure.
|
|
bool areTargetsEqual = existingTarget.get() == origInterface.get();
|
|
|
|
if (!areTargetsEqual) {
|
|
// This check must be done on the main thread
|
|
auto checkFn = [&existingTarget, &origInterface,
|
|
&areTargetsEqual]() -> void {
|
|
RefPtr<IUnknown> unkExisting;
|
|
HRESULT hrExisting = existingTarget->QueryInterface(
|
|
IID_IUnknown, (void**)getter_AddRefs(unkExisting));
|
|
RefPtr<IUnknown> unkNew;
|
|
HRESULT hrNew = origInterface->QueryInterface(
|
|
IID_IUnknown, (void**)getter_AddRefs(unkNew));
|
|
areTargetsEqual =
|
|
SUCCEEDED(hrExisting) && SUCCEEDED(hrNew) && unkExisting == unkNew;
|
|
};
|
|
|
|
MainThreadInvoker invoker;
|
|
invoker.Invoke(NS_NewRunnableFunction(
|
|
"MainThreadHandoff::OnWalkInterface", checkFn));
|
|
}
|
|
|
|
if (areTargetsEqual) {
|
|
// The existing interface and the new interface both belong to the same
|
|
// target object. Let's just use the existing one.
|
|
void* intercepted = nullptr;
|
|
hr = interceptor->GetInterceptorForIID(aIid, &intercepted);
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
*aInterface = intercepted;
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
IID effectiveIid = aIid;
|
|
|
|
RefPtr<IHandlerProvider> payload;
|
|
if (mHandlerProvider) {
|
|
if (aIid == IID_IUnknown) {
|
|
const SavedCallFrame& curFrame = SavedCallFrame::Get();
|
|
|
|
IID callIid;
|
|
ULONG callMethod;
|
|
hr = curFrame.GetIidAndMethod(WrapNotNull(&callIid),
|
|
WrapNotNull(&callMethod));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
effectiveIid =
|
|
mHandlerProvider->GetEffectiveOutParamIid(callIid, callMethod);
|
|
}
|
|
|
|
hr = mHandlerProvider->NewInstance(
|
|
effectiveIid, ToInterceptorTargetPtr(origInterface),
|
|
WrapNotNull((IHandlerProvider**)getter_AddRefs(payload)));
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
// Now create a new MainThreadHandoff wrapper...
|
|
RefPtr<IInterceptorSink> handoff;
|
|
hr = MainThreadHandoff::Create(payload, getter_AddRefs(handoff));
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
REFIID interceptorIid =
|
|
payload ? payload->MarshalAs(effectiveIid) : effectiveIid;
|
|
|
|
RefPtr<IUnknown> wrapped;
|
|
hr = Interceptor::Create(std::move(origInterface), handoff, interceptorIid,
|
|
getter_AddRefs(wrapped));
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// And replace the original interface pointer with the wrapped one.
|
|
wrapped.forget(reinterpret_cast<IUnknown**>(aInterface));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
} // namespace mscom
|
|
} // namespace mozilla
|