/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
using mozilla::CycleCollectedJSContext; using mozilla::Err; using mozilla::Preferences; using mozilla::UniquePtr; using mozilla::WrapNotNull; using mozilla::dom::AutoJSAPI;
// 8.1.3.8.1 HostResolveImportedModule(referencingModule, moduleRequest) /** * Implement the HostResolveImportedModule abstract operation. * * Resolve a module specifier string and look this up in the module * map, returning the result. This is only called for previously * loaded modules and always succeeds. * * @param aReferencingPrivate A JS::Value which is either undefined * or contains a LoadedScript private pointer. * @param aModuleRequest A module request object. * @returns module This is set to the module found.
*/ // static
JSObject* ModuleLoaderBase::HostResolveImportedModule(
JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSObject*> aModuleRequest) {
JS::Rooted<JSObject*> module(aCx);
{ // LoadedScript should only live in this block, otherwise it will be a GC // hazard
RefPtr<LoadedScript> script(
GetLoadedScriptOrNull(aCx, aReferencingPrivate));
// Let url be the result of resolving a module specifier given referencing // module script and specifier.
nsAutoJSString string; if (!string.init(aCx, specifierString)) { return nullptr;
}
RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx); if (!loader) { return nullptr;
}
auto result = loader->ResolveModuleSpecifier(script, string); // This cannot fail because resolving a module specifier must have been // previously successful with these same two arguments.
MOZ_ASSERT(result.isOk());
nsCOMPtr<nsIURI> uri = result.unwrap();
MOZ_ASSERT(uri, "Failed to resolve previously-resolved module specifier");
// Let moduleType be the result of running the module type from module // request steps given moduleRequest.
JS::ModuleType moduleType = JS::GetModuleRequestType(aCx, aModuleRequest);
// Let resolved module script be moduleMap[url]. (This entry must exist for // us to have gotten to this point.)
ModuleScript* ms = loader->GetFetchedModule(ModuleMapKey(uri, moduleType));
MOZ_ASSERT(ms, "Resolved module not found in module map");
MOZ_ASSERT(!ms->HasParseError());
MOZ_ASSERT(ms->ModuleRecord());
{ // 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(JS::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()) {
JS::Rooted<JS::Value> error(aCx);
nsresult rv = loader->HandleResolveFailure(
aCx, script, specifier, result.unwrapErr(), 0,
JS::ColumnNumberOneOrigin(), &error); if (NS_FAILED(rv)) {
JS_ReportOutOfMemory(aCx); return nullptr;
}
JS_SetPendingException(aCx, error);
return nullptr;
}
nsCOMPtr<nsIURI> uri = result.unwrap();
nsAutoCString url;
MOZ_ALWAYS_SUCCEEDS(uri->GetAsciiSpec(url));
// 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) { returnfalse;
}
// Store the 'active script' of the meta object into the function slot. // https://html.spec.whatwg.org/#active-script
RootedObject resolveFuncObj(aCx, JS_GetFunctionObject(resolveFunc));
js::SetFunctionNativeReserved(resolveFuncObj, ModulePrivateSlot,
aReferencingPrivate);
// Let moduleType be the result of running the module type from module // request steps given moduleRequest.
JS::ModuleType moduleType = JS::GetModuleRequestType(aCx, aModuleRequest);
// Create a new top-level load request.
nsCOMPtr<nsIURI> uri = result.unwrap();
RefPtr<ModuleLoadRequest> request = loader->CreateDynamicImport(
aCx, uri, moduleType, script, specifierString, aPromise);
if (!request) { // Throws TypeError if CreateDynamicImport returns nullptr.
JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
JSMSG_DYNAMIC_IMPORT_NOT_SUPPORTED);
returnfalse;
}
nsresult rv = loader->StartDynamicImport(request); if (NS_SUCCEEDED(rv)) {
loader->OnDynamicImportStarted(request);
}
// static
ModuleLoaderBase* ModuleLoaderBase::GetCurrentModuleLoader(JSContext* aCx) { auto reportError = mozilla::MakeScopeExit([aCx]() {
JS_ReportErrorASCII(aCx, "No ScriptLoader found for the current context");
});
JS::Rooted<JSObject*> object(aCx, JS::CurrentGlobalOrNull(aCx)); if (!object) { return nullptr;
}
nsIGlobalObject* global = xpc::NativeGlobal(object); if (!global) { return nullptr;
}
ModuleLoaderBase* loader = global->GetModuleLoader(aCx); if (!loader) { return nullptr;
}
// If we're restarting the request, the module should already be in the // "fetching" map.
MOZ_ASSERT_IF(
aRestart == RestartRequest::Yes,
IsModuleFetching(ModuleMapKey(aRequest->mURI, 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.
ModuleLoadRequest* request = aRequest->AsModuleRequest();
if (aRestart == RestartRequest::No &&
ModuleMapContainsURL(
ModuleMapKey(request->mURI, aRequest->mModuleType))) {
LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest));
WaitForModuleFetch(request); 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->AsModuleRequest());
}
void ModuleLoaderBase::SetModuleFetchStarted(ModuleLoadRequest* aRequest) { // Update the module map to indicate that a module is currently being fetched.
void ModuleLoaderBase::SetModuleFetchFinishedAndResumeWaitingRequests(
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.
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;
}
// 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;
}
nsresult rv = aRv; if (NS_SUCCEEDED(rv)) {
rv = CreateModuleScript(aRequest);
#ifdefined(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
aRequest->ClearScriptSource();
if (NS_FAILED(rv)) {
aRequest->LoadFailed(); return rv;
}
}
// Validate requested modules and treat failure to resolve module specifiers // the same as a parse error.
rv = ResolveRequestedModules(aRequest, nullptr); if (NS_FAILED(rv)) { if (!aRequest->IsErrored()) {
aRequest->mModuleScript = nullptr; return rv;
}
aRequest->ModuleErrored(); 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);
}
for (uint32_t i = 0; i < length; i++) {
JS::Rooted<JSString*> str(
cx, JS::GetRequestedModuleSpecifier(cx, moduleRecord, i)); if (!str) {
JS::Rooted<JS::Value> pendingException(cx); if (!JS_GetPendingException(cx, &pendingException)) { return NS_ERROR_FAILURE;
}
ms->SetParseError(pendingException);
JS_ClearPendingException(cx); return NS_ERROR_FAILURE;
}
nsAutoJSString specifier; if (!specifier.init(cx, str)) { return NS_ERROR_FAILURE;
}
// Let url be the result of resolving a module specifier given module script // and requested.
ModuleLoaderBase* loader = aRequest->mLoader; auto result = loader->ResolveModuleSpecifier(ms, specifier); if (result.isErr()) {
uint32_t lineNumber = 0;
JS::ColumnNumberOneOrigin columnNumber;
JS::GetRequestedModuleSourcePos(cx, moduleRecord, i, &lineNumber,
&columnNumber);
nsCOMPtr<nsIURI> uri = result.unwrap(); if (aRequestedModulesOut) { // Let moduleType be the result of running the module type from module // request steps given moduleRequest.
JS::ModuleType moduleType =
JS::GetRequestedModuleType(cx, moduleRecord, i);
// Remove already visited requested modules from the list. Put unvisited // requested modules into the visited set.
size_t i = 0; while (i < requestedModules.Length()) { if (visitedSet->Contains(requestedModules[i])) {
requestedModules.RemoveElementAt(i);
} else {
visitedSet->PutEntry(requestedModules[i]);
i++;
}
}
if (requestedModules.Length() == 0) { // There are no descendants to load so this request is ready.
aRequest->DependenciesLoaded(); return;
}
// For each requested module in `requestedModules`, fetch a module script // graph given url, module script's CORS setting, and module script's // settings object. for (const ModuleMapKey& requestedModule : requestedModules) {
StartFetchingModuleAndDependencies(aRequest, requestedModule);
}
}
MOZ_ASSERT_IF(NS_SUCCEEDED(aResult),
GetCurrentModuleLoader(aCx) == aRequest->mLoader); // For failure case, aRequest may have already been unlinked by CC.
MOZ_ASSERT_IF(
NS_FAILED(aResult),
GetCurrentModuleLoader(aCx) == aRequest->mLoader || !aRequest->mLoader);
// If aResult is a failed result, we don't have an EvaluationPromise. If it // succeeded, evaluationPromise may still be null, but in this case it will // be handled by rejecting the dynamic module import promise in the JSAPI.
MOZ_ASSERT_IF(NS_FAILED(aResult), !aEvaluationPromise);
// The request should been removed from mDynamicImportRequests.
MOZ_ASSERT(!aRequest->mLoader->HasDynamicImport(aRequest));
// Complete the dynamic import, report failures indicated by aResult or as a // pending exception on the context.
if (!aRequest->mDynamicPromise) { // Import has already been completed. return;
}
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 mDynamicPromise has been cleared, then it should be remove from // mDynamicImportRequests as well.
MOZ_ASSERT(aRequest->mDynamicPromise);
aRequest->Cancel(); // FinishDynamicImport must happen exactly once for each dynamic import // request. If the load is aborted we do it when we remove the request // from mDynamicImportRequests.
FinishDynamicImportAndReject(aRequest, aResult);
}
}
JS::Value parseError = FindFirstParseError(aRequest); if (!parseError.isUndefined()) {
moduleScript->SetErrorToRethrow(parseError);
LOG(("ScriptLoadRequest (%p): found parse error", aRequest)); returntrue;
}
MOZ_ASSERT(moduleScript->ModuleRecord());
AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) { returnfalse;
}
JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord()); if (!xpc::Scriptability::AllowedIfExists(module)) { returntrue;
}
if (!JS::ModuleLink(jsapi.cx(), module)) {
LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest));
MOZ_ASSERT(jsapi.HasException());
JS::RootedValue exception(jsapi.cx()); if (!jsapi.StealException(&exception)) { returnfalse;
}
MOZ_ASSERT(!exception.isUndefined());
moduleScript->SetErrorToRethrow(exception);
}
returntrue;
}
nsresult ModuleLoaderBase::InitDebuggerDataForModuleGraph(
JSContext* aCx, ModuleLoadRequest* aRequest) { // JS scripts can be associated with a DOM element for use by the debugger, // but preloading can cause scripts to be compiled before DOM script element // nodes have been created. This method ensures that this association takes // place before the first time a module script is run.
MOZ_ASSERT(aRequest);
ModuleScript* moduleScript = aRequest->mModuleScript; if (moduleScript->DebuggerDataInitialized()) { return NS_OK;
}
// The script is now ready to be exposed to the debugger.
JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(module)); if (script) {
JS::ExposeScriptToDebugger(aCx, script);
}
ModuleScript* moduleScript = request->mModuleScript; if (moduleScript->HasErrorToRethrow()) {
LOG(("ScriptLoadRequest (%p): module has error to rethrow", aRequest));
JS::Rooted<JS::Value> error(aCx, moduleScript->ErrorToRethrow());
JS_SetPendingException(aCx, error); // For a dynamic import, the promise is rejected. Otherwise an error // is either reported by AutoEntryScript. if (request->IsDynamicImport()) {
FinishDynamicImport(aCx, request, NS_OK, nullptr);
} return NS_OK;
}
// ModuleEvaluate will usually set a pending exception if it returns false, // unless the user cancels execution.
MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aCx));
if (!ok || IsModuleEvaluationAborted(request)) {
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.
JS::Rooted<JSObject*> evaluationPromise(aCx); if (rval.isObject()) {
evaluationPromise.set(&rval.toObject());
}
if (request->IsDynamicImport()) { if (NS_FAILED(rv)) {
FinishDynamicImportAndReject(request, rv);
} else {
FinishDynamicImport(aCx, request, NS_OK, evaluationPromise);
}
} else { // If this is not a dynamic import, and if the promise is rejected, // the value is unwrapped from the promise value. if (!JS::ThrowOnModuleEvaluationFailure(aCx, evaluationPromise,
errorBehaviour)) {
LOG(("ScriptLoadRequest (%p): evaluation failed on throw", aRequest)); // For a dynamic import, the promise is rejected. Otherwise an error is // reported by AutoEntryScript.
}
}
void ModuleLoaderBase::CancelAndClearDynamicImports() { while (ScriptLoadRequest* req = mDynamicImportRequests.getFirst()) { // This also removes the request from the list.
CancelDynamicImport(req->AsModuleRequest(), NS_ERROR_ABORT);
}
}
JS::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->mBaseURL, warning);
}
void ModuleLoaderBase::RegisterImportMap(UniquePtr<ImportMap> aImportMap) { // Check for aImportMap is done in ScriptLoader.
MOZ_ASSERT(aImportMap);
// Step 3. Set global's import map to result's import map.
mImportMap = std::move(aImportMap);
// Any import resolution has been invalidated by the addition of the import // map. 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 SetModuleFetchFinishedAndResumeWaitingRequests. for (constauto& entry : mFetchingModules) {
LoadingRequest* loadingRequest = entry.GetData();
MOZ_DIAGNOSTIC_ASSERT(loadingRequest->mRequest->mLoadContext->IsPreload());
loadingRequest->mRequest->Cancel(); for (constauto& request : loadingRequest->mWaiting) {
MOZ_DIAGNOSTIC_ASSERT(request->mLoadContext->IsPreload());
request->Cancel();
}
}
mFetchingModules.Clear();
// If speculative preloading has added modules to the module map, remove // them. for (constauto& 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"); #ifdefined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) if (JSObject* module = script->ModuleRecord()) {
MOZ_DIAGNOSTIC_ASSERT(!JS::ModuleIsLinked(module));
} #endif
script->Shutdown();
}
}
mFetchedModules.Clear();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.