Issue #618 - Further align error handling for module scripts with the spec

Ref: BZ 1388728
This commit is contained in:
Moonchild 2020-07-04 16:28:30 +00:00 committed by Roy Tam
commit 743bdc04fb
6 changed files with 280 additions and 89 deletions

View file

@ -418,25 +418,23 @@ void
ScriptLoader::SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest *aRequest,
nsresult aResult)
{
// Update module map with the result of fetching a single module script. The
// module script pointer is nullptr on error.
// 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.
MOZ_ASSERT(!aRequest->IsReadyToRun());
RefPtr<GenericPromise::Private> promise;
MOZ_ALWAYS_TRUE(mFetchingModules.Get(aRequest->mURI, getter_AddRefs(promise)));
mFetchingModules.Remove(aRequest->mURI);
RefPtr<ModuleScript> ms(aRequest->mModuleScript);
MOZ_ASSERT(NS_SUCCEEDED(aResult) == (ms != nullptr));
mFetchedModules.Put(aRequest->mURI, ms);
RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);
mFetchedModules.Put(aRequest->mURI, moduleScript);
if (promise) {
if (ms) {
if (moduleScript) {
promise->Resolve(true, __func__);
} else {
promise->Reject(aResult, __func__);
@ -482,19 +480,29 @@ ScriptLoader::ProcessFetchedModuleSource(ModuleLoadRequest* aRequest)
MOZ_ASSERT(!aRequest->mModuleScript);
nsresult rv = CreateModuleScript(aRequest);
MOZ_ASSERT(NS_FAILED(rv) == !aRequest->mModuleScript);
SetModuleFetchFinishedAndResumeWaitingRequests(aRequest, rv);
free(aRequest->mScriptTextBuf);
aRequest->mScriptTextBuf = nullptr;
aRequest->mScriptTextLength = 0;
if (NS_SUCCEEDED(rv)) {
if (NS_FAILED(rv)) {
aRequest->LoadFailed();
return rv;
}
if (!aRequest->mModuleScript->IsErrored()) {
StartFetchingModuleDependencies(aRequest);
}
return rv;
return NS_OK;
}
static nsresult
ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray<nsIURI>& aUrls);
nsresult
ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest)
{
@ -548,9 +556,34 @@ ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest)
}
}
MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
if (module) {
aRequest->mModuleScript =
new ModuleScript(this, aRequest->mBaseURL, module);
RefPtr<ModuleScript> moduleScript = new ModuleScript(this, aRequest->mBaseURL);
aRequest->mModuleScript = moduleScript;
if (!module) {
// Compilation failed
MOZ_ASSERT(aes.HasException());
JS::Rooted<JS::Value> error(cx);
if (!aes.StealException(&error)) {
aRequest->mModuleScript = nullptr;
return NS_ERROR_FAILURE;
}
moduleScript->SetPreInstantiationError(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.
nsCOMArray<nsIURI> urls;
rv = ResolveRequestedModules(aRequest, urls);
if (NS_FAILED(rv)) {
// ResolveRequestedModules sets pre-instanitation error on failure.
MOZ_ASSERT(moduleScript->IsErrored());
aRequest->ModuleErrored();
return NS_OK;
}
}
@ -560,8 +593,8 @@ ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest)
}
static bool
ThrowTypeError(JSContext* aCx, ModuleScript* aScript,
const nsString& aMessage)
CreateTypeError(JSContext* aCx, ModuleScript* aScript, const nsString& aMessage,
JS::MutableHandle<JS::Value> aError)
{
JS::Rooted<JSObject*> module(aCx, aScript->ModuleRecord());
JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(aCx, module));
@ -576,17 +609,11 @@ ThrowTypeError(JSContext* aCx, ModuleScript* aScript,
return false;
}
JS::Rooted<JS::Value> error(aCx);
if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, 0, 0, nullptr,
message, &error)) {
return false;
}
JS_SetPendingException(aCx, error);
return false;
return JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, 0, 0, nullptr,
message, aError);
}
static bool
static nsresult
HandleResolveFailure(JSContext* aCx, ModuleScript* aScript,
const nsAString& aSpecifier)
{
@ -595,19 +622,13 @@ HandleResolveFailure(JSContext* aCx, ModuleScript* aScript,
nsAutoString message(NS_LITERAL_STRING("Error resolving module specifier: "));
message.Append(aSpecifier);
return ThrowTypeError(aCx, aScript, message);
}
JS::Rooted<JS::Value> error(aCx);
if (!CreateTypeError(aCx, aScript, message, &error)) {
return NS_ERROR_OUT_OF_MEMORY;
}
static bool
HandleModuleNotFound(JSContext* aCx, ModuleScript* aScript,
const nsAString& aSpecifier)
{
// TODO: How can we get the line number of the failed import?
nsAutoString message(NS_LITERAL_STRING("Resolved module not found in map: "));
message.Append(aSpecifier);
return ThrowTypeError(aCx, aScript, message);
aScript->SetPreInstantiationError(error);
return NS_OK;
}
static already_AddRefed<nsIURI>
@ -705,7 +726,8 @@ ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray<nsIURI> &aUrls)
ModuleScript* ms = aRequest->mModuleScript;
nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(ms, specifier);
if (!uri) {
HandleResolveFailure(cx, ms, specifier);
nsresult rv = HandleResolveFailure(cx, ms, specifier);
NS_ENSURE_SUCCESS(rv, rv);
return NS_ERROR_FAILURE;
}
@ -724,6 +746,7 @@ void
ScriptLoader::StartFetchingModuleDependencies(ModuleLoadRequest* aRequest)
{
MOZ_ASSERT(aRequest->mModuleScript);
MOZ_ASSERT(!aRequest->mModuleScript->IsErrored());
MOZ_ASSERT(!aRequest->IsReadyToRun());
aRequest->mProgress = ModuleLoadRequest::Progress::FetchingImports;
@ -731,7 +754,7 @@ ScriptLoader::StartFetchingModuleDependencies(ModuleLoadRequest* aRequest)
nsCOMArray<nsIURI> urls;
nsresult rv = ResolveRequestedModules(aRequest, urls);
if (NS_FAILED(rv)) {
aRequest->LoadFailed();
aRequest->ModuleErrored();
return;
}
@ -755,7 +778,7 @@ ScriptLoader::StartFetchingModuleDependencies(ModuleLoadRequest* aRequest)
GenericPromise::All(AbstractThread::GetCurrent(), importsReady);
allReady->Then(AbstractThread::GetCurrent(), __func__, aRequest,
&ModuleLoadRequest::DependenciesLoaded,
&ModuleLoadRequest::LoadFailed);
&ModuleLoadRequest::ModuleErrored);
}
RefPtr<GenericPromise>
@ -778,6 +801,7 @@ ScriptLoader::StartFetchingModuleAndDependencies(ModuleLoadRequest* aRequest,
nsresult rv = StartLoad(childRequest, NS_LITERAL_STRING("module"), false);
if (NS_FAILED(rv)) {
MOZ_ASSERT(!childRequest->mModuleScript);
childRequest->mReady.Reject(rv, __func__);
return ready;
}
@ -786,6 +810,7 @@ ScriptLoader::StartFetchingModuleAndDependencies(ModuleLoadRequest* aRequest,
return ready;
}
// 8.1.3.8.1 HostResolveImportedModule(referencingModule, specifier)
bool
HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp)
{
@ -800,25 +825,25 @@ HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp)
MOZ_ASSERT(script->ModuleRecord() == module);
// Let url be the result of resolving a module specifier given referencing
// module script and specifier. If the result is failure, throw a TypeError
// exception and abort these steps.
// module script and specifier.
nsAutoJSString string;
if (!string.init(aCx, specifier)) {
return false;
}
nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(script, string);
if (!uri) {
return HandleResolveFailure(aCx, script, string);
}
// Let resolved module script be the value of the entry in module map whose
// key is url. If no such entry exists, throw a TypeError exception and abort
// these steps.
// This cannot fail because resolving a module specifier must have been
// previously successful with these same two arguments.
MOZ_ASSERT(uri, "Failed to resolve previously-resolved module specifier");
// Let resolved module script be moduleMap[url]. (This entry must exist for us
// to have gotten to this point.)
ModuleScript* ms = script->Loader()->GetFetchedModule(uri);
if (!ms) {
return HandleModuleNotFound(aCx, script, string);
}
MOZ_ASSERT(ms, "Resolved module not found in module map");
MOZ_ASSERT(!ms->IsErrored());
*vp = JS::ObjectValue(*ms->ModuleRecord());
return true;
@ -842,10 +867,36 @@ EnsureModuleResolveHook(JSContext* aCx)
return NS_OK;
}
void
ScriptLoader::CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest)
{
RefPtr<ModuleScript> moduleScript = aRequest->mModuleScript;
if (moduleScript && !moduleScript->IsErrored()) {
for (auto childRequest : aRequest->mImports) {
ModuleScript* childScript = childRequest->mModuleScript;
if (!childScript) {
// Load error
aRequest->mModuleScript = nullptr;
return;
} else if (childScript->IsErrored()) {
// Script error
moduleScript->SetPreInstantiationError(childScript->Error());
return;
}
}
}
}
void
ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest)
{
if (aRequest->IsTopLevel()) {
ModuleScript* moduleScript = aRequest->mModuleScript;
if (moduleScript && !moduleScript->IsErrored()) {
if (!InstantiateModuleTree(aRequest)) {
aRequest->mModuleScript = nullptr;
}
}
MaybeMoveToLoadedList(aRequest);
ProcessPendingRequests();
}
@ -855,6 +906,43 @@ ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest)
}
}
bool
ScriptLoader::InstantiateModuleTree(ModuleLoadRequest* aRequest)
{
// Instantiate a top-level module and record any error.
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aRequest->IsTopLevel());
ModuleScript* moduleScript = aRequest->mModuleScript;
MOZ_ASSERT(moduleScript);
MOZ_ASSERT(moduleScript->ModuleRecord());
nsAutoMicroTask mt;
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(moduleScript->ModuleRecord()))) {
return false;
}
nsresult rv = EnsureModuleResolveHook(jsapi.cx());
NS_ENSURE_SUCCESS(rv, false);
JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
bool ok = NS_SUCCEEDED(nsJSUtils::ModuleInstantiate(jsapi.cx(), module));
if (!ok) {
MOZ_ASSERT(jsapi.HasException());
JS::RootedValue exception(jsapi.cx());
if (!jsapi.StealException(&exception)) {
return false;
}
MOZ_ASSERT(!exception.isUndefined());
// Ignore the exception. It will be recorded in the module record.
}
return true;
}
nsresult
ScriptLoader::StartLoad(ScriptLoadRequest *aRequest, const nsAString &aType,
bool aScriptFromHead)
@ -1364,13 +1452,19 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
ModuleLoadRequest* modReq = request->AsModuleRequest();
modReq->mBaseURL = mDocument->GetDocBaseURI();
rv = CreateModuleScript(modReq);
NS_ENSURE_SUCCESS(rv, false);
StartFetchingModuleDependencies(modReq);
MOZ_ASSERT(NS_FAILED(rv) == !modReq->mModuleScript);
if (NS_FAILED(rv)) {
modReq->LoadFailed();
return false;
}
if (aElement->GetScriptAsync()) {
mLoadingAsyncRequests.AppendElement(request);
} else {
AddDeferRequest(request);
}
if (!modReq->mModuleScript->IsErrored()) {
StartFetchingModuleDependencies(modReq);
}
return false;
}
request->mProgress = ScriptLoadRequest::Progress::Ready;
@ -1448,11 +1542,7 @@ ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest)
if (aRequest->IsModuleRequest()) {
MOZ_ASSERT(aRequest->mOffThreadToken);
ModuleLoadRequest* request = aRequest->AsModuleRequest();
nsresult rv = ProcessFetchedModuleSource(request);
if (NS_FAILED(rv)) {
request->LoadFailed();
}
return rv;
return ProcessFetchedModuleSource(request);
}
aRequest->SetReady();
@ -1624,7 +1714,7 @@ ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest)
if (aRequest->IsModuleRequest() &&
!aRequest->AsModuleRequest()->mModuleScript)
{
// There was an error parsing a module script. Nothing to do here.
// There was an error fetching a module script. Nothing to do here.
FireScriptAvailable(NS_ERROR_FAILURE, aRequest);
return NS_OK;
}
@ -1846,8 +1936,8 @@ ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest)
// http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block
nsAutoMicroTask mt;
AutoEntryScript aes(globalObject, "<script> element", true);
JS::Rooted<JSObject*> global(aes.cx(),
globalObject->GetGlobalJSObject());
JSContext* cx = aes.cx();
JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
bool oldProcessingScriptTag = context->GetProcessingScriptTag();
context->SetProcessingScriptTag(true);
@ -1868,27 +1958,38 @@ ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest)
}
if (aRequest->IsModuleRequest()) {
rv = EnsureModuleResolveHook(aes.cx());
rv = EnsureModuleResolveHook(cx);
NS_ENSURE_SUCCESS(rv, rv);
ModuleLoadRequest* request = aRequest->AsModuleRequest();
MOZ_ASSERT(request->mModuleScript);
MOZ_ASSERT(!request->mOffThreadToken);
JS::Rooted<JSObject*> module(aes.cx(),
request->mModuleScript->ModuleRecord());
ModuleScript* moduleScript = request->mModuleScript;
if (moduleScript->IsErrored()) {
// Module has an error status
JS::Rooted<JS::Value> error(cx, moduleScript->Error());
JS_SetPendingException(cx, error);
return NS_OK; // An error is reported by AutoEntryScript.
}
JS::Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());
MOZ_ASSERT(module);
rv = nsJSUtils::ModuleInstantiate(aes.cx(), module);
if (NS_SUCCEEDED(rv)) {
rv = nsJSUtils::ModuleEvaluate(aes.cx(), module);
rv = nsJSUtils::ModuleEvaluate(cx, module);
MOZ_ASSERT(NS_FAILED(rv) == aes.HasException());
if (NS_FAILED(rv)) {
// Evaluation failed
rv = NS_OK; // An error is reported by AutoEntryScript.
}
} else {
JS::CompileOptions options(aes.cx());
JS::CompileOptions options(cx);
rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
if (NS_SUCCEEDED(rv)) {
nsAutoString inlineData;
SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData);
rv = nsJSUtils::EvaluateString(aes.cx(), srcBuf, global, options,
rv = nsJSUtils::EvaluateString(cx, srcBuf, global, options,
aRequest->OffThreadTokenPtr());
}
}