nsresult
Logger::Shutdown()
{
  MOZ_ASSERT(NS_IsMainThread());
  nsresult rv = mThread->Dispatch(NewNonOwningRunnableMethod(this,
                                                             &Logger::CloseFile),
                                  NS_DISPATCH_NORMAL);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Dispatch failed");

  rv = mThread->Shutdown();
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Shutdown failed");
  return NS_OK;
}
Logger::Logger(const nsACString& aLeafBaseName)
  : mMutex("mozilla::com::InterceptorLog::Logger")
{
  MOZ_ASSERT(NS_IsMainThread());
  nsCOMPtr<nsIFile> logFileName;
  GeckoProcessType procType = XRE_GetProcessType();
  nsAutoCString leafName(aLeafBaseName);
  nsresult rv;
  if (procType == GeckoProcessType_Default) {
    leafName.AppendLiteral("-Parent-");
    rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(logFileName));
  } else if (procType == GeckoProcessType_Content) {
    leafName.AppendLiteral("-Content-");
#if defined(MOZ_CONTENT_SANDBOX)
    rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
                                getter_AddRefs(logFileName));
#else
    rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
                                getter_AddRefs(logFileName));
#endif // defined(MOZ_CONTENT_SANDBOX)
  } else {
    return;
  }
  if (NS_FAILED(rv)) {
    return;
  }
  DWORD pid = GetCurrentProcessId();
  leafName.AppendPrintf("%u.log", pid);
  // Using AppendNative here because Windows
  rv = logFileName->AppendNative(leafName);
  if (NS_FAILED(rv)) {
    return;
  }
  mLogFileName.swap(logFileName);

  nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
  nsCOMPtr<nsIObserver> shutdownEvent = new ShutdownEvent();
  rv = obsSvc->AddObserver(shutdownEvent, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID,
                           false);
  if (NS_FAILED(rv)) {
    return;
  }

  nsCOMPtr<nsIRunnable> openRunnable(
      NewNonOwningRunnableMethod(this, &Logger::OpenFile));
  rv = NS_NewNamedThread("COM Intcpt Log", getter_AddRefs(mThread),
                         openRunnable);
  if (NS_FAILED(rv)) {
    obsSvc->RemoveObserver(shutdownEvent, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID);
  }
}
void
Logger::LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface)
{
  if (FAILED(aResult)) {
    return;
  }
  double elapsed = GetElapsedTime();

  nsPrintfCString line("%fus\t0x%0p\tIUnknown::QueryInterface\t([in] ", elapsed,
                       aTarget);

  WCHAR buf[39] = {0};
  if (StringFromGUID2(aIid, buf, mozilla::ArrayLength(buf))) {
    line.AppendPrintf("%S", buf);
  } else {
    line.AppendLiteral("(IID Conversion Failed)");
  }
  line.AppendPrintf(", [out] 0x%p)\t0x%08X\n", aInterface, aResult);

  MutexAutoLock lock(mMutex);
  mEntries.AppendElement(line);
  mThread->Dispatch(NewNonOwningRunnableMethod(this, &Logger::Flush),
                    NS_DISPATCH_NORMAL);
}
void
Logger::LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface)
{
  // (1) Gather info about the call
  double elapsed = GetElapsedTime();

  CALLFRAMEINFO callInfo;
  HRESULT hr = aCallFrame->GetInfo(&callInfo);
  if (FAILED(hr)) {
    return;
  }

  PWSTR interfaceName = nullptr;
  PWSTR methodName = nullptr;
  hr = aCallFrame->GetNames(&interfaceName, &methodName);
  if (FAILED(hr)) {
    return;
  }

  // (2) Serialize the call
  nsPrintfCString line("%fus\t0x%p\t%S::%S\t(", elapsed,
                       aTargetInterface, interfaceName, methodName);

  CoTaskMemFree(interfaceName);
  interfaceName = nullptr;
  CoTaskMemFree(methodName);
  methodName = nullptr;

  // Check for supplemental array data
  const ArrayData* arrayData = FindArrayData(callInfo.iid, callInfo.iMethod);

  for (ULONG paramIndex = 0; paramIndex < callInfo.cParams; ++paramIndex) {
    CALLFRAMEPARAMINFO paramInfo;
    hr = aCallFrame->GetParamInfo(paramIndex, &paramInfo);
    if (SUCCEEDED(hr)) {
      line.AppendLiteral("[");
      if (paramInfo.fIn) {
        line.AppendLiteral("in");
      }
      if (paramInfo.fOut) {
        line.AppendLiteral("out");
      }
      line.AppendLiteral("] ");
    }
    VARIANT paramValue;
    hr = aCallFrame->GetParam(paramIndex, &paramValue);
    if (SUCCEEDED(hr)) {
      if (arrayData && paramIndex == arrayData->mArrayParamIndex) {
        VARIANT lengthParam;
        hr = aCallFrame->GetParam(arrayData->mLengthParamIndex, &lengthParam);
        if (SUCCEEDED(hr)) {
          line.AppendLiteral("{ ");
          for (LONG i = 0; i < *lengthParam.plVal; ++i) {
            VariantToString(paramValue, line, i);
            if (i < *lengthParam.plVal - 1) {
              line.AppendLiteral(", ");
            }
          }
          line.AppendLiteral(" }");
        } else {
          line.AppendPrintf("(GetParam failed with HRESULT 0x%08X)", hr);
        }
      } else {
        VariantToString(paramValue, line);
      }
    } else {
      line.AppendPrintf("(GetParam failed with HRESULT 0x%08X)", hr);
    }
    if (paramIndex < callInfo.cParams - 1) {
      line.AppendLiteral(", ");
    }
  }
  line.AppendLiteral(")\t");

  HRESULT callResult = aCallFrame->GetReturnValue();
  line.AppendPrintf("0x%08X\n", callResult);

  // (3) Enqueue event for logging
  MutexAutoLock lock(mMutex);
  mEntries.AppendElement(line);
  mThread->Dispatch(NewNonOwningRunnableMethod(this, &Logger::Flush),
                    NS_DISPATCH_NORMAL);
}