replaced by .editorconfig # ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D287851
1938 lines
63 KiB
C++
1938 lines
63 KiB
C++
/* 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, HostAddRefTopLevelScript,
|
||
HostReleaseTopLevelScript);
|
||
}
|
||
|
||
static bool CreateBadModuleTypeError(JSContext* aCx, LoadedScript* aScript,
|
||
nsIURI* aURI,
|
||
MutableHandle<Value> aErrorOut) {
|
||
Rooted<JSString*> filename(aCx);
|
||
if (aScript) {
|
||
nsAutoCString url;
|
||
aScript->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;
|
||
}
|
||
|
||
{
|
||
// LoadedScript should only live in this block, otherwise it will be a GC
|
||
// hazard
|
||
RefPtr<LoadedScript> script(GetLoadedScriptOrNull(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(script, string);
|
||
|
||
// Step 9. If the previous step threw an exception, then:
|
||
if (result.isErr()) {
|
||
Rooted<Value> error(aCx);
|
||
nsresult rv =
|
||
loader->HandleResolveFailure(aCx, script, 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), script, 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, script, uri, &error)) {
|
||
JS_ReportOutOfMemory(aCx);
|
||
return false;
|
||
}
|
||
JS_SetPendingException(aCx, error);
|
||
return false;
|
||
}
|
||
|
||
RefPtr<ScriptFetchOptions> options = nullptr;
|
||
ReferrerPolicy referrerPolicy;
|
||
nsIURI* fetchReferrer = nullptr;
|
||
if (script) {
|
||
options = script->GetFetchOptions();
|
||
referrerPolicy = script->ReferrerPolicy();
|
||
fetchReferrer = script->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);
|
||
|
||
{
|
||
// ModuleScript should only live in this block, otherwise it will be a GC
|
||
// hazard
|
||
RefPtr<ModuleScript> script =
|
||
static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
|
||
MOZ_ASSERT(script->IsModuleScript());
|
||
MOZ_ASSERT(GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate);
|
||
|
||
RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx);
|
||
if (!loader) {
|
||
return nullptr;
|
||
}
|
||
|
||
nsAutoJSString specifier;
|
||
if (!specifier.init(aCx, aSpecifier)) {
|
||
return nullptr;
|
||
}
|
||
|
||
auto result = loader->ResolveModuleSpecifier(script, specifier);
|
||
if (result.isErr()) {
|
||
Rooted<Value> error(aCx);
|
||
nsresult rv = loader->HandleResolveFailure(
|
||
aCx, script, 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<Value> aReferencingPrivate,
|
||
Handle<JSObject*> aMetaObject) {
|
||
RefPtr<ModuleScript> script =
|
||
static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
|
||
MOZ_ASSERT(script->IsModuleScript());
|
||
MOZ_ASSERT(GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate);
|
||
|
||
nsAutoCString url;
|
||
MOZ_DIAGNOSTIC_ASSERT(script->BaseURL());
|
||
MOZ_ALWAYS_SUCCEEDS(script->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));
|
||
RootedObject moduleRecord(aCx, script->ModuleRecord());
|
||
js::SetFunctionNativeReserved(
|
||
resolveFuncObj, static_cast<size_t>(ImportMetaSlots::ModuleRecordSlot),
|
||
JS::ObjectValue(*moduleRecord));
|
||
|
||
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
|
||
LoadedScript* ModuleLoaderBase::GetLoadedScriptOrNull(
|
||
Handle<JSScript*> aReferrer) {
|
||
if (!aReferrer) {
|
||
return nullptr;
|
||
}
|
||
|
||
Value value = GetScriptPrivate(aReferrer);
|
||
if (value.isUndefined()) {
|
||
return nullptr;
|
||
}
|
||
|
||
return static_cast<LoadedScript*>(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());
|
||
|
||
// NOTE: The LoadedScript::mDataType field used by the IsStencil call can be
|
||
// modified asynchronously after the StartFetch call.
|
||
// In order to avoid the race condition, cache the value here.
|
||
bool isCachedStencil = aRequest->IsCachedStencil();
|
||
|
||
MOZ_ASSERT_IF(isCachedStencil, aRestart == RestartRequest::No);
|
||
|
||
if (!isCachedStencil) {
|
||
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 (isCachedStencil) {
|
||
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) {
|
||
nsIURI* uri = entry.GetData()->BaseURL();
|
||
|
||
nsAutoCString spec;
|
||
nsresult rv = uri->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);
|
||
|
||
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->IsTextSource()) {
|
||
aRequest->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 =
|
||
aRequest->mLoadedScript->AsModuleScript();
|
||
|
||
// Update the module script's referrer policy to reflect any changes made
|
||
// to the ModuleLoadRequest during HTTP response parsing.
|
||
if (moduleScript->ReferrerPolicy() != aRequest->ReferrerPolicy()) {
|
||
moduleScript->UpdateReferrerPolicy(aRequest->ReferrerPolicy());
|
||
}
|
||
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);
|
||
}
|
||
|
||
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, LoadedScript* aScript, const nsAString& aSpecifier,
|
||
ResolveError aError, uint32_t aLineNumber,
|
||
ColumnNumberOneOrigin aColumnNumber, MutableHandle<Value> aErrorOut) {
|
||
Rooted<JSString*> filename(aCx);
|
||
if (aScript) {
|
||
nsAutoCString url;
|
||
aScript->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(
|
||
LoadedScript* aScript, 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, aScript,
|
||
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, LoadedScript* aScript,
|
||
Handle<Value> aHostDefined) {
|
||
// 2. If global does not implement Window, then return.
|
||
if (!mLoader->IsImportMapSupported()) {
|
||
return;
|
||
}
|
||
|
||
bool isPreloadModule = aScript && aScript->IsModuleScript() &&
|
||
aScript->AsModuleScript()->ForPreload();
|
||
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();
|
||
}
|
||
}
|
||
|
||
// We don't clear mFetchingModules here, as the fetching requests might arrive
|
||
// after the global is still shutting down.
|
||
}
|
||
|
||
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);
|
||
|
||
(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;
|
||
}
|
||
}
|
||
|
||
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->IsTextSource());
|
||
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
|