ModuleLoaderBase.cpp (57962B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "GeckoProfiler.h" 8 #include "LoadedScript.h" 9 #include "ModuleLoadRequest.h" 10 #include "ScriptLoadRequest.h" 11 #include "mozilla/dom/ScriptSettings.h" // AutoJSAPI 12 #include "mozilla/dom/ScriptTrace.h" 13 14 #include "js/Array.h" // JS::GetArrayLength 15 #include "js/CompilationAndEvaluation.h" 16 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin 17 #include "js/ContextOptions.h" // JS::ContextOptionsRef 18 #include "js/ErrorReport.h" // JSErrorBase 19 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 20 #include "js/Modules.h" // JS::FinishLoadingImportedModule, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{Load,Metadata}Hook 21 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetElement 22 #include "js/SourceText.h" 23 #include "mozilla/Assertions.h" // MOZ_ASSERT 24 #include "mozilla/BasePrincipal.h" 25 #include "mozilla/dom/AutoEntryScript.h" 26 #include "mozilla/dom/ScriptLoadContext.h" 27 #include "mozilla/CycleCollectedJSContext.h" // nsAutoMicroTask 28 #include "mozilla/Preferences.h" 29 #include "mozilla/RefPtr.h" // mozilla::StaticRefPtr 30 #include "mozilla/StaticPrefs_dom.h" 31 #include "nsContentUtils.h" 32 #include "nsICacheInfoChannel.h" // nsICacheInfoChannel 33 #include "nsNetUtil.h" // NS_NewURI 34 #include "xpcpublic.h" 35 36 using mozilla::AutoSlowOperation; 37 using mozilla::CycleCollectedJSContext; 38 using mozilla::Err; 39 using mozilla::MicroTaskRunnable; 40 using mozilla::Preferences; 41 using mozilla::UniquePtr; 42 using mozilla::WrapNotNull; 43 using mozilla::dom::AutoJSAPI; 44 using mozilla::dom::ReferrerPolicy; 45 46 namespace JS::loader { 47 48 mozilla::LazyLogModule ModuleLoaderBase::gCspPRLog("CSP"); 49 mozilla::LazyLogModule ModuleLoaderBase::gModuleLoaderBaseLog( 50 "ModuleLoaderBase"); 51 52 #undef LOG 53 #define LOG(args) \ 54 MOZ_LOG(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug, \ 55 args) 56 57 #define LOG_ENABLED() \ 58 MOZ_LOG_TEST(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug) 59 60 ////////////////////////////////////////////////////////////// 61 // ModuleLoaderBase::LoadingRequest 62 ////////////////////////////////////////////////////////////// 63 64 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase::LoadingRequest) 65 NS_INTERFACE_MAP_ENTRY(nsISupports) 66 NS_INTERFACE_MAP_END 67 68 NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase::LoadingRequest, mRequest, mWaiting) 69 70 NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase::LoadingRequest) 71 NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase::LoadingRequest) 72 73 ////////////////////////////////////////////////////////////// 74 // ModuleLoaderBase 75 ////////////////////////////////////////////////////////////// 76 77 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase) 78 NS_INTERFACE_MAP_ENTRY(nsISupports) 79 NS_INTERFACE_MAP_END 80 81 NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase, mFetchingModules, mFetchedModules, 82 mDynamicImportRequests, mGlobalObject, mOverriddenBy, 83 mLoader) 84 85 NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase) 86 NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase) 87 88 // static 89 void ModuleLoaderBase::EnsureModuleHooksInitialized() { 90 AutoJSAPI jsapi; 91 jsapi.Init(); 92 JSRuntime* rt = JS_GetRuntime(jsapi.cx()); 93 if (GetModuleLoadHook(rt)) { 94 return; 95 } 96 97 SetModuleLoadHook(rt, HostLoadImportedModule); 98 SetModuleMetadataHook(rt, HostPopulateImportMeta); 99 SetScriptPrivateReferenceHooks(rt, HostAddRefTopLevelScript, 100 HostReleaseTopLevelScript); 101 } 102 103 static bool CreateBadModuleTypeError(JSContext* aCx, LoadedScript* aScript, 104 nsIURI* aURI, 105 MutableHandle<Value> aErrorOut) { 106 Rooted<JSString*> filename(aCx); 107 if (aScript) { 108 nsAutoCString url; 109 aScript->BaseURL()->GetAsciiSpec(url); 110 filename = JS_NewStringCopyZ(aCx, url.get()); 111 } else { 112 filename = JS_NewStringCopyZ(aCx, "(unknown)"); 113 } 114 115 if (!filename) { 116 return false; 117 } 118 119 MOZ_ASSERT(aURI); 120 nsAutoCString url; 121 aURI->GetSpec(url); 122 123 Rooted<JSString*> uri(aCx, JS_NewStringCopyZ(aCx, url.get())); 124 if (!uri) { 125 return false; 126 } 127 128 Rooted<JSString*> msg(aCx, JS_NewStringCopyZ(aCx, ": invalid module type")); 129 if (!msg) { 130 return false; 131 } 132 133 Rooted<JSString*> errMsg(aCx, JS_ConcatStrings(aCx, uri, msg)); 134 if (!errMsg) { 135 return false; 136 } 137 138 return CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, 0, 139 ColumnNumberOneOrigin(), nullptr, errMsg, 140 NothingHandleValue, aErrorOut); 141 } 142 143 // https://html.spec.whatwg.org/#hostloadimportedmodule 144 // static 145 bool ModuleLoaderBase::HostLoadImportedModule( 146 JSContext* aCx, Handle<JSScript*> aReferrer, 147 Handle<JSObject*> aModuleRequest, Handle<Value> aHostDefined, 148 Handle<Value> aPayload, uint32_t aLineNumber, 149 JS::ColumnNumberOneOrigin aColumnNumber) { 150 Rooted<JSObject*> object(aCx); 151 if (aPayload.isObject()) { 152 object = &aPayload.toObject(); 153 } 154 bool isDynamicImport = object && IsPromiseObject(object); 155 156 Rooted<JSString*> specifierString( 157 aCx, GetModuleRequestSpecifier(aCx, aModuleRequest)); 158 if (!specifierString) { 159 JS_ReportOutOfMemory(aCx); 160 return false; 161 } 162 163 // Let url be the result of resolving a module specifier given referencing 164 // module script and specifier. 165 nsAutoJSString string; 166 if (!string.init(aCx, specifierString)) { 167 JS_ReportOutOfMemory(aCx); 168 return false; 169 } 170 171 RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx); 172 if (!loader) { 173 return false; 174 } 175 176 if (isDynamicImport && !loader->IsDynamicImportSupported()) { 177 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, 178 JSMSG_DYNAMIC_IMPORT_NOT_SUPPORTED); 179 return false; 180 } 181 182 { 183 // LoadedScript should only live in this block, otherwise it will be a GC 184 // hazard 185 RefPtr<LoadedScript> script(GetLoadedScriptOrNull(aReferrer)); 186 187 // Step 8. Let url be the result of resolving a module specifier given 188 // referencingScript and moduleRequest.[[Specifier]], catching any 189 // exceptions. If they throw an exception, let resolutionError be the 190 // thrown exception. 191 auto result = loader->ResolveModuleSpecifier(script, string); 192 193 // Step 9. If the previous step threw an exception, then: 194 if (result.isErr()) { 195 Rooted<Value> error(aCx); 196 nsresult rv = 197 loader->HandleResolveFailure(aCx, script, string, result.unwrapErr(), 198 aLineNumber, aColumnNumber, &error); 199 if (NS_FAILED(rv)) { 200 JS_ReportOutOfMemory(aCx); 201 return false; 202 } 203 204 // Step 2. Perform FinishLoadingImportedModule(referrer, moduleRequest, 205 // payload, ThrowCompletion(resolutionError)). 206 FinishLoadingImportedModuleFailed(aCx, aPayload, error); 207 208 // Step 3. Return. 209 return true; 210 } 211 212 MOZ_ASSERT(result.isOk()); 213 nsCOMPtr<nsIURI> uri = result.unwrap(); 214 MOZ_ASSERT(uri, "Failed to resolve module specifier"); 215 216 ModuleType moduleType = GetModuleRequestType(aCx, aModuleRequest); 217 if (!loader->IsModuleTypeAllowed(moduleType)) { 218 LOG(("ModuleLoaderBase::HostLoadImportedModule uri %s, bad module type", 219 uri->GetSpecOrDefault().get())); 220 Rooted<Value> error(aCx); 221 if (!CreateBadModuleTypeError(aCx, script, uri, &error)) { 222 JS_ReportOutOfMemory(aCx); 223 return false; 224 } 225 JS_SetPendingException(aCx, error); 226 return false; 227 } 228 229 RefPtr<ScriptFetchOptions> options = nullptr; 230 ReferrerPolicy referrerPolicy; 231 nsIURI* fetchReferrer = nullptr; 232 if (script) { 233 options = script->GetFetchOptions(); 234 referrerPolicy = script->ReferrerPolicy(); 235 fetchReferrer = script->BaseURL(); 236 } else { 237 options = loader->CreateDefaultScriptFetchOptions(); 238 referrerPolicy = ReferrerPolicy::_empty; 239 fetchReferrer = loader->GetClientReferrerURI(); 240 } 241 242 mozilla::dom::SRIMetadata sriMetadata; 243 loader->GetImportMapSRI( 244 uri, fetchReferrer, 245 loader->GetScriptLoaderInterface()->GetConsoleReportCollector(), 246 &sriMetadata); 247 248 RefPtr<ModuleLoadRequest> request = loader->CreateRequest( 249 aCx, uri, aModuleRequest, aHostDefined, aPayload, isDynamicImport, 250 options, referrerPolicy, fetchReferrer, sriMetadata); 251 if (!request) { 252 MOZ_ASSERT(isDynamicImport); 253 nsAutoCString url; 254 uri->GetSpec(url); 255 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, 256 JSMSG_DYNAMIC_IMPORT_FAILED, url.get()); 257 return false; 258 } 259 260 LOG( 261 ("ModuleLoaderBase::HostLoadImportedModule loader (%p) uri %s referrer " 262 "(%p) request (%p)", 263 loader.get(), uri->GetSpecOrDefault().get(), aReferrer.get(), 264 request.get())); 265 266 request->SetImport(aReferrer, aModuleRequest, aPayload); 267 268 if (isDynamicImport) { 269 loader->AppendDynamicImport(request); 270 } 271 272 nsresult rv = loader->StartModuleLoad(request); 273 if (NS_WARN_IF(NS_FAILED(rv))) { 274 MOZ_ASSERT(!request->mModuleScript); 275 loader->GetScriptLoaderInterface()->ReportErrorToConsole(request, rv); 276 if (isDynamicImport) { 277 loader->RemoveDynamicImport(request); 278 279 nsAutoCString url; 280 uri->GetSpec(url); 281 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, 282 JSMSG_DYNAMIC_IMPORT_FAILED, url.get()); 283 } else { 284 loader->OnFetchFailed(request); 285 return true; 286 } 287 288 return false; 289 } 290 291 if (isDynamicImport) { 292 loader->OnDynamicImportStarted(request); 293 } 294 } 295 296 return true; 297 } 298 299 // static 300 bool ModuleLoaderBase::FinishLoadingImportedModule( 301 JSContext* aCx, ModuleLoadRequest* aRequest) { 302 // The request should been removed from mDynamicImportRequests. 303 MOZ_ASSERT_IF(aRequest->IsDynamicImport(), 304 !aRequest->mLoader->HasDynamicImport(aRequest)); 305 306 Rooted<JSObject*> module(aCx); 307 { 308 ModuleScript* moduleScript = aRequest->mModuleScript; 309 MOZ_ASSERT(moduleScript); 310 MOZ_ASSERT(moduleScript->ModuleRecord()); 311 module.set(moduleScript->ModuleRecord()); 312 } 313 MOZ_ASSERT(module); 314 315 Rooted<JSScript*> referrer(aCx, aRequest->mReferrerScript); 316 Rooted<JSObject*> moduleReqObj(aCx, aRequest->mModuleRequestObj); 317 Rooted<Value> statePrivate(aCx, aRequest->mPayload); 318 Rooted<Value> payload(aCx, aRequest->mPayload); 319 320 LOG(("ScriptLoadRequest (%p): FinishLoadingImportedModule module (%p)", 321 aRequest, module.get())); 322 bool usePromise = aRequest->HasScriptLoadContext(); 323 MOZ_ALWAYS_TRUE(JS::FinishLoadingImportedModule(aCx, referrer, moduleReqObj, 324 payload, module, usePromise)); 325 MOZ_ASSERT(!JS_IsExceptionPending(aCx)); 326 aRequest->ClearImport(); 327 328 return true; 329 } 330 331 // static 332 bool ModuleLoaderBase::ImportMetaResolve(JSContext* cx, unsigned argc, 333 Value* vp) { 334 CallArgs args = CallArgsFromVp(argc, vp); 335 RootedValue modulePrivate( 336 cx, js::GetFunctionNativeReserved( 337 &args.callee(), 338 static_cast<size_t>(ImportMetaSlots::ModulePrivateSlot))); 339 340 // https://html.spec.whatwg.org/#hostgetimportmetaproperties 341 // Step 4.1. Set specifier to ? ToString(specifier). 342 // 343 // https://tc39.es/ecma262/#sec-tostring 344 RootedValue v(cx, args.get(ImportMetaResolveSpecifierArg)); 345 RootedString specifier(cx, ToString(cx, v)); 346 if (!specifier) { 347 return false; 348 } 349 350 // Step 4.2, 4.3 are implemented in ImportMetaResolveImpl. 351 RootedString url(cx, ImportMetaResolveImpl(cx, modulePrivate, specifier)); 352 if (!url) { 353 return false; 354 } 355 356 // Step 4.4. Return the serialization of url. 357 args.rval().setString(url); 358 return true; 359 } 360 361 // static 362 JSString* ModuleLoaderBase::ImportMetaResolveImpl( 363 JSContext* aCx, Handle<Value> aReferencingPrivate, 364 Handle<JSString*> aSpecifier) { 365 RootedString urlString(aCx); 366 367 { 368 // ModuleScript should only live in this block, otherwise it will be a GC 369 // hazard 370 RefPtr<ModuleScript> script = 371 static_cast<ModuleScript*>(aReferencingPrivate.toPrivate()); 372 MOZ_ASSERT(script->IsModuleScript()); 373 MOZ_ASSERT(GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate); 374 375 RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx); 376 if (!loader) { 377 return nullptr; 378 } 379 380 nsAutoJSString specifier; 381 if (!specifier.init(aCx, aSpecifier)) { 382 return nullptr; 383 } 384 385 auto result = loader->ResolveModuleSpecifier(script, specifier); 386 if (result.isErr()) { 387 Rooted<Value> error(aCx); 388 nsresult rv = loader->HandleResolveFailure( 389 aCx, script, specifier, result.unwrapErr(), 0, 390 ColumnNumberOneOrigin(), &error); 391 if (NS_FAILED(rv)) { 392 JS_ReportOutOfMemory(aCx); 393 return nullptr; 394 } 395 396 JS_SetPendingException(aCx, error); 397 398 return nullptr; 399 } 400 401 nsCOMPtr<nsIURI> uri = result.unwrap(); 402 nsAutoCString url; 403 MOZ_ALWAYS_SUCCEEDS(uri->GetAsciiSpec(url)); 404 405 urlString.set(JS_NewStringCopyZ(aCx, url.get())); 406 } 407 408 return urlString; 409 } 410 411 // static 412 bool ModuleLoaderBase::HostPopulateImportMeta(JSContext* aCx, 413 Handle<Value> aReferencingPrivate, 414 Handle<JSObject*> aMetaObject) { 415 RefPtr<ModuleScript> script = 416 static_cast<ModuleScript*>(aReferencingPrivate.toPrivate()); 417 MOZ_ASSERT(script->IsModuleScript()); 418 MOZ_ASSERT(GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate); 419 420 nsAutoCString url; 421 MOZ_DIAGNOSTIC_ASSERT(script->BaseURL()); 422 MOZ_ALWAYS_SUCCEEDS(script->BaseURL()->GetAsciiSpec(url)); 423 424 Rooted<JSString*> urlString(aCx, JS_NewStringCopyZ(aCx, url.get())); 425 if (!urlString) { 426 JS_ReportOutOfMemory(aCx); 427 return false; 428 } 429 430 // https://html.spec.whatwg.org/#import-meta-url 431 if (!JS_DefineProperty(aCx, aMetaObject, "url", urlString, 432 JSPROP_ENUMERATE)) { 433 return false; 434 } 435 436 // https://html.spec.whatwg.org/#import-meta-resolve 437 // Define 'resolve' function on the import.meta object. 438 JSFunction* resolveFunc = js::DefineFunctionWithReserved( 439 aCx, aMetaObject, "resolve", ImportMetaResolve, ImportMetaResolveNumArgs, 440 JSPROP_ENUMERATE); 441 if (!resolveFunc) { 442 return false; 443 } 444 445 // Store the 'active script' of the meta object into the function slot. 446 // https://html.spec.whatwg.org/#active-script 447 RootedObject resolveFuncObj(aCx, JS_GetFunctionObject(resolveFunc)); 448 js::SetFunctionNativeReserved( 449 resolveFuncObj, static_cast<size_t>(ImportMetaSlots::ModulePrivateSlot), 450 aReferencingPrivate); 451 452 return true; 453 } 454 455 AutoOverrideModuleLoader::AutoOverrideModuleLoader(ModuleLoaderBase* aTarget, 456 ModuleLoaderBase* aLoader) 457 : mTarget(aTarget) { 458 mTarget->SetOverride(aLoader); 459 } 460 461 AutoOverrideModuleLoader::~AutoOverrideModuleLoader() { 462 mTarget->ResetOverride(); 463 } 464 465 void ModuleLoaderBase::SetOverride(ModuleLoaderBase* aLoader) { 466 MOZ_ASSERT(!mOverriddenBy); 467 MOZ_ASSERT(!aLoader->mOverriddenBy); 468 MOZ_ASSERT(mGlobalObject == aLoader->mGlobalObject); 469 mOverriddenBy = aLoader; 470 } 471 472 bool ModuleLoaderBase::IsOverridden() { return !!mOverriddenBy; } 473 474 bool ModuleLoaderBase::IsOverriddenBy(ModuleLoaderBase* aLoader) { 475 return mOverriddenBy == aLoader; 476 } 477 478 void ModuleLoaderBase::ResetOverride() { 479 MOZ_ASSERT(mOverriddenBy); 480 mOverriddenBy = nullptr; 481 } 482 483 // static 484 ModuleLoaderBase* ModuleLoaderBase::GetCurrentModuleLoader(JSContext* aCx) { 485 auto reportError = mozilla::MakeScopeExit([aCx]() { 486 JS_ReportErrorASCII(aCx, "No ScriptLoader found for the current context"); 487 }); 488 489 Rooted<JSObject*> object(aCx, CurrentGlobalOrNull(aCx)); 490 if (!object) { 491 return nullptr; 492 } 493 494 nsIGlobalObject* global = xpc::NativeGlobal(object); 495 if (!global) { 496 return nullptr; 497 } 498 499 ModuleLoaderBase* loader = global->GetModuleLoader(aCx); 500 if (!loader) { 501 return nullptr; 502 } 503 504 MOZ_ASSERT(loader->mGlobalObject == global); 505 506 reportError.release(); 507 508 if (loader->mOverriddenBy) { 509 MOZ_ASSERT(loader->mOverriddenBy->mGlobalObject == global); 510 return loader->mOverriddenBy; 511 } 512 return loader; 513 } 514 515 // static 516 LoadedScript* ModuleLoaderBase::GetLoadedScriptOrNull( 517 Handle<JSScript*> aReferrer) { 518 if (!aReferrer) { 519 return nullptr; 520 } 521 522 Value value = GetScriptPrivate(aReferrer); 523 if (value.isUndefined()) { 524 return nullptr; 525 } 526 527 return static_cast<LoadedScript*>(value.toPrivate()); 528 } 529 530 nsresult ModuleLoaderBase::StartModuleLoad(ModuleLoadRequest* aRequest) { 531 return StartOrRestartModuleLoad(aRequest, RestartRequest::No); 532 } 533 534 nsresult ModuleLoaderBase::RestartModuleLoad(ModuleLoadRequest* aRequest) { 535 return StartOrRestartModuleLoad(aRequest, RestartRequest::Yes); 536 } 537 538 nsresult ModuleLoaderBase::StartOrRestartModuleLoad(ModuleLoadRequest* aRequest, 539 RestartRequest aRestart) { 540 MOZ_ASSERT(aRequest->mLoader == this); 541 MOZ_ASSERT(aRequest->IsFetching()); 542 543 // NOTE: The LoadedScript::mDataType field used by the IsStencil call can be 544 // modified asynchronously after the StartFetch call. 545 // In order to avoid the race condition, cache the value here. 546 bool isCachedStencil = aRequest->IsCachedStencil(); 547 548 MOZ_ASSERT_IF(isCachedStencil, aRestart == RestartRequest::No); 549 550 if (!isCachedStencil) { 551 aRequest->SetUnknownDataType(); 552 } 553 554 if (LOG_ENABLED()) { 555 nsAutoCString url; 556 aRequest->URI()->GetAsciiSpec(url); 557 LOG(("ScriptLoadRequest (%p): Start module load %s", aRequest, url.get())); 558 } 559 560 // If we're restarting the request, the module should already be in the 561 // "fetching" map. 562 MOZ_ASSERT_IF( 563 aRestart == RestartRequest::Yes, 564 IsModuleFetching(ModuleMapKey(aRequest->URI(), aRequest->mModuleType))); 565 566 // Check with the derived class whether we should load this module. 567 nsresult rv = NS_OK; 568 if (!CanStartLoad(aRequest, &rv)) { 569 return rv; 570 } 571 572 // Check whether the module has been fetched or is currently being fetched, 573 // and if so wait for it rather than starting a new fetch. 574 if (aRestart == RestartRequest::No && 575 ModuleMapContainsURL( 576 ModuleMapKey(aRequest->URI(), aRequest->mModuleType))) { 577 LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest)); 578 WaitForModuleFetch(aRequest); 579 return NS_OK; 580 } 581 582 rv = StartFetch(aRequest); 583 NS_ENSURE_SUCCESS(rv, rv); 584 585 if (isCachedStencil) { 586 MOZ_ASSERT( 587 IsModuleFetched(ModuleMapKey(aRequest->URI(), aRequest->mModuleType))); 588 return NS_OK; 589 } 590 591 // We successfully started fetching a module so put its URL in the module 592 // map and mark it as fetching. 593 if (aRestart == RestartRequest::No) { 594 SetModuleFetchStarted(aRequest); 595 } 596 597 return NS_OK; 598 } 599 600 bool ModuleLoaderBase::ModuleMapContainsURL(const ModuleMapKey& key) const { 601 return IsModuleFetching(key) || IsModuleFetched(key); 602 } 603 604 bool ModuleLoaderBase::IsModuleFetching(const ModuleMapKey& key) const { 605 return mFetchingModules.Contains(key); 606 } 607 608 bool ModuleLoaderBase::IsModuleFetched(const ModuleMapKey& key) const { 609 return mFetchedModules.Contains(key); 610 } 611 612 nsresult ModuleLoaderBase::GetFetchedModuleURLs(nsTArray<nsCString>& aURLs) { 613 for (const auto& entry : mFetchedModules) { 614 nsIURI* uri = entry.GetData()->BaseURL(); 615 616 nsAutoCString spec; 617 nsresult rv = uri->GetSpec(spec); 618 NS_ENSURE_SUCCESS(rv, rv); 619 620 aURLs.AppendElement(spec); 621 } 622 623 return NS_OK; 624 } 625 626 void ModuleLoaderBase::SetModuleFetchStarted(ModuleLoadRequest* aRequest) { 627 // Update the module map to indicate that a module is currently being fetched. 628 629 ModuleMapKey moduleMapKey(aRequest->URI(), aRequest->mModuleType); 630 631 MOZ_ASSERT(aRequest->IsFetching()); 632 MOZ_ASSERT(!ModuleMapContainsURL(moduleMapKey)); 633 634 RefPtr<LoadingRequest> loadingRequest = new LoadingRequest(); 635 loadingRequest->mRequest = aRequest; 636 mFetchingModules.InsertOrUpdate(moduleMapKey, loadingRequest); 637 } 638 639 already_AddRefed<ModuleLoaderBase::LoadingRequest> 640 ModuleLoaderBase::SetModuleFetchFinishedAndGetWaitingRequests( 641 ModuleLoadRequest* aRequest, nsresult aResult) { 642 // Update module map with the result of fetching a single module script. 643 // 644 // If any requests for the same URL are waiting on this one to complete, call 645 // ModuleLoaded or LoadFailed to resume or fail them as appropriate. 646 647 MOZ_ASSERT(aRequest->mLoader == this); 648 649 LOG( 650 ("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == " 651 "%u)", 652 aRequest, aRequest->mModuleScript.get(), unsigned(aResult))); 653 654 ModuleMapKey moduleMapKey(aRequest->URI(), aRequest->mModuleType); 655 656 auto entry = mFetchingModules.Lookup(moduleMapKey); 657 if (!entry) { 658 LOG( 659 ("ScriptLoadRequest (%p): Key not found in mFetchingModules, " 660 "assuming we have an inline module or have finished fetching already", 661 aRequest)); 662 return nullptr; 663 } 664 665 // It's possible for a request to be cancelled and removed from the fetching 666 // modules map and a new request started for the same URI and added to the 667 // map. In this case we don't want the first cancelled request to complete the 668 // later request (which will cause it to fail) so we ignore it. 669 RefPtr<LoadingRequest> loadingRequest = entry.Data(); 670 if (loadingRequest->mRequest != aRequest) { 671 MOZ_ASSERT(aRequest->IsCanceled()); 672 LOG( 673 ("ScriptLoadRequest (%p): Ignoring completion of cancelled request " 674 "that was removed from the map", 675 aRequest)); 676 return nullptr; 677 } 678 679 MOZ_ALWAYS_TRUE(mFetchingModules.Remove(moduleMapKey)); 680 681 RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript); 682 MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript); 683 684 mFetchedModules.InsertOrUpdate(moduleMapKey, RefPtr{moduleScript}); 685 686 return loadingRequest.forget(); 687 } 688 689 void ModuleLoaderBase::ResumeWaitingRequests(LoadingRequest* aLoadingRequest, 690 bool aSuccess) { 691 for (ModuleLoadRequest* request : aLoadingRequest->mWaiting) { 692 ResumeWaitingRequest(request, aSuccess); 693 } 694 } 695 696 void ModuleLoaderBase::ResumeWaitingRequest(ModuleLoadRequest* aRequest, 697 bool aSuccess) { 698 if (aSuccess) { 699 aRequest->ModuleLoaded(); 700 } 701 702 if (!aRequest->IsErrored()) { 703 OnFetchSucceeded(aRequest); 704 } else { 705 OnFetchFailed(aRequest); 706 } 707 } 708 709 void ModuleLoaderBase::WaitForModuleFetch(ModuleLoadRequest* aRequest) { 710 ModuleMapKey moduleMapKey(aRequest->URI(), aRequest->mModuleType); 711 MOZ_ASSERT(ModuleMapContainsURL(moduleMapKey)); 712 713 if (auto entry = mFetchingModules.Lookup(moduleMapKey)) { 714 RefPtr<LoadingRequest> loadingRequest = entry.Data(); 715 loadingRequest->mWaiting.AppendElement(aRequest); 716 return; 717 } 718 719 RefPtr<ModuleScript> ms; 720 MOZ_ALWAYS_TRUE(mFetchedModules.Get(moduleMapKey, getter_AddRefs(ms))); 721 722 ResumeWaitingRequest(aRequest, bool(ms)); 723 } 724 725 ModuleScript* ModuleLoaderBase::GetFetchedModule( 726 const ModuleMapKey& moduleMapKey) const { 727 if (LOG_ENABLED()) { 728 nsAutoCString url; 729 moduleMapKey.mUri->GetAsciiSpec(url); 730 LOG(("GetFetchedModule %s", url.get())); 731 } 732 733 bool found; 734 ModuleScript* ms = mFetchedModules.GetWeak(moduleMapKey, &found); 735 MOZ_ASSERT(found); 736 return ms; 737 } 738 739 nsresult ModuleLoaderBase::OnFetchComplete(ModuleLoadRequest* aRequest, 740 nsresult aRv) { 741 LOG(("ScriptLoadRequest (%p): OnFetchComplete result %x", aRequest, 742 (unsigned)aRv)); 743 MOZ_ASSERT(aRequest->mLoader == this); 744 MOZ_ASSERT(!aRequest->mModuleScript); 745 746 nsresult rv = aRv; 747 if (NS_SUCCEEDED(rv)) { 748 rv = CreateModuleScript(aRequest); 749 750 #if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) 751 // If a module script was created, it should either have a module record 752 // object or a parse error. 753 if (ModuleScript* ms = aRequest->mModuleScript) { 754 MOZ_DIAGNOSTIC_ASSERT(bool(ms->ModuleRecord()) != ms->HasParseError()); 755 } 756 #endif 757 758 if (aRequest->IsTextSource()) { 759 aRequest->ClearScriptText(); 760 } 761 762 if (NS_FAILED(rv)) { 763 aRequest->LoadFailed(); 764 return rv; 765 } 766 } 767 768 RefPtr<LoadingRequest> waitingRequests = 769 SetModuleFetchFinishedAndGetWaitingRequests(aRequest, rv); 770 MOZ_ASSERT_IF(waitingRequests, waitingRequests->mRequest == aRequest); 771 772 bool success = bool(aRequest->mModuleScript); 773 MOZ_ASSERT(NS_SUCCEEDED(rv) == success); 774 775 // TODO: https://github.com/whatwg/html/issues/11755 776 // Should we check if the module script has an error to rethrow as well? 777 // 778 // https://html.spec.whatwg.org/#hostloadimportedmodule:fetch-a-single-imported-module-script 779 // Step 14. onSingleFetchComplete: step 2, and step 3 780 // return ThrowCompletion if moduleScript is null or its parse error is not 781 // null: 782 // Step 4. Otherwise, set completion to NormalCompletion(moduleScript's 783 // record). 784 if (!aRequest->IsErrored()) { 785 OnFetchSucceeded(aRequest); 786 } else { 787 OnFetchFailed(aRequest); 788 } 789 790 if (!waitingRequests) { 791 return NS_OK; 792 } 793 794 ResumeWaitingRequests(waitingRequests, success); 795 return NS_OK; 796 } 797 798 void ModuleLoaderBase::OnFetchSucceeded(ModuleLoadRequest* aRequest) { 799 // TODO, Bug 1990416: Align the loading descendants behavior of dynamic import 800 // According to the spec, dynamic import should trigger fetching of 801 // descendant modules in the JS engine. However, when the dynamic import’s 802 // module graph is loaded, ScriptLoaders move the request to a Loaded event 803 // queue and invoke ProcessDynamicImport. To account for this behavior, 804 // we perform the fetching of submodules in the host layer instead. 805 if (aRequest->IsTopLevel() || aRequest->IsDynamicImport()) { 806 StartFetchingModuleDependencies(aRequest); 807 } else { 808 MOZ_ASSERT(!aRequest->IsDynamicImport()); 809 AutoJSAPI jsapi; 810 if (!jsapi.Init(mGlobalObject)) { 811 return; 812 } 813 JSContext* cx = jsapi.cx(); 814 FinishLoadingImportedModule(cx, aRequest); 815 816 aRequest->SetReady(); 817 aRequest->LoadFinished(); 818 } 819 } 820 821 void ModuleLoaderBase::OnFetchFailed(ModuleLoadRequest* aRequest) { 822 MOZ_ASSERT(aRequest->IsErrored()); 823 AutoJSAPI jsapi; 824 if (!jsapi.Init(mGlobalObject)) { 825 return; 826 } 827 JSContext* cx = jsapi.cx(); 828 829 if (aRequest->IsTopLevel()) { 830 // https://html.spec.whatwg.org/#fetch-the-descendants-of-and-link-a-module-script 831 // Step 2. If record is null, then: 832 // Step 2.1. Set moduleScript's error to rethrow to moduleScript's parse 833 // error. 834 if (aRequest->mModuleScript && !aRequest->mModuleScript->ModuleRecord()) { 835 MOZ_ASSERT(aRequest->mModuleScript->HasParseError()); 836 Value parseError = aRequest->mModuleScript->ParseError(); 837 LOG(("ScriptLoadRequest (%p): found parse error", aRequest)); 838 aRequest->mModuleScript->SetErrorToRethrow(parseError); 839 } 840 DispatchModuleErrored(aRequest); 841 842 return; 843 } 844 845 // The remaining case is static/dynamic import. 846 MOZ_ASSERT(aRequest->IsStaticImport() || aRequest->IsDynamicImport()); 847 MOZ_ASSERT(!aRequest->mPayload.isUndefined()); 848 Rooted<Value> payload(cx, aRequest->mPayload); 849 850 // https://html.spec.whatwg.org/#hostloadimportedmodule 851 // 852 // Step 14.2. If moduleScript is null, then set completion to Completion 853 // Record { [[Type]]: throw, [[Value]]: a new TypeError, 854 // [[Target]]: empty }. 855 if (!aRequest->mModuleScript) { 856 LOG( 857 ("ScriptLoadRequest (%p): FinishLoadingImportedModule: module script " 858 "is null", 859 aRequest)); 860 861 // Impl note: 862 // For dynamic import, the TypeError will be pass to the rejected handler of 863 // the LoadRequestedModules from the dynamic import. 864 // 865 // For static import/top-level import, the TypeError will be ignore in the 866 // rejected handler of LoadRequestedModules (the state.[[ErrorToRethrow]] 867 // isn't set). So we don't actually create a TypeError for the these two 868 // cases. 869 if (aRequest->GetRootModule()->IsDynamicImport()) { 870 nsAutoCString url; 871 aRequest->URI()->GetSpec(url); 872 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, 873 JSMSG_DYNAMIC_IMPORT_FAILED, url.get()); 874 FinishLoadingImportedModuleFailedWithPendingException(cx, payload); 875 } else { 876 Rooted<Value> error(cx, UndefinedValue()); 877 FinishLoadingImportedModuleFailed(cx, payload, error); 878 } 879 880 aRequest->ModuleErrored(); 881 aRequest->ClearImport(); 882 return; 883 } 884 885 // Step 14.3. Otherwise, if moduleScript's parse error is not null, then: 886 // 1. Let parseError be moduleScript's parse error. 887 // 2. Set completion to Completion Record { [[Type]]: throw, 888 // [[Value]]: parseError, [[Target]]: empty }. 889 // 3. If loadState is not undefined and loadState.[[ErrorToRethrow]] 890 // is null, set loadState.[[ErrorToRethrow]] to parseError. 891 MOZ_ASSERT(aRequest->mModuleScript->HasParseError()); 892 Rooted<Value> parseError(cx, aRequest->mModuleScript->ParseError()); 893 LOG( 894 ("ScriptLoadRequest (%p): FinishLoadingImportedModule: found parse " 895 "error", 896 aRequest)); 897 // Step 14.5. Perform FinishLoadingImportedModule(referrer, moduleRequest, 898 // payload, completion). 899 FinishLoadingImportedModuleFailed(cx, payload, parseError); 900 aRequest->ModuleErrored(); 901 aRequest->ClearImport(); 902 } 903 904 void ModuleLoaderBase::Cancel(ModuleLoadRequest* aRequest) { 905 if (aRequest->IsFinished()) { 906 return; 907 } 908 909 aRequest->ScriptLoadRequest::Cancel(); 910 aRequest->mModuleScript = nullptr; 911 912 if (aRequest->mPayload.isUndefined()) { 913 return; 914 } 915 916 AutoJSAPI jsapi; 917 if (!jsapi.Init(mGlobalObject)) { 918 return; 919 } 920 JSContext* cx = jsapi.cx(); 921 922 Rooted<Value> payload(cx, aRequest->mPayload); 923 Rooted<Value> error(cx, UndefinedValue()); 924 925 LOG( 926 ("ScriptLoadRequest (%p): Canceled, calling " 927 "FinishLoadingImportedModuleFailed", 928 aRequest)); 929 930 FinishLoadingImportedModuleFailed(cx, payload, error); 931 aRequest->ModuleErrored(); 932 aRequest->ClearImport(); 933 } 934 935 class ModuleErroredRunnable : public MicroTaskRunnable { 936 public: 937 explicit ModuleErroredRunnable(ModuleLoadRequest* aRequest) 938 : mRequest(aRequest) {} 939 940 virtual void Run(AutoSlowOperation& aAso) override { 941 mRequest->ModuleErrored(); 942 } 943 944 private: 945 RefPtr<ModuleLoadRequest> mRequest; 946 }; 947 948 void ModuleLoaderBase::DispatchModuleErrored(ModuleLoadRequest* aRequest) { 949 if (aRequest->HasScriptLoadContext() && 950 aRequest->GetScriptLoadContext()->mIsInline) { 951 // https://html.spec.whatwg.org/#prepare-the-script-element 952 // Step 32. If el does not have a src content attribute: 953 // 2. "module".3.1 954 // Queue an element task on the networking task source given el to 955 // perform the following steps: 956 CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); 957 RefPtr<ModuleErroredRunnable> runnable = 958 new ModuleErroredRunnable(aRequest); 959 context->DispatchToMicroTask(runnable.forget()); 960 } else { 961 aRequest->ModuleErrored(); 962 } 963 } 964 965 nsresult ModuleLoaderBase::CreateModuleScript(ModuleLoadRequest* aRequest) { 966 MOZ_ASSERT(!aRequest->mModuleScript); 967 MOZ_ASSERT(aRequest->BaseURL()); 968 969 LOG(("ScriptLoadRequest (%p): Create module script", aRequest)); 970 971 AutoJSAPI jsapi; 972 if (!jsapi.Init(mGlobalObject)) { 973 return NS_ERROR_FAILURE; 974 } 975 976 nsresult rv; 977 { 978 JSContext* cx = jsapi.cx(); 979 Rooted<JSObject*> module(cx); 980 981 CompileOptions options(cx); 982 RootedScript introductionScript(cx); 983 rv = mLoader->FillCompileOptionsForRequest(cx, aRequest, &options, 984 &introductionScript); 985 986 if (NS_SUCCEEDED(rv)) { 987 Rooted<JSObject*> global(cx, mGlobalObject->GetGlobalJSObject()); 988 rv = CompileFetchedModule(cx, global, options, aRequest, &module); 989 } 990 991 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr)); 992 993 if (module) { 994 RootedScript moduleScript(cx, GetModuleScript(module)); 995 if (moduleScript) { 996 RootedValue privateValue(cx); 997 InstantiateOptions instantiateOptions(options); 998 if (!UpdateDebugMetadata(cx, moduleScript, instantiateOptions, 999 privateValue, nullptr, introductionScript, 1000 nullptr)) { 1001 return NS_ERROR_OUT_OF_MEMORY; 1002 } 1003 } 1004 } 1005 1006 MOZ_ASSERT(aRequest->mLoadedScript->IsModuleScript()); 1007 RefPtr<ModuleScript> moduleScript = 1008 aRequest->mLoadedScript->AsModuleScript(); 1009 1010 // Update the module script's referrer policy to reflect any changes made 1011 // to the ModuleLoadRequest during HTTP response parsing. 1012 if (moduleScript->ReferrerPolicy() != aRequest->ReferrerPolicy()) { 1013 moduleScript->UpdateReferrerPolicy(aRequest->ReferrerPolicy()); 1014 } 1015 aRequest->mModuleScript = moduleScript; 1016 1017 moduleScript->SetForPreload(aRequest->mLoadContext->IsPreload()); 1018 moduleScript->SetHadImportMap(HasImportMapRegistered()); 1019 1020 if (!module) { 1021 LOG(("ScriptLoadRequest (%p): compilation failed (%d)", aRequest, 1022 unsigned(rv))); 1023 1024 Rooted<Value> error(cx); 1025 if (!jsapi.HasException() || !jsapi.StealException(&error) || 1026 error.isUndefined()) { 1027 aRequest->mModuleScript = nullptr; 1028 return NS_ERROR_FAILURE; 1029 } 1030 1031 moduleScript->SetParseError(error); 1032 return NS_OK; 1033 } 1034 1035 moduleScript->SetModuleRecord(module); 1036 } 1037 1038 LOG(("ScriptLoadRequest (%p): module script == %p", aRequest, 1039 aRequest->mModuleScript.get())); 1040 1041 return rv; 1042 } 1043 1044 nsresult ModuleLoaderBase::GetResolveFailureMessage(ResolveError aError, 1045 const nsAString& aSpecifier, 1046 nsAString& aResult) { 1047 AutoTArray<nsString, 1> errorParams; 1048 errorParams.AppendElement(aSpecifier); 1049 1050 nsresult rv = nsContentUtils::FormatLocalizedString( 1051 nsContentUtils::eDOM_PROPERTIES, ResolveErrorInfo::GetString(aError), 1052 errorParams, aResult); 1053 NS_ENSURE_SUCCESS(rv, rv); 1054 return NS_OK; 1055 } 1056 1057 nsresult ModuleLoaderBase::HandleResolveFailure( 1058 JSContext* aCx, LoadedScript* aScript, const nsAString& aSpecifier, 1059 ResolveError aError, uint32_t aLineNumber, 1060 ColumnNumberOneOrigin aColumnNumber, MutableHandle<Value> aErrorOut) { 1061 Rooted<JSString*> filename(aCx); 1062 if (aScript) { 1063 nsAutoCString url; 1064 aScript->BaseURL()->GetAsciiSpec(url); 1065 filename = JS_NewStringCopyZ(aCx, url.get()); 1066 } else { 1067 filename = JS_NewStringCopyZ(aCx, "(unknown)"); 1068 } 1069 1070 if (!filename) { 1071 return NS_ERROR_OUT_OF_MEMORY; 1072 } 1073 1074 nsAutoString errorText; 1075 nsresult rv = GetResolveFailureMessage(aError, aSpecifier, errorText); 1076 NS_ENSURE_SUCCESS(rv, rv); 1077 1078 Rooted<JSString*> string(aCx, JS_NewUCStringCopyZ(aCx, errorText.get())); 1079 if (!string) { 1080 return NS_ERROR_OUT_OF_MEMORY; 1081 } 1082 1083 if (!CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber, 1084 aColumnNumber, nullptr, string, NothingHandleValue, 1085 aErrorOut)) { 1086 return NS_ERROR_OUT_OF_MEMORY; 1087 } 1088 1089 return NS_OK; 1090 } 1091 1092 ResolveResult ModuleLoaderBase::ResolveModuleSpecifier( 1093 LoadedScript* aScript, const nsAString& aSpecifier) { 1094 // Import Maps are not supported on workers/worklets. 1095 // See https://github.com/WICG/import-maps/issues/2 1096 MOZ_ASSERT_IF(!NS_IsMainThread(), mImportMap == nullptr); 1097 1098 // Forward to the updated 'Resolve a module specifier' algorithm defined in 1099 // the Import Maps spec. 1100 return ImportMap::ResolveModuleSpecifier(mImportMap.get(), mLoader, aScript, 1101 aSpecifier); 1102 } 1103 1104 void ModuleLoaderBase::StartFetchingModuleDependencies( 1105 ModuleLoadRequest* aRequest) { 1106 if (aRequest->IsCanceled()) { 1107 return; 1108 } 1109 1110 MOZ_ASSERT(aRequest->mModuleScript); 1111 MOZ_ASSERT(!aRequest->mModuleScript->HasParseError()); 1112 ModuleScript* moduleScript = aRequest->mModuleScript; 1113 MOZ_ASSERT(moduleScript->ModuleRecord()); 1114 MOZ_ASSERT(aRequest->IsFetching() || aRequest->IsCompiling()); 1115 MOZ_ASSERT(aRequest->IsTopLevel() || aRequest->IsDynamicImport()); 1116 1117 AutoJSAPI jsapi; 1118 if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) { 1119 return; 1120 } 1121 JSContext* cx = jsapi.cx(); 1122 1123 Rooted<JSObject*> module(cx, moduleScript->ModuleRecord()); 1124 1125 LOG( 1126 ("ScriptLoadRequest (%p): module record (%p) Start fetching module " 1127 "dependencies", 1128 aRequest, module.get())); 1129 1130 // Wrap the request into a JS::Value, and AddRef() it. 1131 // The Release() will be called in the resolved/rejected handlers. 1132 Rooted<Value> hostDefinedVal(cx, PrivateValue(aRequest)); 1133 aRequest->AddRef(); 1134 1135 bool result = false; 1136 1137 // PromiseJobRunnable::Call() is not executed if the global is being 1138 // destroyed. As a result, the promise returned by LoadRequestedModules may 1139 // neither resolve nor reject. To ensure module loading completes reliably in 1140 // chrome pages, we use the synchronous variant of LoadRequestedModules. 1141 bool isSync = aRequest->URI()->SchemeIs("chrome") || 1142 aRequest->URI()->SchemeIs("resource"); 1143 1144 // TODO: Bug1973660: Use Promise version of LoadRequestedModules on Workers. 1145 if (aRequest->HasScriptLoadContext() && !isSync) { 1146 Rooted<JSFunction*> onResolved( 1147 cx, js::NewFunctionWithReserved(cx, OnLoadRequestedModulesResolved, 1148 OnLoadRequestedModulesResolvedNumArgs, 1149 0, "resolved")); 1150 if (!onResolved) { 1151 JS_ReportOutOfMemory(cx); 1152 return; 1153 } 1154 1155 RootedFunction onRejected( 1156 cx, js::NewFunctionWithReserved(cx, OnLoadRequestedModulesRejected, 1157 OnLoadRequestedModulesRejectedNumArgs, 1158 0, "rejected")); 1159 if (!onRejected) { 1160 JS_ReportOutOfMemory(cx); 1161 return; 1162 } 1163 1164 RootedObject resolveFuncObj(cx, JS_GetFunctionObject(onResolved)); 1165 js::SetFunctionNativeReserved(resolveFuncObj, LoadReactionHostDefinedSlot, 1166 hostDefinedVal); 1167 1168 RootedObject rejectFuncObj(cx, JS_GetFunctionObject(onRejected)); 1169 js::SetFunctionNativeReserved(rejectFuncObj, LoadReactionHostDefinedSlot, 1170 hostDefinedVal); 1171 1172 Rooted<JSObject*> loadPromise(cx); 1173 result = LoadRequestedModules(cx, module, hostDefinedVal, &loadPromise); 1174 AddPromiseReactions(cx, loadPromise, resolveFuncObj, rejectFuncObj); 1175 } else { 1176 result = LoadRequestedModules(cx, module, hostDefinedVal, 1177 OnLoadRequestedModulesResolved, 1178 OnLoadRequestedModulesRejected); 1179 } 1180 1181 if (!result) { 1182 LOG(("ScriptLoadRequest (%p): LoadRequestedModules failed", aRequest)); 1183 OnLoadRequestedModulesRejected(cx, aRequest, UndefinedHandleValue); 1184 } 1185 } 1186 1187 // static 1188 bool ModuleLoaderBase::OnLoadRequestedModulesResolved(JSContext* aCx, 1189 unsigned aArgc, 1190 Value* aVp) { 1191 CallArgs args = CallArgsFromVp(aArgc, aVp); 1192 Rooted<Value> hostDefined(aCx); 1193 hostDefined = js::GetFunctionNativeReserved(&args.callee(), 1194 LoadReactionHostDefinedSlot); 1195 return OnLoadRequestedModulesResolved(aCx, hostDefined); 1196 } 1197 1198 // static 1199 bool ModuleLoaderBase::OnLoadRequestedModulesResolved( 1200 JSContext* aCx, Handle<Value> aHostDefined) { 1201 auto* request = static_cast<ModuleLoadRequest*>(aHostDefined.toPrivate()); 1202 MOZ_ASSERT(request); 1203 return OnLoadRequestedModulesResolved(request); 1204 } 1205 1206 // static 1207 bool ModuleLoaderBase::OnLoadRequestedModulesResolved( 1208 ModuleLoadRequest* aRequest) { 1209 LOG(("ScriptLoadRequest (%p): LoadRequestedModules resolved", aRequest)); 1210 if (!aRequest->IsCanceled()) { 1211 aRequest->SetReady(); 1212 aRequest->LoadFinished(); 1213 } 1214 1215 // Decrease the reference 'AddRef'ed when converting the hostDefined. 1216 aRequest->Release(); 1217 return true; 1218 } 1219 1220 // static 1221 bool ModuleLoaderBase::OnLoadRequestedModulesRejected(JSContext* aCx, 1222 unsigned aArgc, 1223 Value* aVp) { 1224 CallArgs args = CallArgsFromVp(aArgc, aVp); 1225 Rooted<Value> error(aCx, args.get(OnLoadRequestedModulesRejectedErrorArg)); 1226 Rooted<Value> hostDefined(aCx); 1227 hostDefined = js::GetFunctionNativeReserved(&args.callee(), 1228 LoadReactionHostDefinedSlot); 1229 return OnLoadRequestedModulesRejected(aCx, hostDefined, error); 1230 } 1231 1232 // static 1233 bool ModuleLoaderBase::OnLoadRequestedModulesRejected( 1234 JSContext* aCx, Handle<Value> aHostDefined, Handle<Value> aError) { 1235 auto* request = static_cast<ModuleLoadRequest*>(aHostDefined.toPrivate()); 1236 MOZ_ASSERT(request); 1237 return OnLoadRequestedModulesRejected(aCx, request, aError); 1238 } 1239 1240 // static 1241 bool ModuleLoaderBase::OnLoadRequestedModulesRejected( 1242 JSContext* aCx, ModuleLoadRequest* aRequest, Handle<Value> error) { 1243 ModuleScript* moduleScript = aRequest->mModuleScript; 1244 1245 if (aRequest->IsCanceled()) { 1246 aRequest->Release(); 1247 return true; 1248 } 1249 1250 // TODO, Bug 1990416: Align the loading descendants behavior of dynamic import 1251 if (aRequest->IsDynamicImport()) { 1252 LOG( 1253 ("ScriptLoadRequest (%p): LoadRequestedModules rejected for dynamic " 1254 "import", 1255 aRequest)); 1256 // https://tc39.es/ecma262/#sec-ContinueDynamicImport 1257 // Step 4.a. Perform ! Call(promiseCapability.[[Reject]], undefined, 1258 // « reason »). 1259 Rooted<Value> payload(aCx, aRequest->mPayload); 1260 if (!error.isUndefined()) { 1261 FinishLoadingImportedModuleFailed(aCx, payload, error); 1262 } else { 1263 nsAutoCString url; 1264 aRequest->URI()->GetSpec(url); 1265 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, 1266 JSMSG_DYNAMIC_IMPORT_FAILED, url.get()); 1267 FinishLoadingImportedModuleFailedWithPendingException(aCx, payload); 1268 } 1269 aRequest->SetErroredLoadingImports(); 1270 } else if (moduleScript && !error.isUndefined()) { 1271 LOG( 1272 ("ScriptLoadRequest (%p): LoadRequestedModules rejected: set error to " 1273 "rethrow", 1274 aRequest)); 1275 // https://html.spec.whatwg.org/#fetch-the-descendants-of-and-link-a-module-script 1276 // Step 7. Upon rejection of loadingPromise, run the following 1277 // steps: 1278 // Step 7.1. If state.[[ErrorToRethrow]] is not null, set moduleScript's 1279 // error to rethrow to state.[[ErrorToRethrow]] and run 1280 // onComplete given moduleScript. 1281 moduleScript->SetErrorToRethrow(error); 1282 } else { 1283 LOG( 1284 ("ScriptLoadRequest (%p): LoadRequestedModules rejected: set module " 1285 "script to null", 1286 aRequest)); 1287 // Step 7.2. Otherwise, run onComplete given null. 1288 aRequest->mModuleScript = nullptr; 1289 } 1290 1291 aRequest->ModuleErrored(); 1292 1293 // Decrease the reference 'AddRef'ed when converting the hostDefined. 1294 aRequest->Release(); 1295 return true; 1296 } 1297 1298 bool ModuleLoaderBase::GetImportMapSRI( 1299 nsIURI* aURI, nsIURI* aSourceURI, nsIConsoleReportCollector* aReporter, 1300 mozilla::dom::SRIMetadata* aMetadataOut) { 1301 MOZ_ASSERT(aMetadataOut->IsEmpty()); 1302 MOZ_ASSERT(aURI); 1303 1304 if (!HasImportMapRegistered()) { 1305 return false; 1306 } 1307 1308 mozilla::Maybe<nsString> entry = 1309 ImportMap::LookupIntegrity(mImportMap.get(), aURI); 1310 if (entry.isNothing()) { 1311 return false; 1312 } 1313 1314 mozilla::dom::SRICheck::IntegrityMetadata( 1315 *entry, aSourceURI->GetSpecOrDefault(), aReporter, aMetadataOut); 1316 return true; 1317 } 1318 1319 void ModuleLoaderBase::AppendDynamicImport(ModuleLoadRequest* aRequest) { 1320 MOZ_ASSERT(aRequest->mLoader == this); 1321 mDynamicImportRequests.AppendElement(aRequest); 1322 } 1323 1324 ModuleLoaderBase::ModuleLoaderBase(ScriptLoaderInterface* aLoader, 1325 nsIGlobalObject* aGlobalObject) 1326 : mGlobalObject(aGlobalObject), mLoader(aLoader) { 1327 MOZ_ASSERT(mGlobalObject); 1328 MOZ_ASSERT(mLoader); 1329 1330 EnsureModuleHooksInitialized(); 1331 } 1332 1333 ModuleLoaderBase::~ModuleLoaderBase() { 1334 mDynamicImportRequests.CancelRequestsAndClear(); 1335 1336 LOG(("ModuleLoaderBase::~ModuleLoaderBase %p", this)); 1337 } 1338 1339 void ModuleLoaderBase::CancelFetchingModules() { 1340 for (const auto& entry : mFetchingModules) { 1341 RefPtr<LoadingRequest> loadingRequest = entry.GetData(); 1342 loadingRequest->mRequest->Cancel(); 1343 1344 for (const auto& request : loadingRequest->mWaiting) { 1345 request->Cancel(); 1346 } 1347 } 1348 1349 // We don't clear mFetchingModules here, as the fetching requests might arrive 1350 // after the global is still shutting down. 1351 } 1352 1353 void ModuleLoaderBase::Shutdown() { 1354 CancelAndClearDynamicImports(); 1355 1356 for (const auto& entry : mFetchingModules) { 1357 RefPtr<LoadingRequest> loadingRequest(entry.GetData()); 1358 if (loadingRequest) { 1359 ResumeWaitingRequests(loadingRequest, false); 1360 } 1361 } 1362 1363 for (const auto& entry : mFetchedModules) { 1364 if (entry.GetData()) { 1365 entry.GetData()->Shutdown(); 1366 } 1367 } 1368 1369 mFetchingModules.Clear(); 1370 mFetchedModules.Clear(); 1371 mGlobalObject = nullptr; 1372 mLoader = nullptr; 1373 } 1374 1375 bool ModuleLoaderBase::HasFetchingModules() const { 1376 return !mFetchingModules.IsEmpty(); 1377 } 1378 1379 bool ModuleLoaderBase::HasPendingDynamicImports() const { 1380 return !mDynamicImportRequests.isEmpty(); 1381 } 1382 1383 void ModuleLoaderBase::CancelDynamicImport(ModuleLoadRequest* aRequest, 1384 nsresult aResult) { 1385 // aRequest may have already been unlinked by CC. 1386 MOZ_ASSERT(aRequest->mLoader == this || !aRequest->mLoader); 1387 1388 RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest); 1389 if (!aRequest->IsCanceled()) { 1390 // If the ClearDynamicImport() has been called, then it should have been 1391 // removed from mDynamicImportRequests as well. 1392 MOZ_ASSERT(!aRequest->mPayload.isUndefined()); 1393 aRequest->Cancel(); 1394 } 1395 } 1396 1397 void ModuleLoaderBase::RemoveDynamicImport(ModuleLoadRequest* aRequest) { 1398 MOZ_ASSERT(aRequest->IsDynamicImport()); 1399 mDynamicImportRequests.Remove(aRequest); 1400 } 1401 1402 #ifdef DEBUG 1403 bool ModuleLoaderBase::HasDynamicImport( 1404 const ModuleLoadRequest* aRequest) const { 1405 MOZ_ASSERT(aRequest->mLoader == this); 1406 return mDynamicImportRequests.Contains( 1407 const_cast<ModuleLoadRequest*>(aRequest)); 1408 } 1409 #endif 1410 1411 bool ModuleLoaderBase::InstantiateModuleGraph(ModuleLoadRequest* aRequest) { 1412 // Instantiate a top-level module and record any error. 1413 1414 MOZ_ASSERT(aRequest); 1415 MOZ_ASSERT(aRequest->mLoader == this); 1416 MOZ_ASSERT(aRequest->IsTopLevel()); 1417 1418 LOG(("ScriptLoadRequest (%p): Instantiate module graph", aRequest)); 1419 1420 AUTO_PROFILER_LABEL("ModuleLoaderBase::InstantiateModuleGraph", JS); 1421 1422 ModuleScript* moduleScript = aRequest->mModuleScript; 1423 MOZ_ASSERT(moduleScript); 1424 1425 MOZ_ASSERT(!moduleScript->HasParseError()); 1426 MOZ_ASSERT(moduleScript->ModuleRecord()); 1427 1428 AutoJSAPI jsapi; 1429 if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) { 1430 return false; 1431 } 1432 1433 JSContext* cx = jsapi.cx(); 1434 Rooted<JSObject*> module(cx, moduleScript->ModuleRecord()); 1435 if (!xpc::Scriptability::AllowedIfExists(module)) { 1436 return true; 1437 } 1438 1439 if (!ModuleLink(jsapi.cx(), module)) { 1440 LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest)); 1441 MOZ_ASSERT(jsapi.HasException()); 1442 RootedValue exception(jsapi.cx()); 1443 if (!jsapi.StealException(&exception)) { 1444 return false; 1445 } 1446 MOZ_ASSERT(!exception.isUndefined()); 1447 moduleScript->SetErrorToRethrow(exception); 1448 } 1449 1450 return true; 1451 } 1452 1453 void ModuleLoaderBase::ProcessDynamicImport(ModuleLoadRequest* aRequest) { 1454 AutoJSAPI jsapi; 1455 if (!jsapi.Init(GetGlobalObject())) { 1456 return; 1457 } 1458 JSContext* cx = jsapi.cx(); 1459 MOZ_ASSERT(aRequest->IsDynamicImport()); 1460 1461 if (aRequest->IsErrored()) { 1462 LOG(("ScriptLoadRequest (%p): ProcessDynamicImport, request has an error", 1463 aRequest)); 1464 // The error is already processed in OnLoadRequestedModulesRejected. 1465 return; 1466 } 1467 1468 LOG(("ScriptLoadRequest (%p): ProcessDynamicImport", aRequest)); 1469 FinishLoadingImportedModule(cx, aRequest); 1470 1471 (void)mLoader->MaybePrepareModuleForDiskCacheAfterExecute(aRequest, NS_OK); 1472 1473 mLoader->MaybeUpdateDiskCache(); 1474 } 1475 1476 nsresult ModuleLoaderBase::EvaluateModule(ModuleLoadRequest* aRequest) { 1477 MOZ_ASSERT(aRequest->mLoader == this); 1478 1479 mozilla::nsAutoMicroTask mt; 1480 mozilla::dom::AutoEntryScript aes(mGlobalObject, "EvaluateModule", 1481 NS_IsMainThread()); 1482 1483 // We would like to handle any evaluation errors synchronously for service 1484 // workers immediately such that if we are performing service worker 1485 // registration we can fail it right away rather than having it fail later on 1486 // an event loop turn which might be too late and the registration process 1487 // would have succeeded by then. 1488 return EvaluateModuleInContext( 1489 aes.cx(), aRequest, 1490 IsForServiceWorker() ? ThrowModuleErrorsSync : ReportModuleErrorsAsync); 1491 } 1492 1493 nsresult ModuleLoaderBase::EvaluateModuleInContext( 1494 JSContext* aCx, ModuleLoadRequest* aRequest, 1495 ModuleErrorBehaviour errorBehaviour) { 1496 MOZ_ASSERT(aRequest->mLoader == this); 1497 MOZ_ASSERT_IF(!mGlobalObject->GetModuleLoader(aCx)->IsOverridden(), 1498 mGlobalObject->GetModuleLoader(aCx) == this); 1499 MOZ_ASSERT_IF(mGlobalObject->GetModuleLoader(aCx)->IsOverridden(), 1500 mGlobalObject->GetModuleLoader(aCx)->IsOverriddenBy(this)); 1501 MOZ_ASSERT(!aRequest->IsDynamicImport()); 1502 1503 AUTO_PROFILER_LABEL("ModuleLoaderBase::EvaluateModule", JS); 1504 1505 nsAutoCString profilerLabelString; 1506 if (aRequest->HasScriptLoadContext()) { 1507 aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); 1508 } 1509 1510 LOG(("ScriptLoadRequest (%p): Evaluate Module", aRequest)); 1511 AUTO_PROFILER_MARKER_TEXT("ModuleEvaluation", JS, 1512 MarkerInnerWindowIdFromJSContext(aCx), 1513 profilerLabelString); 1514 1515 MOZ_ASSERT(aRequest->mModuleScript); 1516 MOZ_ASSERT_IF(aRequest->HasScriptLoadContext(), 1517 !aRequest->GetScriptLoadContext()->mCompileOrDecodeTask); 1518 1519 ModuleScript* moduleScript = aRequest->mModuleScript; 1520 if (moduleScript->HasErrorToRethrow()) { 1521 LOG(("ScriptLoadRequest (%p): module has error to rethrow", aRequest)); 1522 Rooted<Value> error(aCx, moduleScript->ErrorToRethrow()); 1523 JS_SetPendingException(aCx, error); 1524 return NS_OK; 1525 } 1526 1527 Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord()); 1528 MOZ_ASSERT(module); 1529 MOZ_ASSERT(CurrentGlobalOrNull(aCx) == GetNonCCWObjectGlobal(module)); 1530 1531 if (!xpc::Scriptability::AllowedIfExists(module)) { 1532 return NS_OK; 1533 } 1534 1535 if (aRequest->HasScriptLoadContext()) { 1536 TRACE_FOR_TEST(aRequest, "evaluate:module"); 1537 } 1538 1539 Rooted<Value> rval(aCx); 1540 1541 bool ok = ModuleEvaluate(aCx, module, &rval); 1542 1543 // ModuleEvaluate will usually set a pending exception if it returns false, 1544 // unless the user cancels execution. 1545 MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aCx)); 1546 1547 nsresult rv = NS_OK; 1548 if (!ok || IsModuleEvaluationAborted(aRequest)) { 1549 LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest)); 1550 // For a dynamic import, the promise is rejected. Otherwise an error is 1551 // reported by AutoEntryScript. 1552 rv = NS_ERROR_ABORT; 1553 } 1554 1555 // ModuleEvaluate returns a promise unless the user cancels the execution in 1556 // which case rval will be undefined. We should treat it as a failed 1557 // evaluation, and reject appropriately. 1558 Rooted<JSObject*> evaluationPromise(aCx); 1559 if (rval.isObject()) { 1560 evaluationPromise.set(&rval.toObject()); 1561 } 1562 1563 // If the promise is rejected, the value is unwrapped from the promise value. 1564 if (!ThrowOnModuleEvaluationFailure(aCx, evaluationPromise, errorBehaviour)) { 1565 LOG(("ScriptLoadRequest (%p): evaluation failed on throw", aRequest)); 1566 1567 if (IsForServiceWorker()) { 1568 // If this module eval is being done for service workers, then we would 1569 // like to throw/return right from here, such that if we are in 1570 // registration phase, we can fail it synchronously right away. 1571 return NS_ERROR_ABORT; 1572 } 1573 } 1574 1575 rv = mLoader->MaybePrepareModuleForDiskCacheAfterExecute(aRequest, NS_OK); 1576 1577 mLoader->MaybeUpdateDiskCache(); 1578 1579 return rv; 1580 } 1581 1582 void ModuleLoaderBase::CancelAndClearDynamicImports() { 1583 while (ScriptLoadRequest* req = mDynamicImportRequests.getFirst()) { 1584 // This also removes the request from the list. 1585 CancelDynamicImport(req->AsModuleRequest(), NS_ERROR_ABORT); 1586 } 1587 } 1588 1589 UniquePtr<ImportMap> ModuleLoaderBase::ParseImportMap( 1590 ScriptLoadRequest* aRequest) { 1591 AutoJSAPI jsapi; 1592 if (!jsapi.Init(GetGlobalObject())) { 1593 return nullptr; 1594 } 1595 1596 MOZ_ASSERT(aRequest->IsTextSource()); 1597 MaybeSourceText maybeSource; 1598 nsresult rv = aRequest->GetScriptSource(jsapi.cx(), &maybeSource, 1599 aRequest->mLoadContext.get()); 1600 if (NS_FAILED(rv)) { 1601 return nullptr; 1602 } 1603 1604 SourceText<char16_t>& text = maybeSource.ref<SourceText<char16_t>>(); 1605 ReportWarningHelper warning{mLoader, aRequest}; 1606 1607 // https://html.spec.whatwg.org/multipage/webappapis.html#create-an-import-map-parse-result 1608 // Step 2. Parse an import map string given input and baseURL, catching any 1609 // exceptions. If this threw an exception, then set result's error to rethrow 1610 // to that exception. Otherwise, set result's import map to the return value. 1611 // 1612 // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map 1613 // Step 1. If result's error to rethrow is not null, then report the exception 1614 // given by result's error to rethrow and return. 1615 // 1616 // Impl note: We didn't implement 'Import map parse result' from the spec, 1617 // https://html.spec.whatwg.org/multipage/webappapis.html#import-map-parse-result 1618 // As the struct has another item called 'error to rethow' to store the 1619 // exception thrown during parsing import-maps, and report that exception 1620 // while registering an import map. Currently only inline import-maps are 1621 // supported, therefore parsing and registering import-maps will be executed 1622 // consecutively. To simplify the implementation, we didn't create the 'error 1623 // to rethow' item and report the exception immediately(done in ~AutoJSAPI). 1624 return ImportMap::ParseString(jsapi.cx(), text, aRequest->BaseURL(), warning); 1625 } 1626 1627 void ModuleLoaderBase::RegisterImportMap(UniquePtr<ImportMap> aImportMap) { 1628 // Check for aImportMap is done in ScriptLoader. 1629 MOZ_ASSERT(aImportMap); 1630 1631 // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map 1632 // The step 1(report the exception if there's an error) is done in 1633 // ParseImportMap. 1634 // 1635 // Step 2. Assert: global's import map is an empty import map. 1636 // Impl note: The default import map from the spec is an empty import map, but 1637 // from the implementation it defaults to nullptr, so we check if the global's 1638 // import map is null here. 1639 // 1640 // Also see 1641 // https://html.spec.whatwg.org/multipage/webappapis.html#empty-import-map 1642 MOZ_ASSERT(!mImportMap); 1643 1644 // Step 3. Set global's import map to result's import map. 1645 mImportMap = std::move(aImportMap); 1646 1647 // Any import resolution has been invalidated by the addition of the import 1648 // map. If speculative preloading is currently fetching any modules then 1649 // cancel their requests and remove them from the map. 1650 // 1651 // The cancelled requests will still complete later so we have to check this 1652 // in SetModuleFetchFinishedAndGetWaitingRequests. 1653 for (const auto& entry : mFetchingModules) { 1654 LoadingRequest* loadingRequest = entry.GetData(); 1655 MOZ_DIAGNOSTIC_ASSERT(loadingRequest->mRequest->mLoadContext->IsPreload()); 1656 loadingRequest->mRequest->Cancel(); 1657 for (const auto& request : loadingRequest->mWaiting) { 1658 MOZ_DIAGNOSTIC_ASSERT(request->mLoadContext->IsPreload()); 1659 request->Cancel(); 1660 } 1661 } 1662 mFetchingModules.Clear(); 1663 1664 // If speculative preloading has added modules to the module map, remove 1665 // them. 1666 for (const auto& entry : mFetchedModules) { 1667 ModuleScript* script = entry.GetData(); 1668 if (script) { 1669 MOZ_DIAGNOSTIC_ASSERT( 1670 script->ForPreload(), 1671 "Non-preload module loads should block import maps"); 1672 MOZ_DIAGNOSTIC_ASSERT(!script->HadImportMap(), 1673 "Only one import map can be registered"); 1674 #if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) 1675 if (JSObject* module = script->ModuleRecord()) { 1676 MOZ_DIAGNOSTIC_ASSERT(!ModuleIsLinked(module)); 1677 } 1678 #endif 1679 script->Shutdown(); 1680 } 1681 } 1682 mFetchedModules.Clear(); 1683 } 1684 1685 void ModuleLoaderBase::CopyModulesTo(ModuleLoaderBase* aDest) { 1686 MOZ_ASSERT(aDest->mFetchingModules.IsEmpty()); 1687 MOZ_ASSERT(aDest->mFetchedModules.IsEmpty()); 1688 MOZ_ASSERT(mFetchingModules.IsEmpty()); 1689 1690 for (const auto& entry : mFetchedModules) { 1691 RefPtr<ModuleScript> moduleScript = entry.GetData(); 1692 if (!moduleScript) { 1693 continue; 1694 } 1695 aDest->mFetchedModules.InsertOrUpdate(entry, moduleScript); 1696 } 1697 } 1698 1699 void ModuleLoaderBase::MoveModulesTo(ModuleLoaderBase* aDest) { 1700 MOZ_ASSERT(mFetchingModules.IsEmpty()); 1701 MOZ_ASSERT(aDest->mFetchingModules.IsEmpty()); 1702 1703 for (const auto& entry : mFetchedModules) { 1704 RefPtr<ModuleScript> moduleScript = entry.GetData(); 1705 if (!moduleScript) { 1706 continue; 1707 } 1708 1709 #ifdef DEBUG 1710 if (auto existingEntry = aDest->mFetchedModules.Lookup(entry)) { 1711 MOZ_ASSERT(moduleScript == existingEntry.Data()); 1712 } 1713 #endif 1714 1715 aDest->mFetchedModules.InsertOrUpdate(entry, moduleScript); 1716 } 1717 1718 mFetchedModules.Clear(); 1719 } 1720 1721 #undef LOG 1722 #undef LOG_ENABLED 1723 1724 } // namespace JS::loader