/* -*- 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 <stdlib.h>
#include "nscore.h"
#include "nsISupports.h"
#include "nspr.h"
#include "nsCRT.h" // for atoll

#include "nsCategoryManager.h"
#include "nsCOMPtr.h"
#include "nsComponentManager.h"
#include "nsDirectoryService.h"
#include "nsDirectoryServiceDefs.h"
#include "nsCategoryManager.h"
#include "nsCategoryManagerUtils.h"
#include "xptiprivate.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/XPTInterfaceInfoManager.h"
#include "nsIConsoleService.h"
#include "nsIObserverService.h"
#include "nsISimpleEnumerator.h"
#include "nsIStringEnumerator.h"
#include "nsXPCOM.h"
#include "nsXPCOMPrivate.h"
#include "nsISupportsPrimitives.h"
#include "nsIClassInfo.h"
#include "nsLocalFile.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "prcmon.h"
#include "xptinfo.h" // this after nsISupports, to pick up IID so that xpt stuff doesn't try to define it itself...
#include "nsThreadUtils.h"
#include "prthread.h"
#include "private/pprthred.h"
#include "nsTArray.h"
#include "prio.h"
#include "ManifestParser.h"
#include "nsNetUtil.h"
#include "mozilla/Services.h"

#include "mozilla/GenericFactory.h"
#include "nsSupportsPrimitives.h"
#include "nsArray.h"
#include "nsIMutableArray.h"
#include "nsArrayEnumerator.h"
#include "nsStringEnumerator.h"
#include "mozilla/FileUtils.h"
#include "mozilla/URLPreloader.h"
#include "mozilla/UniquePtr.h"
#include "nsDataHashtable.h"

#include <new>     // for placement new

#include "mozilla/Omnijar.h"

#include "mozilla/Logging.h"
#include "LogModulePrefWatcher.h"

using namespace mozilla;

static LazyLogModule nsComponentManagerLog("nsComponentManager");

#if 0
 #define SHOW_DENIED_ON_SHUTDOWN
 #define SHOW_CI_ON_EXISTING_SERVICE
#endif

// Bloated registry buffer size to improve startup performance -- needs to
// be big enough to fit the entire file into memory or it'll thrash.
// 512K is big enough to allow for some future growth in the registry.
#define BIG_REGISTRY_BUFLEN   (512*1024)

// Common Key Names
const char xpcomComponentsKeyName[] = "software/mozilla/XPCOM/components";
const char xpcomKeyName[] = "software/mozilla/XPCOM";

// Common Value Names
const char fileSizeValueName[] = "FileSize";
const char lastModValueName[] = "LastModTimeStamp";
const char nativeComponentType[] = "application/x-mozilla-native";
const char staticComponentType[] = "application/x-mozilla-static";

NS_DEFINE_CID(kCategoryManagerCID, NS_CATEGORYMANAGER_CID);

#define UID_STRING_LENGTH 39

nsresult
nsGetServiceFromCategory::operator()(const nsIID& aIID,
                                     void** aInstancePtr) const
{
  nsresult rv;
  nsCString value;
  nsCOMPtr<nsICategoryManager> catman;
  nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager;
  if (!compMgr) {
    rv = NS_ERROR_NOT_INITIALIZED;
    goto error;
  }

  if (!mCategory || !mEntry) {
    // when categories have defaults, use that for null mEntry
    rv = NS_ERROR_NULL_POINTER;
    goto error;
  }

  rv = compMgr->nsComponentManagerImpl::GetService(kCategoryManagerCID,
                                                   NS_GET_IID(nsICategoryManager),
                                                   getter_AddRefs(catman));
  if (NS_FAILED(rv)) {
    goto error;
  }

  /* find the contractID for category.entry */
  rv = catman->GetCategoryEntry(mCategory, mEntry,
                                getter_Copies(value));
  if (NS_FAILED(rv)) {
    goto error;
  }
  if (value.IsVoid()) {
    rv = NS_ERROR_SERVICE_NOT_AVAILABLE;
    goto error;
  }

  rv = compMgr->nsComponentManagerImpl::GetServiceByContractID(value.get(),
                                                               aIID,
                                                               aInstancePtr);
  if (NS_FAILED(rv)) {
error:
    *aInstancePtr = 0;
  }
  if (mErrorPtr) {
    *mErrorPtr = rv;
  }
  return rv;
}

// GetService and a few other functions need to exit their mutex mid-function
// without reentering it later in the block. This class supports that
// style of early-exit that MutexAutoUnlock doesn't.

namespace {

class MOZ_STACK_CLASS MutexLock
{
public:
  explicit MutexLock(SafeMutex& aMutex)
    : mMutex(aMutex)
    , mLocked(false)
  {
    Lock();
  }

  ~MutexLock()
  {
    if (mLocked) {
      Unlock();
    }
  }

  void Lock()
  {
    NS_ASSERTION(!mLocked, "Re-entering a mutex");
    mMutex.Lock();
    mLocked = true;
  }

  void Unlock()
  {
    NS_ASSERTION(mLocked, "Exiting a mutex that isn't held!");
    mMutex.Unlock();
    mLocked = false;
  }

private:
  SafeMutex& mMutex;
  bool mLocked;
};

} // namespace

// this is safe to call during InitXPCOM
static already_AddRefed<nsIFile>
GetLocationFromDirectoryService(const char* aProp)
{
  nsCOMPtr<nsIProperties> directoryService;
  nsDirectoryService::Create(nullptr,
                             NS_GET_IID(nsIProperties),
                             getter_AddRefs(directoryService));

  if (!directoryService) {
    return nullptr;
  }

  nsCOMPtr<nsIFile> file;
  nsresult rv = directoryService->Get(aProp,
                                      NS_GET_IID(nsIFile),
                                      getter_AddRefs(file));
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  return file.forget();
}

static already_AddRefed<nsIFile>
CloneAndAppend(nsIFile* aBase, const nsACString& aAppend)
{
  nsCOMPtr<nsIFile> f;
  aBase->Clone(getter_AddRefs(f));
  if (!f) {
    return nullptr;
  }

  f->AppendNative(aAppend);
  return f.forget();
}

////////////////////////////////////////////////////////////////////////////////
// nsComponentManagerImpl
////////////////////////////////////////////////////////////////////////////////

nsresult
nsComponentManagerImpl::Create(nsISupports* aOuter, REFNSIID aIID,
                               void** aResult)
{
  if (aOuter) {
    return NS_ERROR_NO_AGGREGATION;
  }

  if (!gComponentManager) {
    return NS_ERROR_FAILURE;
  }

  return gComponentManager->QueryInterface(aIID, aResult);
}

static const int CONTRACTID_HASHTABLE_INITIAL_LENGTH = 1024;

nsComponentManagerImpl::nsComponentManagerImpl()
  : mFactories(CONTRACTID_HASHTABLE_INITIAL_LENGTH)
  , mContractIDs(CONTRACTID_HASHTABLE_INITIAL_LENGTH)
  , mLock("nsComponentManagerImpl.mLock")
  , mStatus(NOT_INITIALIZED)
{
}

nsTArray<const mozilla::Module*>* nsComponentManagerImpl::sStaticModules;

NSMODULE_DEFN(start_kPStaticModules);
NSMODULE_DEFN(end_kPStaticModules);

/* The content between start_kPStaticModules and end_kPStaticModules is gathered
 * by the linker from various objects containing symbols in a specific section.
 * ASAN considers (rightfully) the use of this content as a global buffer
 * overflow. But this is a deliberate and well-considered choice, with no proper
 * way to make ASAN happy. */
MOZ_ASAN_BLACKLIST
/* static */ void
nsComponentManagerImpl::InitializeStaticModules()
{
  if (sStaticModules) {
    return;
  }

  sStaticModules = new nsTArray<const mozilla::Module*>;
  for (const mozilla::Module * const* staticModules =
         &NSMODULE_NAME(start_kPStaticModules) + 1;
       staticModules < &NSMODULE_NAME(end_kPStaticModules); ++staticModules)
    if (*staticModules) { // ASAN adds padding
      sStaticModules->AppendElement(*staticModules);
    }
}

nsTArray<nsComponentManagerImpl::ComponentLocation>*
nsComponentManagerImpl::sModuleLocations;

/* static */ void
nsComponentManagerImpl::InitializeModuleLocations()
{
  if (sModuleLocations) {
    return;
  }

  sModuleLocations = new nsTArray<ComponentLocation>;
}

nsresult
nsComponentManagerImpl::Init()
{
  MOZ_ASSERT(NOT_INITIALIZED == mStatus);

  nsCOMPtr<nsIFile> greDir =
    GetLocationFromDirectoryService(NS_GRE_DIR);
  nsCOMPtr<nsIFile> appDir =
    GetLocationFromDirectoryService(NS_XPCOM_CURRENT_PROCESS_DIR);

  InitializeStaticModules();

  nsCategoryManager::GetSingleton()->SuppressNotifications(true);

  RegisterModule(&kXPCOMModule, nullptr);

  for (uint32_t i = 0; i < sStaticModules->Length(); ++i) {
    RegisterModule((*sStaticModules)[i], nullptr);
  }

  bool loadChromeManifests = (XRE_GetProcessType() != GeckoProcessType_GPU);
  if (loadChromeManifests) {
    // The overall order in which chrome.manifests are expected to be treated
    // is the following:
    // - greDir
    // - greDir's omni.ja
    // - appDir
    // - appDir's omni.ja

    InitializeModuleLocations();
    ComponentLocation* cl = sModuleLocations->AppendElement();
    nsCOMPtr<nsIFile> lf = CloneAndAppend(greDir,
                                          NS_LITERAL_CSTRING("chrome.manifest"));
    cl->type = NS_APP_LOCATION;
    cl->location.Init(lf);

    RefPtr<nsZipArchive> greOmnijar =
      mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
    if (greOmnijar) {
      cl = sModuleLocations->AppendElement();
      cl->type = NS_APP_LOCATION;
      cl->location.Init(greOmnijar, "chrome.manifest");
    }

    bool equals = false;
    appDir->Equals(greDir, &equals);
    if (!equals) {
      cl = sModuleLocations->AppendElement();
      cl->type = NS_APP_LOCATION;
      lf = CloneAndAppend(appDir, NS_LITERAL_CSTRING("chrome.manifest"));
      cl->location.Init(lf);
    }

    RefPtr<nsZipArchive> appOmnijar =
      mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
    if (appOmnijar) {
      cl = sModuleLocations->AppendElement();
      cl->type = NS_APP_LOCATION;
      cl->location.Init(appOmnijar, "chrome.manifest");
    }

    RereadChromeManifests(false);
  }

  nsCategoryManager::GetSingleton()->SuppressNotifications(false);

  RegisterWeakMemoryReporter(this);

  // NB: The logging preference watcher needs to be registered late enough in
  // startup that it's okay to use the preference system, but also as soon as
  // possible so that log modules enabled via preferences are turned on as
  // early as possible.
  //
  // We can't initialize the preference watcher when the log module manager is
  // initialized, as a number of things attempt to start logging before the
  // preference system is initialized.
  //
  // The preference system is registered as a component so at this point during
  // component manager initialization we know it is setup and we can register
  // for notifications.
  LogModulePrefWatcher::RegisterPrefWatcher();

  // Unfortunately, we can't register the nsCategoryManager memory reporter
  // in its constructor (which is triggered by the GetSingleton() call
  // above) because the memory reporter manager isn't initialized at that
  // point.  So we wait until now.
  nsCategoryManager::GetSingleton()->InitMemoryReporter();

  MOZ_LOG(nsComponentManagerLog, LogLevel::Debug,
         ("nsComponentManager: Initialized."));

  mStatus = NORMAL;

  return NS_OK;
}

static bool
ProcessSelectorMatches(Module::ProcessSelector aSelector)
{
  GeckoProcessType type = XRE_GetProcessType();
  if (type == GeckoProcessType_GPU) {
    return !!(aSelector & Module::ALLOW_IN_GPU_PROCESS);
  }

  if (aSelector & Module::MAIN_PROCESS_ONLY) {
    return type == GeckoProcessType_Default;
  }
  if (aSelector & Module::CONTENT_PROCESS_ONLY) {
    return type == GeckoProcessType_Content;
  }
  return true;
}

static const int kModuleVersionWithSelector = 51;

void
nsComponentManagerImpl::RegisterModule(const mozilla::Module* aModule,
                                       FileLocation* aFile)
{
  mLock.AssertNotCurrentThreadOwns();

  if (aModule->mVersion >= kModuleVersionWithSelector &&
      !ProcessSelectorMatches(aModule->selector))
  {
    return;
  }

  {
    // Scope the monitor so that we don't hold it while calling into the
    // category manager.
    MutexLock lock(mLock);

    KnownModule* m;
    if (aFile) {
      nsCString uri;
      aFile->GetURIString(uri);
      NS_ASSERTION(!mKnownModules.Get(uri),
                   "Must not register a binary module twice.");

      m = new KnownModule(aModule, *aFile);
      mKnownModules.Put(uri, m);
    } else {
      m = new KnownModule(aModule);
      mKnownStaticModules.AppendElement(m);
    }

    if (aModule->mCIDs) {
      const mozilla::Module::CIDEntry* entry;
      for (entry = aModule->mCIDs; entry->cid; ++entry) {
        RegisterCIDEntryLocked(entry, m);
      }
    }

    if (aModule->mContractIDs) {
      const mozilla::Module::ContractIDEntry* entry;
      for (entry = aModule->mContractIDs; entry->contractid; ++entry) {
        RegisterContractIDLocked(entry);
      }
      MOZ_ASSERT(!entry->cid, "Incorrectly terminated contract list");
    }
  }

  if (aModule->mCategoryEntries) {
    const mozilla::Module::CategoryEntry* entry;
    for (entry = aModule->mCategoryEntries; entry->category; ++entry)
      nsCategoryManager::GetSingleton()->AddCategoryEntry(entry->category,
                                                          entry->entry,
                                                          entry->value);
  }
}

void
nsComponentManagerImpl::RegisterCIDEntryLocked(
    const mozilla::Module::CIDEntry* aEntry,
    KnownModule* aModule)
{
  mLock.AssertCurrentThreadOwns();

  if (!ProcessSelectorMatches(aEntry->processSelector)) {
    return;
  }

  if (auto entry = mFactories.LookupForAdd(*aEntry->cid)) {
    nsFactoryEntry* f = entry.Data();
    NS_WARNING("Re-registering a CID?");

    char idstr[NSID_LENGTH];
    aEntry->cid->ToProvidedString(idstr);

    nsCString existing;
    if (f->mModule) {
      existing = f->mModule->Description();
    } else {
      existing = "<unknown module>";
    }
    SafeMutexAutoUnlock unlock(mLock);
    LogMessage("While registering XPCOM module %s, trying to re-register CID '%s' already registered by %s.",
               aModule->Description().get(),
               idstr,
               existing.get());
  } else {
    entry.OrInsert([aEntry, aModule] () { return new nsFactoryEntry(aEntry, aModule); });
  }
}

void
nsComponentManagerImpl::RegisterContractIDLocked(
    const mozilla::Module::ContractIDEntry* aEntry)
{
  mLock.AssertCurrentThreadOwns();

  if (!ProcessSelectorMatches(aEntry->processSelector)) {
    return;
  }

  nsFactoryEntry* f = mFactories.Get(*aEntry->cid);
  if (!f) {
    NS_WARNING("No CID found when attempting to map contract ID");

    char idstr[NSID_LENGTH];
    aEntry->cid->ToProvidedString(idstr);

    SafeMutexAutoUnlock unlock(mLock);
    LogMessage("Could not map contract ID '%s' to CID %s because no implementation of the CID is registered.",
               aEntry->contractid,
               idstr);

    return;
  }

  mContractIDs.Put(nsDependentCString(aEntry->contractid), f);
}

static void
CutExtension(nsCString& aPath)
{
  int32_t dotPos = aPath.RFindChar('.');
  if (kNotFound == dotPos) {
    aPath.Truncate();
  } else {
    aPath.Cut(0, dotPos + 1);
  }
}

static void
DoRegisterManifest(NSLocationType aType,
                   FileLocation& aFile,
                   bool aChromeOnly,
                   bool aXPTOnly)
{
  MOZ_ASSERT(!aXPTOnly || !nsComponentManagerImpl::gComponentManager);

  auto result = URLPreloader::Read(aFile);
  if (result.isOk()) {
    nsCString buf(result.unwrap());

    ParseManifest(aType, aFile, buf.BeginWriting(), aChromeOnly, aXPTOnly);
  } else if (NS_BOOTSTRAPPED_LOCATION != aType) {
    nsCString uri;
    aFile.GetURIString(uri);
    LogMessage("Could not read chrome manifest '%s'.", uri.get());
  }
}

void
nsComponentManagerImpl::RegisterManifest(NSLocationType aType,
                                         FileLocation& aFile,
                                         bool aChromeOnly)
{
  DoRegisterManifest(aType, aFile, aChromeOnly, false);
}

void
nsComponentManagerImpl::ManifestManifest(ManifestProcessingContext& aCx,
                                         int aLineNo, char* const* aArgv)
{
  char* file = aArgv[0];
  FileLocation f(aCx.mFile, file);
  RegisterManifest(aCx.mType, f, aCx.mChromeOnly);
}

void
nsComponentManagerImpl::ManifestBinaryComponent(ManifestProcessingContext& aCx,
                                                int aLineNo,
                                                char* const* aArgv)
{
  LogMessageWithContext(aCx.mFile, aLineNo,
                        "Binary XPCOM components are no longer supported.");
}

static void
DoRegisterXPT(FileLocation& aFile)
{
  uint32_t len;
  FileLocation::Data data;
  UniquePtr<char[]> buf;
  nsresult rv = aFile.GetData(data);
  if (NS_SUCCEEDED(rv)) {
    rv = data.GetSize(&len);
  }
  if (NS_SUCCEEDED(rv)) {
    buf = MakeUnique<char[]>(len);
    rv = data.Copy(buf.get(), len);
  }
  if (NS_SUCCEEDED(rv)) {
    XPTInterfaceInfoManager::GetSingleton()->RegisterBuffer(buf.get(), len);
  } else {
    nsCString uri;
    aFile.GetURIString(uri);
    LogMessage("Could not read '%s'.", uri.get());
  }
}

void
nsComponentManagerImpl::ManifestXPT(ManifestProcessingContext& aCx,
                                    int aLineNo, char* const* aArgv)
{
  FileLocation f(aCx.mFile, aArgv[0]);
  DoRegisterXPT(f);
}

void
nsComponentManagerImpl::ManifestComponent(ManifestProcessingContext& aCx,
                                          int aLineNo, char* const* aArgv)
{
  mLock.AssertNotCurrentThreadOwns();

  char* id = aArgv[0];
  char* file = aArgv[1];

  nsID cid;
  if (!cid.Parse(id)) {
    LogMessageWithContext(aCx.mFile, aLineNo,
                          "Malformed CID: '%s'.", id);
    return;
  }

  // Precompute the hash/file data outside of the lock
  FileLocation fl(aCx.mFile, file);
  nsCString hash;
  fl.GetURIString(hash);

  MutexLock lock(mLock);
  auto entry = mFactories.LookupForAdd(cid);
  if (entry) {
    nsFactoryEntry* f = entry.Data();
    char idstr[NSID_LENGTH];
    cid.ToProvidedString(idstr);

    nsCString existing;
    if (f->mModule) {
      existing = f->mModule->Description();
    } else {
      existing = "<unknown module>";
    }

    lock.Unlock();

    LogMessageWithContext(aCx.mFile, aLineNo,
                          "Trying to re-register CID '%s' already registered by %s.",
                          idstr,
                          existing.get());
    return;
  }

  KnownModule* km;

  km = mKnownModules.Get(hash);
  if (!km) {
    km = new KnownModule(fl);
    mKnownModules.Put(hash, km);
  }

  void* place = mArena.Allocate(sizeof(nsCID));
  nsID* permanentCID = static_cast<nsID*>(place);
  *permanentCID = cid;

  place = mArena.Allocate(sizeof(mozilla::Module::CIDEntry));
  auto* e = new (KnownNotNull, place) mozilla::Module::CIDEntry();
  e->cid = permanentCID;

  entry.OrInsert([e, km] () { return new nsFactoryEntry(e, km); });
}

void
nsComponentManagerImpl::ManifestContract(ManifestProcessingContext& aCx,
                                         int aLineNo, char* const* aArgv)
{
  mLock.AssertNotCurrentThreadOwns();

  char* contract = aArgv[0];
  char* id = aArgv[1];

  nsID cid;
  if (!cid.Parse(id)) {
    LogMessageWithContext(aCx.mFile, aLineNo,
                          "Malformed CID: '%s'.", id);
    return;
  }

  MutexLock lock(mLock);
  nsFactoryEntry* f = mFactories.Get(cid);
  if (!f) {
    lock.Unlock();
    LogMessageWithContext(aCx.mFile, aLineNo,
                          "Could not map contract ID '%s' to CID %s because no implementation of the CID is registered.",
                          contract, id);
    return;
  }

  mContractIDs.Put(nsDependentCString(contract), f);
}

void
nsComponentManagerImpl::ManifestCategory(ManifestProcessingContext& aCx,
                                         int aLineNo, char* const* aArgv)
{
  char* category = aArgv[0];
  char* key = aArgv[1];
  char* value = aArgv[2];

  nsCategoryManager::GetSingleton()->
  AddCategoryEntry(category, key, value);
}

void
nsComponentManagerImpl::RereadChromeManifests(bool aChromeOnly)
{
  for (uint32_t i = 0; i < sModuleLocations->Length(); ++i) {
    ComponentLocation& l = sModuleLocations->ElementAt(i);
    RegisterManifest(l.type, l.location, aChromeOnly);
  }

  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (obs) {
    obs->NotifyObservers(nullptr, "chrome-manifests-loaded", nullptr);
  }
}

bool
nsComponentManagerImpl::KnownModule::EnsureLoader()
{
  if (!mLoader) {
    nsCString extension;
    mFile.GetURIString(extension);
    CutExtension(extension);
    mLoader =
      nsComponentManagerImpl::gComponentManager->LoaderForExtension(extension);
  }
  return !!mLoader;
}

bool
nsComponentManagerImpl::KnownModule::Load()
{
  if (mFailed) {
    return false;
  }
  if (!mModule) {
    if (!EnsureLoader()) {
      return false;
    }

    mModule = mLoader->LoadModule(mFile);

    if (!mModule) {
      mFailed = true;
      return false;
    }
  }
  if (!mLoaded) {
    if (mModule->loadProc) {
      nsresult rv = mModule->loadProc();
      if (NS_FAILED(rv)) {
        mFailed = true;
        return false;
      }
    }
    mLoaded = true;
  }
  return true;
}

nsCString
nsComponentManagerImpl::KnownModule::Description() const
{
  nsCString s;
  if (mFile) {
    mFile.GetURIString(s);
  } else {
    s = "<static module>";
  }
  return s;
}

nsresult nsComponentManagerImpl::Shutdown(void)
{
  MOZ_ASSERT(NORMAL == mStatus);

  mStatus = SHUTDOWN_IN_PROGRESS;

  // Shutdown the component manager
  MOZ_LOG(nsComponentManagerLog, LogLevel::Debug,
         ("nsComponentManager: Beginning Shutdown."));

  UnregisterWeakMemoryReporter(this);

  // Release all cached factories
  mContractIDs.Clear();
  mFactories.Clear(); // XXX release the objects, don't just clear
  mLoaderMap.Clear();
  mKnownModules.Clear();
  mKnownStaticModules.Clear();

  delete sStaticModules;
  delete sModuleLocations;

  mStatus = SHUTDOWN_COMPLETE;

  MOZ_LOG(nsComponentManagerLog, LogLevel::Debug,
         ("nsComponentManager: Shutdown complete."));

  return NS_OK;
}

nsComponentManagerImpl::~nsComponentManagerImpl()
{
  MOZ_LOG(nsComponentManagerLog, LogLevel::Debug,
         ("nsComponentManager: Beginning destruction."));

  if (SHUTDOWN_COMPLETE != mStatus) {
    Shutdown();
  }

  MOZ_LOG(nsComponentManagerLog, LogLevel::Debug,
         ("nsComponentManager: Destroyed."));
}

NS_IMPL_ISUPPORTS(nsComponentManagerImpl,
                  nsIComponentManager,
                  nsIServiceManager,
                  nsIComponentRegistrar,
                  nsISupportsWeakReference,
                  nsIInterfaceRequestor,
                  nsIMemoryReporter)

nsresult
nsComponentManagerImpl::GetInterface(const nsIID& aUuid, void** aResult)
{
  NS_WARNING("This isn't supported");
  // fall through to QI as anything QIable is a superset of what can be
  // got via the GetInterface()
  return  QueryInterface(aUuid, aResult);
}

nsFactoryEntry*
nsComponentManagerImpl::GetFactoryEntry(const char* aContractID,
                                        uint32_t aContractIDLen)
{
  SafeMutexAutoLock lock(mLock);
  return mContractIDs.Get(nsDependentCString(aContractID, aContractIDLen));
}


nsFactoryEntry*
nsComponentManagerImpl::GetFactoryEntry(const nsCID& aClass)
{
  SafeMutexAutoLock lock(mLock);
  return mFactories.Get(aClass);
}

already_AddRefed<nsIFactory>
nsComponentManagerImpl::FindFactory(const nsCID& aClass)
{
  nsFactoryEntry* e = GetFactoryEntry(aClass);
  if (!e) {
    return nullptr;
  }

  return e->GetFactory();
}

already_AddRefed<nsIFactory>
nsComponentManagerImpl::FindFactory(const char* aContractID,
                                    uint32_t aContractIDLen)
{
  nsFactoryEntry* entry = GetFactoryEntry(aContractID, aContractIDLen);
  if (!entry) {
    return nullptr;
  }

  return entry->GetFactory();
}

/**
 * GetClassObject()
 *
 * Given a classID, this finds the singleton ClassObject that implements the CID.
 * Returns an interface of type aIID off the singleton classobject.
 */
NS_IMETHODIMP
nsComponentManagerImpl::GetClassObject(const nsCID& aClass, const nsIID& aIID,
                                       void** aResult)
{
  nsresult rv;

  if (MOZ_LOG_TEST(nsComponentManagerLog, LogLevel::Debug)) {
    char* buf = aClass.ToString();
    PR_LogPrint("nsComponentManager: GetClassObject(%s)", buf);
    if (buf) {
      free(buf);
    }
  }

  MOZ_ASSERT(aResult != nullptr);

  nsCOMPtr<nsIFactory> factory = FindFactory(aClass);
  if (!factory) {
    return NS_ERROR_FACTORY_NOT_REGISTERED;
  }

  rv = factory->QueryInterface(aIID, aResult);

  MOZ_LOG(nsComponentManagerLog, LogLevel::Warning,
         ("\t\tGetClassObject() %s", NS_SUCCEEDED(rv) ? "succeeded" : "FAILED"));

  return rv;
}


NS_IMETHODIMP
nsComponentManagerImpl::GetClassObjectByContractID(const char* aContractID,
                                                   const nsIID& aIID,
                                                   void** aResult)
{
  if (NS_WARN_IF(!aResult) ||
      NS_WARN_IF(!aContractID)) {
    return NS_ERROR_INVALID_ARG;
  }

  nsresult rv;

  MOZ_LOG(nsComponentManagerLog, LogLevel::Debug,
         ("nsComponentManager: GetClassObjectByContractID(%s)", aContractID));

  nsCOMPtr<nsIFactory> factory = FindFactory(aContractID, strlen(aContractID));
  if (!factory) {
    return NS_ERROR_FACTORY_NOT_REGISTERED;
  }

  rv = factory->QueryInterface(aIID, aResult);

  MOZ_LOG(nsComponentManagerLog, LogLevel::Warning,
         ("\t\tGetClassObjectByContractID() %s", NS_SUCCEEDED(rv) ? "succeeded" : "FAILED"));

  return rv;
}

/**
 * CreateInstance()
 *
 * Create an instance of an object that implements an interface and belongs
 * to the implementation aClass using the factory. The factory is immediately
 * released and not held onto for any longer.
 */
NS_IMETHODIMP
nsComponentManagerImpl::CreateInstance(const nsCID& aClass,
                                       nsISupports* aDelegate,
                                       const nsIID& aIID,
                                       void** aResult)
{
  // test this first, since there's no point in creating a component during
  // shutdown -- whether it's available or not would depend on the order it
  // occurs in the list
  if (gXPCOMShuttingDown) {
    // When processing shutdown, don't process new GetService() requests
#ifdef SHOW_DENIED_ON_SHUTDOWN
    char cid[NSID_LENGTH], iid[NSID_LENGTH];
    aClass.ToProvidedString(cid);
    aIID.ToProvidedString(iid);
    fprintf(stderr, "Creating new instance on shutdown. Denied.\n"
            "         CID: %s\n         IID: %s\n", cid, iid);
#endif /* SHOW_DENIED_ON_SHUTDOWN */
    return NS_ERROR_UNEXPECTED;
  }

  if (!aResult) {
    return NS_ERROR_NULL_POINTER;
  }
  *aResult = nullptr;

  nsFactoryEntry* entry = GetFactoryEntry(aClass);

  if (!entry) {
    return NS_ERROR_FACTORY_NOT_REGISTERED;
  }

#ifdef SHOW_CI_ON_EXISTING_SERVICE
  if (entry->mServiceObject) {
    char cid[NSID_LENGTH];
    aClass.ToProvidedString(cid);
    nsAutoCString message;
    message = NS_LITERAL_CSTRING("You are calling CreateInstance \"") +
              nsDependentCString(cid) +
              NS_LITERAL_CSTRING("\" when a service for this CID already exists!");
    NS_ERROR(message.get());
  }
#endif

  nsresult rv;
  nsCOMPtr<nsIFactory> factory = entry->GetFactory();
  if (factory) {
    rv = factory->CreateInstance(aDelegate, aIID, aResult);
    if (NS_SUCCEEDED(rv) && !*aResult) {
      NS_ERROR("Factory did not return an object but returned success!");
      rv = NS_ERROR_SERVICE_NOT_FOUND;
    }
  } else {
    // Translate error values
    rv = NS_ERROR_FACTORY_NOT_REGISTERED;
  }

  if (MOZ_LOG_TEST(nsComponentManagerLog, LogLevel::Warning)) {
    char* buf = aClass.ToString();
    MOZ_LOG(nsComponentManagerLog, LogLevel::Warning,
           ("nsComponentManager: CreateInstance(%s) %s", buf,
            NS_SUCCEEDED(rv) ? "succeeded" : "FAILED"));
    if (buf) {
      free(buf);
    }
  }

  return rv;
}

/**
 * CreateInstanceByContractID()
 *
 * A variant of CreateInstance() that creates an instance of the object that
 * implements the interface aIID and whose implementation has a contractID aContractID.
 *
 * This is only a convenience routine that turns around can calls the
 * CreateInstance() with classid and iid.
 */
NS_IMETHODIMP
nsComponentManagerImpl::CreateInstanceByContractID(const char* aContractID,
                                                   nsISupports* aDelegate,
                                                   const nsIID& aIID,
                                                   void** aResult)
{
  if (NS_WARN_IF(!aContractID)) {
    return NS_ERROR_INVALID_ARG;
  }

  // test this first, since there's no point in creating a component during
  // shutdown -- whether it's available or not would depend on the order it
  // occurs in the list
  if (gXPCOMShuttingDown) {
    // When processing shutdown, don't process new GetService() requests
#ifdef SHOW_DENIED_ON_SHUTDOWN
    char iid[NSID_LENGTH];
    aIID.ToProvidedString(iid);
    fprintf(stderr, "Creating new instance on shutdown. Denied.\n"
            "  ContractID: %s\n         IID: %s\n", aContractID, iid);
#endif /* SHOW_DENIED_ON_SHUTDOWN */
    return NS_ERROR_UNEXPECTED;
  }

  if (!aResult) {
    return NS_ERROR_NULL_POINTER;
  }
  *aResult = nullptr;

  nsFactoryEntry* entry = GetFactoryEntry(aContractID, strlen(aContractID));

  if (!entry) {
    return NS_ERROR_FACTORY_NOT_REGISTERED;
  }

#ifdef SHOW_CI_ON_EXISTING_SERVICE
  if (entry->mServiceObject) {
    nsAutoCString message;
    message =
      NS_LITERAL_CSTRING("You are calling CreateInstance \"") +
      nsDependentCString(aContractID) +
      NS_LITERAL_CSTRING("\" when a service for this CID already exists! "
                         "Add it to abusedContracts to track down the service consumer.");
    NS_ERROR(message.get());
  }
#endif

  nsresult rv;
  nsCOMPtr<nsIFactory> factory = entry->GetFactory();
  if (factory) {

    rv = factory->CreateInstance(aDelegate, aIID, aResult);
    if (NS_SUCCEEDED(rv) && !*aResult) {
      NS_ERROR("Factory did not return an object but returned success!");
      rv = NS_ERROR_SERVICE_NOT_FOUND;
    }
  } else {
    // Translate error values
    rv = NS_ERROR_FACTORY_NOT_REGISTERED;
  }

  MOZ_LOG(nsComponentManagerLog, LogLevel::Warning,
         ("nsComponentManager: CreateInstanceByContractID(%s) %s", aContractID,
          NS_SUCCEEDED(rv) ? "succeeded" : "FAILED"));

  return rv;
}

nsresult
nsComponentManagerImpl::FreeServices()
{
  NS_ASSERTION(gXPCOMShuttingDown,
               "Must be shutting down in order to free all services");

  if (!gXPCOMShuttingDown) {
    return NS_ERROR_FAILURE;
  }

  for (auto iter = mFactories.Iter(); !iter.Done(); iter.Next()) {
    nsFactoryEntry* entry = iter.UserData();
    entry->mFactory = nullptr;
    entry->mServiceObject = nullptr;
  }

  return NS_OK;
}

// This should only ever be called within the monitor!
nsComponentManagerImpl::PendingServiceInfo*
nsComponentManagerImpl::AddPendingService(const nsCID& aServiceCID,
                                          PRThread* aThread)
{
  PendingServiceInfo* newInfo = mPendingServices.AppendElement();
  if (newInfo) {
    newInfo->cid = &aServiceCID;
    newInfo->thread = aThread;
  }
  return newInfo;
}

// This should only ever be called within the monitor!
void
nsComponentManagerImpl::RemovePendingService(const nsCID& aServiceCID)
{
  uint32_t pendingCount = mPendingServices.Length();
  for (uint32_t index = 0; index < pendingCount; ++index) {
    const PendingServiceInfo& info = mPendingServices.ElementAt(index);
    if (info.cid->Equals(aServiceCID)) {
      mPendingServices.RemoveElementAt(index);
      return;
    }
  }
}

// This should only ever be called within the monitor!
PRThread*
nsComponentManagerImpl::GetPendingServiceThread(const nsCID& aServiceCID) const
{
  uint32_t pendingCount = mPendingServices.Length();
  for (uint32_t index = 0; index < pendingCount; ++index) {
    const PendingServiceInfo& info = mPendingServices.ElementAt(index);
    if (info.cid->Equals(aServiceCID)) {
      return info.thread;
    }
  }
  return nullptr;
}

NS_IMETHODIMP
nsComponentManagerImpl::GetService(const nsCID& aClass,
                                   const nsIID& aIID,
                                   void** aResult)
{
  // test this first, since there's no point in returning a service during
  // shutdown -- whether it's available or not would depend on the order it
  // occurs in the list
  if (gXPCOMShuttingDown) {
    // When processing shutdown, don't process new GetService() requests
#ifdef SHOW_DENIED_ON_SHUTDOWN
    char cid[NSID_LENGTH], iid[NSID_LENGTH];
    aClass.ToProvidedString(cid);
    aIID.ToProvidedString(iid);
    fprintf(stderr, "Getting service on shutdown. Denied.\n"
            "         CID: %s\n         IID: %s\n", cid, iid);
#endif /* SHOW_DENIED_ON_SHUTDOWN */
    return NS_ERROR_UNEXPECTED;
  }

  // `service` must be released after the lock is released, so it must be
  // declared before the lock in this C++ block.
  nsCOMPtr<nsISupports> service;
  MutexLock lock(mLock);

  nsFactoryEntry* entry = mFactories.Get(aClass);
  if (!entry) {
    return NS_ERROR_FACTORY_NOT_REGISTERED;
  }

  if (entry->mServiceObject) {
    lock.Unlock();
    return entry->mServiceObject->QueryInterface(aIID, aResult);
  }

  PRThread* currentPRThread = PR_GetCurrentThread();
  MOZ_ASSERT(currentPRThread, "This should never be null!");

  // Needed to optimize the event loop below.
  nsIThread* currentThread = nullptr;

  PRThread* pendingPRThread;
  while ((pendingPRThread = GetPendingServiceThread(aClass))) {
    if (pendingPRThread == currentPRThread) {
      NS_ERROR("Recursive GetService!");
      return NS_ERROR_NOT_AVAILABLE;
    }


    SafeMutexAutoUnlock unlockPending(mLock);

    if (!currentThread) {
      currentThread = NS_GetCurrentThread();
      MOZ_ASSERT(currentThread, "This should never be null!");
    }

    // This will process a single event or yield the thread if no event is
    // pending.
    if (!NS_ProcessNextEvent(currentThread, false)) {
      PR_Sleep(PR_INTERVAL_NO_WAIT);
    }
  }

  // It's still possible that the other thread failed to create the
  // service so we're not guaranteed to have an entry or service yet.
  if (entry->mServiceObject) {
    lock.Unlock();
    return entry->mServiceObject->QueryInterface(aIID, aResult);
  }

#ifdef DEBUG
  PendingServiceInfo* newInfo =
#endif
    AddPendingService(aClass, currentPRThread);
  NS_ASSERTION(newInfo, "Failed to add info to the array!");

  // We need to not be holding the service manager's lock while calling
  // CreateInstance, because it invokes user code which could try to re-enter
  // the service manager:

  nsresult rv;
  {
    SafeMutexAutoUnlock unlock(mLock);
    rv = CreateInstance(aClass, nullptr, aIID, getter_AddRefs(service));
  }
  if (NS_SUCCEEDED(rv) && !service) {
    NS_ERROR("Factory did not return an object but returned success");
    return NS_ERROR_SERVICE_NOT_FOUND;
  }

#ifdef DEBUG
  pendingPRThread = GetPendingServiceThread(aClass);
  MOZ_ASSERT(pendingPRThread == currentPRThread,
             "Pending service array has been changed!");
#endif
  RemovePendingService(aClass);

  if (NS_FAILED(rv)) {
    return rv;
  }

  NS_ASSERTION(!entry->mServiceObject, "Created two instances of a service!");

  entry->mServiceObject = service.forget();

  lock.Unlock();
  nsISupports** sresult = reinterpret_cast<nsISupports**>(aResult);
  *sresult = entry->mServiceObject;
  (*sresult)->AddRef();

  return NS_OK;
}

NS_IMETHODIMP
nsComponentManagerImpl::IsServiceInstantiated(const nsCID& aClass,
                                              const nsIID& aIID,
                                              bool* aResult)
{
  // Now we want to get the service if we already got it. If not, we don't want
  // to create an instance of it. mmh!

  // test this first, since there's no point in returning a service during
  // shutdown -- whether it's available or not would depend on the order it
  // occurs in the list
  if (gXPCOMShuttingDown) {
    // When processing shutdown, don't process new GetService() requests
#ifdef SHOW_DENIED_ON_SHUTDOWN
    char cid[NSID_LENGTH], iid[NSID_LENGTH];
    aClass.ToProvidedString(cid);
    aIID.ToProvidedString(iid);
    fprintf(stderr, "Checking for service on shutdown. Denied.\n"
            "         CID: %s\n         IID: %s\n", cid, iid);
#endif /* SHOW_DENIED_ON_SHUTDOWN */
    return NS_ERROR_UNEXPECTED;
  }

  nsresult rv = NS_OK;
  nsFactoryEntry* entry;

  {
    SafeMutexAutoLock lock(mLock);
    entry = mFactories.Get(aClass);
  }

  if (entry && entry->mServiceObject) {
    nsCOMPtr<nsISupports> service;
    rv = entry->mServiceObject->QueryInterface(aIID, getter_AddRefs(service));
    *aResult = (service != nullptr);
  } else {
    *aResult = false;
  }

  return rv;
}

NS_IMETHODIMP
nsComponentManagerImpl::IsServiceInstantiatedByContractID(
    const char* aContractID,
    const nsIID& aIID,
    bool* aResult)
{
  // Now we want to get the service if we already got it. If not, we don't want
  // to create an instance of it. mmh!

  // test this first, since there's no point in returning a service during
  // shutdown -- whether it's available or not would depend on the order it
  // occurs in the list
  if (gXPCOMShuttingDown) {
    // When processing shutdown, don't process new GetService() requests
#ifdef SHOW_DENIED_ON_SHUTDOWN
    char iid[NSID_LENGTH];
    aIID.ToProvidedString(iid);
    fprintf(stderr, "Checking for service on shutdown. Denied.\n"
            "  ContractID: %s\n         IID: %s\n", aContractID, iid);
#endif /* SHOW_DENIED_ON_SHUTDOWN */
    return NS_ERROR_UNEXPECTED;
  }

  nsresult rv = NS_OK;
  nsFactoryEntry* entry;
  {
    SafeMutexAutoLock lock(mLock);
    entry = mContractIDs.Get(nsDependentCString(aContractID));
  }

  if (entry && entry->mServiceObject) {
    nsCOMPtr<nsISupports> service;
    rv = entry->mServiceObject->QueryInterface(aIID, getter_AddRefs(service));
    *aResult = (service != nullptr);
  } else {
    *aResult = false;
  }
  return rv;
}


NS_IMETHODIMP
nsComponentManagerImpl::GetServiceByContractID(const char* aContractID,
                                               const nsIID& aIID,
                                               void** aResult)
{
  // test this first, since there's no point in returning a service during
  // shutdown -- whether it's available or not would depend on the order it
  // occurs in the list
  if (gXPCOMShuttingDown) {
    // When processing shutdown, don't process new GetService() requests
#ifdef SHOW_DENIED_ON_SHUTDOWN
    char iid[NSID_LENGTH];
    aIID.ToProvidedString(iid);
    fprintf(stderr, "Getting service on shutdown. Denied.\n"
            "  ContractID: %s\n         IID: %s\n", aContractID, iid);
#endif /* SHOW_DENIED_ON_SHUTDOWN */
    return NS_ERROR_UNEXPECTED;
  }

  // `service` must be released after the lock is released, so it must be
  // declared before the lock in this C++ block.
  nsCOMPtr<nsISupports> service;
  MutexLock lock(mLock);

  nsFactoryEntry* entry = mContractIDs.Get(nsDependentCString(aContractID));
  if (!entry) {
    return NS_ERROR_FACTORY_NOT_REGISTERED;
  }

  if (entry->mServiceObject) {
    // We need to not be holding the service manager's monitor while calling
    // QueryInterface, because it invokes user code which could try to re-enter
    // the service manager, or try to grab some other lock/monitor/condvar
    // and deadlock, e.g. bug 282743.
    // `entry` is valid until XPCOM shutdown, so we can safely use it after
    // exiting the lock.
    lock.Unlock();
    return entry->mServiceObject->QueryInterface(aIID, aResult);
  }

  PRThread* currentPRThread = PR_GetCurrentThread();
  MOZ_ASSERT(currentPRThread, "This should never be null!");

  // Needed to optimize the event loop below.
  nsIThread* currentThread = nullptr;

  PRThread* pendingPRThread;
  while ((pendingPRThread = GetPendingServiceThread(*entry->mCIDEntry->cid))) {
    if (pendingPRThread == currentPRThread) {
      NS_ERROR("Recursive GetService!");
      return NS_ERROR_NOT_AVAILABLE;
    }

    SafeMutexAutoUnlock unlockPending(mLock);

    if (!currentThread) {
      currentThread = NS_GetCurrentThread();
      MOZ_ASSERT(currentThread, "This should never be null!");
    }

    // This will process a single event or yield the thread if no event is
    // pending.
    if (!NS_ProcessNextEvent(currentThread, false)) {
      PR_Sleep(PR_INTERVAL_NO_WAIT);
    }
  }

  if (currentThread && entry->mServiceObject) {
    // If we have a currentThread then we must have waited on another thread
    // to create the service. Grab it now if that succeeded.
    lock.Unlock();
    return entry->mServiceObject->QueryInterface(aIID, aResult);
  }

#ifdef DEBUG
  PendingServiceInfo* newInfo =
#endif
    AddPendingService(*entry->mCIDEntry->cid, currentPRThread);
  NS_ASSERTION(newInfo, "Failed to add info to the array!");

  // We need to not be holding the service manager's lock while calling
  // CreateInstance, because it invokes user code which could try to re-enter
  // the service manager:

  nsresult rv;
  {
    SafeMutexAutoUnlock unlock(mLock);
    rv = CreateInstanceByContractID(aContractID, nullptr, aIID,
                                    getter_AddRefs(service));
  }
  if (NS_SUCCEEDED(rv) && !service) {
    NS_ERROR("Factory did not return an object but returned success");
    return NS_ERROR_SERVICE_NOT_FOUND;
  }

#ifdef DEBUG
  pendingPRThread = GetPendingServiceThread(*entry->mCIDEntry->cid);
  MOZ_ASSERT(pendingPRThread == currentPRThread,
             "Pending service array has been changed!");
#endif
  RemovePendingService(*entry->mCIDEntry->cid);

  if (NS_FAILED(rv)) {
    return rv;
  }

  NS_ASSERTION(!entry->mServiceObject, "Created two instances of a service!");

  entry->mServiceObject = service.forget();

  lock.Unlock();

  nsISupports** sresult = reinterpret_cast<nsISupports**>(aResult);
  *sresult = entry->mServiceObject;
  (*sresult)->AddRef();

  return NS_OK;
}

already_AddRefed<mozilla::ModuleLoader>
nsComponentManagerImpl::LoaderForExtension(const nsACString& aExt)
{
  nsCOMPtr<mozilla::ModuleLoader> loader = mLoaderMap.Get(aExt);
  if (!loader) {
    loader = do_GetServiceFromCategory("module-loader",
                                       PromiseFlatCString(aExt).get());
    if (!loader) {
      return nullptr;
    }

    mLoaderMap.Put(aExt, loader);
  }

  return loader.forget();
}

NS_IMETHODIMP
nsComponentManagerImpl::RegisterFactory(const nsCID& aClass,
                                        const char* aName,
                                        const char* aContractID,
                                        nsIFactory* aFactory)
{
  if (!aFactory) {
    // If a null factory is passed in, this call just wants to reset
    // the contract ID to point to an existing CID entry.
    if (!aContractID) {
      return NS_ERROR_INVALID_ARG;
    }

    SafeMutexAutoLock lock(mLock);
    nsFactoryEntry* oldf = mFactories.Get(aClass);
    if (!oldf) {
      return NS_ERROR_FACTORY_NOT_REGISTERED;
    }

    mContractIDs.Put(nsDependentCString(aContractID), oldf);
    return NS_OK;
  }

  nsAutoPtr<nsFactoryEntry> f(new nsFactoryEntry(aClass, aFactory));

  SafeMutexAutoLock lock(mLock);
  if (auto entry = mFactories.LookupForAdd(aClass)) {
    return NS_ERROR_FACTORY_EXISTS;
  } else {
    if (aContractID) {
      mContractIDs.Put(nsDependentCString(aContractID), f);
    }
    entry.OrInsert([&f] () { return f.forget(); });
  }

  return NS_OK;
}

NS_IMETHODIMP
nsComponentManagerImpl::UnregisterFactory(const nsCID& aClass,
                                          nsIFactory* aFactory)
{
  // Don't release the dying factory or service object until releasing
  // the component manager monitor.
  nsCOMPtr<nsIFactory> dyingFactory;
  nsCOMPtr<nsISupports> dyingServiceObject;

  {
    SafeMutexAutoLock lock(mLock);
    auto entry = mFactories.Lookup(aClass);
    nsFactoryEntry* f = entry ? entry.Data() : nullptr;
    if (!f || f->mFactory != aFactory) {
      return NS_ERROR_FACTORY_NOT_REGISTERED;
    }

    entry.Remove();

    // This might leave a stale contractid -> factory mapping in
    // place, so null out the factory entry (see
    // nsFactoryEntry::GetFactory)
    f->mFactory.swap(dyingFactory);
    f->mServiceObject.swap(dyingServiceObject);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsComponentManagerImpl::AutoRegister(nsIFile* aLocation)
{
  XRE_AddManifestLocation(NS_EXTENSION_LOCATION, aLocation);
  return NS_OK;
}

NS_IMETHODIMP
nsComponentManagerImpl::AutoUnregister(nsIFile* aLocation)
{
  NS_ERROR("AutoUnregister not implemented.");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsComponentManagerImpl::RegisterFactoryLocation(const nsCID& aCID,
                                                const char* aClassName,
                                                const char* aContractID,
                                                nsIFile* aFile,
                                                const char* aLoaderStr,
                                                const char* aType)
{
  NS_ERROR("RegisterFactoryLocation not implemented.");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsComponentManagerImpl::UnregisterFactoryLocation(const nsCID& aCID,
                                                  nsIFile* aFile)
{
  NS_ERROR("UnregisterFactoryLocation not implemented.");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsComponentManagerImpl::IsCIDRegistered(const nsCID& aClass,
                                        bool* aResult)
{
  *aResult = (nullptr != GetFactoryEntry(aClass));
  return NS_OK;
}

NS_IMETHODIMP
nsComponentManagerImpl::IsContractIDRegistered(const char* aClass,
                                               bool* aResult)
{
  if (NS_WARN_IF(!aClass)) {
    return NS_ERROR_INVALID_ARG;
  }

  nsFactoryEntry* entry = GetFactoryEntry(aClass, strlen(aClass));

  if (entry) {
    // UnregisterFactory might have left a stale nsFactoryEntry in
    // mContractIDs, so we should check to see whether this entry has
    // anything useful.
    *aResult = (bool(entry->mModule) ||
                bool(entry->mFactory) ||
                bool(entry->mServiceObject));
  } else {
    *aResult = false;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsComponentManagerImpl::EnumerateCIDs(nsISimpleEnumerator** aEnumerator)
{
  nsCOMArray<nsISupports> array;
  for (auto iter = mFactories.Iter(); !iter.Done(); iter.Next()) {
    const nsID& id = iter.Key();
    nsCOMPtr<nsISupportsID> wrapper = new nsSupportsID();
    wrapper->SetData(&id);
    array.AppendObject(wrapper);
  }
  return NS_NewArrayEnumerator(aEnumerator, array);
}

NS_IMETHODIMP
nsComponentManagerImpl::EnumerateContractIDs(nsISimpleEnumerator** aEnumerator)
{
  auto* array = new nsTArray<nsCString>;
  for (auto iter = mContractIDs.Iter(); !iter.Done(); iter.Next()) {
    const nsACString& contract = iter.Key();
    array->AppendElement(contract);
  }

  nsCOMPtr<nsIUTF8StringEnumerator> e;
  nsresult rv = NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(e), array);
  if (NS_FAILED(rv)) {
    return rv;
  }

  return CallQueryInterface(e, aEnumerator);
}

NS_IMETHODIMP
nsComponentManagerImpl::CIDToContractID(const nsCID& aClass,
                                        char** aResult)
{
  NS_ERROR("CIDTOContractID not implemented");
  return NS_ERROR_FACTORY_NOT_REGISTERED;
}

NS_IMETHODIMP
nsComponentManagerImpl::ContractIDToCID(const char* aContractID,
                                        nsCID** aResult)
{
  {
    SafeMutexAutoLock lock(mLock);
    nsFactoryEntry* entry = mContractIDs.Get(nsDependentCString(aContractID));
    if (entry) {
      *aResult = (nsCID*)moz_xmalloc(sizeof(nsCID));
      **aResult = *entry->mCIDEntry->cid;
      return NS_OK;
    }
  }
  *aResult = nullptr;
  return NS_ERROR_FACTORY_NOT_REGISTERED;
}

MOZ_DEFINE_MALLOC_SIZE_OF(ComponentManagerMallocSizeOf)

NS_IMETHODIMP
nsComponentManagerImpl::CollectReports(nsIHandleReportCallback* aHandleReport,
                                       nsISupports* aData, bool aAnonymize)
{
  MOZ_COLLECT_REPORT(
    "explicit/xpcom/component-manager", KIND_HEAP, UNITS_BYTES,
    SizeOfIncludingThis(ComponentManagerMallocSizeOf),
    "Memory used for the XPCOM component manager.");

  return NS_OK;
}

size_t
nsComponentManagerImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
  const
{
  size_t n = aMallocSizeOf(this);

  n += mLoaderMap.ShallowSizeOfExcludingThis(aMallocSizeOf);

  n += mFactories.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (auto iter = mFactories.ConstIter(); !iter.Done(); iter.Next()) {
    n += iter.Data()->SizeOfIncludingThis(aMallocSizeOf);
  }

  n += mContractIDs.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (auto iter = mContractIDs.ConstIter(); !iter.Done(); iter.Next()) {
    // We don't measure the nsFactoryEntry data because it's owned by
    // mFactories (which is measured above).
    n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
  }

  n += sStaticModules->ShallowSizeOfIncludingThis(aMallocSizeOf);
  if (sModuleLocations) {
    n += sModuleLocations->ShallowSizeOfIncludingThis(aMallocSizeOf);
  }

  n += mKnownStaticModules.ShallowSizeOfExcludingThis(aMallocSizeOf);
  n += mKnownModules.ShallowSizeOfExcludingThis(aMallocSizeOf);

  n += mArena.SizeOfExcludingThis(aMallocSizeOf);

  n += mPendingServices.ShallowSizeOfExcludingThis(aMallocSizeOf);

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mLoaderMap's keys and values
  // - mMon
  // - sStaticModules' entries
  // - sModuleLocations' entries
  // - mKnownStaticModules' entries?
  // - mKnownModules' keys and values?

  return n;
}

////////////////////////////////////////////////////////////////////////////////
// nsFactoryEntry
////////////////////////////////////////////////////////////////////////////////

nsFactoryEntry::nsFactoryEntry(const mozilla::Module::CIDEntry* aEntry,
                               nsComponentManagerImpl::KnownModule* aModule)
  : mCIDEntry(aEntry)
  , mModule(aModule)
{
}

nsFactoryEntry::nsFactoryEntry(const nsCID& aCID, nsIFactory* aFactory)
  : mCIDEntry(nullptr)
  , mModule(nullptr)
  , mFactory(aFactory)
{
  auto* e = new mozilla::Module::CIDEntry();
  auto* cid = new nsCID;
  *cid = aCID;
  e->cid = cid;
  mCIDEntry = e;
}

nsFactoryEntry::~nsFactoryEntry()
{
  // If this was a RegisterFactory entry, we own the CIDEntry/CID
  if (!mModule) {
    delete mCIDEntry->cid;
    delete mCIDEntry;
  }
}

already_AddRefed<nsIFactory>
nsFactoryEntry::GetFactory()
{
  nsComponentManagerImpl::gComponentManager->mLock.AssertNotCurrentThreadOwns();

  if (!mFactory) {
    // RegisterFactory then UnregisterFactory can leave an entry in mContractIDs
    // pointing to an unusable nsFactoryEntry.
    if (!mModule) {
      return nullptr;
    }

    if (!mModule->Load()) {
      return nullptr;
    }

    // Don't set mFactory directly, it needs to be locked
    nsCOMPtr<nsIFactory> factory;

    if (mModule->Module()->getFactoryProc) {
      factory = mModule->Module()->getFactoryProc(*mModule->Module(),
                                                  *mCIDEntry);
    } else if (mCIDEntry->getFactoryProc) {
      factory = mCIDEntry->getFactoryProc(*mModule->Module(), *mCIDEntry);
    } else {
      NS_ASSERTION(mCIDEntry->constructorProc, "no getfactory or constructor");
      factory = new mozilla::GenericFactory(mCIDEntry->constructorProc);
    }
    if (!factory) {
      return nullptr;
    }

    SafeMutexAutoLock lock(nsComponentManagerImpl::gComponentManager->mLock);
    // Threads can race to set mFactory
    if (!mFactory) {
      factory.swap(mFactory);
    }
  }
  nsCOMPtr<nsIFactory> factory = mFactory;
  return factory.forget();
}

size_t
nsFactoryEntry::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
{
  size_t n = aMallocSizeOf(this);

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mCIDEntry;
  // - mModule;
  // - mFactory;
  // - mServiceObject;

  return n;
}

////////////////////////////////////////////////////////////////////////////////
// Static Access Functions
////////////////////////////////////////////////////////////////////////////////

nsresult
NS_GetComponentManager(nsIComponentManager** aResult)
{
  if (!nsComponentManagerImpl::gComponentManager) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager);
  return NS_OK;
}

nsresult
NS_GetServiceManager(nsIServiceManager** aResult)
{
  if (!nsComponentManagerImpl::gComponentManager) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager);
  return NS_OK;
}


nsresult
NS_GetComponentRegistrar(nsIComponentRegistrar** aResult)
{
  if (!nsComponentManagerImpl::gComponentManager) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager);
  return NS_OK;
}

EXPORT_XPCOM_API(nsresult)
XRE_AddStaticComponent(const mozilla::Module* aComponent)
{
  nsComponentManagerImpl::InitializeStaticModules();
  nsComponentManagerImpl::sStaticModules->AppendElement(aComponent);

  if (nsComponentManagerImpl::gComponentManager &&
      nsComponentManagerImpl::NORMAL ==
        nsComponentManagerImpl::gComponentManager->mStatus) {
    nsComponentManagerImpl::gComponentManager->RegisterModule(aComponent,
                                                              nullptr);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsComponentManagerImpl::AddBootstrappedManifestLocation(nsIFile* aLocation)
{
  nsString path;
  nsresult rv = aLocation->GetPath(path);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (Substring(path, path.Length() - 4).EqualsLiteral(".xpi")) {
    return XRE_AddJarManifestLocation(NS_BOOTSTRAPPED_LOCATION, aLocation);
  }

  nsCOMPtr<nsIFile> manifest =
    CloneAndAppend(aLocation, NS_LITERAL_CSTRING("chrome.manifest"));
  return XRE_AddManifestLocation(NS_BOOTSTRAPPED_LOCATION, manifest);
}

NS_IMETHODIMP
nsComponentManagerImpl::RemoveBootstrappedManifestLocation(nsIFile* aLocation)
{
  nsCOMPtr<nsIChromeRegistry> cr = mozilla::services::GetChromeRegistryService();
  if (!cr) {
    return NS_ERROR_FAILURE;
  }

  nsString path;
  nsresult rv = aLocation->GetPath(path);
  if (NS_FAILED(rv)) {
    return rv;
  }

  nsComponentManagerImpl::ComponentLocation elem;
  elem.type = NS_BOOTSTRAPPED_LOCATION;

  if (Substring(path, path.Length() - 4).EqualsLiteral(".xpi")) {
    elem.location.Init(aLocation, "chrome.manifest");
  } else {
    nsCOMPtr<nsIFile> lf =
      CloneAndAppend(aLocation, NS_LITERAL_CSTRING("chrome.manifest"));
    elem.location.Init(lf);
  }

  // Remove reference.
  nsComponentManagerImpl::sModuleLocations->RemoveElement(elem,
                                                          ComponentLocationComparator());

  rv = cr->CheckForNewChrome();
  return rv;
}

NS_IMETHODIMP
nsComponentManagerImpl::GetManifestLocations(nsIArray** aLocations)
{
  NS_ENSURE_ARG_POINTER(aLocations);
  *aLocations = nullptr;

  if (!sModuleLocations) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  nsCOMPtr<nsIMutableArray> locations = nsArray::Create();
  nsresult rv;
  for (uint32_t i = 0; i < sModuleLocations->Length(); ++i) {
    ComponentLocation& l = sModuleLocations->ElementAt(i);
    FileLocation loc = l.location;
    nsCString uriString;
    loc.GetURIString(uriString);
    nsCOMPtr<nsIURI> uri;
    rv = NS_NewURI(getter_AddRefs(uri), uriString);
    if (NS_SUCCEEDED(rv)) {
      locations->AppendElement(uri);
    }
  }

  locations.forget(aLocations);
  return NS_OK;
}

EXPORT_XPCOM_API(nsresult)
XRE_AddManifestLocation(NSLocationType aType, nsIFile* aLocation)
{
  nsComponentManagerImpl::InitializeModuleLocations();
  nsComponentManagerImpl::ComponentLocation* c =
    nsComponentManagerImpl::sModuleLocations->AppendElement();
  c->type = aType;
  c->location.Init(aLocation);

  if (nsComponentManagerImpl::gComponentManager &&
      nsComponentManagerImpl::NORMAL ==
        nsComponentManagerImpl::gComponentManager->mStatus) {
    nsComponentManagerImpl::gComponentManager->RegisterManifest(aType,
                                                                c->location,
                                                                false);
  }

  return NS_OK;
}

EXPORT_XPCOM_API(nsresult)
XRE_AddJarManifestLocation(NSLocationType aType, nsIFile* aLocation)
{
  nsComponentManagerImpl::InitializeModuleLocations();
  nsComponentManagerImpl::ComponentLocation* c =
    nsComponentManagerImpl::sModuleLocations->AppendElement();

  c->type = aType;
  c->location.Init(aLocation, "chrome.manifest");

  if (nsComponentManagerImpl::gComponentManager &&
      nsComponentManagerImpl::NORMAL ==
        nsComponentManagerImpl::gComponentManager->mStatus) {
    nsComponentManagerImpl::gComponentManager->RegisterManifest(aType,
                                                                c->location,
                                                                false);
  }

  return NS_OK;
}

