/* -*- 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 "StaticComponents.h"

#include "mozilla/ArrayUtils.h"
#ifdef MOZ_BACKGROUNDTASKS
#  include "mozilla/BackgroundTasks.h"
#endif
#include "mozilla/PerfectHash.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozJSModuleLoader.h"
#include "nsCOMPtr.h"
#include "nsComponentManager.h"
#include "nsContentUtils.h"
#include "nsIFactory.h"
#include "nsISupports.h"
#include "nsIXPConnect.h"
#include "nsString.h"
#include "nsStringEnumerator.h"
#include "nsTArray.h"
#include "xptdata.h"
#include "xptinfo.h"
#include "js/PropertyAndElement.h"  // JS_GetProperty

// Cleanup pollution from zipstruct.h
#undef UNSUPPORTED

// Public includes
//# @includes@

// Relative includes
//# @relative_includes@

//# @decls@

namespace mozilla {

using dom::AutoJSAPI;

namespace xpcom {

static constexpr uint32_t kNoContractID = 0xffffffff;

namespace {
// Template helpers for constructor function sanity checks.
template <typename T>
struct RemoveAlreadyAddRefed {
  using Type = T;
};

template <typename T>
struct RemoveAlreadyAddRefed<already_AddRefed<T>> {
  using Type = T;
};
}  // anonymous namespace


uint8_t gInvalidContracts[kContractCount / 8 + 1];

static StaticRefPtr<nsISupports> gServiceInstances[kStaticModuleCount];

uint8_t gInitCalled[kModuleInitCount / 8 + 1];

static const char gStrings[] =
//# @strings@
  "";

const StaticCategory gStaticCategories[kStaticCategoryCount] = {
//# @categories@
};
const StaticCategoryEntry gStaticCategoryEntries[] = {
//# @category_entries@
};

const nsXPTInterface gInterfaces[] = {
//# @interfaces@
};

const StringOffset gComponentJSMs[] = {
//# @component_jsms@
};

const StringOffset gComponentESModules[] = {
//# @component_esmodules@
};

/**
 * Returns a nsCString corresponding to the given entry in the `gStrings` string
 * table. The resulting nsCString points directly to static storage, and does
 * not incur any memory allocation overhead.
 */
static inline nsCString GetString(const StringOffset& aOffset) {
  const char* str = &gStrings[aOffset.mOffset];
  nsCString result;
  result.AssignLiteral(str, strlen(str));
  return result;
}

nsCString ContractEntry::ContractID() const {
  return GetString(mContractID);
}

bool ContractEntry::Matches(const nsACString& aContractID) const {
  return aContractID == ContractID() && Module().Active();
}

enum class ComponentType { JSM, ESM };

template <ComponentType type>
static nsresult ConstructJSMOrESMComponent(const nsACString& aURI,
                                           const char* aConstructor,
                                           nsISupports** aResult) {
  if (!nsComponentManagerImpl::JSLoaderReady()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  AutoJSAPI jsapi;
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
  JSContext* cx = jsapi.cx();

  JS::Rooted<JSObject*> exports(cx);
  if constexpr (type == ComponentType::JSM) {
    JS::Rooted<JSObject*> global(cx);
    MOZ_TRY(mozJSModuleLoader::Get()->Import(cx, aURI, &global, &exports));
  } else {
    MOZ_TRY(mozJSModuleLoader::Get()->ImportESModule(cx, aURI, &exports));
  }

  JS::Rooted<JS::Value> ctor(cx);
  if (!JS_GetProperty(cx, exports, aConstructor, &ctor) ||
      !ctor.isObject()) {
    return NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED;
  }

  JS::Rooted<JSObject*> inst(cx);
  if (!JS::Construct(cx, ctor, JS::HandleValueArray::empty(), &inst)) {
    return NS_ERROR_FAILURE;
  }

  return nsContentUtils::XPConnect()->WrapJS(cx, inst, NS_GET_IID(nsISupports),
                                             (void**)aResult);
}

static nsresult ConstructJSMComponent(const nsACString& aURI,
                                      const char* aConstructor,
                                      nsISupports** aResult) {
  return ConstructJSMOrESMComponent<ComponentType::JSM>(
    aURI, aConstructor, aResult);
}

static nsresult ConstructESModuleComponent(const nsACString& aURI,
                                      const char* aConstructor,
                                      nsISupports** aResult) {
  return ConstructJSMOrESMComponent<ComponentType::ESM>(
    aURI, aConstructor, aResult);
}

//# @module_cid_table@

//# @module_contract_id_table@

//# @js_services_table@

//# @protocol_handlers_table@

static inline bool CalledInit(size_t aIdx) {
  return GetBit(gInitCalled, aIdx);
}

static nsresult CallInitFunc(size_t aIdx) {
  if (CalledInit(aIdx)) {
    return NS_OK;
  }

  nsresult rv = NS_OK;
  switch (aIdx) {
//# @init_funcs@
  }

  SetBit(gInitCalled, aIdx);

  MOZ_ASSERT(NS_SUCCEEDED(rv));
  return rv;
}

static void CallUnloadFuncs() {
//# @unload_funcs@
}

nsresult CreateInstanceImpl(ModuleID aID, const nsIID& aIID, void** aResult) {
  // The full set of constructors for all static modules.
  // This switch statement will be compiled to a relative address jump table
  // with no runtime relocations and a single indirect jump.
  switch (aID) {
//# @constructors@
  }

  MOZ_ASSERT_UNREACHABLE("Constructor didn't return");
  return NS_ERROR_FAILURE;
}


namespace {

class StaticModuleFactory final : public nsIFactory {
  NS_DECL_ISUPPORTS
  NS_DECL_NSIFACTORY

  explicit StaticModuleFactory(ModuleID aID) : mID(aID) {}

private:
  ~StaticModuleFactory() = default;

  const ModuleID mID;
};

NS_IMPL_ISUPPORTS(StaticModuleFactory, nsIFactory)

NS_IMETHODIMP StaticModuleFactory::CreateInstance(const nsIID& aIID,
                                                  void** aResult) {
  return CreateInstanceImpl(mID, aIID, aResult);
}

}  // anonymous namespace


already_AddRefed<nsIFactory> StaticModule::GetFactory() const {
  return do_AddRef(new StaticModuleFactory(ID()));
}

bool StaticModule::Active() const {
  return FastProcessSelectorMatches(mProcessSelector);
}

bool StaticModule::Overridable() const {
  return mContractID.mOffset != kNoContractID;
}

nsCString StaticModule::ContractID() const {
  MOZ_ASSERT(Overridable());
  return GetString(mContractID);
}

nsresult StaticModule::CreateInstance(const nsIID& aIID, void** aResult) const {
  return CreateInstanceImpl(ID(), aIID, aResult);
}

GetServiceHelper StaticModule::GetService() const {
  return { ID(), nullptr };
}

GetServiceHelper StaticModule::GetService(nsresult* aRv) const {
  return { ID(), aRv };
}


nsISupports* StaticModule::ServiceInstance() const {
  return gServiceInstances[Idx()];
}

void StaticModule::SetServiceInstance(
    already_AddRefed<nsISupports> aInst) const {
  gServiceInstances[Idx()] = aInst;
}


nsCString StaticCategoryEntry::Entry() const {
  return GetString(mEntry);
}

nsCString StaticCategoryEntry::Value() const {
  return GetString(mValue);
}

bool StaticCategoryEntry::Active() const {
  if (!FastProcessSelectorMatches(mProcessSelector)) {
    return false;
  }
#ifdef MOZ_BACKGROUNDTASKS
  if (MOZ_UNLIKELY(BackgroundTasks::IsBackgroundTaskMode())) {
    return mBackgroundTasksSelector != Module::BackgroundTasksSelector::NO_TASKS;
  }
#endif /* MOZ_BACKGROUNDTASKS */
  return true;
}

nsCString StaticCategory::Name() const {
  return GetString(mName);
}

nsCString JSServiceEntry::Name() const {
  return GetString(mName);
}

JSServiceEntry::InterfaceList JSServiceEntry::Interfaces() const {
  InterfaceList iids;
  iids.SetCapacity(mInterfaceCount);

  for (size_t i = 0; i < mInterfaceCount; i++) {
    nsXPTInterface ifaceID = gInterfaces[mInterfaceOffset.mOffset + i];
    iids.AppendElement(&nsXPTInterfaceInfo::Get(ifaceID)->IID());
  }
  return iids;
}


/* static */
const JSServiceEntry* JSServiceEntry::Lookup(const nsACString& aName) {
  return LookupJSService(aName);
}

nsCString StaticProtocolHandler::Scheme() const {
  return GetString(mScheme);
}

/* static */
const StaticProtocolHandler* StaticProtocolHandler::Lookup(const nsACString& aScheme) {
  return LookupProtocolHandler(aScheme);
}

/* static */ const StaticModule* StaticComponents::LookupByCID(
    const nsID& aCID) {
  return ModuleByCID(aCID);
}

/* static */ const StaticModule* StaticComponents::LookupByContractID(
    const nsACString& aContractID) {
  if (const ContractEntry* entry = LookupContractID(aContractID)) {
    if (!entry->Invalid()) {
      return &entry->Module();
    }
  }
  return nullptr;
}

/* static */ bool StaticComponents::InvalidateContractID(
    const nsACString& aContractID, bool aInvalid) {
  if (const ContractEntry* entry = LookupContractID(aContractID)) {
    entry->SetInvalid(aInvalid);
    return true;
  }
  return false;
}

/* static */ already_AddRefed<nsIUTF8StringEnumerator>
StaticComponents::GetComponentJSMs() {
  auto jsms = MakeUnique<nsTArray<nsCString>>(MOZ_ARRAY_LENGTH(gComponentJSMs));

  for (const auto& entry : gComponentJSMs) {
    jsms->AppendElement(GetString(entry));
  }

  nsCOMPtr<nsIUTF8StringEnumerator> result;
  MOZ_ALWAYS_SUCCEEDS(NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(result),
                                                         jsms.release()));
  return result.forget();
}

/* static */ already_AddRefed<nsIUTF8StringEnumerator>
StaticComponents::GetComponentESModules() {
  auto esModules = MakeUnique<nsTArray<nsCString>>(MOZ_ARRAY_LENGTH(gComponentESModules));

  for (const auto& entry : gComponentESModules) {
    esModules->AppendElement(GetString(entry));
  }

  nsCOMPtr<nsIUTF8StringEnumerator> result;
  MOZ_ALWAYS_SUCCEEDS(NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(result),
                                                         esModules.release()));
  return result.forget();
}

/* static */ Span<const JSServiceEntry> StaticComponents::GetJSServices() {
  return { gJSServices, ArrayLength(gJSServices) };
}

/* static */ void StaticComponents::Shutdown() {
  CallUnloadFuncs();
}

/* static */ const nsID& Components::GetCID(ModuleID aID) {
  return gStaticModules[size_t(aID)].CID();
}

nsresult GetServiceHelper::operator()(const nsIID& aIID, void** aResult) const {
  nsresult rv =
      nsComponentManagerImpl::gComponentManager->GetService(mId, aIID, aResult);
  return SetResult(rv);
}

nsresult CreateInstanceHelper::operator()(const nsIID& aIID,
                                          void** aResult) const {
  const auto& entry = gStaticModules[size_t(mId)];
  if (!entry.Active()) {
    return SetResult(NS_ERROR_FACTORY_NOT_REGISTERED);
  }

  nsresult rv = entry.CreateInstance(aIID, aResult);
  return SetResult(rv);
}

}  // namespace xpcom
}  // namespace mozilla