/* 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 "GeckoProfiler.h"
#include "LoadedScript.h"
#include "ModuleLoadRequest.h"
#include "ScriptLoadRequest.h"
#include "mozilla/dom/ScriptSettings.h"  // AutoJSAPI
#include "mozilla/dom/ScriptTrace.h"

#include "js/Array.h"  // JS::GetArrayLength
#include "js/CompilationAndEvaluation.h"
#include "js/ColumnNumber.h"          // JS::ColumnNumberOneOrigin
#include "js/ContextOptions.h"        // JS::ContextOptionsRef
#include "js/ErrorReport.h"           // JSErrorBase
#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "js/Modules.h"  // JS::FinishLoadingImportedModule, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{Load,Metadata}Hook
#include "js/PropertyAndElement.h"  // JS_DefineProperty, JS_GetElement
#include "js/SourceText.h"
#include "mozilla/Assertions.h"  // MOZ_ASSERT
#include "mozilla/BasePrincipal.h"
#include "mozilla/dom/AutoEntryScript.h"
#include "mozilla/dom/ScriptLoadContext.h"
#include "mozilla/CycleCollectedJSContext.h"  // nsAutoMicroTask
#include "mozilla/Preferences.h"
#include "mozilla/RefPtr.h"  // mozilla::StaticRefPtr
#include "mozilla/StaticPrefs_dom.h"
#include "nsContentUtils.h"
#include "nsICacheInfoChannel.h"  // nsICacheInfoChannel
#include "nsNetUtil.h"            // NS_NewURI
#include "xpcpublic.h"

using mozilla::AutoSlowOperation;
using mozilla::CycleCollectedJSContext;
using mozilla::Err;
using mozilla::MakeUnique;
using mozilla::MicroTaskRunnable;
using mozilla::Preferences;
using mozilla::UniquePtr;
using mozilla::dom::AutoJSAPI;
using mozilla::dom::ReferrerPolicy;

namespace JS::loader {

mozilla::LazyLogModule ModuleLoaderBase::gCspPRLog("CSP");
mozilla::LazyLogModule ModuleLoaderBase::gModuleLoaderBaseLog(
    "ModuleLoaderBase");

#undef LOG
#define LOG(args)                                                           \
  MOZ_LOG(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug, \
          args)

#define LOG_ENABLED() \
  MOZ_LOG_TEST(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug)

//////////////////////////////////////////////////////////////
// ModuleLoaderBase::LoadingRequest
//////////////////////////////////////////////////////////////

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase::LoadingRequest)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase::LoadingRequest, mRequest, mWaiting)

NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase::LoadingRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase::LoadingRequest)

//////////////////////////////////////////////////////////////
// ModuleLoaderBase
//////////////////////////////////////////////////////////////

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase, mFetchingModules, mFetchedModules,
                         mDynamicImportRequests, mGlobalObject, mOverriddenBy,
                         mLoader)

NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase)

// static
void ModuleLoaderBase::EnsureModuleHooksInitialized() {
  AutoJSAPI jsapi;
  jsapi.Init();
  JSRuntime* rt = JS_GetRuntime(jsapi.cx());
  if (GetModuleLoadHook(rt)) {
    return;
  }

  SetModuleLoadHook(rt, HostLoadImportedModule);
  SetModuleMetadataHook(rt, HostPopulateImportMeta);
  SetScriptPrivateReferenceHooks(rt, HostAddRefScriptFetchInfo,
                                 HostReleaseScriptFetchInfo);
}

static bool CreateBadModuleTypeError(JSContext* aCx,
                                     ScriptFetchInfo* aFetchInfo, nsIURI* aURI,
                                     MutableHandle<Value> aErrorOut) {
  Rooted<JSString*> filename(aCx);
  if (aFetchInfo) {
    nsAutoCString url;
    aFetchInfo->BaseURL()->GetAsciiSpec(url);
    filename = JS_NewStringCopyZ(aCx, url.get());
  } else {
    filename = JS_NewStringCopyZ(aCx, "(unknown)");
  }

  if (!filename) {
    return false;
  }

  MOZ_ASSERT(aURI);
  nsAutoCString url;
  aURI->GetSpec(url);

  Rooted<JSString*> uri(aCx, JS_NewStringCopyZ(aCx, url.get()));
  if (!uri) {
    return false;
  }

  Rooted<JSString*> msg(aCx, JS_NewStringCopyZ(aCx, ": invalid module type"));
  if (!msg) {
    return false;
  }

  Rooted<JSString*> errMsg(aCx, JS_ConcatStrings(aCx, uri, msg));
  if (!errMsg) {
    return false;
  }

  return CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, 0,
                     ColumnNumberOneOrigin(), nullptr, errMsg,
                     NothingHandleValue, aErrorOut);
}

// https://html.spec.whatwg.org/#hostloadimportedmodule
// static
bool ModuleLoaderBase::HostLoadImportedModule(
    JSContext* aCx, Handle<JSScript*> aReferrer,
    Handle<JSObject*> aModuleRequest, Handle<Value> aHostDefined,
    Handle<Value> aPayload, uint32_t aLineNumber,
    JS::ColumnNumberOneOrigin aColumnNumber) {
  Rooted<JSObject*> object(aCx);
  if (aPayload.isObject()) {
    object = &aPayload.toObject();
  }
  bool isDynamicImport = object && IsPromiseObject(object);

  Rooted<JSString*> specifierString(
      aCx, GetModuleRequestSpecifier(aCx, aModuleRequest));
  if (!specifierString) {
    JS_ReportOutOfMemory(aCx);
    return false;
  }

  // Let url be the result of resolving a module specifier given referencing
  // module script and specifier.
  nsAutoJSString string;
  if (!string.init(aCx, specifierString)) {
    JS_ReportOutOfMemory(aCx);
    return false;
  }

  RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx);
  if (!loader) {
    return false;
  }

  if (isDynamicImport && !loader->IsDynamicImportSupported()) {
    JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
                              JSMSG_DYNAMIC_IMPORT_NOT_SUPPORTED);
    return false;
  }

  RefPtr<ScriptFetchInfo> fetchInfo(GetScriptFetchInfoOrNull(aReferrer));

  // Step 8. Let url be the result of resolving a module specifier given
  //   referencingScript and moduleRequest.[[Specifier]], catching any
  //   exceptions. If they throw an exception, let resolutionError be the
  //   thrown exception.
  auto result = loader->ResolveModuleSpecifier(fetchInfo, string);

  // Step 9. If the previous step threw an exception, then:
  if (result.isErr()) {
    Rooted<Value> error(aCx);
    nsresult rv =
        loader->HandleResolveFailure(aCx, fetchInfo, string, result.unwrapErr(),
                                     aLineNumber, aColumnNumber, &error);
    if (NS_FAILED(rv)) {
      JS_ReportOutOfMemory(aCx);
      return false;
    }

    // Step 2. Perform FinishLoadingImportedModule(referrer, moduleRequest,
    //   payload, ThrowCompletion(resolutionError)).
    FinishLoadingImportedModuleFailed(aCx, aPayload, error);

    // Step 3. Return.
    return true;
  }

  MOZ_ASSERT(result.isOk());
  auto record = result.unwrap();
  nsCOMPtr<nsIURI> uri = record->Result();
  MOZ_ASSERT(uri, "Failed to resolve module specifier");

  if (ImportMap::IsMultipleImportMapsSupported()) {
    // This implements the 'Add module to resolved module set' part defined in
    // 'resolve a module specifier'. This is done here because we need to
    // process the resolved specifiers differently for preloading module
    // scripts.
    //
    // See https://html.spec.whatwg.org/#resolve-a-module-specifier
    loader->AddToResolvedModuleSet(std::move(record), fetchInfo, aHostDefined);
  }

  ModuleType moduleType = GetModuleRequestType(aCx, aModuleRequest);
  if (!loader->IsModuleTypeAllowed(moduleType)) {
    LOG(("ModuleLoaderBase::HostLoadImportedModule uri %s, bad module type",
         uri->GetSpecOrDefault().get()));
    Rooted<Value> error(aCx);
    if (!CreateBadModuleTypeError(aCx, fetchInfo, uri, &error)) {
      JS_ReportOutOfMemory(aCx);
      return false;
    }
    JS_SetPendingException(aCx, error);
    return false;
  }

  RefPtr<ScriptFetchOptions> options = nullptr;
  ReferrerPolicy referrerPolicy;
  nsIURI* fetchReferrer = nullptr;
  if (fetchInfo) {
    options = fetchInfo->FetchOptions();
    referrerPolicy = fetchInfo->ReferrerPolicy();
    fetchReferrer = fetchInfo->BaseURL();
  } else {
    options = loader->CreateDefaultScriptFetchOptions();
    referrerPolicy = ReferrerPolicy::_empty;
    fetchReferrer = loader->GetClientReferrerURI();
  }

  mozilla::dom::SRIMetadata sriMetadata;
  loader->GetImportMapSRI(
      uri, fetchReferrer,
      loader->GetScriptLoaderInterface()->GetConsoleReportCollector(),
      &sriMetadata);

  RefPtr<ModuleLoadRequest> request = loader->CreateRequest(
      aCx, uri, aModuleRequest, aHostDefined, aPayload, isDynamicImport,
      options, referrerPolicy, fetchReferrer, sriMetadata);
  if (!request) {
    MOZ_ASSERT(isDynamicImport);
    nsAutoCString url;
    uri->GetSpec(url);
    JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
                              JSMSG_DYNAMIC_IMPORT_FAILED, url.get());
    return false;
  }

  LOG(
      ("ModuleLoaderBase::HostLoadImportedModule loader (%p) uri %s referrer "
       "(%p) request (%p)",
       loader.get(), uri->GetSpecOrDefault().get(), aReferrer.get(),
       request.get()));

  request->SetImport(aReferrer, aModuleRequest, aPayload);

  if (isDynamicImport) {
    loader->AppendDynamicImport(request);
  }

  nsresult rv = loader->StartModuleLoad(request);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    MOZ_ASSERT(!request->mModuleScript);
    loader->GetScriptLoaderInterface()->ReportErrorToConsole(request, rv);
    if (isDynamicImport) {
      loader->RemoveDynamicImport(request);

      nsAutoCString url;
      uri->GetSpec(url);
      JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
                                JSMSG_DYNAMIC_IMPORT_FAILED, url.get());
    } else {
      loader->OnFetchFailed(request);
      return true;
    }

    return false;
  }

  if (isDynamicImport) {
    loader->OnDynamicImportStarted(request);
  }

  return true;
}

// static
bool ModuleLoaderBase::FinishLoadingImportedModule(
    JSContext* aCx, ModuleLoadRequest* aRequest) {
  // The request should been removed from mDynamicImportRequests.
  MOZ_ASSERT_IF(aRequest->IsDynamicImport(),
                !aRequest->mLoader->HasDynamicImport(aRequest));

  Rooted<JSObject*> module(aCx);
  {
    ModuleScript* moduleScript = aRequest->mModuleScript;
    MOZ_ASSERT(moduleScript);
    MOZ_ASSERT(moduleScript->ModuleRecord());
    module.set(moduleScript->ModuleRecord());
  }
  MOZ_ASSERT(module);

  Rooted<JSScript*> referrer(aCx, aRequest->mReferrerScript);
  Rooted<JSObject*> moduleReqObj(aCx, aRequest->mModuleRequestObj);
  Rooted<Value> statePrivate(aCx, aRequest->mPayload);
  Rooted<Value> payload(aCx, aRequest->mPayload);

  LOG(("ScriptLoadRequest (%p): FinishLoadingImportedModule module (%p)",
       aRequest, module.get()));
  bool usePromise = aRequest->HasScriptLoadContext();
  MOZ_ALWAYS_TRUE(JS::FinishLoadingImportedModule(aCx, referrer, moduleReqObj,
                                                  payload, module, usePromise));
  MOZ_ASSERT(!JS_IsExceptionPending(aCx));
  aRequest->ClearImport();

  return true;
}

// static
bool ModuleLoaderBase::ImportMetaResolve(JSContext* cx, unsigned argc,
                                         Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  RootedValue moduleValue(
      cx, js::GetFunctionNativeReserved(
              &args.callee(),
              static_cast<size_t>(ImportMetaSlots::ModuleRecordSlot)));
  MOZ_ASSERT(!moduleValue.isUndefined());
  RootedObject moduleRecord(cx, &moduleValue.toObject());
  RootedValue modulePrivate(cx, GetModulePrivate(moduleRecord));
  MOZ_ASSERT(!modulePrivate.isUndefined());

  // https://html.spec.whatwg.org/#hostgetimportmetaproperties
  // Step 4.1. Set specifier to ? ToString(specifier).
  //
  // https://tc39.es/ecma262/#sec-tostring
  RootedValue v(cx, args.get(ImportMetaResolveSpecifierArg));
  RootedString specifier(cx, ToString(cx, v));
  if (!specifier) {
    return false;
  }

  // Step 4.2, 4.3 are implemented in ImportMetaResolveImpl.
  RootedString url(cx, ImportMetaResolveImpl(cx, modulePrivate, specifier));
  if (!url) {
    return false;
  }

  // Step 4.4. Return the serialization of url.
  args.rval().setString(url);
  return true;
}

// static
JSString* ModuleLoaderBase::ImportMetaResolveImpl(
    JSContext* aCx, Handle<Value> aReferencingPrivate,
    Handle<JSString*> aSpecifier) {
  RootedString urlString(aCx);

  // The destructor of RefPtr local variables can GC, and they should be
  // performed before returning the raw JSString* pointer.
  {
    RefPtr<ScriptFetchInfo> fetchInfo =
        static_cast<ScriptFetchInfo*>(aReferencingPrivate.toPrivate());
    MOZ_ASSERT(fetchInfo->IsForModuleScript());

    RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx);
    if (!loader) {
      return nullptr;
    }

    nsAutoJSString specifier;
    if (!specifier.init(aCx, aSpecifier)) {
      return nullptr;
    }

    auto result = loader->ResolveModuleSpecifier(fetchInfo, specifier);
    if (result.isErr()) {
      Rooted<Value> error(aCx);
      nsresult rv = loader->HandleResolveFailure(
          aCx, fetchInfo, specifier, result.unwrapErr(), 0,
          ColumnNumberOneOrigin(), &error);
      if (NS_FAILED(rv)) {
        JS_ReportOutOfMemory(aCx);
        return nullptr;
      }

      JS_SetPendingException(aCx, error);

      return nullptr;
    }

    MOZ_ASSERT(result.isOk());
    auto record = result.unwrap();

    nsCOMPtr<nsIURI> uri = record->Result();
    if (ImportMap::IsMultipleImportMapsSupported()) {
      // This implements the 'Add module to resolved module set' part defined in
      // 'resolve a module specifier'.
      loader->AddToResolvedModuleSet(std::move(record));
    }

    nsAutoCString url;
    MOZ_ALWAYS_SUCCEEDS(uri->GetAsciiSpec(url));

    urlString.set(JS_NewStringCopyZ(aCx, url.get()));
  }

  return urlString;
}

// static
bool ModuleLoaderBase::HostPopulateImportMeta(JSContext* aCx,
                                              Handle<JSObject*> aModuleRecord,
                                              Handle<JSObject*> aMetaObject) {
  RefPtr<ScriptFetchInfo> fetchInfo = static_cast<ScriptFetchInfo*>(
      JS::GetModulePrivate(aModuleRecord).toPrivate());
  MOZ_ASSERT(fetchInfo->IsForModuleScript());

  nsAutoCString url;
  MOZ_DIAGNOSTIC_ASSERT(fetchInfo->BaseURL());
  MOZ_ALWAYS_SUCCEEDS(fetchInfo->BaseURL()->GetAsciiSpec(url));

  Rooted<JSString*> urlString(aCx, JS_NewStringCopyZ(aCx, url.get()));
  if (!urlString) {
    JS_ReportOutOfMemory(aCx);
    return false;
  }

  // https://html.spec.whatwg.org/#import-meta-url
  if (!JS_DefineProperty(aCx, aMetaObject, "url", urlString,
                         JSPROP_ENUMERATE)) {
    return false;
  }

  // https://html.spec.whatwg.org/#import-meta-resolve
  // Define 'resolve' function on the import.meta object.
  JSFunction* resolveFunc = js::DefineFunctionWithReserved(
      aCx, aMetaObject, "resolve", ImportMetaResolve, ImportMetaResolveNumArgs,
      JSPROP_ENUMERATE);
  if (!resolveFunc) {
    return false;
  }

  // Store the 'active script' of the meta object into the function slot.
  // See https://html.spec.whatwg.org/#active-script
  //
  // Note: Hold a reference to the module record which in turn keeps the
  // ModuleScript alive when import.resolve is called.
  RootedObject resolveFuncObj(aCx, JS_GetFunctionObject(resolveFunc));
  js::SetFunctionNativeReserved(
      resolveFuncObj, static_cast<size_t>(ImportMetaSlots::ModuleRecordSlot),
      JS::ObjectValue(*aModuleRecord));

  return true;
}

AutoOverrideModuleLoader::AutoOverrideModuleLoader(ModuleLoaderBase* aTarget,
                                                   ModuleLoaderBase* aLoader)
    : mTarget(aTarget) {
  mTarget->SetOverride(aLoader);
}

AutoOverrideModuleLoader::~AutoOverrideModuleLoader() {
  mTarget->ResetOverride();
}

void ModuleLoaderBase::SetOverride(ModuleLoaderBase* aLoader) {
  MOZ_ASSERT(!mOverriddenBy);
  MOZ_ASSERT(!aLoader->mOverriddenBy);
  MOZ_ASSERT(mGlobalObject == aLoader->mGlobalObject);
  mOverriddenBy = aLoader;
}

bool ModuleLoaderBase::IsOverridden() { return !!mOverriddenBy; }

bool ModuleLoaderBase::IsOverriddenBy(ModuleLoaderBase* aLoader) {
  return mOverriddenBy == aLoader;
}

void ModuleLoaderBase::ResetOverride() {
  MOZ_ASSERT(mOverriddenBy);
  mOverriddenBy = nullptr;
}

// static
ModuleLoaderBase* ModuleLoaderBase::GetCurrentModuleLoader(JSContext* aCx) {
  auto reportError = mozilla::MakeScopeExit([aCx]() {
    JS_ReportErrorASCII(aCx, "No ScriptLoader found for the current context");
  });

  Rooted<JSObject*> object(aCx, CurrentGlobalOrNull(aCx));
  if (!object) {
    return nullptr;
  }

  nsIGlobalObject* global = xpc::NativeGlobal(object);
  if (!global) {
    return nullptr;
  }

  ModuleLoaderBase* loader = global->GetModuleLoader(aCx);
  if (!loader) {
    return nullptr;
  }

  MOZ_ASSERT(loader->mGlobalObject == global);

  reportError.release();

  if (loader->mOverriddenBy) {
    MOZ_ASSERT(loader->mOverriddenBy->mGlobalObject == global);
    return loader->mOverriddenBy;
  }
  return loader;
}

// static
ScriptFetchInfo* ModuleLoaderBase::GetScriptFetchInfoOrNull(
    Handle<JSScript*> aReferrer) {
  if (!aReferrer) {
    return nullptr;
  }

  Value value = GetScriptPrivate(aReferrer);
  if (value.isUndefined()) {
    return nullptr;
  }

  return static_cast<ScriptFetchInfo*>(value.toPrivate());
}

nsresult ModuleLoaderBase::StartModuleLoad(ModuleLoadRequest* aRequest) {
  return StartOrRestartModuleLoad(aRequest, RestartRequest::No);
}

nsresult ModuleLoaderBase::RestartModuleLoad(ModuleLoadRequest* aRequest) {
  return StartOrRestartModuleLoad(aRequest, RestartRequest::Yes);
}

nsresult ModuleLoaderBase::StartOrRestartModuleLoad(ModuleLoadRequest* aRequest,
                                                    RestartRequest aRestart) {
  MOZ_ASSERT(aRequest->mLoader == this);
  MOZ_ASSERT(aRequest->IsFetching());

  MOZ_ASSERT_IF(aRequest->IsRetrievedFromMemoryCache(),
                aRestart == RestartRequest::No);

  if (!aRequest->IsRetrievedFromMemoryCache()) {
    aRequest->SetUnknownDataType();
  }

  if (LOG_ENABLED()) {
    nsAutoCString url;
    aRequest->URI()->GetAsciiSpec(url);
    LOG(("ScriptLoadRequest (%p): Start module load %s", aRequest, url.get()));
  }

  // If we're restarting the request, the module should already be in the
  // "fetching" map.
  MOZ_ASSERT_IF(
      aRestart == RestartRequest::Yes,
      IsModuleFetching(ModuleMapKey(aRequest->URI(), aRequest->mModuleType)));

  // Check with the derived class whether we should load this module.
  nsresult rv = NS_OK;
  if (!CanStartLoad(aRequest, &rv)) {
    return rv;
  }

  // Check whether the module has been fetched or is currently being fetched,
  // and if so wait for it rather than starting a new fetch.
  if (aRestart == RestartRequest::No &&
      ModuleMapContainsURL(
          ModuleMapKey(aRequest->URI(), aRequest->mModuleType))) {
    LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest));
    WaitForModuleFetch(aRequest);
    return NS_OK;
  }

  rv = StartFetch(aRequest);
  NS_ENSURE_SUCCESS(rv, rv);

  if (aRequest->IsRetrievedFromMemoryCache()) {
    MOZ_ASSERT(
        IsModuleFetched(ModuleMapKey(aRequest->URI(), aRequest->mModuleType)));
    return NS_OK;
  }

  // We successfully started fetching a module so put its URL in the module
  // map and mark it as fetching.
  if (aRestart == RestartRequest::No) {
    SetModuleFetchStarted(aRequest);
  }

  return NS_OK;
}

bool ModuleLoaderBase::ModuleMapContainsURL(const ModuleMapKey& key) const {
  return IsModuleFetching(key) || IsModuleFetched(key);
}

bool ModuleLoaderBase::IsModuleFetching(const ModuleMapKey& key) const {
  return mFetchingModules.Contains(key);
}

bool ModuleLoaderBase::IsModuleFetched(const ModuleMapKey& key) const {
  return mFetchedModules.Contains(key);
}

nsresult ModuleLoaderBase::GetFetchedModuleURLs(nsTArray<nsCString>& aURLs) {
  for (const auto& entry : mFetchedModules) {
    nsAutoCString spec;
    nsresult rv = entry.mUri->GetSpec(spec);
    NS_ENSURE_SUCCESS(rv, rv);

    aURLs.AppendElement(spec);
  }

  return NS_OK;
}

void ModuleLoaderBase::SetModuleFetchStarted(ModuleLoadRequest* aRequest) {
  // Update the module map to indicate that a module is currently being fetched.

  ModuleMapKey moduleMapKey(aRequest->URI(), aRequest->mModuleType);

  MOZ_ASSERT(aRequest->IsFetching());
  MOZ_ASSERT(!ModuleMapContainsURL(moduleMapKey));

  RefPtr<LoadingRequest> loadingRequest = new LoadingRequest();
  loadingRequest->mRequest = aRequest;
  mFetchingModules.InsertOrUpdate(moduleMapKey, loadingRequest);
}

already_AddRefed<ModuleLoaderBase::LoadingRequest>
ModuleLoaderBase::SetModuleFetchFinishedAndGetWaitingRequests(
    ModuleLoadRequest* aRequest, nsresult aResult) {
  // Update module map with the result of fetching a single module script.
  //
  // If any requests for the same URL are waiting on this one to complete, call
  // ModuleLoaded or LoadFailed to resume or fail them as appropriate.

  MOZ_ASSERT(aRequest->mLoader == this);

  LOG(
      ("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == "
       "%u)",
       aRequest, aRequest->mModuleScript.get(), unsigned(aResult)));

  ModuleMapKey moduleMapKey(aRequest->URI(), aRequest->mModuleType);

  // The entry may be absent for an inline module, because we already finished
  // fetching it, or because it was canceled and dropped by
  // CancelFetchingModules. There is nothing to complete in those cases.
  auto entry = mFetchingModules.Lookup(moduleMapKey);
  if (!entry) {
    LOG(
        ("ScriptLoadRequest (%p): Key not found in mFetchingModules, "
         "assuming we have an inline module or have finished fetching already",
         aRequest));
    return nullptr;
  }

  // It's possible for a request to be cancelled and removed from the fetching
  // modules map and a new request started for the same URI and added to the
  // map. In this case we don't want the first cancelled request to complete the
  // later request (which will cause it to fail) so we ignore it.
  RefPtr<LoadingRequest> loadingRequest = entry.Data();
  if (loadingRequest->mRequest != aRequest) {
    MOZ_ASSERT(aRequest->IsCanceled());
    LOG(
        ("ScriptLoadRequest (%p): Ignoring completion of cancelled request "
         "that was removed from the map",
         aRequest));
    return nullptr;
  }

  MOZ_ALWAYS_TRUE(mFetchingModules.Remove(moduleMapKey));

  RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
  MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);

  mFetchedModules.InsertOrUpdate(moduleMapKey, RefPtr{moduleScript});

  return loadingRequest.forget();
}

void ModuleLoaderBase::ResumeWaitingRequests(LoadingRequest* aLoadingRequest,
                                             bool aSuccess) {
  for (ModuleLoadRequest* request : aLoadingRequest->mWaiting) {
    ResumeWaitingRequest(request, aSuccess);
  }
}

void ModuleLoaderBase::ResumeWaitingRequest(ModuleLoadRequest* aRequest,
                                            bool aSuccess) {
  if (aSuccess) {
    aRequest->ModuleLoaded();
  }

  if (!aRequest->IsErrored()) {
    OnFetchSucceeded(aRequest);
  } else {
    OnFetchFailed(aRequest);
  }
}

void ModuleLoaderBase::WaitForModuleFetch(ModuleLoadRequest* aRequest) {
  ModuleMapKey moduleMapKey(aRequest->URI(), aRequest->mModuleType);
  MOZ_ASSERT(ModuleMapContainsURL(moduleMapKey));

  if (auto entry = mFetchingModules.Lookup(moduleMapKey)) {
    RefPtr<LoadingRequest> loadingRequest = entry.Data();
    loadingRequest->mWaiting.AppendElement(aRequest);
    return;
  }

  RefPtr<ModuleScript> ms;
  MOZ_ALWAYS_TRUE(mFetchedModules.Get(moduleMapKey, getter_AddRefs(ms)));

  ResumeWaitingRequest(aRequest, bool(ms));
}

ModuleScript* ModuleLoaderBase::GetFetchedModule(
    const ModuleMapKey& moduleMapKey) const {
  if (LOG_ENABLED()) {
    nsAutoCString url;
    moduleMapKey.mUri->GetAsciiSpec(url);
    LOG(("GetFetchedModule %s", url.get()));
  }

  bool found;
  ModuleScript* ms = mFetchedModules.GetWeak(moduleMapKey, &found);
  MOZ_ASSERT(found);
  return ms;
}

nsresult ModuleLoaderBase::OnFetchComplete(ModuleLoadRequest* aRequest,
                                           nsresult aRv) {
  LOG(("ScriptLoadRequest (%p): OnFetchComplete result %x", aRequest,
       (unsigned)aRv));
  MOZ_ASSERT(aRequest->mLoader == this);
  MOZ_ASSERT(!aRequest->mModuleScript);

  nsresult rv = aRv;
  if (NS_SUCCEEDED(rv)) {
    rv = CreateModuleScript(aRequest);

#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
    // If a module script was created, it should either have a module record
    // object or a parse error.
    if (ModuleScript* ms = aRequest->mModuleScript) {
      MOZ_DIAGNOSTIC_ASSERT(bool(ms->ModuleRecord()) != ms->HasParseError());
    }
#endif

    if (aRequest->getLoadedScript()->IsTextSource()) {
      aRequest->getLoadedScript()->ClearScriptText();
    }

    if (NS_FAILED(rv)) {
      aRequest->LoadFailed();
      return rv;
    }
  }

  RefPtr<LoadingRequest> waitingRequests =
      SetModuleFetchFinishedAndGetWaitingRequests(aRequest, rv);
  MOZ_ASSERT_IF(waitingRequests, waitingRequests->mRequest == aRequest);

  bool success = bool(aRequest->mModuleScript);
  MOZ_ASSERT(NS_SUCCEEDED(rv) == success);

  // TODO: https://github.com/whatwg/html/issues/11755
  // Should we check if the module script has an error to rethrow as well?
  //
  // https://html.spec.whatwg.org/#hostloadimportedmodule:fetch-a-single-imported-module-script
  // Step 14. onSingleFetchComplete: step 2, and step 3
  //   return ThrowCompletion if moduleScript is null or its parse error is not
  //   null:
  //   Step 4. Otherwise, set completion to NormalCompletion(moduleScript's
  //     record).
  if (!aRequest->IsErrored()) {
    OnFetchSucceeded(aRequest);
  } else {
    OnFetchFailed(aRequest);
  }

  if (!waitingRequests) {
    return NS_OK;
  }

  ResumeWaitingRequests(waitingRequests, success);
  return NS_OK;
}

void ModuleLoaderBase::OnFetchSucceeded(ModuleLoadRequest* aRequest) {
  // TODO, Bug 1990416: Align the loading descendants behavior of dynamic import
  // According to the spec, dynamic import should trigger fetching of
  // descendant modules in the JS engine. However, when the dynamic import’s
  // module graph is loaded, ScriptLoaders move the request to a Loaded event
  // queue and invoke ProcessDynamicImport. To account for this behavior,
  // we perform the fetching of submodules in the host layer instead.
  if (aRequest->IsTopLevel() || aRequest->IsDynamicImport()) {
    StartFetchingModuleDependencies(aRequest);
  } else {
    MOZ_ASSERT(!aRequest->IsDynamicImport());
    AutoJSAPI jsapi;
    if (!jsapi.Init(mGlobalObject)) {
      return;
    }
    JSContext* cx = jsapi.cx();
    FinishLoadingImportedModule(cx, aRequest);

    aRequest->SetReady();
    aRequest->LoadFinished();
  }
}

void ModuleLoaderBase::OnFetchFailed(ModuleLoadRequest* aRequest) {
  MOZ_ASSERT(aRequest->IsErrored());
  AutoJSAPI jsapi;
  if (!jsapi.Init(mGlobalObject)) {
    return;
  }
  JSContext* cx = jsapi.cx();

  if (aRequest->IsTopLevel()) {
    // https://html.spec.whatwg.org/#fetch-the-descendants-of-and-link-a-module-script
    // Step 2. If record is null, then:
    // Step 2.1. Set moduleScript's error to rethrow to moduleScript's parse
    //           error.
    if (aRequest->mModuleScript && !aRequest->mModuleScript->ModuleRecord()) {
      MOZ_ASSERT(aRequest->mModuleScript->HasParseError());
      Value parseError = aRequest->mModuleScript->ParseError();
      LOG(("ScriptLoadRequest (%p): found parse error", aRequest));
      aRequest->mModuleScript->SetErrorToRethrow(parseError);
    }
    DispatchModuleErrored(aRequest);

    return;
  }

  // The remaining case is static/dynamic import.
  MOZ_ASSERT(aRequest->IsStaticImport() || aRequest->IsDynamicImport());
  MOZ_ASSERT(!aRequest->mPayload.isUndefined());
  Rooted<Value> payload(cx, aRequest->mPayload);

  // https://html.spec.whatwg.org/#hostloadimportedmodule
  //
  // Step 14.2. If moduleScript is null, then set completion to Completion
  //            Record { [[Type]]: throw, [[Value]]: a new TypeError,
  //            [[Target]]: empty }.
  if (!aRequest->mModuleScript) {
    LOG(
        ("ScriptLoadRequest (%p): FinishLoadingImportedModule: module script "
         "is null",
         aRequest));

    // Impl note:
    // For dynamic import, the TypeError will be pass to the rejected handler of
    // the LoadRequestedModules from the dynamic import.
    //
    // For static import/top-level import, the TypeError will be ignore in the
    // rejected handler of LoadRequestedModules (the state.[[ErrorToRethrow]]
    // isn't set). So we don't actually create a TypeError for the these two
    // cases.
    if (aRequest->GetRootModule()->IsDynamicImport()) {
      nsAutoCString url;
      aRequest->URI()->GetSpec(url);
      JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
                                JSMSG_DYNAMIC_IMPORT_FAILED, url.get());
      FinishLoadingImportedModuleFailedWithPendingException(cx, payload);
    } else {
      Rooted<Value> error(cx, UndefinedValue());
      FinishLoadingImportedModuleFailed(cx, payload, error);
    }

    aRequest->ModuleErrored();
    aRequest->ClearImport();
    return;
  }

  // Step 14.3. Otherwise, if moduleScript's parse error is not null, then:
  //   1. Let parseError be moduleScript's parse error.
  //   2. Set completion to Completion Record { [[Type]]: throw,
  //      [[Value]]: parseError, [[Target]]: empty }.
  //   3. If loadState is not undefined and loadState.[[ErrorToRethrow]]
  //      is null, set loadState.[[ErrorToRethrow]] to parseError.
  MOZ_ASSERT(aRequest->mModuleScript->HasParseError());
  Rooted<Value> parseError(cx, aRequest->mModuleScript->ParseError());
  LOG(
      ("ScriptLoadRequest (%p): FinishLoadingImportedModule: found parse "
       "error",
       aRequest));
  // Step 14.5. Perform FinishLoadingImportedModule(referrer, moduleRequest,
  //            payload, completion).
  FinishLoadingImportedModuleFailed(cx, payload, parseError);
  aRequest->ModuleErrored();
  aRequest->ClearImport();
}

void ModuleLoaderBase::Cancel(ModuleLoadRequest* aRequest) {
  if (aRequest->IsFinished()) {
    return;
  }

  aRequest->ScriptLoadRequest::Cancel();
  aRequest->mModuleScript = nullptr;

  if (aRequest->mPayload.isUndefined()) {
    return;
  }

  AutoJSAPI jsapi;
  if (!jsapi.Init(mGlobalObject)) {
    return;
  }
  JSContext* cx = jsapi.cx();

  Rooted<Value> payload(cx, aRequest->mPayload);
  Rooted<Value> error(cx, UndefinedValue());

  LOG(
      ("ScriptLoadRequest (%p): Canceled, calling "
       "FinishLoadingImportedModuleFailed",
       aRequest));

  FinishLoadingImportedModuleFailed(cx, payload, error);
  aRequest->ModuleErrored();
  aRequest->ClearImport();
}

class ModuleErroredRunnable : public MicroTaskRunnable {
 public:
  explicit ModuleErroredRunnable(ModuleLoadRequest* aRequest)
      : mRequest(aRequest) {}

  virtual void Run(AutoSlowOperation& aAso) override {
    mRequest->ModuleErrored();
  }

 private:
  RefPtr<ModuleLoadRequest> mRequest;
};

void ModuleLoaderBase::DispatchModuleErrored(ModuleLoadRequest* aRequest) {
  if (aRequest->HasScriptLoadContext() &&
      aRequest->GetScriptLoadContext()->mIsInline) {
    // https://html.spec.whatwg.org/#prepare-the-script-element
    // Step 32. If el does not have a src content attribute:
    //   2. "module".3.1
    //   Queue an element task on the networking task source given el to
    //   perform the following steps:
    CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
    RefPtr<ModuleErroredRunnable> runnable =
        new ModuleErroredRunnable(aRequest);
    context->DispatchToMicroTask(runnable.forget());
  } else {
    aRequest->ModuleErrored();
  }
}

nsresult ModuleLoaderBase::CreateModuleScript(ModuleLoadRequest* aRequest) {
  MOZ_ASSERT(!aRequest->mModuleScript);
  MOZ_ASSERT(aRequest->BaseURL());

  LOG(("ScriptLoadRequest (%p): Create module script", aRequest));

  AutoJSAPI jsapi;
  if (!jsapi.Init(mGlobalObject)) {
    return NS_ERROR_FAILURE;
  }

  nsresult rv;
  {
    JSContext* cx = jsapi.cx();
    Rooted<JSObject*> module(cx);

    CompileOptions options(cx);
    RootedScript introductionScript(cx);
    rv = mLoader->FillCompileOptionsForRequest(cx, aRequest, &options,
                                               &introductionScript);

    if (NS_SUCCEEDED(rv)) {
      Rooted<JSObject*> global(cx, mGlobalObject->GetGlobalJSObject());
      rv = CompileFetchedModule(cx, global, options, aRequest, &module);
    }

    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));

    if (module) {
      RootedScript moduleScript(cx, GetModuleScript(module));
      if (moduleScript) {
        RootedValue privateValue(cx);
        InstantiateOptions instantiateOptions(options);
        if (!UpdateDebugMetadata(cx, moduleScript, instantiateOptions,
                                 privateValue, nullptr, introductionScript,
                                 nullptr)) {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
    }

    MOZ_ASSERT(aRequest->mLoadedScript->IsModuleScript());

    RefPtr<ModuleScript> moduleScript = new ModuleScript(aRequest->FetchInfo());

    aRequest->mModuleScript = moduleScript;

    moduleScript->SetForPreload(aRequest->mLoadContext->IsPreload());
    moduleScript->SetHadImportMap(HasImportMapRegistered());

    if (!module) {
      LOG(("ScriptLoadRequest (%p):   compilation failed (%d)", aRequest,
           unsigned(rv)));

      Rooted<Value> error(cx);
      if (!jsapi.HasException() || !jsapi.StealException(&error) ||
          error.isUndefined()) {
        aRequest->mModuleScript = nullptr;
        return NS_ERROR_FAILURE;
      }

      moduleScript->SetParseError(error);
      return NS_OK;
    }

    moduleScript->SetModuleRecord(module);

    if (IsCyclicModule(module)) {
      aRequest->FetchInfo()->AssociateWithModule(module);
    }
  }

  LOG(("ScriptLoadRequest (%p):   module script == %p ForPreload %d", aRequest,
       aRequest->mModuleScript.get(), aRequest->mModuleScript->ForPreload()));

  return rv;
}

nsresult ModuleLoaderBase::GetResolveFailureMessage(ResolveError aError,
                                                    const nsAString& aSpecifier,
                                                    nsAString& aResult) {
  AutoTArray<nsString, 1> errorParams;
  errorParams.AppendElement(aSpecifier);

  nsresult rv = nsContentUtils::FormatLocalizedString(
      PropertiesFile::DOM_PROPERTIES, ResolveErrorInfo::GetString(aError),
      errorParams, aResult);
  NS_ENSURE_SUCCESS(rv, rv);
  return NS_OK;
}

nsresult ModuleLoaderBase::HandleResolveFailure(
    JSContext* aCx, ScriptFetchInfo* aFetchInfo, const nsAString& aSpecifier,
    ResolveError aError, uint32_t aLineNumber,
    ColumnNumberOneOrigin aColumnNumber, MutableHandle<Value> aErrorOut) {
  Rooted<JSString*> filename(aCx);
  if (aFetchInfo) {
    nsAutoCString url;
    aFetchInfo->BaseURL()->GetAsciiSpec(url);
    filename = JS_NewStringCopyZ(aCx, url.get());
  } else {
    filename = JS_NewStringCopyZ(aCx, "(unknown)");
  }

  if (!filename) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  nsAutoString errorText;
  nsresult rv = GetResolveFailureMessage(aError, aSpecifier, errorText);
  NS_ENSURE_SUCCESS(rv, rv);

  Rooted<JSString*> string(aCx, JS_NewUCStringCopyZ(aCx, errorText.get()));
  if (!string) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  if (!CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber,
                   aColumnNumber, nullptr, string, NothingHandleValue,
                   aErrorOut)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  return NS_OK;
}

ResolveResult ModuleLoaderBase::ResolveModuleSpecifier(
    ScriptFetchInfo* aFetchInfo, const nsAString& aSpecifier) {
  // Import Maps are not supported on workers/worklets.
  // See https://github.com/WICG/import-maps/issues/2
  MOZ_ASSERT_IF(!NS_IsMainThread(), mImportMap == nullptr);

  // Forward to the updated 'Resolve a module specifier' algorithm defined in
  // the Import Maps spec.
  return ImportMap::ResolveModuleSpecifier(mImportMap.get(), mLoader,
                                           aFetchInfo, aSpecifier);
}

ResolvedModuleSet* ModuleLoaderBase::GetResolvedModuleSet() {
  MOZ_ASSERT(ImportMap::IsMultipleImportMapsSupported());
  if (!mResolvedModuleSet) {
    mResolvedModuleSet = MakeUnique<ResolvedModuleSet>();
  }

  return mResolvedModuleSet.get();
}

static void AddToResolvedSet(ResolvedModuleSet* aSet,
                             UniquePtr<SpecifierResolutionRecord> aRecord) {
  MOZ_ASSERT(ImportMap::IsMultipleImportMapsSupported());
  auto ptr = aSet->lookupForAdd(aRecord);
  if (ptr) {
    return;
  }

  MOZ_ALWAYS_TRUE(aSet->add(ptr, std::move(aRecord)));
}

void ModuleLoaderBase::AddToPreloadedResolvedSet(
    ModuleLoadRequest* aRootRequest,
    UniquePtr<SpecifierResolutionRecord> aRecord) {
  MOZ_ASSERT(aRootRequest);
  MOZ_ASSERT(aRootRequest->mModuleScript);

  ResolvedModuleSet* set =
      aRootRequest->mModuleScript->GetPreloadedResolvedSet();
  MOZ_ASSERT(set);
  AddToResolvedSet(set, std::move(aRecord));
}

void ModuleLoaderBase::AddToGlobalResolvedSet(
    UniquePtr<SpecifierResolutionRecord> aRecord) {
  AddToResolvedSet(GetResolvedModuleSet(), std::move(aRecord));
}

static ModuleLoadRequest* GetPreloadRootModuleRequest(
    JS::Handle<JS::Value> aHostDefined) {
  MOZ_ASSERT(!aHostDefined.isUndefined());
  ModuleLoadRequest* parent =
      static_cast<ModuleLoadRequest*>(aHostDefined.toPrivate());
  MOZ_ASSERT(parent);
  return parent->GetRootModule();
}

void ModuleLoaderBase::AddToResolvedModuleSet(
    UniquePtr<SpecifierResolutionRecord> aRecord,
    ScriptFetchInfo* aFetchInfo /* = nullptr */,
    Handle<Value> aHostDefined /* = UndefinedHandleValue */) {
  // 2. If global does not implement Window, then return.
  if (!mLoader->IsImportMapSupported()) {
    return;
  }

  bool isPreloadModule = aFetchInfo && aFetchInfo->IsForModuleScript() &&
                         aFetchInfo->IsForModulePreload();
  if (isPreloadModule) {
    RefPtr<ModuleLoadRequest> root = GetPreloadRootModuleRequest(aHostDefined);
    AddToPreloadedResolvedSet(root, std::move(aRecord));
    return;
  }

  // release the mResult from the record as it is not needed.
  nsCOMPtr<nsIURI> _ = aRecord->TakeResult();
  AddToGlobalResolvedSet(std::move(aRecord));
}

// This just flip the preload flag in the module script and ModuleObject.
void ModuleLoaderBase::ResetPreloadFlag(nsIURI* aURI) {
  MOZ_ASSERT(aURI);
  ModuleMapKey key(aURI, ModuleType::JavaScript);
  if (!IsModuleFetched(key)) {
    return;
  }

  RefPtr<ModuleScript> ms;
  MOZ_ALWAYS_TRUE(mFetchedModules.Get(key, getter_AddRefs(ms)));

  // This function is called from a preloaded module graph, but it may have a
  // module script from another preloaded module graph which has been turned
  // into a normal load, so we don't assert the module script is a preloaded
  // one.
  ms->SetForPreload(false);
}

// This is called when a preloaded module request is reused, so we need to move
// the resolved set into the global's set.
// Also, we need to flip the preload flag in the module script.
void ModuleLoaderBase::MovePreloadedSetToResolvedSet(
    ModuleLoadRequest* aRootRequest) {
  LOG(("ScriptLoadRequest (%p): MovePreloadedSetToResolvedSet", aRootRequest));
  MOZ_ASSERT(ImportMap::IsMultipleImportMapsSupported());
  MOZ_ASSERT(aRootRequest);

  // ScriptLoader has flipped the preload flag to false.
  MOZ_ASSERT(!aRootRequest->mLoadContext->IsPreload());

  ModuleScript* ms = aRootRequest->mModuleScript;
  if (!ms) {
    return;
  }

  ms->SetForPreload(false);
  if (!ms->HasPreloadedResolvedSet()) {
    return;
  }

  ResolvedModuleSet* set = ms->GetPreloadedResolvedSet();
  for (auto iter = set->modIter(); !iter.done(); iter.next()) {
    auto record = std::move(iter.getMutable());
    nsCOMPtr<nsIURI> uri = record->TakeResult();
    ResetPreloadFlag(uri);
    AddToGlobalResolvedSet(std::move(record));
  }

  set->clear();
  ms->ReleasePreloadedResolvedSet();
}

// If the module script is preloaded, reset the [[LoadedModules]]
//
void ModuleLoaderBase::ResetPreloadedModule(nsIURI* aURI) {
  MOZ_ASSERT(aURI);
  ModuleMapKey key(aURI, ModuleType::JavaScript);
  if (!IsModuleFetched(key)) {
    return;
  }

  RefPtr<ModuleScript> ms;
  MOZ_ALWAYS_TRUE(mFetchedModules.Get(key, getter_AddRefs(ms)));
  if (!ms->ForPreload()) {
    return;
  }

  LOG(
      ("ModuleLoaderBase::ResetPreloadedModule: module script (%p) reset "
       "preloaded info",
       ms.get()));
  ms->ResetPreload();
}

// This is called before registering an import map, so we clear the remaing
// mPreloads from the ScriptLoader
void ModuleLoaderBase::ClearPreloadedModuleGraph(
    ModuleLoadRequest* aRootRequest) {
  MOZ_ASSERT(ImportMap::IsMultipleImportMapsSupported());
  MOZ_ASSERT(aRootRequest);
  MOZ_ASSERT(aRootRequest->mLoadContext->IsPreload());
  LOG(("ScriptLoadRequest (%p): ClearPreloadedModuleGraph", aRootRequest));

  ModuleScript* ms = aRootRequest->mModuleScript;
  if (!ms) {
    return;
  }

  // If the preloaded request has a module script which has been used previously
  // in another preloaed module graph, which has been reused and is a normal
  // load now, we don't need to reset its data anymore.
  if (!ms->ForPreload()) {
    return;
  }

  ms->ResetPreload();

  if (!ms->HasPreloadedResolvedSet()) {
    return;
  }

  ResolvedModuleSet* set = ms->GetPreloadedResolvedSet();
  for (auto iter = set->iter(); !iter.done(); iter.next()) {
    nsCOMPtr<nsIURI> uri = iter.get()->TakeResult();
    MOZ_ASSERT(uri);
    ResetPreloadedModule(uri);
  }
  set->clear();
  ms->ReleasePreloadedResolvedSet();
}

void ModuleLoaderBase::StartFetchingModuleDependencies(
    ModuleLoadRequest* aRequest) {
  if (aRequest->IsCanceled()) {
    return;
  }

  MOZ_ASSERT(aRequest->mModuleScript);
  MOZ_ASSERT(!aRequest->mModuleScript->HasParseError());
  ModuleScript* moduleScript = aRequest->mModuleScript;
  MOZ_ASSERT(moduleScript->ModuleRecord());
  MOZ_ASSERT(aRequest->IsFetching() || aRequest->IsCompiling());
  MOZ_ASSERT(aRequest->IsTopLevel() || aRequest->IsDynamicImport());

  AutoJSAPI jsapi;
  if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) {
    return;
  }
  JSContext* cx = jsapi.cx();

  Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());

  LOG(
      ("ScriptLoadRequest (%p): module record (%p) Start fetching module "
       "dependencies",
       aRequest, module.get()));

  // Wrap the request into a JS::Value, and AddRef() it.
  // The Release() will be called in the resolved/rejected handlers.
  Rooted<Value> hostDefinedVal(cx, PrivateValue(aRequest));
  aRequest->AddRef();

  bool result = false;

  // A microtask job is not executed if the global is being
  // destroyed. As a result, the promise returned by LoadRequestedModules may
  // neither resolve nor reject. To ensure module loading completes reliably in
  // chrome pages, we use the synchronous variant of LoadRequestedModules.
  bool isSync = aRequest->URI()->SchemeIs("chrome") ||
                aRequest->URI()->SchemeIs("resource");

  // TODO: Bug1973660: Use Promise version of LoadRequestedModules on Workers.
  if (aRequest->HasScriptLoadContext() && !isSync) {
    Rooted<JSFunction*> onResolved(
        cx, js::NewFunctionWithReserved(cx, OnLoadRequestedModulesResolved,
                                        OnLoadRequestedModulesResolvedNumArgs,
                                        0, "resolved"));
    if (!onResolved) {
      JS_ReportOutOfMemory(cx);
      return;
    }

    RootedFunction onRejected(
        cx, js::NewFunctionWithReserved(cx, OnLoadRequestedModulesRejected,
                                        OnLoadRequestedModulesRejectedNumArgs,
                                        0, "rejected"));
    if (!onRejected) {
      JS_ReportOutOfMemory(cx);
      return;
    }

    RootedObject resolveFuncObj(cx, JS_GetFunctionObject(onResolved));
    js::SetFunctionNativeReserved(resolveFuncObj, LoadReactionHostDefinedSlot,
                                  hostDefinedVal);

    RootedObject rejectFuncObj(cx, JS_GetFunctionObject(onRejected));
    js::SetFunctionNativeReserved(rejectFuncObj, LoadReactionHostDefinedSlot,
                                  hostDefinedVal);

    Rooted<JSObject*> loadPromise(cx);
    result = LoadRequestedModules(cx, module, hostDefinedVal, &loadPromise);
    AddPromiseReactions(cx, loadPromise, resolveFuncObj, rejectFuncObj);
  } else {
    result = LoadRequestedModules(cx, module, hostDefinedVal,
                                  OnLoadRequestedModulesResolved,
                                  OnLoadRequestedModulesRejected);
  }

  if (!result) {
    LOG(("ScriptLoadRequest (%p): LoadRequestedModules failed", aRequest));
    OnLoadRequestedModulesRejected(cx, aRequest, UndefinedHandleValue);
  }
}

// static
bool ModuleLoaderBase::OnLoadRequestedModulesResolved(JSContext* aCx,
                                                      unsigned aArgc,
                                                      Value* aVp) {
  CallArgs args = CallArgsFromVp(aArgc, aVp);
  Rooted<Value> hostDefined(aCx);
  hostDefined = js::GetFunctionNativeReserved(&args.callee(),
                                              LoadReactionHostDefinedSlot);
  return OnLoadRequestedModulesResolved(aCx, hostDefined);
}

// static
bool ModuleLoaderBase::OnLoadRequestedModulesResolved(
    JSContext* aCx, Handle<Value> aHostDefined) {
  auto* request = static_cast<ModuleLoadRequest*>(aHostDefined.toPrivate());
  MOZ_ASSERT(request);
  return OnLoadRequestedModulesResolved(request);
}

// static
bool ModuleLoaderBase::OnLoadRequestedModulesResolved(
    ModuleLoadRequest* aRequest) {
  LOG(("ScriptLoadRequest (%p): LoadRequestedModules resolved", aRequest));
  if (!aRequest->IsCanceled()) {
    aRequest->SetReady();
    aRequest->LoadFinished();
  }

  // Decrease the reference 'AddRef'ed when converting the hostDefined.
  aRequest->Release();
  return true;
}

// static
bool ModuleLoaderBase::OnLoadRequestedModulesRejected(JSContext* aCx,
                                                      unsigned aArgc,
                                                      Value* aVp) {
  CallArgs args = CallArgsFromVp(aArgc, aVp);
  Rooted<Value> error(aCx, args.get(OnLoadRequestedModulesRejectedErrorArg));
  Rooted<Value> hostDefined(aCx);
  hostDefined = js::GetFunctionNativeReserved(&args.callee(),
                                              LoadReactionHostDefinedSlot);
  return OnLoadRequestedModulesRejected(aCx, hostDefined, error);
}

// static
bool ModuleLoaderBase::OnLoadRequestedModulesRejected(
    JSContext* aCx, Handle<Value> aHostDefined, Handle<Value> aError) {
  auto* request = static_cast<ModuleLoadRequest*>(aHostDefined.toPrivate());
  MOZ_ASSERT(request);
  return OnLoadRequestedModulesRejected(aCx, request, aError);
}

// static
bool ModuleLoaderBase::OnLoadRequestedModulesRejected(
    JSContext* aCx, ModuleLoadRequest* aRequest, Handle<Value> error) {
  ModuleScript* moduleScript = aRequest->mModuleScript;

  if (aRequest->IsCanceled()) {
    aRequest->Release();
    return true;
  }

  // TODO, Bug 1990416: Align the loading descendants behavior of dynamic import
  if (aRequest->IsDynamicImport()) {
    LOG(
        ("ScriptLoadRequest (%p): LoadRequestedModules rejected for dynamic "
         "import",
         aRequest));
    // https://tc39.es/ecma262/#sec-ContinueDynamicImport
    // Step 4.a. Perform ! Call(promiseCapability.[[Reject]], undefined,
    //   « reason »).
    Rooted<Value> payload(aCx, aRequest->mPayload);
    if (!error.isUndefined()) {
      FinishLoadingImportedModuleFailed(aCx, payload, error);
    } else {
      nsAutoCString url;
      aRequest->URI()->GetSpec(url);
      JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
                                JSMSG_DYNAMIC_IMPORT_FAILED, url.get());
      FinishLoadingImportedModuleFailedWithPendingException(aCx, payload);
    }
    aRequest->SetErroredLoadingImports();
  } else if (moduleScript && !error.isUndefined()) {
    LOG(
        ("ScriptLoadRequest (%p): LoadRequestedModules rejected: set error to "
         "rethrow",
         aRequest));
    // https://html.spec.whatwg.org/#fetch-the-descendants-of-and-link-a-module-script
    // Step 7. Upon rejection of loadingPromise, run the following
    //         steps:
    // Step 7.1. If state.[[ErrorToRethrow]] is not null, set moduleScript's
    //           error to rethrow to state.[[ErrorToRethrow]] and run
    //           onComplete given moduleScript.
    moduleScript->SetErrorToRethrow(error);
  } else {
    LOG(
        ("ScriptLoadRequest (%p): LoadRequestedModules rejected: set module "
         "script to null",
         aRequest));
    // Step 7.2. Otherwise, run onComplete given null.
    aRequest->mModuleScript = nullptr;
  }

  aRequest->ModuleErrored();

  // Decrease the reference 'AddRef'ed when converting the hostDefined.
  aRequest->Release();
  return true;
}

bool ModuleLoaderBase::GetImportMapSRI(
    nsIURI* aURI, nsIURI* aSourceURI, nsIConsoleReportCollector* aReporter,
    mozilla::dom::SRIMetadata* aMetadataOut) {
  MOZ_ASSERT(aMetadataOut->IsEmpty());
  MOZ_ASSERT(aURI);

  if (!HasImportMapRegistered()) {
    return false;
  }

  mozilla::Maybe<nsString> entry =
      ImportMap::LookupIntegrity(mImportMap.get(), aURI);
  if (entry.isNothing()) {
    return false;
  }

  mozilla::dom::SRICheck::IntegrityMetadata(
      *entry, aSourceURI->GetSpecOrDefault(), aReporter, aMetadataOut);
  return true;
}

void ModuleLoaderBase::AppendDynamicImport(ModuleLoadRequest* aRequest) {
  MOZ_ASSERT(aRequest->mLoader == this);
  mDynamicImportRequests.AppendElement(aRequest);
}

ModuleLoaderBase::ModuleLoaderBase(ScriptLoaderInterface* aLoader,
                                   nsIGlobalObject* aGlobalObject)
    : mGlobalObject(aGlobalObject), mLoader(aLoader) {
  MOZ_ASSERT(mGlobalObject);
  MOZ_ASSERT(mLoader);

  EnsureModuleHooksInitialized();
}

ModuleLoaderBase::~ModuleLoaderBase() {
  mDynamicImportRequests.CancelRequestsAndClear();

  LOG(("ModuleLoaderBase::~ModuleLoaderBase %p", this));
}

void ModuleLoaderBase::CancelFetchingModules() {
  for (const auto& entry : mFetchingModules) {
    RefPtr<LoadingRequest> loadingRequest = entry.GetData();
    loadingRequest->mRequest->Cancel();

    for (const auto& request : loadingRequest->mWaiting) {
      request->Cancel();
    }
  }

  // Drop the canceled requests rather than leaving HasFetchingModules() true
  // forever. A request's fetch may still complete (unsuccessfully) afterwards;
  // OnFetchComplete tolerates the entry no longer being in mFetchingModules.
  mFetchingModules.Clear();
}

void ModuleLoaderBase::Shutdown() {
  CancelAndClearDynamicImports();

  for (const auto& entry : mFetchingModules) {
    RefPtr<LoadingRequest> loadingRequest(entry.GetData());
    if (loadingRequest) {
      ResumeWaitingRequests(loadingRequest, false);
    }
  }

  for (const auto& entry : mFetchedModules) {
    if (entry.GetData()) {
      entry.GetData()->Shutdown();
    }
  }

  mFetchingModules.Clear();
  mFetchedModules.Clear();
  mGlobalObject = nullptr;
  mLoader = nullptr;
}

bool ModuleLoaderBase::HasFetchingModules() const {
  return !mFetchingModules.IsEmpty();
}

bool ModuleLoaderBase::HasPendingDynamicImports() const {
  return !mDynamicImportRequests.isEmpty();
}

void ModuleLoaderBase::CancelDynamicImport(ModuleLoadRequest* aRequest,
                                           nsresult aResult) {
  // aRequest may have already been unlinked by CC.
  MOZ_ASSERT(aRequest->mLoader == this || !aRequest->mLoader);

  RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
  if (!aRequest->IsCanceled()) {
    // If the ClearDynamicImport() has been called, then it should have been
    // removed from mDynamicImportRequests as well.
    MOZ_ASSERT(!aRequest->mPayload.isUndefined());
    aRequest->Cancel();
  }
}

void ModuleLoaderBase::RemoveDynamicImport(ModuleLoadRequest* aRequest) {
  MOZ_ASSERT(aRequest->IsDynamicImport());
  mDynamicImportRequests.Remove(aRequest);
}

#ifdef DEBUG
bool ModuleLoaderBase::HasDynamicImport(
    const ModuleLoadRequest* aRequest) const {
  MOZ_ASSERT(aRequest->mLoader == this);
  return mDynamicImportRequests.Contains(
      const_cast<ModuleLoadRequest*>(aRequest));
}
#endif

bool ModuleLoaderBase::InstantiateModuleGraph(ModuleLoadRequest* aRequest) {
  // Instantiate a top-level module and record any error.

  MOZ_ASSERT(aRequest);
  MOZ_ASSERT(aRequest->mLoader == this);
  MOZ_ASSERT(aRequest->IsTopLevel());

  LOG(("ScriptLoadRequest (%p): Instantiate module graph", aRequest));

  AUTO_PROFILER_LABEL("ModuleLoaderBase::InstantiateModuleGraph", JS);

  ModuleScript* moduleScript = aRequest->mModuleScript;
  MOZ_ASSERT(moduleScript);

  MOZ_ASSERT(!moduleScript->HasParseError());
  MOZ_ASSERT(moduleScript->ModuleRecord());

  AutoJSAPI jsapi;
  if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) {
    return false;
  }

  JSContext* cx = jsapi.cx();
  Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());
  if (!xpc::Scriptability::AllowedIfExists(module)) {
    return true;
  }

  if (!ModuleLink(jsapi.cx(), module)) {
    LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest));
    MOZ_ASSERT(jsapi.HasException());
    RootedValue exception(jsapi.cx());
    if (!jsapi.StealException(&exception)) {
      return false;
    }
    MOZ_ASSERT(!exception.isUndefined());
    moduleScript->SetErrorToRethrow(exception);
  }

  return true;
}

void ModuleLoaderBase::ProcessDynamicImport(ModuleLoadRequest* aRequest) {
  AutoJSAPI jsapi;
  if (!jsapi.Init(GetGlobalObject())) {
    return;
  }
  JSContext* cx = jsapi.cx();
  MOZ_ASSERT(aRequest->IsDynamicImport());

  if (aRequest->IsErrored()) {
    LOG(("ScriptLoadRequest (%p): ProcessDynamicImport, request has an error",
         aRequest));
    // The error is already processed in OnLoadRequestedModulesRejected.
    return;
  }

  LOG(("ScriptLoadRequest (%p): ProcessDynamicImport", aRequest));
  FinishLoadingImportedModule(cx, aRequest);

  // TODO: Implement caching for wasm modules (Bug 1998240).
  if (!aRequest->IsWasmBytes()) {
    (void)mLoader->MaybePrepareModuleForDiskCacheAfterExecute(aRequest, NS_OK);
  }

  mLoader->MaybeUpdateDiskCache();
}

nsresult ModuleLoaderBase::EvaluateModule(ModuleLoadRequest* aRequest) {
  MOZ_ASSERT(aRequest->mLoader == this);

  mozilla::nsAutoMicroTask mt;
  mozilla::dom::AutoEntryScript aes(mGlobalObject, "EvaluateModule",
                                    NS_IsMainThread());

  // We would like to handle any evaluation errors synchronously for service
  // workers immediately such that if we are performing service worker
  // registration we can fail it right away rather than having it fail later on
  // an event loop turn which might be too late and the registration process
  // would have succeeded by then.
  return EvaluateModuleInContext(
      aes.cx(), aRequest,
      IsForServiceWorker() ? ThrowModuleErrorsSync : ReportModuleErrorsAsync);
}

nsresult ModuleLoaderBase::EvaluateModuleInContext(
    JSContext* aCx, ModuleLoadRequest* aRequest,
    ModuleErrorBehaviour errorBehaviour) {
  MOZ_ASSERT(aRequest->mLoader == this);
  MOZ_ASSERT_IF(!mGlobalObject->GetModuleLoader(aCx)->IsOverridden(),
                mGlobalObject->GetModuleLoader(aCx) == this);
  MOZ_ASSERT_IF(mGlobalObject->GetModuleLoader(aCx)->IsOverridden(),
                mGlobalObject->GetModuleLoader(aCx)->IsOverriddenBy(this));
  MOZ_ASSERT(!aRequest->IsDynamicImport());

  AUTO_PROFILER_LABEL("ModuleLoaderBase::EvaluateModule", JS);

  nsAutoCString profilerLabelString;
  if (aRequest->HasScriptLoadContext()) {
    aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString);
  }

  LOG(("ScriptLoadRequest (%p): Evaluate Module", aRequest));
  AUTO_PROFILER_MARKER_TEXT("ModuleEvaluation", JS,
                            MarkerInnerWindowIdFromJSContext(aCx),
                            profilerLabelString);

  MOZ_ASSERT(aRequest->mModuleScript);
  MOZ_ASSERT_IF(aRequest->HasScriptLoadContext(),
                !aRequest->GetScriptLoadContext()->mCompileOrDecodeTask);

  ModuleScript* moduleScript = aRequest->mModuleScript;
  if (moduleScript->HasErrorToRethrow()) {
    LOG(("ScriptLoadRequest (%p):   module has error to rethrow", aRequest));
    Rooted<Value> error(aCx, moduleScript->ErrorToRethrow());
    JS_SetPendingException(aCx, error);
    return NS_OK;
  }

  Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
  MOZ_ASSERT(module);
  MOZ_ASSERT(CurrentGlobalOrNull(aCx) == GetNonCCWObjectGlobal(module));

  if (!xpc::Scriptability::AllowedIfExists(module)) {
    return NS_OK;
  }

  if (aRequest->HasScriptLoadContext()) {
    TRACE_FOR_TEST(aRequest, "evaluate:module");
  }

  Rooted<Value> rval(aCx);

  bool ok = ModuleEvaluate(aCx, module, &rval);

  // ModuleEvaluate will usually set a pending exception if it returns false,
  // unless the user cancels execution.
  MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aCx));

  nsresult rv = NS_OK;
  if (!ok || IsModuleEvaluationAborted(aRequest)) {
    LOG(("ScriptLoadRequest (%p):   evaluation failed", aRequest));
    // For a dynamic import, the promise is rejected. Otherwise an error is
    // reported by AutoEntryScript.
    rv = NS_ERROR_ABORT;
  }

  // ModuleEvaluate returns a promise unless the user cancels the execution in
  // which case rval will be undefined. We should treat it as a failed
  // evaluation, and reject appropriately.
  Rooted<JSObject*> evaluationPromise(aCx);
  if (rval.isObject()) {
    evaluationPromise.set(&rval.toObject());
  }

  // If the promise is rejected, the value is unwrapped from the promise value.
  if (!ThrowOnModuleEvaluationFailure(aCx, evaluationPromise, errorBehaviour)) {
    LOG(("ScriptLoadRequest (%p):   evaluation failed on throw", aRequest));

    if (IsForServiceWorker()) {
      // If this module eval is being done for service workers, then we would
      // like to throw/return right from here, such that if we are in
      // registration phase, we can fail it synchronously right away.
      return NS_ERROR_ABORT;
    }
  }

  // TODO: Implement caching for wasm modules (Bug 1998240).
  if (!aRequest->IsWasmBytes()) {
    rv = mLoader->MaybePrepareModuleForDiskCacheAfterExecute(aRequest, NS_OK);
  }

  mLoader->MaybeUpdateDiskCache();

  return rv;
}

void ModuleLoaderBase::CancelAndClearDynamicImports() {
  while (ScriptLoadRequest* req = mDynamicImportRequests.getFirst()) {
    // This also removes the request from the list.
    CancelDynamicImport(req->AsModuleRequest(), NS_ERROR_ABORT);
  }
}

UniquePtr<ImportMap> ModuleLoaderBase::ParseImportMap(
    ScriptLoadRequest* aRequest) {
  AutoJSAPI jsapi;
  if (!jsapi.Init(GetGlobalObject())) {
    return nullptr;
  }

  MOZ_ASSERT(aRequest->IsFetchedAsTextSource());
  MaybeSourceText maybeSource;
  nsresult rv = aRequest->GetScriptSource(jsapi.cx(), &maybeSource,
                                          aRequest->mLoadContext.get());
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  SourceText<char16_t>& text = maybeSource.ref<SourceText<char16_t>>();
  ReportWarningHelper warning{mLoader, aRequest};

  // https://html.spec.whatwg.org/multipage/webappapis.html#create-an-import-map-parse-result
  // Step 2. Parse an import map string given input and baseURL, catching any
  // exceptions. If this threw an exception, then set result's error to rethrow
  // to that exception. Otherwise, set result's import map to the return value.
  //
  // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
  // Step 1. If result's error to rethrow is not null, then report the exception
  // given by result's error to rethrow and return.
  //
  // Impl note: We didn't implement 'Import map parse result' from the spec,
  // https://html.spec.whatwg.org/multipage/webappapis.html#import-map-parse-result
  // As the struct has another item called 'error to rethow' to store the
  // exception thrown during parsing import-maps, and report that exception
  // while registering an import map. Currently only inline import-maps are
  // supported, therefore parsing and registering import-maps will be executed
  // consecutively. To simplify the implementation, we didn't create the 'error
  // to rethow' item and report the exception immediately(done in ~AutoJSAPI).
  return ImportMap::ParseString(jsapi.cx(), text, aRequest->BaseURL(), warning);
}

void ModuleLoaderBase::RegisterImportMap(UniquePtr<ImportMap> aImportMap,
                                         ScriptLoadRequest* aRequest) {
  LOG(("RegisterImportMap"));

  // Check for aImportMap is done in ScriptLoader.
  MOZ_ASSERT(aImportMap);

  // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
  // The step 1(report the exception if there's an error) is done in
  // ParseImportMap.

  bool multiImportMapsEnabled = ImportMap::IsMultipleImportMapsSupported();
  if (!multiImportMapsEnabled) {
    MOZ_ASSERT(!mImportMap);
    mImportMap = std::move(aImportMap);
  } else {
    ReportWarningHelper warning{mLoader, aRequest};

    // Step 2. Merge existing and new import maps, given global and result's
    //         import map.
    ImportMap::Merge(this, std::move(aImportMap), warning);
    MOZ_ASSERT(mImportMap);
  }

  // If speculative preloading is currently fetching any modules then
  // cancel their requests and remove them from the map.
  //
  // The cancelled requests will still complete later so we have to check this
  // in SetModuleFetchFinishedAndGetWaitingRequests.
  mFetchingModules.RemoveIf([](auto& iter) {
    LoadingRequest* loadingRequest = iter.Data();
    bool isPreload = loadingRequest->mRequest->mLoadContext->IsPreload();
    if (!isPreload) {
      return false;
    }

    loadingRequest->mRequest->Cancel();
    for (const auto& request : loadingRequest->mWaiting) {
      MOZ_DIAGNOSTIC_ASSERT(request->mLoadContext->IsPreload());
      request->Cancel();
    }
    return true;
  });

  // When the pref of multiple import maps is enabled, for preloaded and fetched
  // module scripts, the module graphs have been cleared in
  // ClearPreloadedModuleGraph.
  if (!multiImportMapsEnabled) {
    // If speculative preloading has added modules to the module map, remove
    // them.
    for (const auto& entry : mFetchedModules) {
      ModuleScript* script = entry.GetData();
      if (script) {
        MOZ_DIAGNOSTIC_ASSERT(
            script->ForPreload(),
            "Non-preload module loads should block import maps");
        MOZ_DIAGNOSTIC_ASSERT(!script->HadImportMap(),
                              "Only one import map can be registered");
#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
        if (JSObject* module = script->ModuleRecord()) {
          MOZ_DIAGNOSTIC_ASSERT(!ModuleIsLinked(module));
        }
#endif
        script->Shutdown();
      }
    }
    mFetchedModules.Clear();
  }
}

void ModuleLoaderBase::CopyModulesTo(ModuleLoaderBase* aDest) {
  MOZ_ASSERT(aDest->mFetchingModules.IsEmpty());
  MOZ_ASSERT(aDest->mFetchedModules.IsEmpty());
  MOZ_ASSERT(mFetchingModules.IsEmpty());

  for (const auto& entry : mFetchedModules) {
    RefPtr<ModuleScript> moduleScript = entry.GetData();

    // NOTE: moduleScript can be nullptr for modules that fails to import.
    //       Copy them too, so that we don't import them again.
    aDest->mFetchedModules.InsertOrUpdate(entry, moduleScript);
  }
}

void ModuleLoaderBase::MoveModulesTo(ModuleLoaderBase* aDest) {
  MOZ_ASSERT(mFetchingModules.IsEmpty());
  MOZ_ASSERT(aDest->mFetchingModules.IsEmpty());

  for (const auto& entry : mFetchedModules) {
    RefPtr<ModuleScript> moduleScript = entry.GetData();

#ifdef DEBUG
    if (auto existingEntry = aDest->mFetchedModules.Lookup(entry)) {
      MOZ_ASSERT(moduleScript == existingEntry.Data());
    }
#endif

    aDest->mFetchedModules.InsertOrUpdate(entry, moduleScript);
  }

  mFetchedModules.Clear();
}

#undef LOG
#undef LOG_ENABLED

}  // namespace JS::loader
