Bug 1311726 - Split ScriptLoader into ScriptLoader and ModuleLoader; r=jonco
Differential Revision: https://phabricator.services.mozilla.com/D132683
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#include "ScriptTrace.h"
|
||||
#include "LoadedScript.h"
|
||||
#include "ModuleLoadRequest.h"
|
||||
#include "ModuleLoader.h"
|
||||
|
||||
#include "prsystem.h"
|
||||
#include "jsapi.h"
|
||||
@@ -21,7 +22,7 @@
|
||||
#include "js/MemoryFunctions.h"
|
||||
#include "js/Modules.h" // JS::FinishDynamicModuleImport, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{DynamicImport,Metadata}Hook
|
||||
#include "js/OffThreadScriptCompilation.h"
|
||||
#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetElement
|
||||
#include "js/PropertyAndElement.h" // JS_DefineProperty
|
||||
#include "js/Realm.h"
|
||||
#include "js/SourceText.h"
|
||||
#include "js/Transcoding.h"
|
||||
@@ -95,8 +96,7 @@ using JS::SourceText;
|
||||
|
||||
using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace mozilla::dom {
|
||||
|
||||
LazyLogModule ScriptLoader::gCspPRLog("CSP");
|
||||
LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader");
|
||||
@@ -153,57 +153,6 @@ inline void ImplCycleCollectionTraverse(
|
||||
ImplCycleCollectionTraverse(aCallback, aField.mRequest, aName, aFlags);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// ScriptLoader::mFetchingModules / ScriptLoader::mFetchingModules
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
inline void ImplCycleCollectionUnlink(
|
||||
nsRefPtrHashtable<ModuleMapKey,
|
||||
mozilla::GenericNonExclusivePromise::Private>& aField) {
|
||||
for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) {
|
||||
ImplCycleCollectionUnlink(iter.Key());
|
||||
|
||||
RefPtr<GenericNonExclusivePromise::Private> promise = iter.UserData();
|
||||
if (promise) {
|
||||
promise->Reject(NS_ERROR_ABORT, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
aField.Clear();
|
||||
}
|
||||
|
||||
inline void ImplCycleCollectionTraverse(
|
||||
nsCycleCollectionTraversalCallback& aCallback,
|
||||
nsRefPtrHashtable<ModuleMapKey,
|
||||
mozilla::GenericNonExclusivePromise::Private>& aField,
|
||||
const char* aName, uint32_t aFlags = 0) {
|
||||
for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) {
|
||||
ImplCycleCollectionTraverse(aCallback, iter.Key(), "mFetchingModules key",
|
||||
aFlags);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ImplCycleCollectionUnlink(
|
||||
nsRefPtrHashtable<ModuleMapKey, ModuleScript>& aField) {
|
||||
for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) {
|
||||
ImplCycleCollectionUnlink(iter.Key());
|
||||
}
|
||||
|
||||
aField.Clear();
|
||||
}
|
||||
|
||||
inline void ImplCycleCollectionTraverse(
|
||||
nsCycleCollectionTraversalCallback& aCallback,
|
||||
nsRefPtrHashtable<ModuleMapKey, ModuleScript>& aField, const char* aName,
|
||||
uint32_t aFlags = 0) {
|
||||
for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) {
|
||||
ImplCycleCollectionTraverse(aCallback, iter.Key(), "mFetchedModules key",
|
||||
aFlags);
|
||||
CycleCollectionNoteChild(aCallback, iter.UserData(), "mFetchedModules data",
|
||||
aFlags);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// ScriptLoader
|
||||
//////////////////////////////////////////////////////////////
|
||||
@@ -221,15 +170,6 @@ NS_IMPL_CYCLE_COLLECTION(ScriptLoader, mNonAsyncExternalScriptInsertedRequests,
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoader)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(ModuleLoader, mFetchingModules, mFetchedModules,
|
||||
mDynamicImportRequests, mLoader)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoader)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoader)
|
||||
|
||||
ScriptLoader::ScriptLoader(Document* aDocument)
|
||||
: mDocument(aDocument),
|
||||
mParserBlockingBlockerCount(0),
|
||||
@@ -501,468 +441,11 @@ bool ScriptLoader::IsAboutPageLoadingChromeURI(ScriptLoadRequest* aRequest,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModuleLoader::ModuleMapContainsURL(nsIURI* aURL,
|
||||
nsIGlobalObject* aGlobal) const {
|
||||
// Returns whether we have fetched, or are currently fetching, a module script
|
||||
// for a URL.
|
||||
ModuleMapKey key(aURL, aGlobal);
|
||||
return mFetchingModules.Contains(key) || mFetchedModules.Contains(key);
|
||||
}
|
||||
|
||||
void ModuleLoader::SetModuleFetchStarted(ModuleLoadRequest* aRequest) {
|
||||
// Update the module map to indicate that a module is currently being fetched.
|
||||
|
||||
MOZ_ASSERT(aRequest->IsLoading());
|
||||
MOZ_ASSERT(
|
||||
!ModuleMapContainsURL(aRequest->mURI, aRequest->GetWebExtGlobal()));
|
||||
ModuleMapKey key(aRequest->mURI, aRequest->GetWebExtGlobal());
|
||||
mFetchingModules.InsertOrUpdate(
|
||||
key, RefPtr<GenericNonExclusivePromise::Private>{});
|
||||
}
|
||||
|
||||
void ModuleLoader::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, they
|
||||
// will have ModuleLoaded or LoadFailed on them when the promise is
|
||||
// resolved/rejected. This is set up in StartLoad.
|
||||
|
||||
LOG(
|
||||
("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == "
|
||||
"%u)",
|
||||
aRequest, aRequest->mModuleScript.get(), unsigned(aResult)));
|
||||
|
||||
ModuleMapKey key(aRequest->mURI, aRequest->GetWebExtGlobal());
|
||||
RefPtr<GenericNonExclusivePromise::Private> promise;
|
||||
if (!mFetchingModules.Remove(key, getter_AddRefs(promise))) {
|
||||
LOG(("ScriptLoadRequest (%p): Key not found in mFetchingModules",
|
||||
aRequest));
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
|
||||
MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);
|
||||
|
||||
mFetchedModules.InsertOrUpdate(key, RefPtr{moduleScript});
|
||||
|
||||
if (promise) {
|
||||
if (moduleScript) {
|
||||
LOG(("ScriptLoadRequest (%p): resolving %p", aRequest, promise.get()));
|
||||
promise->Resolve(true, __func__);
|
||||
} else {
|
||||
LOG(("ScriptLoadRequest (%p): rejecting %p", aRequest, promise.get()));
|
||||
promise->Reject(aResult, __func__);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<GenericNonExclusivePromise> ModuleLoader::WaitForModuleFetch(
|
||||
nsIURI* aURL, nsIGlobalObject* aGlobal) {
|
||||
MOZ_ASSERT(ModuleMapContainsURL(aURL, aGlobal));
|
||||
|
||||
ModuleMapKey key(aURL, aGlobal);
|
||||
if (auto entry = mFetchingModules.Lookup(key)) {
|
||||
if (!entry.Data()) {
|
||||
entry.Data() = new GenericNonExclusivePromise::Private(__func__);
|
||||
}
|
||||
return entry.Data();
|
||||
}
|
||||
|
||||
RefPtr<ModuleScript> ms;
|
||||
MOZ_ALWAYS_TRUE(mFetchedModules.Get(key, getter_AddRefs(ms)));
|
||||
if (!ms) {
|
||||
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
|
||||
__func__);
|
||||
}
|
||||
|
||||
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
|
||||
}
|
||||
|
||||
ModuleScript* ModuleLoader::GetFetchedModule(nsIURI* aURL,
|
||||
nsIGlobalObject* aGlobal) const {
|
||||
if (LOG_ENABLED()) {
|
||||
nsAutoCString url;
|
||||
aURL->GetAsciiSpec(url);
|
||||
LOG(("GetFetchedModule %s %p", url.get(), aGlobal));
|
||||
}
|
||||
|
||||
bool found;
|
||||
ModuleMapKey key(aURL, aGlobal);
|
||||
ModuleScript* ms = mFetchedModules.GetWeak(key, &found);
|
||||
MOZ_ASSERT(found);
|
||||
return ms;
|
||||
}
|
||||
|
||||
nsresult ModuleLoader::ProcessFetchedModuleSource(ModuleLoadRequest* aRequest) {
|
||||
MOZ_ASSERT(!aRequest->mModuleScript);
|
||||
|
||||
nsresult rv = CreateModuleScript(aRequest);
|
||||
MOZ_ASSERT(NS_FAILED(rv) == !aRequest->mModuleScript);
|
||||
|
||||
aRequest->ClearScriptSource();
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
aRequest->LoadFailed();
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (!aRequest->mIsInline) {
|
||||
SetModuleFetchFinishedAndResumeWaitingRequests(aRequest, rv);
|
||||
}
|
||||
|
||||
if (!aRequest->mModuleScript->HasParseError()) {
|
||||
StartFetchingModuleDependencies(aRequest);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult ModuleLoader::CreateModuleScript(ModuleLoadRequest* aRequest) {
|
||||
MOZ_ASSERT(!aRequest->mModuleScript);
|
||||
MOZ_ASSERT(aRequest->mBaseURL);
|
||||
|
||||
LOG(("ScriptLoadRequest (%p): Create module script", aRequest));
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> globalObject =
|
||||
mLoader->GetGlobalForRequest(aRequest);
|
||||
if (!globalObject) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsAutoMicroTask mt;
|
||||
|
||||
AutoAllowLegacyScriptExecution exemption;
|
||||
|
||||
AutoEntryScript aes(globalObject, "CompileModule", true);
|
||||
|
||||
nsresult rv;
|
||||
{
|
||||
JSContext* cx = aes.cx();
|
||||
JS::Rooted<JSObject*> module(cx);
|
||||
JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
|
||||
|
||||
JS::CompileOptions options(cx);
|
||||
JS::RootedScript introductionScript(cx);
|
||||
rv = mLoader->FillCompileOptionsForRequest(cx, aRequest, global, &options,
|
||||
&introductionScript);
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
if (aRequest->mWasCompiledOMT) {
|
||||
module = JS::FinishOffThreadModule(cx, aRequest->mOffThreadToken);
|
||||
aRequest->mOffThreadToken = nullptr;
|
||||
rv = module ? NS_OK : NS_ERROR_FAILURE;
|
||||
} else {
|
||||
MaybeSourceText maybeSource;
|
||||
rv = aRequest->GetScriptSource(cx, &maybeSource);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = maybeSource.constructed<SourceText<char16_t>>()
|
||||
? nsJSUtils::CompileModule(
|
||||
cx, maybeSource.ref<SourceText<char16_t>>(), global,
|
||||
options, &module)
|
||||
: nsJSUtils::CompileModule(
|
||||
cx, maybeSource.ref<SourceText<Utf8Unit>>(), global,
|
||||
options, &module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
|
||||
|
||||
if (module) {
|
||||
JS::RootedValue privateValue(cx);
|
||||
JS::RootedScript moduleScript(cx, JS::GetModuleScript(module));
|
||||
JS::InstantiateOptions instantiateOptions(options);
|
||||
if (!JS::UpdateDebugMetadata(cx, moduleScript, instantiateOptions,
|
||||
privateValue, nullptr, introductionScript,
|
||||
nullptr)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<ModuleScript> moduleScript =
|
||||
new ModuleScript(aRequest->mFetchOptions, aRequest->mBaseURL);
|
||||
aRequest->mModuleScript = moduleScript;
|
||||
|
||||
if (!module) {
|
||||
LOG(("ScriptLoadRequest (%p): compilation failed (%d)", aRequest,
|
||||
unsigned(rv)));
|
||||
|
||||
MOZ_ASSERT(aes.HasException());
|
||||
JS::Rooted<JS::Value> error(cx);
|
||||
if (!aes.StealException(&error)) {
|
||||
aRequest->mModuleScript = nullptr;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
moduleScript->SetParseError(error);
|
||||
aRequest->ModuleErrored();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
moduleScript->SetModuleRecord(module);
|
||||
|
||||
// Validate requested modules and treat failure to resolve module specifiers
|
||||
// the same as a parse error.
|
||||
rv = ModuleLoader::ResolveRequestedModules(aRequest, nullptr);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRequest->ModuleErrored();
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
LOG(("ScriptLoadRequest (%p): module script == %p", aRequest,
|
||||
aRequest->mModuleScript.get()));
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult ModuleLoader::HandleResolveFailure(
|
||||
JSContext* aCx, LoadedScript* aScript, const nsAString& aSpecifier,
|
||||
uint32_t aLineNumber, uint32_t aColumnNumber,
|
||||
JS::MutableHandle<JS::Value> errorOut) {
|
||||
JS::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;
|
||||
}
|
||||
|
||||
AutoTArray<nsString, 1> errorParams;
|
||||
errorParams.AppendElement(aSpecifier);
|
||||
|
||||
nsAutoString errorText;
|
||||
nsresult rv = nsContentUtils::FormatLocalizedString(
|
||||
nsContentUtils::eDOM_PROPERTIES, "ModuleResolveFailure", errorParams,
|
||||
errorText);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
JS::Rooted<JSString*> string(aCx, JS_NewUCStringCopyZ(aCx, errorText.get()));
|
||||
if (!string) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber,
|
||||
aColumnNumber, nullptr, string, errorOut)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsIURI* ScriptLoader::GetBaseURI() const {
|
||||
MOZ_ASSERT(mDocument);
|
||||
return mDocument->GetDocBaseURI();
|
||||
}
|
||||
|
||||
already_AddRefed<nsIURI> ModuleLoader::ResolveModuleSpecifier(
|
||||
ScriptLoaderInterface* aLoader, LoadedScript* aScript,
|
||||
const nsAString& aSpecifier) {
|
||||
// The following module specifiers are allowed by the spec:
|
||||
// - a valid absolute URL
|
||||
// - a valid relative URL that starts with "/", "./" or "../"
|
||||
//
|
||||
// Bareword module specifiers are currently disallowed as these may be given
|
||||
// special meanings in the future.
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
return uri.forget();
|
||||
}
|
||||
|
||||
if (rv != NS_ERROR_MALFORMED_URI) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!StringBeginsWith(aSpecifier, u"/"_ns) &&
|
||||
!StringBeginsWith(aSpecifier, u"./"_ns) &&
|
||||
!StringBeginsWith(aSpecifier, u"../"_ns)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the document's base URL if we don't have a referencing script here.
|
||||
nsCOMPtr<nsIURI> baseURL;
|
||||
if (aScript) {
|
||||
baseURL = aScript->BaseURL();
|
||||
} else {
|
||||
baseURL = aLoader->GetBaseURI();
|
||||
}
|
||||
|
||||
rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, baseURL);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
return uri.forget();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsresult ModuleLoader::ResolveRequestedModules(ModuleLoadRequest* aRequest,
|
||||
nsCOMArray<nsIURI>* aUrlsOut) {
|
||||
ModuleScript* ms = aRequest->mModuleScript;
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (!jsapi.Init(ms->ModuleRecord())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::Rooted<JSObject*> moduleRecord(cx, ms->ModuleRecord());
|
||||
JS::Rooted<JSObject*> requestedModules(cx);
|
||||
requestedModules = JS::GetRequestedModules(cx, moduleRecord);
|
||||
MOZ_ASSERT(requestedModules);
|
||||
|
||||
uint32_t length;
|
||||
if (!JS::GetArrayLength(cx, requestedModules, &length)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> element(cx);
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
if (!JS_GetElement(cx, requestedModules, i, &element)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JS::Rooted<JSString*> str(cx, JS::GetRequestedModuleSpecifier(cx, element));
|
||||
MOZ_ASSERT(str);
|
||||
|
||||
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.
|
||||
ModuleLoader* requestModuleLoader = aRequest->mLoader;
|
||||
nsCOMPtr<nsIURI> uri =
|
||||
ResolveModuleSpecifier(requestModuleLoader->mLoader, ms, specifier);
|
||||
if (!uri) {
|
||||
uint32_t lineNumber = 0;
|
||||
uint32_t columnNumber = 0;
|
||||
JS::GetRequestedModuleSourcePos(cx, element, &lineNumber, &columnNumber);
|
||||
|
||||
JS::Rooted<JS::Value> error(cx);
|
||||
nsresult rv = HandleResolveFailure(cx, ms, specifier, lineNumber,
|
||||
columnNumber, &error);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
ms->SetParseError(error);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (aUrlsOut) {
|
||||
aUrlsOut->AppendElement(uri.forget());
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void ModuleLoader::StartFetchingModuleDependencies(
|
||||
ModuleLoadRequest* aRequest) {
|
||||
LOG(("ScriptLoadRequest (%p): Start fetching module dependencies", aRequest));
|
||||
|
||||
if (aRequest->IsCanceled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aRequest->mModuleScript);
|
||||
MOZ_ASSERT(!aRequest->mModuleScript->HasParseError());
|
||||
MOZ_ASSERT(!aRequest->IsReadyToRun());
|
||||
|
||||
auto visitedSet = aRequest->mVisitedSet;
|
||||
MOZ_ASSERT(visitedSet->Contains(aRequest->mURI));
|
||||
|
||||
aRequest->mProgress = ModuleLoadRequest::Progress::eFetchingImports;
|
||||
|
||||
nsCOMArray<nsIURI> urls;
|
||||
nsresult rv = ModuleLoader::ResolveRequestedModules(aRequest, &urls);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRequest->mModuleScript = nullptr;
|
||||
aRequest->ModuleErrored();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove already visited URLs from the list. Put unvisited URLs into the
|
||||
// visited set.
|
||||
int32_t i = 0;
|
||||
while (i < urls.Count()) {
|
||||
nsIURI* url = urls[i];
|
||||
if (visitedSet->Contains(url)) {
|
||||
urls.RemoveObjectAt(i);
|
||||
} else {
|
||||
visitedSet->PutEntry(url);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (urls.Count() == 0) {
|
||||
// There are no descendants to load so this request is ready.
|
||||
aRequest->DependenciesLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
// For each url in urls, fetch a module script tree given url, module script's
|
||||
// CORS setting, and module script's settings object.
|
||||
nsTArray<RefPtr<GenericPromise>> importsReady;
|
||||
for (auto url : urls) {
|
||||
RefPtr<GenericPromise> childReady =
|
||||
StartFetchingModuleAndDependencies(aRequest, url);
|
||||
importsReady.AppendElement(childReady);
|
||||
}
|
||||
|
||||
// Wait for all imports to become ready.
|
||||
RefPtr<GenericPromise::AllPromiseType> allReady =
|
||||
GenericPromise::All(GetMainThreadSerialEventTarget(), importsReady);
|
||||
allReady->Then(GetMainThreadSerialEventTarget(), __func__, aRequest,
|
||||
&ModuleLoadRequest::DependenciesLoaded,
|
||||
&ModuleLoadRequest::ModuleErrored);
|
||||
}
|
||||
|
||||
RefPtr<GenericPromise> ModuleLoader::StartFetchingModuleAndDependencies(
|
||||
ModuleLoadRequest* aParent, nsIURI* aURI) {
|
||||
MOZ_ASSERT(aURI);
|
||||
|
||||
RefPtr<ModuleLoadRequest> childRequest =
|
||||
ModuleLoadRequest::CreateStaticImport(aURI, aParent);
|
||||
|
||||
aParent->mImports.AppendElement(childRequest);
|
||||
|
||||
if (LOG_ENABLED()) {
|
||||
nsAutoCString url1;
|
||||
aParent->mURI->GetAsciiSpec(url1);
|
||||
|
||||
nsAutoCString url2;
|
||||
aURI->GetAsciiSpec(url2);
|
||||
|
||||
LOG(("ScriptLoadRequest (%p): Start fetching dependency %p", aParent,
|
||||
childRequest.get()));
|
||||
LOG(("StartFetchingModuleAndDependencies \"%s\" -> \"%s\"", url1.get(),
|
||||
url2.get()));
|
||||
}
|
||||
|
||||
RefPtr<GenericPromise> ready = childRequest->mReady.Ensure(__func__);
|
||||
|
||||
nsresult rv = mLoader->StartModuleLoad(childRequest);
|
||||
if (NS_FAILED(rv)) {
|
||||
MOZ_ASSERT(!childRequest->mModuleScript);
|
||||
LOG(("ScriptLoadRequest (%p): rejecting %p", aParent,
|
||||
&childRequest->mReady));
|
||||
|
||||
mLoader->ReportErrorToConsole(childRequest, rv);
|
||||
childRequest->mReady.Reject(rv, __func__);
|
||||
return ready;
|
||||
}
|
||||
|
||||
return ready;
|
||||
}
|
||||
|
||||
static ScriptLoader* GetCurrentScriptLoader(JSContext* aCx) {
|
||||
auto reportError = mozilla::MakeScopeExit([aCx]() {
|
||||
JS_ReportErrorASCII(aCx, "No ScriptLoader found for the current context");
|
||||
@@ -1009,7 +492,7 @@ static LoadedScript* GetLoadedScriptOrNull(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto script = static_cast<LoadedScript*>(aReferencingPrivate.toPrivate());
|
||||
auto* script = static_cast<LoadedScript*>(aReferencingPrivate.toPrivate());
|
||||
if (script->IsEventScript()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1204,66 +687,6 @@ bool HostImportModuleDynamically(JSContext* aCx,
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModuleLoader::StartDynamicImport(ModuleLoadRequest* aRequest) {
|
||||
LOG(("ScriptLoadRequest (%p): Start dynamic import", aRequest));
|
||||
|
||||
mDynamicImportRequests.AppendElement(aRequest);
|
||||
|
||||
nsresult rv = mLoader->StartModuleLoad(aRequest);
|
||||
if (NS_FAILED(rv)) {
|
||||
mLoader->ReportErrorToConsole(aRequest, rv);
|
||||
FinishDynamicImportAndReject(aRequest, rv);
|
||||
}
|
||||
}
|
||||
|
||||
void ModuleLoader::FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
|
||||
nsresult aResult) {
|
||||
AutoJSAPI jsapi;
|
||||
MOZ_ASSERT(NS_FAILED(aResult));
|
||||
MOZ_ALWAYS_TRUE(jsapi.Init(aRequest->mDynamicPromise));
|
||||
FinishDynamicImport(jsapi.cx(), aRequest, aResult, nullptr);
|
||||
}
|
||||
|
||||
void ModuleLoader::FinishDynamicImport(
|
||||
JSContext* aCx, ModuleLoadRequest* aRequest, nsresult aResult,
|
||||
JS::Handle<JSObject*> aEvaluationPromise) {
|
||||
// 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);
|
||||
LOG(("ScriptLoadRequest (%p): Finish dynamic import %x %d", aRequest,
|
||||
unsigned(aResult), JS_IsExceptionPending(aCx)));
|
||||
|
||||
// Complete the dynamic import, report failures indicated by aResult or as a
|
||||
// pending exception on the context.
|
||||
|
||||
if (NS_FAILED(aResult) &&
|
||||
aResult != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
|
||||
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
|
||||
JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
|
||||
JSMSG_DYNAMIC_IMPORT_FAILED);
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> referencingScript(aCx,
|
||||
aRequest->mDynamicReferencingPrivate);
|
||||
JS::Rooted<JSString*> specifier(aCx, aRequest->mDynamicSpecifier);
|
||||
JS::Rooted<JSObject*> promise(aCx, aRequest->mDynamicPromise);
|
||||
|
||||
JS::Rooted<JSObject*> moduleRequest(aCx,
|
||||
JS::CreateModuleRequest(aCx, specifier));
|
||||
if (!moduleRequest) {
|
||||
JS_ReportOutOfMemory(aCx);
|
||||
}
|
||||
|
||||
JS::FinishDynamicModuleImport(aCx, aEvaluationPromise, referencingScript,
|
||||
moduleRequest, promise);
|
||||
|
||||
// FinishDynamicModuleImport clears any pending exception.
|
||||
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
|
||||
|
||||
aRequest->ClearDynamicImport();
|
||||
}
|
||||
|
||||
void DynamicImportPrefChangedCallback(const char* aPrefName, void* aClosure) {
|
||||
bool enabled = Preferences::GetBool(aPrefName);
|
||||
JS::ModuleDynamicImportHook hook =
|
||||
@@ -1294,16 +717,6 @@ void ScriptLoader::EnsureModuleHooksInitialized() {
|
||||
(void*)nullptr);
|
||||
}
|
||||
|
||||
ModuleLoader::ModuleLoader(ScriptLoaderInterface* aLoader) : mLoader(aLoader) {
|
||||
mLoader->EnsureModuleHooksInitialized();
|
||||
}
|
||||
|
||||
ModuleLoader::~ModuleLoader() {
|
||||
mDynamicImportRequests.CancelRequestsAndClear();
|
||||
|
||||
LOG(("ModuleLoader::~ModuleLoader %p", this));
|
||||
}
|
||||
|
||||
class ScriptRequestProcessor : public Runnable {
|
||||
private:
|
||||
RefPtr<ScriptLoader> mLoader;
|
||||
@@ -1327,22 +740,10 @@ class ScriptRequestProcessor : public Runnable {
|
||||
};
|
||||
|
||||
void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) {
|
||||
auto runnable = new ScriptRequestProcessor(this, aRequest);
|
||||
auto* runnable = new ScriptRequestProcessor(this, aRequest);
|
||||
nsContentUtils::AddScriptRunner(runnable);
|
||||
}
|
||||
|
||||
void ModuleLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest) {
|
||||
MOZ_ASSERT(aRequest->IsReadyToRun());
|
||||
|
||||
if (aRequest->IsDynamicImport()) {
|
||||
MOZ_ASSERT(aRequest->isInList());
|
||||
RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
|
||||
mLoader->RunScriptWhenSafe(aRequest);
|
||||
} else {
|
||||
mLoader->ProcessLoadedModuleTree(aRequest);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest) {
|
||||
MOZ_ASSERT(aRequest->IsReadyToRun());
|
||||
|
||||
@@ -1360,99 +761,6 @@ void ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest) {
|
||||
aRequest->MaybeUnblockOnload();
|
||||
}
|
||||
|
||||
JS::Value ModuleLoader::FindFirstParseError(ModuleLoadRequest* aRequest) {
|
||||
MOZ_ASSERT(aRequest);
|
||||
|
||||
ModuleScript* moduleScript = aRequest->mModuleScript;
|
||||
MOZ_ASSERT(moduleScript);
|
||||
|
||||
if (moduleScript->HasParseError()) {
|
||||
return moduleScript->ParseError();
|
||||
}
|
||||
|
||||
for (ModuleLoadRequest* childRequest : aRequest->mImports) {
|
||||
JS::Value error = FindFirstParseError(childRequest);
|
||||
if (!error.isUndefined()) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
bool ModuleLoader::InstantiateModuleTree(ModuleLoadRequest* aRequest) {
|
||||
// Instantiate a top-level module and record any error.
|
||||
|
||||
MOZ_ASSERT(aRequest);
|
||||
MOZ_ASSERT(aRequest->IsTopLevel());
|
||||
|
||||
LOG(("ScriptLoadRequest (%p): Instantiate module tree", aRequest));
|
||||
|
||||
ModuleScript* moduleScript = aRequest->mModuleScript;
|
||||
MOZ_ASSERT(moduleScript);
|
||||
|
||||
JS::Value parseError = FindFirstParseError(aRequest);
|
||||
if (!parseError.isUndefined()) {
|
||||
moduleScript->SetErrorToRethrow(parseError);
|
||||
LOG(("ScriptLoadRequest (%p): found parse error", aRequest));
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(moduleScript->ModuleRecord());
|
||||
|
||||
nsAutoMicroTask mt;
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(moduleScript->ModuleRecord()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
|
||||
bool ok = NS_SUCCEEDED(nsJSUtils::ModuleInstantiate(jsapi.cx(), module));
|
||||
|
||||
if (!ok) {
|
||||
LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest));
|
||||
MOZ_ASSERT(jsapi.HasException());
|
||||
JS::RootedValue exception(jsapi.cx());
|
||||
if (!jsapi.StealException(&exception)) {
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(!exception.isUndefined());
|
||||
moduleScript->SetErrorToRethrow(exception);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult ModuleLoader::InitDebuggerDataForModuleTree(
|
||||
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;
|
||||
}
|
||||
|
||||
for (ModuleLoadRequest* childRequest : aRequest->mImports) {
|
||||
nsresult rv = InitDebuggerDataForModuleTree(aCx, childRequest);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
|
||||
MOZ_ASSERT(module);
|
||||
|
||||
// The script is now ready to be exposed to the debugger.
|
||||
JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(module));
|
||||
JS::ExposeScriptToDebugger(aCx, script);
|
||||
|
||||
moduleScript->SetDebuggerDataInitialized();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) {
|
||||
MOZ_ASSERT(aRequest->IsBytecode());
|
||||
aRequest->mScriptBytecode.clearAndFree();
|
||||
@@ -1898,7 +1206,7 @@ bool ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement) {
|
||||
|
||||
bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
|
||||
ScriptKind aScriptKind,
|
||||
nsAutoString aTypeAttr,
|
||||
const nsAutoString& aTypeAttr,
|
||||
nsIContent* aScriptContent) {
|
||||
LOG(("ScriptLoader (%p): Process external script for element %p", this,
|
||||
aElement));
|
||||
@@ -2763,23 +2071,6 @@ nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
void ModuleLoader::ProcessDynamicImport(ModuleLoadRequest* aRequest) {
|
||||
if (aRequest->mModuleScript) {
|
||||
if (!InstantiateModuleTree(aRequest)) {
|
||||
aRequest->mModuleScript = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult rv = NS_ERROR_FAILURE;
|
||||
if (aRequest->mModuleScript) {
|
||||
rv = EvaluateModule(aRequest);
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
FinishDynamicImportAndReject(aRequest, rv);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptLoader::FireScriptAvailable(nsresult aResult,
|
||||
ScriptLoadRequest* aRequest) {
|
||||
for (int32_t i = 0; i < mObservers.Count(); i++) {
|
||||
@@ -3066,93 +2357,6 @@ nsresult ScriptLoader::EvaluateScriptElement(ScriptLoadRequest* aRequest) {
|
||||
return EvaluateScript(globalObject, aRequest);
|
||||
}
|
||||
|
||||
nsresult ModuleLoader::EvaluateModule(nsIGlobalObject* aGlobalObject,
|
||||
ScriptLoadRequest* aRequest) {
|
||||
nsAutoMicroTask mt;
|
||||
AutoEntryScript aes(aGlobalObject, "EvaluateModule", true);
|
||||
JSContext* cx = aes.cx();
|
||||
|
||||
nsAutoCString profilerLabelString;
|
||||
aRequest->GetProfilerLabel(profilerLabelString);
|
||||
|
||||
LOG(("ScriptLoadRequest (%p): Evaluate Module", aRequest));
|
||||
AUTO_PROFILER_MARKER_TEXT("ModuleEvaluation", JS,
|
||||
MarkerInnerWindowIdFromJSContext(cx),
|
||||
profilerLabelString);
|
||||
|
||||
// When a module is already loaded, it is not feched a second time and the
|
||||
// mDataType of the request might remain set to DataType::Unknown.
|
||||
MOZ_ASSERT(aRequest->IsTextSource() || aRequest->IsUnknownDataType());
|
||||
|
||||
ModuleLoadRequest* request = aRequest->AsModuleRequest();
|
||||
MOZ_ASSERT(request->mModuleScript);
|
||||
MOZ_ASSERT(!request->mOffThreadToken);
|
||||
|
||||
ModuleScript* moduleScript = request->mModuleScript;
|
||||
if (moduleScript->HasErrorToRethrow()) {
|
||||
LOG(("ScriptLoadRequest (%p): module has error to rethrow", aRequest));
|
||||
JS::Rooted<JS::Value> error(cx, moduleScript->ErrorToRethrow());
|
||||
JS_SetPendingException(cx, error);
|
||||
// For a dynamic import, the promise is rejected. Otherwise an error
|
||||
// is either reported by AutoEntryScript.
|
||||
if (request->IsDynamicImport()) {
|
||||
FinishDynamicImport(cx, request, NS_OK, nullptr);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());
|
||||
MOZ_ASSERT(module);
|
||||
|
||||
nsresult rv = InitDebuggerDataForModuleTree(cx, request);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
TRACE_FOR_TEST(aRequest->GetScriptElement(), "scriptloader_evaluate_module");
|
||||
|
||||
JS::Rooted<JS::Value> rval(cx);
|
||||
|
||||
rv = nsJSUtils::ModuleEvaluate(cx, module, &rval);
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// If we have an infinite loop in a module, which is stopped by the
|
||||
// user, the module evaluation will fail, but we will not have an
|
||||
// AutoEntryScript exception.
|
||||
MOZ_ASSERT(!aes.HasException());
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest));
|
||||
// For a dynamic import, the promise is rejected. Otherwise an error is
|
||||
// either reported by AutoEntryScript.
|
||||
rv = NS_OK;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> aEvaluationPromise(cx);
|
||||
if (rval.isObject()) {
|
||||
// If the user cancels the evaluation on an infinite loop, we need
|
||||
// to skip this step. In that case, ModuleEvaluate will not return a
|
||||
// promise, rval will be undefined. We should treat it as a failed
|
||||
// evaluation, and reject appropriately.
|
||||
aEvaluationPromise.set(&rval.toObject());
|
||||
}
|
||||
if (request->IsDynamicImport()) {
|
||||
FinishDynamicImport(cx, request, rv, aEvaluationPromise);
|
||||
} 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(cx, aEvaluationPromise)) {
|
||||
LOG(("ScriptLoadRequest (%p): evaluation failed on throw", aRequest));
|
||||
// For a dynamic import, the promise is rejected. Otherwise an
|
||||
// error is either reported by AutoEntryScript.
|
||||
rv = NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
TRACE_FOR_TEST_NONE(aRequest->GetScriptElement(), "scriptloader_no_encode");
|
||||
aRequest->mCacheInfo = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult ScriptLoader::CompileOrDecodeClassicScript(
|
||||
JSContext* aCx, JSExecutionContext& aExec, ScriptLoadRequest* aRequest) {
|
||||
nsAutoCString profilerLabelString;
|
||||
@@ -3292,16 +2496,6 @@ nsresult ScriptLoader::EvaluateScript(nsIGlobalObject* aGlobalObject,
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult ModuleLoader::EvaluateModule(ScriptLoadRequest* aRequest) {
|
||||
nsCOMPtr<nsIGlobalObject> globalObject =
|
||||
mLoader->GetGlobalForRequest(aRequest);
|
||||
if (!globalObject) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return EvaluateModule(globalObject, aRequest);
|
||||
}
|
||||
|
||||
/* static */
|
||||
LoadedScript* ScriptLoader::GetActiveScript(JSContext* aCx) {
|
||||
JS::Value value = JS::GetScriptedCallerPrivate(aCx);
|
||||
@@ -3934,7 +3128,7 @@ void ScriptLoader::ReportPreloadErrorsToConsole(ScriptLoadRequest* aRequest) {
|
||||
}
|
||||
|
||||
if (aRequest->IsModuleRequest()) {
|
||||
for (auto childRequest : aRequest->AsModuleRequest()->mImports) {
|
||||
for (const auto& childRequest : aRequest->AsModuleRequest()->mImports) {
|
||||
ReportPreloadErrorsToConsole(childRequest);
|
||||
}
|
||||
}
|
||||
@@ -3955,7 +3149,7 @@ void ScriptLoader::HandleLoadError(ScriptLoadRequest* aRequest,
|
||||
}
|
||||
|
||||
if (aRequest->IsModuleRequest() && !aRequest->mIsInline) {
|
||||
auto request = aRequest->AsModuleRequest();
|
||||
auto* request = aRequest->AsModuleRequest();
|
||||
mModuleLoader->SetModuleFetchFinishedAndResumeWaitingRequests(request,
|
||||
aResult);
|
||||
}
|
||||
@@ -4316,18 +3510,6 @@ void ScriptLoader::ParsingComplete(bool aTerminated) {
|
||||
DeferCheckpointReached();
|
||||
}
|
||||
|
||||
void ModuleLoader::CancelAndClearDynamicImports() {
|
||||
for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
|
||||
req = req->getNext()) {
|
||||
req->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(req->AsModuleRequest(), NS_ERROR_ABORT);
|
||||
}
|
||||
mDynamicImportRequests.CancelRequestsAndClear();
|
||||
}
|
||||
|
||||
void ScriptLoader::PreloadURI(nsIURI* aURI, const nsAString& aCharset,
|
||||
const nsAString& aType,
|
||||
const nsAString& aCrossOrigin,
|
||||
@@ -4485,5 +3667,4 @@ nsAutoScriptLoaderDisabler::~nsAutoScriptLoaderDisabler() {
|
||||
|
||||
#undef LOG
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
} // namespace mozilla::dom
|
||||
|
||||
Reference in New Issue
Block a user