/* -*- 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/dom/ContentChild.h"
#include "mozilla/Move.h"
#include "mozilla/mscom/DispatchForwarder.h"
#include "mozilla/mscom/FastMarshaler.h"
#include "mozilla/mscom/Interceptor.h"
#include "mozilla/mscom/InterceptorLog.h"
#include "mozilla/mscom/MainThreadInvoker.h"
#include "mozilla/mscom/Objref.h"
#include "mozilla/mscom/Registration.h"
#include "mozilla/mscom/Utils.h"
#include "mozilla/ThreadLocal.h"
#include "MainThreadUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsRefPtrHashtable.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"

#if defined(MOZ_DEV_EDITION) || defined(RELEASE_OR_BETA) || !defined(MOZ_CRASHREPORTER)

#define ENSURE_HR_SUCCEEDED(hr) \
  if (FAILED((HRESULT)hr)) { \
    return hr; \
  }

#else

#include "nsExceptionHandler.h"
#include "nsPrintfCString.h"

#define ENSURE_HR_SUCCEEDED(hr) \
  if (FAILED((HRESULT)hr)) { \
    nsPrintfCString location("ENSURE_HR_SUCCEEDED \"%s\": %u", __FILE__, __LINE__); \
    nsPrintfCString hrAsStr("0x%08X", (HRESULT)hr); \
    CrashReporter::AnnotateCrashReport(location, hrAsStr); \
    MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED((HRESULT)hr)); \
    return hr; \
  }

#endif // defined(MOZ_DEV_EDITION) || defined(RELEASE_OR_BETA) || !defined(MOZ_CRASHREPORTER)

namespace mozilla {
namespace mscom {
namespace detail {

class LiveSet final
{
public:
  LiveSet()
    : mMutex("mozilla::mscom::LiveSet::mMutex")
  {
  }

  void Lock()
  {
    mMutex.Lock();
  }

  void Unlock()
  {
    mMutex.Unlock();
  }

  void Put(IUnknown* aKey, already_AddRefed<IWeakReference> aValue)
  {
    mMutex.AssertCurrentThreadOwns();
    mLiveSet.Put(aKey, Move(aValue));
  }

  RefPtr<IWeakReference> Get(IUnknown* aKey)
  {
    mMutex.AssertCurrentThreadOwns();
    RefPtr<IWeakReference> result;
    mLiveSet.Get(aKey, getter_AddRefs(result));
    return result;
  }

  void Remove(IUnknown* aKey)
  {
    mMutex.AssertCurrentThreadOwns();
    mLiveSet.Remove(aKey);
  }

private:
  Mutex mMutex;
  nsRefPtrHashtable<nsPtrHashKey<IUnknown>, IWeakReference> mLiveSet;
};

/**
 * We don't use the normal XPCOM BaseAutoLock because we need the ability
 * to explicitly Unlock.
 */
class MOZ_RAII LiveSetAutoLock final
{
public:
  explicit LiveSetAutoLock(LiveSet& aLiveSet)
    : mLiveSet(&aLiveSet)
  {
    aLiveSet.Lock();
  }

  ~LiveSetAutoLock()
  {
    if (mLiveSet) {
      mLiveSet->Unlock();
    }
  }

  void Unlock()
  {
    MOZ_ASSERT(mLiveSet);
    mLiveSet->Unlock();
    mLiveSet = nullptr;
  }

  LiveSetAutoLock(const LiveSetAutoLock& aOther) = delete;
  LiveSetAutoLock(LiveSetAutoLock&& aOther) = delete;
  LiveSetAutoLock& operator=(const LiveSetAutoLock& aOther) = delete;
  LiveSetAutoLock& operator=(LiveSetAutoLock&& aOther) = delete;

private:
  LiveSet*  mLiveSet;
};

class MOZ_RAII ReentrySentinel final
{
public:
  explicit ReentrySentinel(Interceptor* aCurrent)
    : mCurInterceptor(aCurrent)
  {
    static const bool kHasTls = tlsSentinelStackTop.init();
    MOZ_RELEASE_ASSERT(kHasTls);

    mPrevSentinel = tlsSentinelStackTop.get();
    tlsSentinelStackTop.set(this);
  }

  ~ReentrySentinel()
  {
    tlsSentinelStackTop.set(mPrevSentinel);
  }

  bool IsOutermost() const
  {
    return !(mPrevSentinel && mPrevSentinel->IsMarshaling(mCurInterceptor));
  }

  ReentrySentinel(const ReentrySentinel&) = delete;
  ReentrySentinel(ReentrySentinel&&) = delete;
  ReentrySentinel& operator=(const ReentrySentinel&) = delete;
  ReentrySentinel& operator=(ReentrySentinel&&) = delete;

private:
  bool IsMarshaling(Interceptor* aTopInterceptor) const
  {
    return aTopInterceptor == mCurInterceptor ||
           (mPrevSentinel && mPrevSentinel->IsMarshaling(aTopInterceptor));
  }

private:
  Interceptor*      mCurInterceptor;
  ReentrySentinel*  mPrevSentinel;

  static MOZ_THREAD_LOCAL(ReentrySentinel*) tlsSentinelStackTop;
};

MOZ_THREAD_LOCAL(ReentrySentinel*) ReentrySentinel::tlsSentinelStackTop;

class MOZ_RAII LoggedQIResult final
{
public:
  explicit LoggedQIResult(REFIID aIid)
    : mIid(aIid)
    , mHr(E_UNEXPECTED)
    , mTarget(nullptr)
    , mInterceptor(nullptr)
    , mBegin(TimeStamp::Now())
  {
  }

  ~LoggedQIResult()
  {
    if (!mTarget) {
      return;
    }

    TimeStamp end(TimeStamp::Now());
    TimeDuration total(end - mBegin);
    TimeDuration overhead(total - mNonOverheadDuration);

    InterceptorLog::QI(mHr, mTarget, mIid, mInterceptor, &overhead,
                       &mNonOverheadDuration);
  }

  void Log(IUnknown* aTarget, IUnknown* aInterceptor)
  {
    mTarget = aTarget;
    mInterceptor = aInterceptor;
  }

  void operator=(HRESULT aHr)
  {
    mHr = aHr;
  }

  operator HRESULT()
  {
    return mHr;
  }

  operator TimeDuration*()
  {
    return &mNonOverheadDuration;
  }

  LoggedQIResult(const LoggedQIResult&) = delete;
  LoggedQIResult(LoggedQIResult&&) = delete;
  LoggedQIResult& operator=(const LoggedQIResult&) = delete;
  LoggedQIResult& operator=(LoggedQIResult&&) = delete;

private:
  REFIID        mIid;
  HRESULT       mHr;
  IUnknown*     mTarget;
  IUnknown*     mInterceptor;
  TimeDuration  mNonOverheadDuration;
  TimeStamp     mBegin;
};

} // namespace detail

static detail::LiveSet&
GetLiveSet()
{
  static detail::LiveSet sLiveSet;
  return sLiveSet;
}

/* static */ HRESULT
Interceptor::Create(STAUniquePtr<IUnknown> aTarget, IInterceptorSink* aSink,
                    REFIID aInitialIid, void** aOutInterface)
{
  MOZ_ASSERT(aOutInterface && aTarget && aSink);
  if (!aOutInterface) {
    return E_INVALIDARG;
  }

  detail::LiveSetAutoLock lock(GetLiveSet());

  RefPtr<IWeakReference> existingWeak(Move(GetLiveSet().Get(aTarget.get())));
  if (existingWeak) {
    RefPtr<IWeakReferenceSource> existingStrong;
    if (SUCCEEDED(existingWeak->ToStrongRef(getter_AddRefs(existingStrong)))) {
      // QI on existingStrong may touch other threads. Since we now hold a
      // strong ref on the interceptor, we may now release the lock.
      lock.Unlock();
      return existingStrong->QueryInterface(aInitialIid, aOutInterface);
    }
  }

  *aOutInterface = nullptr;

  if (!aTarget || !aSink) {
    return E_INVALIDARG;
  }

  RefPtr<Interceptor> intcpt(new Interceptor(aSink));
  return intcpt->GetInitialInterceptorForIID(lock, aInitialIid, Move(aTarget),
                                             aOutInterface);
}

Interceptor::Interceptor(IInterceptorSink* aSink)
  : WeakReferenceSupport(WeakReferenceSupport::Flags::eDestroyOnMainThread)
  , mEventSink(aSink)
  , mMutex("mozilla::mscom::Interceptor::mMutex")
  , mStdMarshal(nullptr)
{
  MOZ_ASSERT(aSink);
  RefPtr<IWeakReference> weakRef;
  if (SUCCEEDED(GetWeakReference(getter_AddRefs(weakRef)))) {
    aSink->SetInterceptor(weakRef);
  }
}

Interceptor::~Interceptor()
{
  { // Scope for lock
    detail::LiveSetAutoLock lock(GetLiveSet());
    GetLiveSet().Remove(mTarget.get());
  }

  // This needs to run on the main thread because it releases target interface
  // reference counts which may not be thread-safe.
  MOZ_ASSERT(NS_IsMainThread());
  for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len; ++index) {
    MapEntry& entry = mInterceptorMap[index];
    entry.mInterceptor = nullptr;
    entry.mTargetInterface->Release();
  }
}

HRESULT
Interceptor::GetClassForHandler(DWORD aDestContext, void* aDestContextPtr,
                                CLSID* aHandlerClsid)
{
  if (aDestContextPtr || !aHandlerClsid ||
      aDestContext == MSHCTX_DIFFERENTMACHINE) {
    return E_INVALIDARG;
  }

  MOZ_ASSERT(mEventSink);
  return mEventSink->GetHandler(WrapNotNull(aHandlerClsid));
}

REFIID
Interceptor::MarshalAs(REFIID aIid) const
{
#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
  return IsCallerExternalProcess() ? aIid : mEventSink->MarshalAs(aIid);
#else
  return mEventSink->MarshalAs(aIid);
#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
}

HRESULT
Interceptor::GetUnmarshalClass(REFIID riid, void* pv, DWORD dwDestContext,
                               void* pvDestContext, DWORD mshlflags,
                               CLSID* pCid)
{
  return mStdMarshal->GetUnmarshalClass(MarshalAs(riid), pv, dwDestContext,
                                        pvDestContext, mshlflags, pCid);
}

HRESULT
Interceptor::GetMarshalSizeMax(REFIID riid, void* pv, DWORD dwDestContext,
                               void* pvDestContext, DWORD mshlflags,
                               DWORD* pSize)
{
  detail::ReentrySentinel sentinel(this);

  HRESULT hr = mStdMarshal->GetMarshalSizeMax(MarshalAs(riid), pv, dwDestContext,
                                              pvDestContext, mshlflags, pSize);
  if (FAILED(hr) || !sentinel.IsOutermost()) {
    return hr;
  }

  DWORD payloadSize = 0;
  hr = mEventSink->GetHandlerPayloadSize(WrapNotNull(this),
                                         WrapNotNull(&payloadSize));
  if (hr == E_NOTIMPL) {
    return S_OK;
  }

  if (SUCCEEDED(hr)) {
    *pSize += payloadSize;
  }
  return hr;
}

HRESULT
Interceptor::MarshalInterface(IStream* pStm, REFIID riid, void* pv,
                              DWORD dwDestContext, void* pvDestContext,
                              DWORD mshlflags)
{
  detail::ReentrySentinel sentinel(this);

  HRESULT hr;

#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
  // Save the current stream position
  LARGE_INTEGER seekTo;
  seekTo.QuadPart = 0;

  ULARGE_INTEGER objrefPos;

  hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &objrefPos);
  if (FAILED(hr)) {
    return hr;
  }

#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)

  hr = mStdMarshal->MarshalInterface(pStm, MarshalAs(riid), pv, dwDestContext,
                                     pvDestContext, mshlflags);
  if (FAILED(hr) || !sentinel.IsOutermost()) {
    return hr;
  }

#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
  if (XRE_IsContentProcess() && IsCallerExternalProcess()) {
    // The caller isn't our chrome process, so do not provide a handler.

    // First, save the current position that marks the current end of the
    // OBJREF in the stream.
    ULARGE_INTEGER endPos;
    hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &endPos);
    if (FAILED(hr)) {
      return hr;
    }

    // Now strip out the handler.
    if (!StripHandlerFromOBJREF(WrapNotNull(pStm), objrefPos.QuadPart,
                                endPos.QuadPart)) {
      return E_FAIL;
    }

    return S_OK;
  }
#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)

  hr = mEventSink->WriteHandlerPayload(WrapNotNull(this), WrapNotNull(pStm));
  if (hr == E_NOTIMPL) {
    return S_OK;
  }

  return hr;
}

HRESULT
Interceptor::UnmarshalInterface(IStream* pStm, REFIID riid,
                                void** ppv)
{
  return mStdMarshal->UnmarshalInterface(pStm, riid, ppv);
}

HRESULT
Interceptor::ReleaseMarshalData(IStream* pStm)
{
  return mStdMarshal->ReleaseMarshalData(pStm);
}

HRESULT
Interceptor::DisconnectObject(DWORD dwReserved)
{
  return mStdMarshal->DisconnectObject(dwReserved);
}

Interceptor::MapEntry*
Interceptor::Lookup(REFIID aIid)
{
  mMutex.AssertCurrentThreadOwns();

  for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len; ++index) {
    if (mInterceptorMap[index].mIID == aIid) {
      return &mInterceptorMap[index];
    }
  }
  return nullptr;
}

HRESULT
Interceptor::GetTargetForIID(REFIID aIid,
                             InterceptorTargetPtr<IUnknown>& aTarget)
{
  MutexAutoLock lock(mMutex);
  MapEntry* entry = Lookup(aIid);
  if (entry) {
    aTarget.reset(entry->mTargetInterface);
    return S_OK;
  }

  return E_NOINTERFACE;
}

// CoGetInterceptor requires type metadata to be able to generate its emulated
// vtable. If no registered metadata is available, CoGetInterceptor returns
// kFileNotFound.
static const HRESULT kFileNotFound = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);

HRESULT
Interceptor::CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput)
{
  // In order to aggregate, we *must* request IID_IUnknown as the initial
  // interface for the interceptor, as that IUnknown is non-delegating.
  // This is a fundamental rule for creating aggregated objects in COM.
  HRESULT hr = ::CoGetInterceptor(aIid, aOuter, IID_IUnknown, (void**)aOutput);
  if (hr != kFileNotFound) {
    return hr;
  }

  // In the case that CoGetInterceptor returns kFileNotFound, we can try to
  // explicitly load typelib data from our runtime registration facility and
  // pass that into CoGetInterceptorFromTypeInfo.

  RefPtr<ITypeInfo> typeInfo;
  bool found = RegisteredProxy::Find(aIid, getter_AddRefs(typeInfo));
  // If this assert fires then we have omitted registering the typelib for a
  // required interface. To fix this, review our calls to mscom::RegisterProxy
  // and mscom::RegisterTypelib, and add the additional typelib as necessary.
  MOZ_ASSERT(found);
  if (!found) {
    return kFileNotFound;
  }

  hr = ::CoGetInterceptorFromTypeInfo(aIid, aOuter, typeInfo, IID_IUnknown,
                                      (void**)aOutput);
  // If this assert fires then the interceptor doesn't like something about
  // the format of the typelib. One thing in particular that it doesn't like
  // is complex types that contain unions.
  MOZ_ASSERT(SUCCEEDED(hr));
  return hr;
}

HRESULT
Interceptor::PublishTarget(detail::LiveSetAutoLock& aLiveSetLock,
                           RefPtr<IUnknown> aInterceptor,
                           REFIID aTargetIid,
                           STAUniquePtr<IUnknown> aTarget)
{
  RefPtr<IWeakReference> weakRef;
  HRESULT hr = GetWeakReference(getter_AddRefs(weakRef));
  if (FAILED(hr)) {
    return hr;
  }

  // mTarget is a weak reference to aTarget. This is safe because we transfer
  // ownership of aTarget into mInterceptorMap which remains live for the
  // lifetime of this Interceptor.
  mTarget = ToInterceptorTargetPtr(aTarget);
  GetLiveSet().Put(mTarget.get(), weakRef.forget());

  // Now we transfer aTarget's ownership into mInterceptorMap.
  mInterceptorMap.AppendElement(MapEntry(aTargetIid,
                                         aInterceptor,
                                         aTarget.release()));

  // Release the live set lock because subsequent operations may post work to
  // the main thread, creating potential for deadlocks.
  aLiveSetLock.Unlock();
  return S_OK;
}

HRESULT
Interceptor::GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLiveSetLock,
                                         REFIID aTargetIid,
                                         STAUniquePtr<IUnknown> aTarget,
                                         void** aOutInterceptor)
{
  MOZ_ASSERT(aOutInterceptor);
  MOZ_ASSERT(aTargetIid != IID_IMarshal);
  MOZ_ASSERT(!IsProxy(aTarget.get()));

  if (aTargetIid == IID_IUnknown) {
    // We must lock ourselves so that nothing can race with us once we have been
    // published to the live set.
    AutoLock lock(*this);

    HRESULT hr = PublishTarget(aLiveSetLock, nullptr, aTargetIid, Move(aTarget));
    ENSURE_HR_SUCCEEDED(hr);

    hr = QueryInterface(aTargetIid, aOutInterceptor);
    ENSURE_HR_SUCCEEDED(hr);
    return hr;
  }

  // Raise the refcount for stabilization purposes during aggregation
  RefPtr<IUnknown> kungFuDeathGrip(static_cast<IUnknown*>(
        static_cast<WeakReferenceSupport*>(this)));

  RefPtr<IUnknown> unkInterceptor;
  HRESULT hr = CreateInterceptor(aTargetIid, kungFuDeathGrip,
                                 getter_AddRefs(unkInterceptor));
  ENSURE_HR_SUCCEEDED(hr);

  RefPtr<ICallInterceptor> interceptor;
  hr = unkInterceptor->QueryInterface(IID_ICallInterceptor,
                                      getter_AddRefs(interceptor));
  ENSURE_HR_SUCCEEDED(hr);

  hr = interceptor->RegisterSink(mEventSink);
  ENSURE_HR_SUCCEEDED(hr);

  // We must lock ourselves so that nothing can race with us once we have been
  // published to the live set.
  AutoLock lock(*this);

  hr = PublishTarget(aLiveSetLock, unkInterceptor, aTargetIid, Move(aTarget));
  ENSURE_HR_SUCCEEDED(hr);

  if (MarshalAs(aTargetIid) == aTargetIid) {
    hr = unkInterceptor->QueryInterface(aTargetIid, aOutInterceptor);
    ENSURE_HR_SUCCEEDED(hr);
    return hr;
  }

  hr = GetInterceptorForIID(aTargetIid, aOutInterceptor);
  ENSURE_HR_SUCCEEDED(hr);
  return hr;
}

/**
 * This method contains the core guts of the handling of QueryInterface calls
 * that are delegated to us from the ICallInterceptor.
 *
 * @param aIid ID of the desired interface
 * @param aOutInterceptor The resulting emulated vtable that corresponds to
 * the interface specified by aIid.
 */
HRESULT
Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor)
{
  detail::LoggedQIResult result(aIid);

  if (!aOutInterceptor) {
    return E_INVALIDARG;
  }

  if (aIid == IID_IUnknown) {
    // Special case: When we see IUnknown, we just provide a reference to this
    RefPtr<IInterceptor> intcpt(this);
    intcpt.forget(aOutInterceptor);
    return S_OK;
  }

  REFIID interceptorIid = MarshalAs(aIid);

  RefPtr<IUnknown> unkInterceptor;
  IUnknown* interfaceForQILog = nullptr;

  // (1) Check to see if we already have an existing interceptor for
  // interceptorIid.

  { // Scope for lock
    MutexAutoLock lock(mMutex);
    MapEntry* entry = Lookup(interceptorIid);
    if (entry) {
      unkInterceptor = entry->mInterceptor;
      interfaceForQILog = entry->mTargetInterface;
    }
  }

  // (1a) A COM interceptor already exists for this interface, so all we need
  // to do is run a QI on it.
  if (unkInterceptor) {
    // Technically we didn't actually execute a QI on the target interface, but
    // for logging purposes we would like to record the fact that this interface
    // was requested.
    result.Log(mTarget.get(), interfaceForQILog);
    result = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
    ENSURE_HR_SUCCEEDED(result);
    return result;
  }

  // (2) Obtain a new target interface.

  // (2a) First, make sure that the target interface is available
  // NB: We *MUST* query the correct interface! ICallEvents::Invoke casts its
  // pvReceiver argument directly to the required interface! DO NOT assume
  // that COM will use QI or upcast/downcast!
  HRESULT hr;

  STAUniquePtr<IUnknown> targetInterface;
  IUnknown* rawTargetInterface = nullptr;
  hr = QueryInterfaceTarget(interceptorIid, (void**)&rawTargetInterface, result);
  targetInterface.reset(rawTargetInterface);
  result = hr;
  result.Log(mTarget.get(), targetInterface.get());
  MOZ_ASSERT(SUCCEEDED(hr) || hr == E_NOINTERFACE);
  if (hr == E_NOINTERFACE) {
    return hr;
  }
  ENSURE_HR_SUCCEEDED(hr);

  // We *really* shouldn't be adding interceptors to proxies
  MOZ_ASSERT(aIid != IID_IMarshal);

  // (3) Create a new COM interceptor to that interface that delegates its
  // IUnknown to |this|.

  // Raise the refcount for stabilization purposes during aggregation
  RefPtr<IUnknown> kungFuDeathGrip(static_cast<IUnknown*>(
        static_cast<WeakReferenceSupport*>(this)));

  hr = CreateInterceptor(interceptorIid, kungFuDeathGrip,
                         getter_AddRefs(unkInterceptor));
  ENSURE_HR_SUCCEEDED(hr);

  // (4) Obtain the interceptor's ICallInterceptor interface and register our
  // event sink.
  RefPtr<ICallInterceptor> interceptor;
  hr = unkInterceptor->QueryInterface(IID_ICallInterceptor,
                                      (void**)getter_AddRefs(interceptor));
  ENSURE_HR_SUCCEEDED(hr);

  hr = interceptor->RegisterSink(mEventSink);
  ENSURE_HR_SUCCEEDED(hr);

  // (5) Now that we have this new COM interceptor, insert it into the map.

  { // Scope for lock
    MutexAutoLock lock(mMutex);
    // We might have raced with another thread, so first check that we don't
    // already have an entry for this
    MapEntry* entry = Lookup(interceptorIid);
    if (entry && entry->mInterceptor) {
      unkInterceptor = entry->mInterceptor;
    } else {
      // MapEntry has a RefPtr to unkInterceptor, OTOH we must not touch the
      // refcount for the target interface because we are just moving it into
      // the map and its refcounting might not be thread-safe.
      IUnknown* rawTargetInterface = targetInterface.release();
      mInterceptorMap.AppendElement(MapEntry(interceptorIid,
                                             unkInterceptor,
                                             rawTargetInterface));
    }
  }

  hr = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
  ENSURE_HR_SUCCEEDED(hr);
  return hr;
}

HRESULT
Interceptor::QueryInterfaceTarget(REFIID aIid, void** aOutput,
                                  TimeDuration* aOutDuration)
{
  // NB: This QI needs to run on the main thread because the target object
  // is probably Gecko code that is not thread-safe. Note that this main
  // thread invocation is *synchronous*.
  MainThreadInvoker invoker;
  HRESULT hr;
  auto runOnMainThread = [&]() -> void {
    MOZ_ASSERT(NS_IsMainThread());
    hr = mTarget->QueryInterface(aIid, aOutput);
  };
  if (!invoker.Invoke(NS_NewRunnableFunction("Interceptor::QueryInterface", runOnMainThread))) {
    return E_FAIL;
  }
  if (aOutDuration) {
    *aOutDuration = invoker.GetDuration();
  }
  return hr;
}

HRESULT
Interceptor::QueryInterface(REFIID riid, void** ppv)
{
  if (riid == IID_INoMarshal) {
    // This entire library is designed around marshaling, so there's no point
    // propagating this QI request all over the place!
    return E_NOINTERFACE;
  }

  return WeakReferenceSupport::QueryInterface(riid, ppv);
}

HRESULT
Interceptor::ThreadSafeQueryInterface(REFIID aIid, IUnknown** aOutInterface)
{
  if (aIid == IID_IStdMarshalInfo) {
    detail::ReentrySentinel sentinel(this);

    if (!sentinel.IsOutermost()) {
      return E_NOINTERFACE;
    }

    // Do not indicate that this interface is available unless we actually
    // support it. We'll check that by looking for a successful call to
    // IInterceptorSink::GetHandler()
    CLSID dummy;
    if (FAILED(mEventSink->GetHandler(WrapNotNull(&dummy)))) {
      return E_NOINTERFACE;
    }

    RefPtr<IStdMarshalInfo> std(this);
    std.forget(aOutInterface);
    return S_OK;
  }

  if (aIid == IID_IMarshal) {
    HRESULT hr;

    if (!mStdMarshalUnk) {
      if (XRE_IsContentProcess()) {
        hr = FastMarshaler::Create(static_cast<IWeakReferenceSource*>(this),
                                   getter_AddRefs(mStdMarshalUnk));
      } else {
        hr = ::CoGetStdMarshalEx(static_cast<IWeakReferenceSource*>(this),
                                 SMEXF_SERVER, getter_AddRefs(mStdMarshalUnk));
      }

      ENSURE_HR_SUCCEEDED(hr);
    }

    if (!mStdMarshal) {
      hr = mStdMarshalUnk->QueryInterface(IID_IMarshal, (void**)&mStdMarshal);
      ENSURE_HR_SUCCEEDED(hr);

      // mStdMarshal is weak, so drop its refcount
      mStdMarshal->Release();
    }

    RefPtr<IMarshal> marshal(this);
    marshal.forget(aOutInterface);
    return S_OK;
  }

  if (aIid == IID_IInterceptor) {
    RefPtr<IInterceptor> intcpt(this);
    intcpt.forget(aOutInterface);
    return S_OK;
  }

  if (aIid == IID_IDispatch) {
    STAUniquePtr<IDispatch> disp;
    IDispatch* rawDisp = nullptr;
    HRESULT hr = QueryInterfaceTarget(aIid, (void**)&rawDisp);
    ENSURE_HR_SUCCEEDED(hr);

    disp.reset(rawDisp);
    return DispatchForwarder::Create(this, disp, aOutInterface);
  }

  return GetInterceptorForIID(aIid, (void**)aOutInterface);
}

ULONG
Interceptor::AddRef()
{
  return WeakReferenceSupport::AddRef();
}

ULONG
Interceptor::Release()
{
  return WeakReferenceSupport::Release();
}

} // namespace mscom
} // namespace mozilla
