ScriptLoader.cpp (179517B)
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 "ScriptLoader.h" 8 9 #include "GeckoProfiler.h" 10 #include "ModuleLoader.h" 11 #include "ReferrerInfo.h" 12 #include "ScriptCompression.h" 13 #include "ScriptLoadHandler.h" 14 #include "ScriptTrace.h" 15 #include "SharedScriptCache.h" 16 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin 17 #include "js/CompilationAndEvaluation.h" 18 #include "js/CompileOptions.h" // JS::CompileOptions, JS::OwningCompileOptions, JS::DecodeOptions, JS::OwningDecodeOptions, JS::DelazificationOption 19 #include "js/ContextOptions.h" // JS::ContextOptionsRef 20 #include "js/MemoryFunctions.h" 21 #include "js/Modules.h" 22 #include "js/PropertyAndElement.h" // JS_DefineProperty 23 #include "js/Transcoding.h" // JS::TranscodeRange, JS::TranscodeResult, JS::IsTranscodeFailureResult 24 #include "js/Utility.h" 25 #include "js/experimental/CompileScript.h" // JS::FrontendContext, JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::ThreadStackQuotaForSize, JS::CompilationStorage, JS::CompileGlobalScriptToStencil, JS::CompileModuleScriptToStencil, JS::DecodeStencil, JS::PrepareForInstantiate 26 #include "js/experimental/JSStencil.h" // JS::Stencil, JS::InstantiationStorage, JS::StartCollectingDelazifications, JS::IsStencilCacheable 27 #include "js/loader/LoadedScript.h" 28 #include "js/loader/ModuleLoadRequest.h" 29 #include "js/loader/ModuleLoaderBase.h" 30 #include "js/loader/ScriptLoadRequest.h" 31 #include "mozilla/Assertions.h" 32 #include "mozilla/AsyncEventDispatcher.h" 33 #include "mozilla/Attributes.h" 34 #include "mozilla/ConsoleReportCollector.h" 35 #include "mozilla/CycleCollectedJSContext.h" 36 #include "mozilla/EventQueue.h" 37 #include "mozilla/LoadInfo.h" 38 #include "mozilla/Logging.h" 39 #include "mozilla/Maybe.h" 40 #include "mozilla/Mutex.h" // mozilla::Mutex 41 #include "mozilla/ScopeExit.h" 42 #include "mozilla/StaticPrefs_dom.h" 43 #include "mozilla/StaticPrefs_javascript.h" 44 #include "mozilla/StaticPrefs_network.h" 45 #include "mozilla/TaskController.h" 46 #include "mozilla/Telemetry.h" 47 #include "mozilla/TimeStamp.h" 48 #include "mozilla/UniquePtr.h" 49 #include "mozilla/Utf8.h" // mozilla::Utf8Unit 50 #include "mozilla/dom/AutoEntryScript.h" 51 #include "mozilla/dom/DocGroup.h" 52 #include "mozilla/dom/DocumentInlines.h" // Document::GetPresContext 53 #include "mozilla/dom/Element.h" 54 #include "mozilla/dom/FetchPriority.h" 55 #include "mozilla/dom/JSExecutionUtils.h" // mozilla::dom::Compile, mozilla::dom::InstantiateStencil, mozilla::dom::EvaluationExceptionToNSResult 56 #include "mozilla/dom/PolicyContainer.h" 57 #include "mozilla/dom/RequestBinding.h" 58 #include "mozilla/dom/SRILogHelper.h" 59 #include "mozilla/dom/ScriptDecoding.h" // mozilla::dom::ScriptDecoding 60 #include "mozilla/dom/ScriptSettings.h" 61 #include "mozilla/dom/WindowContext.h" 62 #include "mozilla/glean/DomMetrics.h" 63 #include "mozilla/net/HttpBaseChannel.h" 64 #include "mozilla/net/UrlClassifierFeatureFactory.h" 65 #include "nsAboutProtocolUtils.h" 66 #include "nsCRT.h" 67 #include "nsContentCreatorFunctions.h" 68 #include "nsContentPolicyUtils.h" 69 #include "nsContentSecurityManager.h" 70 #include "nsContentSecurityUtils.h" 71 #include "nsContentUtils.h" 72 #include "nsCycleCollectionParticipant.h" 73 #include "nsError.h" 74 #include "nsGenericHTMLElement.h" 75 #include "nsGkAtoms.h" 76 #include "nsIAsyncOutputStream.h" 77 #include "nsICacheInfoChannel.h" 78 #include "nsIClassOfService.h" 79 #include "nsIClassifiedChannel.h" 80 #include "nsIContent.h" 81 #include "nsIContentSecurityPolicy.h" 82 #include "nsIDocShell.h" 83 #include "nsIHttpChannel.h" 84 #include "nsIHttpChannelInternal.h" 85 #include "nsIPrincipal.h" 86 #include "nsIScriptContext.h" 87 #include "nsIScriptElement.h" 88 #include "nsIScriptError.h" 89 #include "nsIScriptGlobalObject.h" 90 #include "nsISupportsPriority.h" 91 #include "nsITimedChannel.h" 92 #include "nsITimer.h" 93 #include "nsJSPrincipals.h" 94 #include "nsJSUtils.h" 95 #include "nsNetUtil.h" 96 #include "nsPresContext.h" // nsPresContext 97 #include "nsProxyRelease.h" 98 #include "nsQueryObject.h" 99 #include "nsThreadUtils.h" 100 #include "nsUnicharUtils.h" 101 #include "prsystem.h" 102 #include "xpcpublic.h" 103 104 using namespace JS::loader; 105 106 namespace mozilla::dom { 107 108 LazyLogModule ScriptLoader::gCspPRLog("CSP"); 109 LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader"); 110 111 #undef LOG 112 #define LOG(args) \ 113 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args) 114 115 #define LOG_ENABLED() \ 116 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug) 117 118 // Alternate Data MIME type used by the ScriptLoader to register that we want to 119 // store the disk cache without reading it. 120 static constexpr auto kNullMimeType = "javascript/null"_ns; 121 122 ///////////////////////////////////////////////////////////// 123 // AsyncCompileShutdownObserver 124 ///////////////////////////////////////////////////////////// 125 126 NS_IMPL_ISUPPORTS(AsyncCompileShutdownObserver, nsIObserver) 127 128 void AsyncCompileShutdownObserver::OnShutdown() { 129 if (mScriptLoader) { 130 mScriptLoader->Destroy(); 131 MOZ_ASSERT(!mScriptLoader); 132 } 133 } 134 135 void AsyncCompileShutdownObserver::Unregister() { 136 if (mScriptLoader) { 137 mScriptLoader = nullptr; 138 nsContentUtils::UnregisterShutdownObserver(this); 139 } 140 } 141 142 NS_IMETHODIMP 143 AsyncCompileShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, 144 const char16_t* aData) { 145 OnShutdown(); 146 return NS_OK; 147 } 148 149 ////////////////////////////////////////////////////////////// 150 // ScriptLoader::PreloadInfo 151 ////////////////////////////////////////////////////////////// 152 153 inline void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField) { 154 ImplCycleCollectionUnlink(aField.mRequest); 155 } 156 157 inline void ImplCycleCollectionTraverse( 158 nsCycleCollectionTraversalCallback& aCallback, 159 ScriptLoader::PreloadInfo& aField, const char* aName, uint32_t aFlags = 0) { 160 ImplCycleCollectionTraverse(aCallback, aField.mRequest, aName, aFlags); 161 } 162 163 ////////////////////////////////////////////////////////////// 164 // ScriptLoader 165 ////////////////////////////////////////////////////////////// 166 167 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoader) 168 NS_INTERFACE_MAP_END 169 170 NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptLoader) 171 172 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptLoader) 173 if (tmp->mDocument) { 174 tmp->DropDocumentReference(); 175 } 176 NS_IMPL_CYCLE_COLLECTION_UNLINK( 177 mNonAsyncExternalScriptInsertedRequests, mLoadingAsyncRequests, 178 mLoadedAsyncRequests, mOffThreadCompilingRequests, mDeferRequests, 179 mXSLTRequests, mParserBlockingRequest, mDiskCacheQueue, mPreloads, 180 mPendingChildLoaders, mModuleLoader, mWebExtModuleLoaders, 181 mShadowRealmModuleLoaders) 182 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 183 184 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptLoader) 185 NS_IMPL_CYCLE_COLLECTION_TRAVERSE( 186 mNonAsyncExternalScriptInsertedRequests, mLoadingAsyncRequests, 187 mLoadedAsyncRequests, mOffThreadCompilingRequests, mDeferRequests, 188 mXSLTRequests, mParserBlockingRequest, mDiskCacheQueue, mPreloads, 189 mPendingChildLoaders, mModuleLoader, mWebExtModuleLoaders, 190 mShadowRealmModuleLoaders) 191 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 192 193 NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader) 194 NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader) 195 196 ScriptLoader::ScriptLoader(Document* aDocument) 197 : mDocument(aDocument), 198 mParserBlockingBlockerCount(0), 199 mBlockerCount(0), 200 mNumberOfProcessors(0), 201 mTotalFullParseSize(0), 202 mPhysicalSizeOfMemory(-1), 203 mEnabled(true), 204 mDeferEnabled(false), 205 mSpeculativeOMTParsingEnabled(false), 206 mDeferCheckpointReached(false), 207 mBlockingDOMContentLoaded(false), 208 mLoadEventFired(false), 209 mGiveUpDiskCaching(false), 210 mContinueParsingDocumentAfterCurrentScript(false), 211 mHadFCPDoNotUseDirectly(false), 212 mReporter(new ConsoleReportCollector()) { 213 LOG(("ScriptLoader::ScriptLoader %p", this)); 214 215 mSpeculativeOMTParsingEnabled = StaticPrefs:: 216 dom_script_loader_external_scripts_speculative_omt_parse_enabled(); 217 218 #ifdef NIGHTLY_BUILD 219 // NOTE: The loader for the system principal aren't supposed to 220 // load remote contents, and it doesn't have to use the in-memory cache. 221 // A non-system-principal document can also load internal resources, 222 // and those cases should be filtered out by 223 // ScriptLoader::GetCacheBehavior. 224 if (!LoaderPrincipal()->IsSystemPrincipal() && 225 StaticPrefs::dom_script_loader_experimental_navigation_cache()) { 226 mCache = SharedScriptCache::Get(); 227 RegisterToCache(); 228 LOG(("ScriptLoader (%p): Using in-memory cache.", this)); 229 } 230 #endif 231 232 mShutdownObserver = new AsyncCompileShutdownObserver(this); 233 nsContentUtils::RegisterShutdownObserver(mShutdownObserver); 234 } 235 236 ScriptLoader::~ScriptLoader() { 237 LOG(("ScriptLoader::~ScriptLoader %p", this)); 238 239 mObservers.Clear(); 240 241 if (mParserBlockingRequest) { 242 FireScriptAvailable(NS_ERROR_ABORT, mParserBlockingRequest); 243 } 244 245 for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req; 246 req = req->getNext()) { 247 FireScriptAvailable(NS_ERROR_ABORT, req); 248 } 249 250 for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req; 251 req = req->getNext()) { 252 FireScriptAvailable(NS_ERROR_ABORT, req); 253 } 254 255 for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req; 256 req = req->getNext()) { 257 FireScriptAvailable(NS_ERROR_ABORT, req); 258 } 259 260 for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req; 261 req = req->getNext()) { 262 FireScriptAvailable(NS_ERROR_ABORT, req); 263 } 264 265 for (ScriptLoadRequest* req = 266 mNonAsyncExternalScriptInsertedRequests.getFirst(); 267 req; req = req->getNext()) { 268 FireScriptAvailable(NS_ERROR_ABORT, req); 269 } 270 271 // Unblock the kids, in case any of them moved to a different document 272 // subtree in the meantime and therefore aren't actually going away. 273 for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) { 274 mPendingChildLoaders[j]->RemoveParserBlockingScriptExecutionBlocker(); 275 } 276 277 if (mShutdownObserver) { 278 mShutdownObserver->Unregister(); 279 mShutdownObserver = nullptr; 280 } 281 282 mModuleLoader = nullptr; 283 284 if (mProcessPendingRequestsAsyncBypassParserBlocking) { 285 mProcessPendingRequestsAsyncBypassParserBlocking->Cancel(); 286 } 287 } 288 289 void ScriptLoader::SetGlobalObject(nsIGlobalObject* aGlobalObject) { 290 if (!aGlobalObject) { 291 // The document is being detached. 292 CancelAndClearScriptLoadRequests(); 293 return; 294 } 295 296 MOZ_ASSERT(!HasPendingRequests()); 297 298 if (!mModuleLoader) { 299 // The module loader is associated with a global object, so don't create it 300 // until we have a global set. 301 mModuleLoader = new ModuleLoader(this, aGlobalObject, ModuleLoader::Normal); 302 } 303 304 MOZ_ASSERT(mModuleLoader->GetGlobalObject() == aGlobalObject); 305 MOZ_ASSERT(aGlobalObject->GetModuleLoader(dom::danger::GetJSContext()) == 306 mModuleLoader); 307 } 308 309 void ScriptLoader::DropDocumentReference() { 310 if (mDocument && mCache) { 311 DeregisterFromCache(); 312 } 313 314 mDocument = nullptr; 315 } 316 317 void ScriptLoader::RegisterToCache() { 318 if (mCache) { 319 MOZ_ASSERT(mDocument); 320 mCache->RegisterLoader(*this); 321 } 322 } 323 324 void ScriptLoader::DeregisterFromCache() { 325 if (mCache) { 326 MOZ_ASSERT(mDocument); 327 mCache->CancelLoadsForLoader(*this); 328 mCache->UnregisterLoader(*this); 329 } 330 } 331 332 nsIPrincipal* ScriptLoader::LoaderPrincipal() const { 333 return mDocument->NodePrincipal(); 334 } 335 336 nsIPrincipal* ScriptLoader::PartitionedPrincipal() const { 337 return mDocument->PartitionedPrincipal(); 338 } 339 340 bool ScriptLoader::ShouldBypassCache() const { 341 return mDocument && nsContentUtils::ShouldBypassSubResourceCache(mDocument); 342 } 343 344 void ScriptLoader::RegisterContentScriptModuleLoader(ModuleLoader* aLoader) { 345 MOZ_ASSERT(aLoader); 346 MOZ_ASSERT(aLoader->GetScriptLoader() == this); 347 348 mWebExtModuleLoaders.AppendElement(aLoader); 349 } 350 351 void ScriptLoader::RegisterShadowRealmModuleLoader(ModuleLoader* aLoader) { 352 MOZ_ASSERT(aLoader); 353 MOZ_ASSERT(aLoader->GetScriptLoader() == this); 354 355 mShadowRealmModuleLoaders.AppendElement(aLoader); 356 } 357 358 // Collect telemtry data about the cache information, and the kind of source 359 // which are being loaded, and where it is being loaded from. 360 static void CollectScriptTelemetry(ScriptLoadRequest* aRequest) { 361 using namespace mozilla::glean::dom; 362 363 MOZ_ASSERT(aRequest->IsFetching()); 364 365 // Skip this function if we are not running telemetry. 366 if (!mozilla::Telemetry::CanRecordExtended()) { 367 return; 368 } 369 370 // Report the type of source. This is used to monitor the status of the 371 // JavaScript Start-up Bytecode Cache, with the expectation of an almost zero 372 // source-fallback and alternate-data being roughtly equal to source loads. 373 if (aRequest->mFetchSourceOnly) { 374 if (aRequest->GetScriptLoadContext()->mIsInline) { 375 script_loading_source.EnumGet(ScriptLoadingSourceLabel::eInline).Add(); 376 } else if (aRequest->IsTextSource()) { 377 script_loading_source.EnumGet(ScriptLoadingSourceLabel::eSourcefallback) 378 .Add(); 379 } 380 } else { 381 if (aRequest->IsTextSource()) { 382 script_loading_source.EnumGet(ScriptLoadingSourceLabel::eSource).Add(); 383 } else if (aRequest->IsSerializedStencil()) { 384 script_loading_source.EnumGet(ScriptLoadingSourceLabel::eAltdata).Add(); 385 } 386 } 387 } 388 389 // Helper method for checking if the script element is an event-handler 390 // This means that it has both a for-attribute and a event-attribute. 391 // Also, if the for-attribute has a value that matches "\s*window\s*", 392 // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an 393 // eventhandler. (both matches are case insensitive). 394 // This is how IE seems to filter out a window's onload handler from a 395 // <script for=... event=...> element. 396 397 static bool IsScriptEventHandler(ScriptKind kind, nsIContent* aScriptElement) { 398 if (kind != ScriptKind::eClassic) { 399 return false; 400 } 401 402 if (!aScriptElement->IsHTMLElement()) { 403 return false; 404 } 405 406 nsAutoString forAttr, eventAttr; 407 if (!aScriptElement->AsElement()->GetAttr(nsGkAtoms::_for, forAttr) || 408 !aScriptElement->AsElement()->GetAttr(nsGkAtoms::event, eventAttr)) { 409 return false; 410 } 411 412 const nsAString& for_str = 413 nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(forAttr); 414 if (!for_str.LowerCaseEqualsLiteral("window")) { 415 return true; 416 } 417 418 // We found for="window", now check for event="onload". 419 const nsAString& event_str = 420 nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>( 421 eventAttr); 422 if (!event_str.LowerCaseEqualsLiteral("onload") && 423 !event_str.LowerCaseEqualsLiteral("onload()")) { 424 return true; 425 } 426 427 // If the `for` attribute has the value "window" and the `event` attribute is 428 // either "onload" or "onload()", then it isn't an event handler. 429 return false; 430 } 431 432 nsContentPolicyType ScriptLoadRequestToContentPolicyType( 433 ScriptLoadRequest* aRequest) { 434 if (aRequest->GetScriptLoadContext()->IsPreload()) { 435 if (aRequest->IsModuleRequest()) { 436 switch (aRequest->AsModuleRequest()->mModuleType) { 437 case JS::ModuleType::JavaScript: 438 return nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD; 439 case JS::ModuleType::JSON: 440 return nsIContentPolicy::TYPE_INTERNAL_JSON_PRELOAD; 441 case JS::ModuleType::CSS: 442 return nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD; 443 case JS::ModuleType::Bytes: 444 case JS::ModuleType::Unknown: 445 MOZ_ASSERT_UNREACHABLE("Unknown module type"); 446 } 447 } 448 449 return nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD; 450 } 451 452 if (aRequest->IsModuleRequest()) { 453 switch (aRequest->AsModuleRequest()->mModuleType) { 454 case JS::ModuleType::Unknown: 455 case JS::ModuleType::Bytes: 456 MOZ_CRASH("Unexpected module type"); 457 case JS::ModuleType::JavaScript: 458 return nsIContentPolicy::TYPE_INTERNAL_MODULE; 459 case JS::ModuleType::JSON: 460 return nsIContentPolicy::TYPE_JSON; 461 case JS::ModuleType::CSS: 462 return nsIContentPolicy::TYPE_STYLESHEET; 463 } 464 } 465 466 return nsIContentPolicy::TYPE_INTERNAL_SCRIPT; 467 } 468 469 RequestMode ComputeRequestModeForContentPolicy( 470 const ScriptLoadRequest* aRequest, ScriptFetchOptions* aFetchOptions) { 471 auto corsMapping = 472 aRequest->IsModuleRequest() 473 ? nsContentSecurityManager::REQUIRE_CORS_CHECKS 474 : nsContentSecurityManager::CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS; 475 return nsContentSecurityManager::SecurityModeToRequestMode( 476 nsContentSecurityManager::ComputeSecurityMode( 477 nsContentSecurityManager::ComputeSecurityFlags( 478 aFetchOptions->mCORSMode, corsMapping))); 479 } 480 481 nsresult ScriptLoader::CheckContentPolicy(nsIScriptElement* aElement, 482 const nsAString& aNonce, 483 ScriptLoadRequest* aRequest, 484 ScriptFetchOptions* aFetchOptions, 485 nsIURI* aURI) { 486 MOZ_ASSERT(aRequest); 487 MOZ_ASSERT(aFetchOptions); 488 MOZ_ASSERT(aURI); 489 490 nsContentPolicyType contentPolicyType = 491 ScriptLoadRequestToContentPolicyType(aRequest); 492 493 nsCOMPtr<nsINode> requestingNode; 494 if (aElement) { 495 requestingNode = do_QueryInterface(aElement); 496 } 497 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = MOZ_TRY(net::LoadInfo::Create( 498 mDocument->NodePrincipal(), // loading principal 499 mDocument->NodePrincipal(), // triggering principal 500 requestingNode, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, 501 contentPolicyType)); 502 secCheckLoadInfo->SetParserCreatedScript(aElement && 503 aElement->GetParserCreated() != 504 mozilla::dom::NOT_FROM_PARSER); 505 Maybe<RequestMode> requestMode = 506 Some(ComputeRequestModeForContentPolicy(aRequest, aFetchOptions)); 507 secCheckLoadInfo->SetRequestMode(requestMode); 508 // Use nonce of the current element, instead of the preload, because those 509 // are allowed to differ. 510 secCheckLoadInfo->SetCspNonce(aNonce); 511 secCheckLoadInfo->SetIntegrityMetadata( 512 aRequest->mIntegrity.GetIntegrityString()); 513 514 int16_t shouldLoad = nsIContentPolicy::ACCEPT; 515 nsresult rv = NS_CheckContentLoadPolicy(aURI, secCheckLoadInfo, &shouldLoad, 516 nsContentUtils::GetContentPolicy()); 517 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { 518 if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) { 519 return NS_ERROR_CONTENT_BLOCKED; 520 } 521 return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; 522 } 523 524 return NS_OK; 525 } 526 527 /* static */ 528 bool ScriptLoader::IsAboutPageLoadingChromeURI(ScriptLoadRequest* aRequest, 529 Document* aDocument) { 530 // if the uri to be loaded is not of scheme chrome:, there is nothing to do. 531 if (!aRequest->URI()->SchemeIs("chrome")) { 532 return false; 533 } 534 535 // we can either get here with a regular contentPrincipal or with a 536 // NullPrincipal in case we are showing an error page in a sandboxed iframe. 537 // In either case if the about: page is linkable from content, there is 538 // nothing to do. 539 uint32_t aboutModuleFlags = 0; 540 nsresult rv = NS_OK; 541 542 nsCOMPtr<nsIPrincipal> triggeringPrincipal = aRequest->TriggeringPrincipal(); 543 if (triggeringPrincipal->GetIsContentPrincipal()) { 544 if (!triggeringPrincipal->SchemeIs("about")) { 545 return false; 546 } 547 rv = triggeringPrincipal->GetAboutModuleFlags(&aboutModuleFlags); 548 NS_ENSURE_SUCCESS(rv, false); 549 } else if (triggeringPrincipal->GetIsNullPrincipal()) { 550 nsCOMPtr<nsIURI> docURI = aDocument->GetDocumentURI(); 551 if (!docURI->SchemeIs("about")) { 552 return false; 553 } 554 555 nsCOMPtr<nsIAboutModule> aboutModule; 556 rv = NS_GetAboutModule(docURI, getter_AddRefs(aboutModule)); 557 if (NS_FAILED(rv) || !aboutModule) { 558 return false; 559 } 560 rv = aboutModule->GetURIFlags(docURI, &aboutModuleFlags); 561 NS_ENSURE_SUCCESS(rv, false); 562 } else { 563 return false; 564 } 565 566 if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) { 567 return false; 568 } 569 570 // seems like an about page wants to load a chrome URI. 571 return true; 572 } 573 574 nsIURI* ScriptLoader::GetBaseURI() const { 575 MOZ_ASSERT(mDocument); 576 return mDocument->GetDocBaseURI(); 577 } 578 579 class ScriptRequestProcessor : public Runnable { 580 private: 581 RefPtr<ScriptLoader> mLoader; 582 RefPtr<ScriptLoadRequest> mRequest; 583 584 public: 585 ScriptRequestProcessor(ScriptLoader* aLoader, ScriptLoadRequest* aRequest) 586 : Runnable("dom::ScriptRequestProcessor"), 587 mLoader(aLoader), 588 mRequest(aRequest) {} 589 NS_IMETHOD Run() override { return mLoader->ProcessRequest(mRequest); } 590 }; 591 592 void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) { 593 auto* runnable = new ScriptRequestProcessor(this, aRequest); 594 nsContentUtils::AddScriptRunner(runnable); 595 } 596 597 nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) { 598 aRequest->DropSRIOrSRIAndSerializedStencil(); 599 TRACE_FOR_TEST(aRequest, "load:fallback"); 600 601 // Notify preload restart so that we can register this preload request again. 602 aRequest->GetScriptLoadContext()->NotifyRestart(mDocument); 603 604 // Start a new channel from which we explicitly request to stream the source 605 // instead of the serialized stencil. 606 aRequest->mFetchSourceOnly = true; 607 nsresult rv; 608 if (aRequest->IsModuleRequest()) { 609 rv = aRequest->AsModuleRequest()->RestartModuleLoad(); 610 } else { 611 rv = StartLoad(aRequest, Nothing()); 612 } 613 if (NS_FAILED(rv)) { 614 return rv; 615 } 616 617 // Close the current channel and this ScriptLoadHandler as we created a new 618 // one for the same request. 619 return NS_BINDING_RETARGETED; 620 } 621 622 nsresult ScriptLoader::StartLoad( 623 ScriptLoadRequest* aRequest, 624 const Maybe<nsAutoString>& aCharsetForPreload) { 625 if (aRequest->IsModuleRequest()) { 626 return aRequest->AsModuleRequest()->StartModuleLoad(); 627 } 628 629 return StartClassicLoad(aRequest, aCharsetForPreload); 630 } 631 632 static nsSecurityFlags CORSModeToSecurityFlags(CORSMode aCORSMode) { 633 nsSecurityFlags securityFlags = 634 nsContentSecurityManager::ComputeSecurityFlags( 635 aCORSMode, nsContentSecurityManager::CORSSecurityMapping:: 636 CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS); 637 638 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME; 639 640 return securityFlags; 641 } 642 643 nsresult ScriptLoader::StartClassicLoad( 644 ScriptLoadRequest* aRequest, 645 const Maybe<nsAutoString>& aCharsetForPreload) { 646 if (aRequest->IsCachedStencil()) { 647 EmulateNetworkEvents(aRequest); 648 return NS_OK; 649 } 650 651 MOZ_ASSERT(aRequest->IsFetching()); 652 NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER); 653 aRequest->SetUnknownDataType(); 654 655 // If this document is sandboxed without 'allow-scripts', abort. 656 if (mDocument->HasScriptsBlockedBySandbox()) { 657 return NS_OK; 658 } 659 660 if (LOG_ENABLED()) { 661 nsAutoCString url; 662 aRequest->URI()->GetAsciiSpec(url); 663 LOG(("ScriptLoadRequest (%p): Start Classic Load (url = %s)", aRequest, 664 url.get())); 665 } 666 667 nsSecurityFlags securityFlags = CORSModeToSecurityFlags(aRequest->CORSMode()); 668 669 nsresult rv = StartLoadInternal(aRequest, securityFlags, aCharsetForPreload); 670 671 NS_ENSURE_SUCCESS(rv, rv); 672 673 return NS_OK; 674 } 675 676 static bool IsWebExtensionRequest(ScriptLoadRequest* aRequest) { 677 if (!aRequest->IsModuleRequest()) { 678 return false; 679 } 680 681 ModuleLoader* loader = 682 ModuleLoader::From(aRequest->AsModuleRequest()->mLoader); 683 return loader->GetKind() == ModuleLoader::WebExtension; 684 } 685 686 static nsresult CreateChannelForScriptLoading( 687 nsIChannel** aOutChannel, Document* aDocument, nsIURI* aURI, 688 nsINode* aContext, nsIPrincipal* aTriggeringPrincipal, 689 nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType) { 690 nsCOMPtr<nsILoadGroup> loadGroup = aDocument->GetDocumentLoadGroup(); 691 nsCOMPtr<nsPIDOMWindowOuter> window = aDocument->GetWindow(); 692 NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER); 693 nsIDocShell* docshell = window->GetDocShell(); 694 nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell)); 695 696 return NS_NewChannelWithTriggeringPrincipal( 697 aOutChannel, aURI, aContext, aTriggeringPrincipal, aSecurityFlags, 698 aContentPolicyType, 699 /* aPerformanceStorage = */ nullptr, loadGroup, prompter); 700 } 701 702 static nsresult CreateChannelForScriptLoading(nsIChannel** aOutChannel, 703 Document* aDocument, 704 ScriptLoadRequest* aRequest, 705 nsSecurityFlags aSecurityFlags) { 706 nsContentPolicyType contentPolicyType = 707 ScriptLoadRequestToContentPolicyType(aRequest); 708 nsCOMPtr<nsINode> context; 709 if (aRequest->GetScriptLoadContext()->HasScriptElement()) { 710 context = do_QueryInterface( 711 aRequest->GetScriptLoadContext()->GetScriptElementForLoadingNode()); 712 } else { 713 context = aDocument; 714 } 715 716 return CreateChannelForScriptLoading(aOutChannel, aDocument, aRequest->URI(), 717 context, aRequest->TriggeringPrincipal(), 718 aSecurityFlags, contentPolicyType); 719 } 720 721 static void PrepareLoadInfoForScriptLoading(nsIChannel* aChannel, 722 const ScriptLoadRequest* aRequest) { 723 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 724 loadInfo->SetParserCreatedScript(aRequest->ParserMetadata() == 725 ParserMetadata::ParserInserted); 726 loadInfo->SetCspNonce(aRequest->Nonce()); 727 loadInfo->SetIntegrityMetadata(aRequest->mIntegrity.GetIntegrityString()); 728 } 729 730 // static 731 void ScriptLoader::PrepareCacheInfoChannel(nsIChannel* aChannel, 732 ScriptLoadRequest* aRequest) { 733 // To avoid decoding issues, the build-id is part of the disk cache MIME type 734 // constant. 735 aRequest->getLoadedScript()->DropDiskCacheReference(); 736 nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(aChannel)); 737 if (cic && StaticPrefs::dom_script_loader_bytecode_cache_enabled()) { 738 MOZ_ASSERT(!IsWebExtensionRequest(aRequest), 739 "Web extension scripts are not compatible with the disk cache"); 740 if (!aRequest->mFetchSourceOnly) { 741 // Inform the HTTP cache that we prefer to have information coming from 742 // the serialized stencil disk cache instead of the sources, if such entry 743 // is already registered. 744 LOG(("ScriptLoadRequest (%p): Maybe request the disk cache", aRequest)); 745 cic->PreferAlternativeDataType( 746 ScriptLoader::BytecodeMimeTypeFor(aRequest), ""_ns, 747 nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC); 748 } else { 749 // If we are explicitly loading from the sources, such as after a 750 // restarted request, we might still want to save to the disk cache after. 751 // 752 // The following tell the cache to look for an alternative data type which 753 // does not exist, such that we can later save the serialized Stencil 754 // with a different alternative data type. 755 LOG(("ScriptLoadRequest (%p): Request saving to the disk cache later", 756 aRequest)); 757 cic->PreferAlternativeDataType( 758 kNullMimeType, ""_ns, 759 nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC); 760 } 761 } 762 } 763 764 static void AdjustPriorityAndClassOfServiceForLinkPreloadScripts( 765 nsIChannel* aChannel, ScriptLoadRequest* aRequest) { 766 MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsLinkPreloadScript()); 767 768 // Put it to the group that is not blocked by leaders and doesn't block 769 // follower at the same time. 770 // Giving it a much higher priority will make this request be processed 771 // ahead of other Unblocked requests, but with the same weight as 772 // Leaders. This will make us behave similar way for both http2 and http1. 773 ScriptLoadContext::PrioritizeAsPreload(aChannel); 774 775 if (!StaticPrefs::network_fetchpriority_enabled()) { 776 return; 777 } 778 779 const auto fetchPriority = ToFetchPriority(aRequest->FetchPriority()); 780 if (nsCOMPtr<nsISupportsPriority> supportsPriority = 781 do_QueryInterface(aChannel)) { 782 LOG(("Is <link rel=[module]preload")); 783 784 // The spec defines the priority to be set in an implementation defined 785 // manner (<https://fetch.spec.whatwg.org/#concept-fetch>, step 15 and 786 // <https://html.spec.whatwg.org/#concept-script-fetch-options-fetch-priority>). 787 // See corresponding preferences in StaticPrefList.yaml for more context. 788 const int32_t supportsPriorityDelta = 789 FETCH_PRIORITY_ADJUSTMENT_FOR(link_preload_script, fetchPriority); 790 supportsPriority->AdjustPriority(supportsPriorityDelta); 791 #ifdef DEBUG 792 int32_t adjustedPriority; 793 supportsPriority->GetPriority(&adjustedPriority); 794 LogPriorityMapping(ScriptLoader::gScriptLoaderLog, fetchPriority, 795 adjustedPriority); 796 #endif 797 } 798 799 if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(aChannel)) { 800 cos->SetFetchPriorityDOM(fetchPriority); 801 } 802 } 803 804 void AdjustPriorityForNonLinkPreloadScripts(nsIChannel* aChannel, 805 ScriptLoadRequest* aRequest) { 806 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->IsLinkPreloadScript()); 807 808 if (!StaticPrefs::network_fetchpriority_enabled()) { 809 return; 810 } 811 812 const auto fetchPriority = ToFetchPriority(aRequest->FetchPriority()); 813 if (nsCOMPtr<nsISupportsPriority> supportsPriority = 814 do_QueryInterface(aChannel)) { 815 LOG(("Is not <link rel=[module]preload")); 816 817 // The spec defines the priority to be set in an implementation defined 818 // manner (<https://fetch.spec.whatwg.org/#concept-fetch>, step 15 and 819 // <https://html.spec.whatwg.org/#concept-script-fetch-options-fetch-priority>). 820 // See corresponding preferences in StaticPrefList.yaml for more context. 821 const int32_t supportsPriorityDelta = [&]() { 822 const ScriptLoadContext* scriptLoadContext = 823 aRequest->GetScriptLoadContext(); 824 if (aRequest->IsModuleRequest()) { 825 return FETCH_PRIORITY_ADJUSTMENT_FOR(module_script, fetchPriority); 826 } 827 828 if (scriptLoadContext->IsAsyncScript() || 829 scriptLoadContext->IsDeferredScript()) { 830 return FETCH_PRIORITY_ADJUSTMENT_FOR(async_or_defer_script, 831 fetchPriority); 832 } 833 834 if (scriptLoadContext->mScriptFromHead) { 835 return FETCH_PRIORITY_ADJUSTMENT_FOR(script_in_head, fetchPriority); 836 } 837 838 return FETCH_PRIORITY_ADJUSTMENT_FOR(other_script, fetchPriority); 839 }(); 840 841 if (supportsPriorityDelta) { 842 supportsPriority->AdjustPriority(supportsPriorityDelta); 843 #ifdef DEBUG 844 int32_t adjustedPriority; 845 supportsPriority->GetPriority(&adjustedPriority); 846 LogPriorityMapping(ScriptLoader::gScriptLoaderLog, fetchPriority, 847 adjustedPriority); 848 #endif 849 } 850 } 851 if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(aChannel)) { 852 cos->SetFetchPriorityDOM(fetchPriority); 853 } 854 } 855 856 // static 857 void ScriptLoader::PrepareRequestPriorityAndRequestDependencies( 858 nsIChannel* aChannel, ScriptLoadRequest* aRequest) { 859 if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) { 860 // This is <link rel="preload" as="script"> or <link rel="modulepreload"> 861 // initiated speculative load 862 // (https://developer.mozilla.org/en-US/docs/Web/Performance/Speculative_loading). 863 AdjustPriorityAndClassOfServiceForLinkPreloadScripts(aChannel, aRequest); 864 ScriptLoadContext::AddLoadBackgroundFlag(aChannel); 865 } else if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(aChannel)) { 866 AdjustPriorityForNonLinkPreloadScripts(aChannel, aRequest); 867 868 if (aRequest->GetScriptLoadContext()->mScriptFromHead && 869 aRequest->GetScriptLoadContext()->IsBlockingScript()) { 870 // synchronous head scripts block loading of most other non js/css 871 // content such as images, Leader implicitely disallows tailing 872 cos->AddClassFlags(nsIClassOfService::Leader); 873 } else if (aRequest->GetScriptLoadContext()->IsDeferredScript() && 874 !StaticPrefs::network_http_tailing_enabled()) { 875 // Bug 1395525 and the !StaticPrefs::network_http_tailing_enabled() bit: 876 // We want to make sure that turing tailing off by the pref makes the 877 // browser behave exactly the same way as before landing the tailing 878 // patch. 879 880 // head/body deferred scripts are blocked by leaders but are not 881 // allowed tailing because they block DOMContentLoaded 882 cos->AddClassFlags(nsIClassOfService::TailForbidden); 883 } else { 884 // other scripts (=body sync or head/body async) are neither blocked 885 // nor prioritized 886 cos->AddClassFlags(nsIClassOfService::Unblocked); 887 888 if (aRequest->GetScriptLoadContext()->IsAsyncScript()) { 889 // async scripts are allowed tailing, since those and only those 890 // don't block DOMContentLoaded; this flag doesn't enforce tailing, 891 // just overweights the Unblocked flag when the channel is found 892 // to be a thrird-party tracker and thus set the Tail flag to engage 893 // tailing. 894 cos->AddClassFlags(nsIClassOfService::TailAllowed); 895 } 896 } 897 } 898 } 899 900 inline nsLiteralString GetInitiatorType(ScriptLoadRequest* aRequest) { 901 if (aRequest->mEarlyHintPreloaderId) { 902 return u"early-hints"_ns; 903 } 904 905 if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) { 906 return u"link"_ns; 907 } 908 909 return u"script"_ns; 910 } 911 912 // static 913 nsresult ScriptLoader::PrepareHttpRequestAndInitiatorType( 914 nsIChannel* aChannel, ScriptLoadRequest* aRequest, 915 const Maybe<nsAutoString>& aCharsetForPreload) { 916 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); 917 nsresult rv = NS_OK; 918 919 if (httpChannel) { 920 // The 'Accept' HTTP header should be set in 921 // nsHttpHandler::AddStandardRequestHeaders. 922 923 nsCOMPtr<nsIReferrerInfo> referrerInfo = 924 new ReferrerInfo(aRequest->mReferrer, aRequest->ReferrerPolicy()); 925 rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo); 926 MOZ_ASSERT(NS_SUCCEEDED(rv)); 927 928 nsAutoString hintCharset; 929 if (!aRequest->GetScriptLoadContext()->IsPreload() && 930 aRequest->GetScriptLoadContext()->HasScriptElement()) { 931 aRequest->GetScriptLoadContext()->GetHintCharset(hintCharset); 932 } else if (aCharsetForPreload.isSome()) { 933 hintCharset = aCharsetForPreload.ref(); 934 } 935 936 rv = httpChannel->SetClassicScriptHintCharset(hintCharset); 937 NS_ENSURE_SUCCESS(rv, rv); 938 } 939 940 // Set the initiator type 941 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel)); 942 if (timedChannel) { 943 timedChannel->SetInitiatorType(GetInitiatorType(aRequest)); 944 } 945 946 return rv; 947 } 948 949 nsresult ScriptLoader::PrepareIncrementalStreamLoader( 950 nsIIncrementalStreamLoader** aOutLoader, nsIChannel* aChannel, 951 ScriptLoadRequest* aRequest) { 952 UniquePtr<mozilla::dom::SRICheckDataVerifier> sriDataVerifier; 953 if (!aRequest->mIntegrity.IsEmpty()) { 954 sriDataVerifier = MakeUnique<SRICheckDataVerifier>(aRequest->mIntegrity, 955 aChannel, mReporter); 956 } 957 958 RefPtr<ScriptLoadHandler> handler = 959 new ScriptLoadHandler(this, aRequest, std::move(sriDataVerifier)); 960 961 aChannel->SetNotificationCallbacks(handler); 962 963 nsresult rv = NS_NewIncrementalStreamLoader(aOutLoader, handler); 964 NS_ENSURE_SUCCESS(rv, rv); 965 return rv; 966 } 967 968 nsresult ScriptLoader::StartLoadInternal( 969 ScriptLoadRequest* aRequest, nsSecurityFlags securityFlags, 970 const Maybe<nsAutoString>& aCharsetForPreload) { 971 nsCOMPtr<nsIChannel> channel; 972 nsresult rv = CreateChannelForScriptLoading( 973 getter_AddRefs(channel), mDocument, aRequest, securityFlags); 974 975 NS_ENSURE_SUCCESS(rv, rv); 976 977 if (aRequest->mEarlyHintPreloaderId) { 978 nsCOMPtr<nsIHttpChannelInternal> channelInternal = 979 do_QueryInterface(channel); 980 NS_ENSURE_TRUE(channelInternal != nullptr, NS_ERROR_FAILURE); 981 982 rv = channelInternal->SetEarlyHintPreloaderId( 983 aRequest->mEarlyHintPreloaderId); 984 NS_ENSURE_SUCCESS(rv, rv); 985 } 986 987 PrepareLoadInfoForScriptLoading(channel, aRequest); 988 989 nsCOMPtr<nsIScriptGlobalObject> scriptGlobal = GetScriptGlobalObject(); 990 if (!scriptGlobal) { 991 return NS_ERROR_FAILURE; 992 } 993 994 ScriptLoader::PrepareCacheInfoChannel(channel, aRequest); 995 996 LOG(("ScriptLoadRequest (%p): mode=%u tracking=%d", aRequest, 997 unsigned(aRequest->GetScriptLoadContext()->mScriptMode), 998 net::UrlClassifierCommon::IsTrackingClassificationFlag( 999 aRequest->GetScriptLoadContext() 1000 ->GetClassificationFlags() 1001 .thirdPartyFlags, 1002 NS_UsePrivateBrowsing(channel)))); 1003 1004 PrepareRequestPriorityAndRequestDependencies(channel, aRequest); 1005 1006 rv = 1007 PrepareHttpRequestAndInitiatorType(channel, aRequest, aCharsetForPreload); 1008 NS_ENSURE_SUCCESS(rv, rv); 1009 1010 nsCOMPtr<nsIIncrementalStreamLoader> loader; 1011 rv = 1012 PrepareIncrementalStreamLoader(getter_AddRefs(loader), channel, aRequest); 1013 NS_ENSURE_SUCCESS(rv, rv); 1014 1015 auto key = PreloadHashKey::CreateAsScript( 1016 aRequest->URI(), aRequest->CORSMode(), aRequest->mKind); 1017 aRequest->GetScriptLoadContext()->NotifyOpen( 1018 key, channel, mDocument, 1019 aRequest->GetScriptLoadContext()->IsLinkPreloadScript(), 1020 aRequest->IsModuleRequest()); 1021 1022 rv = channel->AsyncOpen(loader); 1023 1024 if (NS_FAILED(rv)) { 1025 // Make sure to inform any <link preload> tags about failure to load the 1026 // resource. 1027 aRequest->GetScriptLoadContext()->NotifyStart(channel); 1028 aRequest->GetScriptLoadContext()->NotifyStop(rv); 1029 } 1030 1031 NS_ENSURE_SUCCESS(rv, rv); 1032 1033 return NS_OK; 1034 } 1035 1036 bool ScriptLoader::PreloadURIComparator::Equals(const PreloadInfo& aPi, 1037 nsIURI* const& aURI) const { 1038 bool same; 1039 return NS_SUCCEEDED(aPi.mRequest->URI()->Equals(aURI, &same)) && same; 1040 } 1041 1042 static bool CSPAllowsInlineScript(nsIScriptElement* aElement, 1043 const nsAString& aSourceText, 1044 const nsAString& aNonce, 1045 Document* aDocument) { 1046 nsCOMPtr<nsIContentSecurityPolicy> csp = 1047 PolicyContainer::GetCSP(aDocument->GetPolicyContainer()); 1048 if (!csp) { 1049 // no CSP --> allow 1050 return true; 1051 } 1052 1053 bool parserCreated = 1054 aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER; 1055 nsCOMPtr<Element> element = do_QueryInterface(aElement); 1056 1057 bool allowInlineScript = false; 1058 nsresult rv = csp->GetAllowsInline( 1059 nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE, 1060 false /* aHasUnsafeHash */, aNonce, parserCreated, element, 1061 nullptr /* nsICSPEventListener */, aSourceText, 1062 aElement->GetScriptLineNumber(), 1063 aElement->GetScriptColumnNumber().oneOriginValue(), &allowInlineScript); 1064 return NS_SUCCEEDED(rv) && allowInlineScript; 1065 } 1066 1067 namespace { 1068 RequestPriority FetchPriorityToRequestPriority( 1069 const FetchPriority aFetchPriority) { 1070 switch (aFetchPriority) { 1071 case FetchPriority::High: 1072 return RequestPriority::High; 1073 case FetchPriority::Low: 1074 return RequestPriority::Low; 1075 case FetchPriority::Auto: 1076 return RequestPriority::Auto; 1077 } 1078 1079 MOZ_ASSERT_UNREACHABLE(); 1080 return RequestPriority::Auto; 1081 } 1082 } // namespace 1083 1084 void ScriptLoader::NotifyObserversForCachedScript( 1085 nsIURI* aURI, nsINode* aContext, nsIPrincipal* aTriggeringPrincipal, 1086 nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, 1087 SubResourceNetworkMetadataHolder* aNetworkMetadata) { 1088 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); 1089 1090 if (!obsService->HasObservers("http-on-resource-cache-response")) { 1091 return; 1092 } 1093 1094 nsCOMPtr<nsIChannel> channel; 1095 nsresult rv = CreateChannelForScriptLoading( 1096 getter_AddRefs(channel), mDocument, aURI, aContext, aTriggeringPrincipal, 1097 aSecurityFlags, aContentPolicyType); 1098 if (NS_FAILED(rv)) { 1099 return; 1100 } 1101 1102 RefPtr<net::HttpBaseChannel> httpBaseChannel = do_QueryObject(channel); 1103 if (httpBaseChannel) { 1104 const net::nsHttpResponseHead* responseHead = nullptr; 1105 if (aNetworkMetadata) { 1106 responseHead = aNetworkMetadata->GetResponseHead(); 1107 } 1108 httpBaseChannel->SetDummyChannelForCachedResource(responseHead); 1109 } 1110 1111 // TODO: Populate fields. 1112 1113 // TODO: Move the handling into SharedSubResourceCache once the notification 1114 // is merged between CSS and JS (bug 1919218) 1115 1116 obsService->NotifyObservers(channel, "http-on-resource-cache-response", 1117 nullptr); 1118 } 1119 1120 already_AddRefed<ScriptLoadRequest> ScriptLoader::CreateLoadRequest( 1121 ScriptKind aKind, nsIURI* aURI, nsIScriptElement* aElement, 1122 const nsAString& aScriptContent, nsIPrincipal* aTriggeringPrincipal, 1123 CORSMode aCORSMode, const nsAString& aNonce, 1124 RequestPriority aRequestPriority, const SRIMetadata& aIntegrity, 1125 ReferrerPolicy aReferrerPolicy, ParserMetadata aParserMetadata, 1126 ScriptLoadRequestType aRequestType) { 1127 nsIURI* referrer = mDocument->GetDocumentURIAsReferrer(); 1128 RefPtr<ScriptFetchOptions> fetchOptions = 1129 new ScriptFetchOptions(aCORSMode, aNonce, aRequestPriority, 1130 aParserMetadata, aTriggeringPrincipal); 1131 RefPtr<ScriptLoadContext> context = 1132 new ScriptLoadContext(aElement, aScriptContent); 1133 1134 if (aKind == ScriptKind::eModule) { 1135 RefPtr<ModuleLoadRequest> request = mModuleLoader->CreateTopLevel( 1136 aURI, aElement, aReferrerPolicy, fetchOptions, aIntegrity, referrer, 1137 context, aRequestType); 1138 1139 return request.forget(); 1140 } 1141 1142 MOZ_ASSERT(aKind == ScriptKind::eClassic || aKind == ScriptKind::eImportMap); 1143 1144 RefPtr<ScriptLoadRequest> request = 1145 new ScriptLoadRequest(aKind, aIntegrity, referrer, context); 1146 1147 TryUseCache(aReferrerPolicy, fetchOptions, aURI, request, aElement, aNonce, 1148 aRequestType); 1149 1150 return request.forget(); 1151 } 1152 1153 void ScriptLoader::TryUseCache(ReferrerPolicy aReferrerPolicy, 1154 ScriptFetchOptions* aFetchOptions, nsIURI* aURI, 1155 ScriptLoadRequest* aRequest, 1156 nsIScriptElement* aElement, 1157 const nsAString& aNonce, 1158 ScriptLoadRequestType aRequestType) { 1159 if (aRequestType == ScriptLoadRequestType::Inline) { 1160 aRequest->NoCacheEntryFound(aReferrerPolicy, aFetchOptions, aURI); 1161 LOG( 1162 ("ScriptLoader (%p): Created LoadedScript (%p) for " 1163 "ScriptLoadRequest(%p) %s.", 1164 this, aRequest->getLoadedScript(), aRequest, 1165 aRequest->URI()->GetSpecOrDefault().get())); 1166 return; 1167 } 1168 1169 if (!mCache) { 1170 aRequest->NoCacheEntryFound(aReferrerPolicy, aFetchOptions, aURI); 1171 LOG( 1172 ("ScriptLoader (%p): Created LoadedScript (%p) for " 1173 "ScriptLoadRequest(%p) %s.", 1174 this, aRequest->getLoadedScript(), aRequest, 1175 aRequest->URI()->GetSpecOrDefault().get())); 1176 return; 1177 } 1178 1179 // NOTE: Some ScriptLoadRequest fields aren't yet accessible until 1180 // either NoCacheEntryFound or CacheEntryFound is called, 1181 // which constructs LoadedScript. 1182 // aRequest->FetchOptions() and aRequest->URI() are backed by 1183 // LoadedScript, and we cannot use them here. 1184 ScriptHashKey key(this, aRequest, aReferrerPolicy, aFetchOptions, aURI); 1185 auto cacheResult = mCache->Lookup(*this, key, /* aSyncLoad = */ true); 1186 if (cacheResult.mState != CachedSubResourceState::Complete) { 1187 aRequest->NoCacheEntryFound(aReferrerPolicy, aFetchOptions, aURI); 1188 LOG( 1189 ("ScriptLoader (%p): Created LoadedScript (%p) for " 1190 "ScriptLoadRequest(%p) %s.", 1191 this, aRequest->getLoadedScript(), aRequest, 1192 aRequest->URI()->GetSpecOrDefault().get())); 1193 return; 1194 } 1195 1196 if (cacheResult.mCompleteValue->IsDirty()) { 1197 // The cache entry needs revalidation. 1198 // Fetch from necko and validate in ScriptLoader::OnStreamComplete. 1199 TRACE_FOR_TEST(aRequest, "memorycache:dirty:hit"); 1200 aRequest->SetHasDirtyCache(); 1201 aRequest->NoCacheEntryFound(aReferrerPolicy, aFetchOptions, aURI); 1202 LOG( 1203 ("ScriptLoader (%p): Created LoadedScript (%p) for " 1204 "ScriptLoadRequest(%p) because of dirty flag %s.", 1205 this, aRequest->getLoadedScript(), aRequest, 1206 aRequest->URI()->GetSpecOrDefault().get())); 1207 return; 1208 } 1209 1210 if (aRequestType == ScriptLoadRequestType::External) { 1211 // NOTE: The preload case checks the same after the 1212 // LookupPreloadRequest call. 1213 if (NS_FAILED(CheckContentPolicy(aElement, aNonce, aRequest, aFetchOptions, 1214 aURI))) { 1215 aRequest->NoCacheEntryFound(aReferrerPolicy, aFetchOptions, aURI); 1216 LOG( 1217 ("ScriptLoader (%p): Created LoadedScript (%p) for " 1218 "ScriptLoadRequest(%p) %s.", 1219 this, aRequest->getLoadedScript(), aRequest, 1220 aRequest->URI()->GetSpecOrDefault().get())); 1221 return; 1222 } 1223 } 1224 1225 aRequest->mNetworkMetadata = cacheResult.mNetworkMetadata; 1226 1227 MOZ_ASSERT(cacheResult.mCompleteValue->ReferrerPolicy() == aReferrerPolicy); 1228 MOZ_ASSERT(aFetchOptions->IsCompatible( 1229 cacheResult.mCompleteValue->GetFetchOptions())); 1230 1231 aRequest->CacheEntryFound(cacheResult.mCompleteValue); 1232 LOG( 1233 ("ScriptLoader (%p): Found in-memory cache LoadedScript (%p) for " 1234 "ScriptLoadRequest(%p) %s.", 1235 this, aRequest->getLoadedScript(), aRequest, 1236 aRequest->URI()->GetSpecOrDefault().get())); 1237 TRACE_FOR_TEST(aRequest, "load:memorycache"); 1238 1239 cacheResult.mCompleteValue->AddFetchCount(); 1240 return; 1241 } 1242 1243 void ScriptLoader::EmulateNetworkEvents(ScriptLoadRequest* aRequest) { 1244 MOZ_ASSERT(aRequest->IsCachedStencil()); 1245 MOZ_ASSERT(aRequest->mNetworkMetadata); 1246 1247 nsIScriptElement* element = aRequest->GetScriptLoadContext()->mScriptElement; 1248 1249 nsCOMPtr<nsINode> context; 1250 if (element) { 1251 context = do_QueryInterface(element); 1252 } else { 1253 context = mDocument; 1254 } 1255 1256 NotifyObserversForCachedScript( 1257 aRequest->URI(), context, aRequest->FetchOptions()->mTriggeringPrincipal, 1258 CORSModeToSecurityFlags(aRequest->FetchOptions()->mCORSMode), 1259 nsIContentPolicy::TYPE_INTERNAL_SCRIPT, aRequest->mNetworkMetadata); 1260 1261 { 1262 nsAutoCString name; 1263 nsString entryName; 1264 aRequest->URI()->GetSpec(name); 1265 CopyUTF8toUTF16(name, entryName); 1266 1267 auto now = TimeStamp::Now(); 1268 1269 SharedSubResourceCacheUtils::AddPerformanceEntryForCache( 1270 entryName, GetInitiatorType(aRequest), aRequest->mNetworkMetadata, now, 1271 now, mDocument); 1272 } 1273 } 1274 1275 bool ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement, 1276 const nsAString& aSourceText) { 1277 // We need a document to evaluate scripts. 1278 NS_ENSURE_TRUE(mDocument, false); 1279 1280 // Check to see if scripts has been turned off. 1281 if (!mEnabled || !mDocument->IsScriptEnabled()) { 1282 return false; 1283 } 1284 1285 NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script"); 1286 1287 nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement); 1288 1289 ScriptKind scriptKind; 1290 if (aElement->GetScriptIsModule()) { 1291 scriptKind = ScriptKind::eModule; 1292 } else if (aElement->GetScriptIsImportMap()) { 1293 scriptKind = ScriptKind::eImportMap; 1294 } else { 1295 scriptKind = ScriptKind::eClassic; 1296 } 1297 1298 // Step 13. Check that the script is not an eventhandler 1299 if (IsScriptEventHandler(scriptKind, scriptContent)) { 1300 return false; 1301 } 1302 1303 // "In modern user agents that support module scripts, the script element with 1304 // the nomodule attribute will be ignored". 1305 // "The nomodule attribute must not be specified on module scripts (and will 1306 // be ignored if it is)." 1307 if (scriptKind == ScriptKind::eClassic && scriptContent->IsHTMLElement() && 1308 scriptContent->AsElement()->HasAttr(nsGkAtoms::nomodule)) { 1309 return false; 1310 } 1311 1312 // Step 15. and later in the HTML5 spec 1313 if (aElement->GetScriptExternal()) { 1314 return ProcessExternalScript(aElement, scriptKind, scriptContent); 1315 } 1316 1317 return ProcessInlineScript(aElement, scriptKind, aSourceText); 1318 } 1319 1320 static ParserMetadata GetParserMetadata(nsIScriptElement* aElement) { 1321 return aElement->GetParserCreated() == mozilla::dom::NOT_FROM_PARSER 1322 ? ParserMetadata::NotParserInserted 1323 : ParserMetadata::ParserInserted; 1324 } 1325 1326 bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement, 1327 ScriptKind aScriptKind, 1328 nsIContent* aScriptContent) { 1329 LOG(("ScriptLoader (%p): Process external script for element %p", this, 1330 aElement)); 1331 1332 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element 1333 // Step 30.1. If el's type is "importmap", then queue an element task on the 1334 // DOM manipulation task source given el to fire an event named error at el, 1335 // and return. 1336 if (aScriptKind == ScriptKind::eImportMap) { 1337 NS_DispatchToCurrentThread( 1338 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement, 1339 &nsIScriptElement::FireErrorEvent)); 1340 nsContentUtils::ReportToConsole( 1341 nsIScriptError::warningFlag, "Script Loader"_ns, mDocument, 1342 nsContentUtils::eDOM_PROPERTIES, "ImportMapExternalNotSupported"); 1343 return false; 1344 } 1345 1346 nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI(); 1347 if (!scriptURI) { 1348 // Asynchronously report the failure to create a URI object 1349 NS_DispatchToCurrentThread( 1350 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement, 1351 &nsIScriptElement::FireErrorEvent)); 1352 return false; 1353 } 1354 1355 nsString nonce = nsContentSecurityUtils::GetIsElementNonceableNonce( 1356 *aScriptContent->AsElement()); 1357 SRIMetadata sriMetadata; 1358 { 1359 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element 1360 // Step 31.11. 1361 // - module: If el does not have an integrity attribute, then set options's 1362 // integrity metadata to the result of resolving a module integrity metadata 1363 // with url and settings object. 1364 nsAutoString integrity; 1365 if (aScriptContent->AsElement()->GetAttr(nsGkAtoms::integrity, integrity)) { 1366 GetSRIMetadata(integrity, &sriMetadata); 1367 } else if (aScriptKind == ScriptKind::eModule) { 1368 mModuleLoader->GetImportMapSRI(scriptURI, 1369 mDocument->GetDocumentURIAsReferrer(), 1370 mReporter, &sriMetadata); 1371 } 1372 } 1373 1374 RefPtr<ScriptLoadRequest> request = 1375 LookupPreloadRequest(aElement, aScriptKind, sriMetadata); 1376 if (request) { 1377 if (NS_FAILED(CheckContentPolicy(aElement, nonce, request, 1378 request->FetchOptions(), 1379 request->URI()))) { 1380 LOG(("ScriptLoader (%p): content policy check failed for preload", this)); 1381 1382 // Probably plans have changed; even though the preload was allowed seems 1383 // like the actual load is not; let's cancel the preload request. 1384 request->Cancel(); 1385 return false; 1386 } 1387 1388 // Use the preload request. 1389 1390 LOG(("ScriptLoadRequest (%p): Using preload request", request.get())); 1391 1392 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-script-tree 1393 // Step 1. Disallow further import maps given settings object. 1394 if (request->IsModuleRequest()) { 1395 LOG(("ScriptLoadRequest (%p): Disallow further import maps.", 1396 request.get())); 1397 mModuleLoader->DisallowImportMaps(); 1398 } 1399 1400 // It's possible these attributes changed since we started the preload so 1401 // update them here. 1402 request->GetScriptLoadContext()->SetScriptMode( 1403 aElement->GetScriptDeferred(), aElement->GetScriptAsync(), false); 1404 1405 // The request will be added to another list or set as 1406 // mParserBlockingRequest below. 1407 if (request->GetScriptLoadContext()->mInCompilingList) { 1408 mOffThreadCompilingRequests.Remove(request); 1409 request->GetScriptLoadContext()->mInCompilingList = false; 1410 } 1411 } else { 1412 // No usable preload found. 1413 1414 nsCOMPtr<nsIPrincipal> principal = 1415 aElement->GetScriptURITriggeringPrincipal(); 1416 if (!principal) { 1417 principal = aScriptContent->NodePrincipal(); 1418 } 1419 1420 CORSMode ourCORSMode = aElement->GetCORSMode(); 1421 const FetchPriority fetchPriority = aElement->GetFetchPriority(); 1422 ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement); 1423 ParserMetadata parserMetadata = GetParserMetadata(aElement); 1424 1425 request = CreateLoadRequest( 1426 aScriptKind, scriptURI, aElement, VoidString(), principal, ourCORSMode, 1427 nonce, FetchPriorityToRequestPriority(fetchPriority), sriMetadata, 1428 referrerPolicy, parserMetadata, ScriptLoadRequestType::External); 1429 request->GetScriptLoadContext()->mIsInline = false; 1430 request->GetScriptLoadContext()->SetScriptMode( 1431 aElement->GetScriptDeferred(), aElement->GetScriptAsync(), false); 1432 // keep request->GetScriptLoadContext()->mScriptFromHead to false so we 1433 // don't treat non preloaded scripts as blockers for full page load. See bug 1434 // 792438. 1435 1436 LOG(("ScriptLoadRequest (%p): Created request for external script", 1437 request.get())); 1438 1439 nsresult rv = StartLoad(request, Nothing()); 1440 if (NS_FAILED(rv)) { 1441 ReportErrorToConsole(request, rv); 1442 1443 // If this is a script element that with an https URL scheme would block 1444 // the parser, we need to block the parser. 1445 bool block = !(request->GetScriptLoadContext()->IsAsyncScript() || 1446 !aElement->GetParserCreated() || 1447 request->GetScriptLoadContext()->IsDeferredScript()); 1448 1449 // Asynchronously report the load failure 1450 nsCOMPtr<nsIRunnable> runnable; 1451 if (block) { 1452 mParserBlockingRequest = request; 1453 runnable = NewRunnableMethod<RefPtr<ScriptLoadRequest>, nsresult>( 1454 "ScriptLoader::HandleLoadErrorAndProcessPendingRequests", this, 1455 &ScriptLoader::HandleLoadErrorAndProcessPendingRequests, request, 1456 rv); 1457 } else { 1458 runnable = 1459 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement, 1460 &nsIScriptElement::FireErrorEvent); 1461 } 1462 1463 if (mDocument) { 1464 mDocument->Dispatch(runnable.forget()); 1465 } else { 1466 NS_DispatchToCurrentThread(runnable.forget()); 1467 } 1468 return block; 1469 } 1470 1471 if (request->IsCachedStencil()) { 1472 // https://html.spec.whatwg.org/#prepare-the-script-element 1473 // 1474 // Step 33. If el's type is "classic" and el has a src attribute, or el's 1475 // type is "module": 1476 // ... 1477 // Step 33.2. If el has an async attribute or el's force async is true: 1478 // Step 33.2.1. Let scripts be el's preparation-time document's set of 1479 // scripts that will execute as soon as possible. 1480 // Step 33.2.2. Append el to scripts. 1481 // ... 1482 // Step 33.3. Otherwise, if el is not parser-inserted: 1483 // Step 33.3.1. Let scripts be el's preparation-time document's list of 1484 // scripts that will execute in order as soon as possible. 1485 // Step 33.3.2. Append el to scripts. 1486 // ... 1487 // 1488 // https://html.spec.whatwg.org/#the-end 1489 // 1490 // Step 7. Spin the event loop until the set of scripts that will execute 1491 // as soon as possible and the list of scripts that will execute 1492 // in order as soon as possible are empty. 1493 // 1494 // For scripts that creates the actual necko channel, the request is 1495 // associated with the document's load group, and the load group manages 1496 // the script set and the script list above implicitly, and the above 1497 // "spin the event loop" is handled by IsBusy() check inside 1498 // nsDocLoader::DocLoaderIsEmpty. 1499 // 1500 // https://searchfox.org/mozilla-central/rev/e85232b4b28ecc970240d39203e417d1c320623c/uriloader/base/nsDocLoader.cpp#704 1501 // 1502 // For in-memory-cached scripts, no channel is created, and those scripts 1503 // should explicitly block the step 7 above. 1504 // 1505 // NOTE: IsAsyncScript represents both "async" and "force async". 1506 if (request->GetScriptLoadContext()->IsAsyncScript() || 1507 parserMetadata == ParserMetadata::NotParserInserted) { 1508 request->GetScriptLoadContext()->BlockOnload(mDocument); 1509 } 1510 } 1511 } 1512 1513 // We should still be in loading stage of script unless we're loading a 1514 // module or speculatively off-main-thread parsing a script. 1515 NS_ASSERTION(SpeculativeOMTParsingEnabled() || 1516 !request->GetScriptLoadContext()->CompileStarted() || 1517 request->IsModuleRequest(), 1518 "Request should not yet be in compiling stage."); 1519 1520 if (request->GetScriptLoadContext()->IsAsyncScript()) { 1521 AddAsyncRequest(request); 1522 if (request->IsFinished()) { 1523 // The script is available already. Run it ASAP when the event 1524 // loop gets a chance to spin. 1525 1526 // KVKV TODO: Instead of processing immediately, try off-thread-parsing 1527 // it and only schedule a pending ProcessRequest if that fails. 1528 ProcessPendingRequestsAsync(); 1529 } 1530 return false; 1531 } 1532 if (!aElement->GetParserCreated()) { 1533 // Violate the HTML5 spec in order to make LABjs and the "order" plug-in 1534 // for RequireJS work with their Gecko-sniffed code path. See 1535 // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html 1536 request->GetScriptLoadContext()->mIsNonAsyncScriptInserted = true; 1537 mNonAsyncExternalScriptInsertedRequests.AppendElement(request); 1538 if (request->IsFinished()) { 1539 // The script is available already. Run it ASAP when the event 1540 // loop gets a chance to spin. 1541 ProcessPendingRequestsAsync(); 1542 } 1543 return false; 1544 } 1545 // we now have a parser-inserted request that may or may not be still 1546 // loading 1547 if (request->GetScriptLoadContext()->IsDeferredScript()) { 1548 // We don't want to run this yet. 1549 // If we come here, the script is a parser-created script and it has 1550 // the defer attribute but not the async attribute OR it is a module 1551 // script without the async attribute. Since a 1552 // a parser-inserted script is being run, we came here by the parser 1553 // running the script, which means the parser is still alive and the 1554 // parse is ongoing. 1555 NS_ASSERTION(mDocument->GetCurrentContentSink() || 1556 aElement->GetParserCreated() == FROM_PARSER_XSLT, 1557 "Non-XSLT Defer script on a document without an active " 1558 "parser; bug 592366."); 1559 AddDeferRequest(request); 1560 return false; 1561 } 1562 1563 if (aElement->GetParserCreated() == FROM_PARSER_XSLT) { 1564 // Need to maintain order for XSLT-inserted scripts 1565 NS_ASSERTION(!mParserBlockingRequest, 1566 "Parser-blocking scripts and XSLT scripts in the same doc!"); 1567 request->GetScriptLoadContext()->mIsXSLT = true; 1568 mXSLTRequests.AppendElement(request); 1569 if (request->IsFinished()) { 1570 // The script is available already. Run it ASAP when the event 1571 // loop gets a chance to spin. 1572 ProcessPendingRequestsAsync(); 1573 } 1574 return true; 1575 } 1576 1577 if (request->IsFinished() && ReadyToExecuteParserBlockingScripts()) { 1578 // The request has already been loaded and there are no pending style 1579 // sheets. If the script comes from the network stream, cheat for 1580 // performance reasons and avoid a trip through the event loop. 1581 if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) { 1582 return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK; 1583 } 1584 // Otherwise, we've got a document.written script, make a trip through 1585 // the event loop to hide the preload effects from the scripts on the 1586 // Web page. 1587 NS_ASSERTION(!mParserBlockingRequest, 1588 "There can be only one parser-blocking script at a time"); 1589 NS_ASSERTION(mXSLTRequests.isEmpty(), 1590 "Parser-blocking scripts and XSLT scripts in the same doc!"); 1591 mParserBlockingRequest = request; 1592 ProcessPendingRequestsAsync(); 1593 return true; 1594 } 1595 1596 // The script hasn't loaded yet or there's a style sheet blocking it. 1597 // The script will be run when it loads or the style sheet loads. 1598 NS_ASSERTION(!mParserBlockingRequest, 1599 "There can be only one parser-blocking script at a time"); 1600 NS_ASSERTION(mXSLTRequests.isEmpty(), 1601 "Parser-blocking scripts and XSLT scripts in the same doc!"); 1602 mParserBlockingRequest = request; 1603 return true; 1604 } 1605 1606 bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement, 1607 ScriptKind aScriptKind, 1608 const nsAString& aSourceText) { 1609 // Is this document sandboxed without 'allow-scripts'? 1610 if (mDocument->HasScriptsBlockedBySandbox()) { 1611 return false; 1612 } 1613 1614 nsCOMPtr<Element> element = do_QueryInterface(aElement); 1615 nsString nonce = nsContentSecurityUtils::GetIsElementNonceableNonce(*element); 1616 1617 // Does CSP allow this inline script to run? 1618 if (!CSPAllowsInlineScript(aElement, aSourceText, nonce, mDocument)) { 1619 return false; 1620 } 1621 1622 // Check if adding an import map script is allowed. If not, we bail out 1623 // early to prevent creating a load request. 1624 if (aScriptKind == ScriptKind::eImportMap) { 1625 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element 1626 // Step 31.2 type is "importmap": 1627 // Step 1. If el's relevant global object's import maps allowed is false, 1628 // then queue an element task on the DOM manipulation task source given el 1629 // to fire an event named error at el, and return. 1630 if (!mModuleLoader->IsImportMapAllowed()) { 1631 NS_WARNING("ScriptLoader: import maps allowed is false."); 1632 const char* msg = mModuleLoader->HasImportMapRegistered() 1633 ? "ImportMapNotAllowedMultiple" 1634 : "ImportMapNotAllowedAfterModuleLoad"; 1635 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, 1636 "Script Loader"_ns, mDocument, 1637 nsContentUtils::eDOM_PROPERTIES, msg); 1638 NS_DispatchToCurrentThread( 1639 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement, 1640 &nsIScriptElement::FireErrorEvent)); 1641 return false; 1642 } 1643 } 1644 1645 // Inline classic scripts ignore their CORS mode and are always CORS_NONE. 1646 CORSMode corsMode = CORS_NONE; 1647 if (aScriptKind == ScriptKind::eModule) { 1648 corsMode = aElement->GetCORSMode(); 1649 } 1650 // <https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element> 1651 // step 29 specifies to use the fetch priority. Presumably it has no effect 1652 // for inline scripts. 1653 const auto fetchPriority = aElement->GetFetchPriority(); 1654 1655 ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement); 1656 ParserMetadata parserMetadata = GetParserMetadata(aElement); 1657 1658 // NOTE: The `nonce` as specified here is significant, because it's inherited 1659 // by other scripts (e.g. modules created via dynamic imports). 1660 RefPtr<ScriptLoadRequest> request = CreateLoadRequest( 1661 aScriptKind, mDocument->GetDocumentURI(), aElement, aSourceText, 1662 mDocument->NodePrincipal(), corsMode, nonce, 1663 FetchPriorityToRequestPriority(fetchPriority), 1664 SRIMetadata(), // SRI doesn't apply 1665 referrerPolicy, parserMetadata, ScriptLoadRequestType::Inline); 1666 request->GetScriptLoadContext()->mIsInline = true; 1667 request->GetScriptLoadContext()->mLineNo = aElement->GetScriptLineNumber(); 1668 request->GetScriptLoadContext()->mColumnNo = 1669 aElement->GetScriptColumnNumber(); 1670 request->mFetchSourceOnly = true; 1671 request->SetTextSource(request->mLoadContext.get()); 1672 TRACE_FOR_TEST(request, "load:source"); 1673 CollectScriptTelemetry(request); 1674 1675 // Only the 'async' attribute is heeded on an inline module script and 1676 // inline classic scripts ignore both these attributes. 1677 MOZ_ASSERT(!aElement->GetScriptDeferred()); 1678 MOZ_ASSERT_IF(!request->IsModuleRequest(), !aElement->GetScriptAsync()); 1679 request->GetScriptLoadContext()->SetScriptMode( 1680 false, aElement->GetScriptAsync(), false); 1681 1682 LOG(("ScriptLoadRequest (%p): Created request for inline script", 1683 request.get())); 1684 1685 request->SetBaseURL(mDocument->GetDocBaseURI()); 1686 1687 if (request->IsModuleRequest()) { 1688 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph 1689 // Step 1. Disallow further import maps given settings object. 1690 mModuleLoader->DisallowImportMaps(); 1691 1692 ModuleLoadRequest* modReq = request->AsModuleRequest(); 1693 if (aElement->GetParserCreated() != NOT_FROM_PARSER) { 1694 if (aElement->GetScriptAsync()) { 1695 AddAsyncRequest(modReq); 1696 } else { 1697 AddDeferRequest(modReq); 1698 } 1699 } 1700 1701 // This calls OnFetchComplete directly since there's no need to start 1702 // fetching an inline script. 1703 nsresult rv = modReq->OnFetchComplete(NS_OK); 1704 if (NS_FAILED(rv)) { 1705 ReportErrorToConsole(modReq, rv); 1706 HandleLoadError(modReq, rv); 1707 } 1708 1709 return false; 1710 } 1711 1712 if (request->IsImportMapRequest()) { 1713 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element 1714 // Step 31.2 type is "importmap": 1715 // Impl note: Step 1 is done above before creating a ScriptLoadRequest. 1716 MOZ_ASSERT(mModuleLoader->IsImportMapAllowed()); 1717 1718 // Step 2. Set el's relevant global object's import maps allowed to false. 1719 mModuleLoader->DisallowImportMaps(); 1720 1721 // Step 3. Let result be the result of creating an import map parse result 1722 // given source text and base URL. 1723 UniquePtr<ImportMap> importMap = mModuleLoader->ParseImportMap(request); 1724 if (!importMap) { 1725 // If parsing import maps fails, the exception will be reported in 1726 // ModuleLoaderBase::ParseImportMap, and the registration of the import 1727 // map will bail out early. 1728 return false; 1729 } 1730 1731 // Remove any module preloads. Module specifier resolution is invalidated by 1732 // adding an import map, and incorrect dependencies may have been loaded. 1733 mPreloads.RemoveElementsBy([](const PreloadInfo& info) { 1734 if (info.mRequest->IsModuleRequest()) { 1735 info.mRequest->Cancel(); 1736 return true; 1737 } 1738 return false; 1739 }); 1740 1741 // TODO: Bug 1781758: Move RegisterImportMap into EvaluateScriptElement. 1742 // 1743 // https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-element 1744 // The spec defines 'register an import map' should be done in 1745 // 'execute the script element', because inside 'execute the script element' 1746 // it will perform a 'preparation-time document check'. 1747 // However, as import maps could be only inline scripts by now, the 1748 // 'preparation-time document check' will never fail for import maps. 1749 // So we simply call 'register an import map' here. 1750 mModuleLoader->RegisterImportMap(std::move(importMap)); 1751 return false; 1752 } 1753 1754 request->mState = ScriptLoadRequest::State::Ready; 1755 if (aElement->GetParserCreated() == FROM_PARSER_XSLT && 1756 (!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) { 1757 // Need to maintain order for XSLT-inserted scripts 1758 NS_ASSERTION(!mParserBlockingRequest, 1759 "Parser-blocking scripts and XSLT scripts in the same doc!"); 1760 mXSLTRequests.AppendElement(request); 1761 return true; 1762 } 1763 if (aElement->GetParserCreated() == NOT_FROM_PARSER) { 1764 RunScriptWhenSafe(request); 1765 return false; 1766 } 1767 if (aElement->GetParserCreated() == FROM_PARSER_NETWORK && 1768 !ReadyToExecuteParserBlockingScripts()) { 1769 NS_ASSERTION(!mParserBlockingRequest, 1770 "There can be only one parser-blocking script at a time"); 1771 mParserBlockingRequest = request; 1772 NS_ASSERTION(mXSLTRequests.isEmpty(), 1773 "Parser-blocking scripts and XSLT scripts in the same doc!"); 1774 return true; 1775 } 1776 // We now have a document.written inline script or we have an inline script 1777 // from the network but there is no style sheet that is blocking scripts. 1778 // Don't check for style sheets blocking scripts in the document.write 1779 // case to avoid style sheet network activity affecting when 1780 // document.write returns. It's not really necessary to do this if 1781 // there's no document.write currently on the call stack. However, 1782 // this way matches IE more closely than checking if document.write 1783 // is on the call stack. 1784 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), 1785 "Not safe to run a parser-inserted script?"); 1786 return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK; 1787 } 1788 1789 ScriptLoadRequest* ScriptLoader::LookupPreloadRequest( 1790 nsIScriptElement* aElement, ScriptKind aScriptKind, 1791 const SRIMetadata& aSRIMetadata) { 1792 MOZ_ASSERT(aElement); 1793 1794 nsTArray<PreloadInfo>::index_type i = 1795 mPreloads.IndexOf(aElement->GetScriptURI(), 0, PreloadURIComparator()); 1796 if (i == nsTArray<PreloadInfo>::NoIndex) { 1797 return nullptr; 1798 } 1799 RefPtr<ScriptLoadRequest> request = mPreloads[i].mRequest; 1800 if (aScriptKind != request->mKind) { 1801 return nullptr; 1802 } 1803 1804 // Found preloaded request. Note that a script-inserted script can steal a 1805 // preload! 1806 request->GetScriptLoadContext()->SetIsLoadRequest(aElement); 1807 1808 if (request->GetScriptLoadContext()->mWasCompiledOMT && 1809 !request->IsModuleRequest()) { 1810 request->SetReady(); 1811 } 1812 1813 nsString preloadCharset(mPreloads[i].mCharset); 1814 mPreloads.RemoveElementAt(i); 1815 1816 // Double-check that the charset the preload used is the same as the charset 1817 // we have now. 1818 nsAutoString elementCharset; 1819 aElement->GetScriptCharset(elementCharset); 1820 1821 // Bug 1832361: charset and crossorigin attributes shouldn't affect matching 1822 // of module scripts and modulepreload 1823 if (!request->IsModuleRequest() && 1824 (!elementCharset.Equals(preloadCharset) || 1825 aElement->GetCORSMode() != request->CORSMode())) { 1826 // Drop the preload. 1827 request->Cancel(); 1828 return nullptr; 1829 } 1830 1831 if (!aSRIMetadata.CanTrustBeDelegatedTo(request->mIntegrity)) { 1832 // Don't cancel link preload requests, we want to deliver onload according 1833 // the result of the load, cancellation would unexpectedly lead to error 1834 // notification. 1835 if (!request->GetScriptLoadContext()->IsLinkPreloadScript()) { 1836 request->Cancel(); 1837 } 1838 return nullptr; 1839 } 1840 1841 // Report any errors that we skipped while preloading. 1842 ReportPreloadErrorsToConsole(request); 1843 1844 // This makes sure the pending preload (if exists) for this resource is 1845 // properly marked as used and thus not notified in the console as unused. 1846 request->GetScriptLoadContext()->NotifyUsage(mDocument); 1847 // A used preload must no longer be found in the Document's hash table. Any 1848 // <link preload> tag after the <script> tag will start a new request, that 1849 // can be satisfied from a different cache, but not from the preload cache. 1850 request->GetScriptLoadContext()->RemoveSelf(mDocument); 1851 1852 return request; 1853 } 1854 1855 void ScriptLoader::GetSRIMetadata(const nsAString& aIntegrityAttr, 1856 SRIMetadata* aMetadataOut) { 1857 MOZ_ASSERT(aMetadataOut->IsEmpty()); 1858 1859 if (aIntegrityAttr.IsEmpty()) { 1860 return; 1861 } 1862 1863 MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, 1864 ("ScriptLoader::GetSRIMetadata, integrity=%s", 1865 NS_ConvertUTF16toUTF8(aIntegrityAttr).get())); 1866 1867 nsAutoCString sourceUri; 1868 if (mDocument->GetDocumentURI()) { 1869 mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri); 1870 } 1871 SRICheck::IntegrityMetadata(aIntegrityAttr, sourceUri, mReporter, 1872 aMetadataOut); 1873 } 1874 1875 ReferrerPolicy ScriptLoader::GetReferrerPolicy(nsIScriptElement* aElement) { 1876 ReferrerPolicy scriptReferrerPolicy = aElement->GetReferrerPolicy(); 1877 if (scriptReferrerPolicy != ReferrerPolicy::_empty) { 1878 return scriptReferrerPolicy; 1879 } 1880 return mDocument->GetReferrerPolicy(); 1881 } 1882 1883 void ScriptLoader::CancelAndClearScriptLoadRequests() { 1884 // Cancel all requests that have not been executed and remove them. 1885 1886 if (mParserBlockingRequest) { 1887 mParserBlockingRequest->Cancel(); 1888 mParserBlockingRequest = nullptr; 1889 } 1890 1891 mDeferRequests.CancelRequestsAndClear(); 1892 mLoadingAsyncRequests.CancelRequestsAndClear(); 1893 mLoadedAsyncRequests.CancelRequestsAndClear(); 1894 mNonAsyncExternalScriptInsertedRequests.CancelRequestsAndClear(); 1895 mXSLTRequests.CancelRequestsAndClear(); 1896 mOffThreadCompilingRequests.CancelRequestsAndClear(); 1897 1898 if (mModuleLoader) { 1899 mModuleLoader->CancelFetchingModules(); 1900 mModuleLoader->CancelAndClearDynamicImports(); 1901 } 1902 1903 for (ModuleLoader* loader : mWebExtModuleLoaders) { 1904 loader->CancelAndClearDynamicImports(); 1905 } 1906 1907 for (ModuleLoader* loader : mShadowRealmModuleLoaders) { 1908 loader->CancelAndClearDynamicImports(); 1909 } 1910 1911 for (size_t i = 0; i < mPreloads.Length(); i++) { 1912 mPreloads[i].mRequest->Cancel(); 1913 } 1914 mPreloads.Clear(); 1915 } 1916 1917 nsresult ScriptLoader::CompileOffThreadOrProcessRequest( 1918 ScriptLoadRequest* aRequest) { 1919 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), 1920 "Processing requests when running scripts is unsafe."); 1921 1922 if (!aRequest->IsCachedStencil() && 1923 !aRequest->GetScriptLoadContext()->mCompileOrDecodeTask && 1924 !aRequest->GetScriptLoadContext()->CompileStarted()) { 1925 bool couldCompile = false; 1926 nsresult rv = AttemptOffThreadScriptCompile(aRequest, &couldCompile); 1927 if (NS_FAILED(rv)) { 1928 HandleLoadError(aRequest, rv); 1929 return rv; 1930 } 1931 1932 if (couldCompile) { 1933 return NS_OK; 1934 } 1935 } 1936 1937 return ProcessRequest(aRequest); 1938 } 1939 1940 namespace { 1941 1942 class OffThreadCompilationCompleteTask : public Task { 1943 public: 1944 OffThreadCompilationCompleteTask(ScriptLoadRequest* aRequest, 1945 ScriptLoader* aLoader) 1946 : Task(Kind::MainThreadOnly, EventQueuePriority::Normal), 1947 mRequest(aRequest), 1948 mLoader(aLoader) { 1949 MOZ_ASSERT(NS_IsMainThread()); 1950 } 1951 1952 void RecordStartTime() { mStartTime = TimeStamp::Now(); } 1953 void RecordStopTime() { mStopTime = TimeStamp::Now(); } 1954 1955 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 1956 bool GetName(nsACString& aName) override { 1957 aName.AssignLiteral("dom::OffThreadCompilationCompleteTask"); 1958 return true; 1959 } 1960 #endif 1961 1962 TaskResult Run() override { 1963 MOZ_ASSERT(NS_IsMainThread()); 1964 1965 RefPtr<ScriptLoadContext> context = mRequest->GetScriptLoadContext(); 1966 1967 if (!context->mCompileOrDecodeTask) { 1968 // Request has been cancelled by MaybeCancelOffThreadScript. 1969 return TaskResult::Complete; 1970 } 1971 1972 RecordStopTime(); 1973 1974 if (profiler_is_active()) { 1975 ProfilerString8View scriptSourceString; 1976 if (mRequest->IsTextSource()) { 1977 scriptSourceString = "ScriptCompileOffThread"; 1978 } else { 1979 MOZ_ASSERT(mRequest->IsSerializedStencil()); 1980 scriptSourceString = "DecodeStencilOffThread"; 1981 } 1982 1983 nsAutoCString profilerLabelString; 1984 mRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); 1985 PROFILER_MARKER_TEXT(scriptSourceString, JS, 1986 MarkerTiming::Interval(mStartTime, mStopTime), 1987 profilerLabelString); 1988 } 1989 1990 (void)mLoader->ProcessOffThreadRequest(mRequest); 1991 1992 mRequest = nullptr; 1993 mLoader = nullptr; 1994 return TaskResult::Complete; 1995 } 1996 1997 private: 1998 // NOTE: 1999 // These fields are main-thread only, and this task shouldn't be freed off 2000 // main thread. 2001 // 2002 // This is guaranteed by not having off-thread tasks which depends on this 2003 // task, because otherwise the off-thread task's mDependencies can be the 2004 // last reference, which results in freeing this task off main thread. 2005 // 2006 // If such task is added, these fields must be moved to separate storage. 2007 RefPtr<ScriptLoadRequest> mRequest; 2008 RefPtr<ScriptLoader> mLoader; 2009 2010 TimeStamp mStartTime; 2011 TimeStamp mStopTime; 2012 }; 2013 2014 } /* anonymous namespace */ 2015 2016 // TODO: This uses the same heuristics and the same threshold as the 2017 // JS::CanCompileOffThread / JS::CanDecodeOffThread APIs, but the 2018 // heuristics needs to be updated to reflect the change regarding the 2019 // Stencil API, and also the thread management on the consumer side 2020 // (bug 1846160). 2021 static constexpr size_t OffThreadMinimumTextLength = 5 * 1000; 2022 static constexpr size_t OffThreadMinimumSerializedStencilLength = 5 * 1000; 2023 2024 nsresult ScriptLoader::AttemptOffThreadScriptCompile( 2025 ScriptLoadRequest* aRequest, bool* aCouldCompileOut) { 2026 // If speculative parsing is enabled, the request may not be ready to run if 2027 // the element is not yet available. 2028 MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled() && !aRequest->IsModuleRequest(), 2029 aRequest->IsFinished()); 2030 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT); 2031 MOZ_ASSERT(aCouldCompileOut && !*aCouldCompileOut); 2032 2033 // Don't off-thread compile inline scripts. 2034 if (aRequest->GetScriptLoadContext()->mIsInline) { 2035 return NS_OK; 2036 } 2037 2038 if (aRequest->IsCachedStencil()) { 2039 // This is a revived cache. 2040 return NS_OK; 2041 } 2042 2043 // Don't off-thread compile JSON or CSS modules. 2044 // https://bugzilla.mozilla.org/show_bug.cgi?id=1912112 (JSON) 2045 // https://bugzilla.mozilla.org/show_bug.cgi?id=1987143 (CSS) 2046 if (aRequest->IsModuleRequest() && 2047 (aRequest->AsModuleRequest()->mModuleType == JS::ModuleType::JSON || 2048 aRequest->AsModuleRequest()->mModuleType == JS::ModuleType::CSS)) { 2049 return NS_OK; 2050 } 2051 2052 nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalForRequest(aRequest); 2053 if (!globalObject) { 2054 return NS_ERROR_FAILURE; 2055 } 2056 2057 AutoJSAPI jsapi; 2058 if (!jsapi.Init(globalObject)) { 2059 return NS_ERROR_FAILURE; 2060 } 2061 2062 JSContext* cx = jsapi.cx(); 2063 JS::CompileOptions options(cx); 2064 2065 // Introduction script will actually be computed and set when the script is 2066 // collected from offthread 2067 JS::Rooted<JSScript*> dummyIntroductionScript(cx); 2068 nsresult rv = FillCompileOptionsForRequest(cx, aRequest, &options, 2069 &dummyIntroductionScript); 2070 if (NS_WARN_IF(NS_FAILED(rv))) { 2071 return rv; 2072 } 2073 2074 if (aRequest->IsTextSource()) { 2075 if (!StaticPrefs::javascript_options_parallel_parsing() || 2076 aRequest->ScriptTextLength() < OffThreadMinimumTextLength) { 2077 TRACE_FOR_TEST(aRequest, "compile:main thread"); 2078 return NS_OK; 2079 } 2080 } else { 2081 MOZ_ASSERT(aRequest->IsSerializedStencil()); 2082 2083 JS::TranscodeRange range = aRequest->SerializedStencil(); 2084 if (!StaticPrefs::javascript_options_parallel_parsing() || 2085 range.length() < OffThreadMinimumSerializedStencilLength) { 2086 return NS_OK; 2087 } 2088 } 2089 2090 RefPtr<CompileOrDecodeTask> compileOrDecodeTask; 2091 rv = CreateOffThreadTask(cx, aRequest, options, 2092 getter_AddRefs(compileOrDecodeTask)); 2093 NS_ENSURE_SUCCESS(rv, rv); 2094 2095 RefPtr<OffThreadCompilationCompleteTask> completeTask = 2096 new OffThreadCompilationCompleteTask(aRequest, this); 2097 2098 completeTask->RecordStartTime(); 2099 2100 aRequest->GetScriptLoadContext()->mCompileOrDecodeTask = compileOrDecodeTask; 2101 completeTask->AddDependency(compileOrDecodeTask); 2102 2103 TaskController::Get()->AddTask(compileOrDecodeTask.forget()); 2104 TaskController::Get()->AddTask(completeTask.forget()); 2105 2106 aRequest->GetScriptLoadContext()->BlockOnload(mDocument); 2107 2108 // Once the compilation is finished, the completeTask will be run on 2109 // the main thread to call ScriptLoader::ProcessOffThreadRequest for the 2110 // request. 2111 aRequest->mState = ScriptLoadRequest::State::Compiling; 2112 2113 // Requests that are not tracked elsewhere are added to a list while they are 2114 // being compiled off-thread, so we can cancel the compilation later if 2115 // necessary. 2116 // 2117 // Non-top-level modules not tracked because these are cancelled from their 2118 // importing module. 2119 if (aRequest->IsTopLevel() && !aRequest->isInList()) { 2120 mOffThreadCompilingRequests.AppendElement(aRequest); 2121 aRequest->GetScriptLoadContext()->mInCompilingList = true; 2122 } 2123 2124 *aCouldCompileOut = true; 2125 2126 return NS_OK; 2127 } 2128 2129 CompileOrDecodeTask::CompileOrDecodeTask() 2130 : Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal), 2131 mMutex("CompileOrDecodeTask"), 2132 mOptions(JS::OwningCompileOptions::ForFrontendContext()) {} 2133 2134 CompileOrDecodeTask::~CompileOrDecodeTask() { 2135 if (mFrontendContext) { 2136 JS::DestroyFrontendContext(mFrontendContext); 2137 mFrontendContext = nullptr; 2138 } 2139 } 2140 2141 nsresult CompileOrDecodeTask::InitFrontendContext() { 2142 mFrontendContext = JS::NewFrontendContext(); 2143 if (!mFrontendContext) { 2144 mIsCancelled = true; 2145 return NS_ERROR_OUT_OF_MEMORY; 2146 } 2147 return NS_OK; 2148 } 2149 2150 void CompileOrDecodeTask::DidRunTask(const MutexAutoLock& aProofOfLock, 2151 RefPtr<JS::Stencil>&& aStencil) { 2152 if (aStencil) { 2153 if (!JS::PrepareForInstantiate(mFrontendContext, *aStencil, 2154 mInstantiationStorage)) { 2155 aStencil = nullptr; 2156 } 2157 } 2158 2159 mStencil = std::move(aStencil); 2160 } 2161 2162 already_AddRefed<JS::Stencil> CompileOrDecodeTask::StealResult( 2163 JSContext* aCx, JS::InstantiationStorage* aInstantiationStorage) { 2164 JS::FrontendContext* fc = mFrontendContext; 2165 mFrontendContext = nullptr; 2166 auto destroyFrontendContext = 2167 mozilla::MakeScopeExit([&]() { JS::DestroyFrontendContext(fc); }); 2168 2169 MOZ_ASSERT(fc); 2170 2171 if (JS::HadFrontendErrors(fc)) { 2172 (void)JS::ConvertFrontendErrorsToRuntimeErrors(aCx, fc, mOptions); 2173 return nullptr; 2174 } 2175 2176 if (!mStencil && JS::IsTranscodeFailureResult(mResult)) { 2177 // Decode failure with bad content isn't reported as error. 2178 JS_ReportErrorASCII(aCx, "failed to decode cache"); 2179 return nullptr; 2180 } 2181 2182 // Report warnings. 2183 if (!JS::ConvertFrontendErrorsToRuntimeErrors(aCx, fc, mOptions)) { 2184 return nullptr; 2185 } 2186 2187 MOZ_ASSERT(mStencil, 2188 "If this task is cancelled, StealResult shouldn't be called"); 2189 2190 // This task is started and finished successfully. 2191 *aInstantiationStorage = std::move(mInstantiationStorage); 2192 2193 return mStencil.forget(); 2194 } 2195 2196 void CompileOrDecodeTask::Cancel() { 2197 MOZ_ASSERT(NS_IsMainThread()); 2198 2199 MutexAutoLock lock(mMutex); 2200 2201 mIsCancelled = true; 2202 } 2203 2204 enum class CompilationTarget { Script, Module }; 2205 2206 template <CompilationTarget target> 2207 class ScriptOrModuleCompileTask final : public CompileOrDecodeTask { 2208 public: 2209 explicit ScriptOrModuleCompileTask( 2210 ScriptLoader::MaybeSourceText&& aMaybeSource) 2211 : CompileOrDecodeTask(), mMaybeSource(std::move(aMaybeSource)) {} 2212 2213 nsresult Init(JS::CompileOptions& aOptions) { 2214 nsresult rv = InitFrontendContext(); 2215 NS_ENSURE_SUCCESS(rv, rv); 2216 2217 if (!mOptions.copy(mFrontendContext, aOptions)) { 2218 mIsCancelled = true; 2219 return NS_ERROR_OUT_OF_MEMORY; 2220 } 2221 2222 return NS_OK; 2223 } 2224 2225 TaskResult Run() override { 2226 MutexAutoLock lock(mMutex); 2227 2228 if (IsCancelled(lock)) { 2229 return TaskResult::Complete; 2230 } 2231 2232 RefPtr<JS::Stencil> stencil = Compile(); 2233 2234 DidRunTask(lock, std::move(stencil)); 2235 return TaskResult::Complete; 2236 } 2237 2238 private: 2239 already_AddRefed<JS::Stencil> Compile() { 2240 size_t stackSize = TaskController::GetThreadStackSize(); 2241 JS::SetNativeStackQuota(mFrontendContext, 2242 JS::ThreadStackQuotaForSize(stackSize)); 2243 2244 auto compile = [&](auto& source) { 2245 if constexpr (target == CompilationTarget::Script) { 2246 return JS::CompileGlobalScriptToStencil(mFrontendContext, mOptions, 2247 source); 2248 } 2249 return JS::CompileModuleScriptToStencil(mFrontendContext, mOptions, 2250 source); 2251 }; 2252 return mMaybeSource.mapNonEmpty(compile); 2253 } 2254 2255 public: 2256 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 2257 bool GetName(nsACString& aName) override { 2258 if constexpr (target == CompilationTarget::Script) { 2259 aName.AssignLiteral("ScriptCompileTask"); 2260 } else { 2261 aName.AssignLiteral("ModuleCompileTask"); 2262 } 2263 return true; 2264 } 2265 #endif 2266 2267 private: 2268 ScriptLoader::MaybeSourceText mMaybeSource; 2269 }; 2270 2271 using ScriptCompileTask = 2272 class ScriptOrModuleCompileTask<CompilationTarget::Script>; 2273 using ModuleCompileTask = 2274 class ScriptOrModuleCompileTask<CompilationTarget::Module>; 2275 2276 class ScriptDecodeTask final : public CompileOrDecodeTask { 2277 public: 2278 explicit ScriptDecodeTask(const JS::TranscodeRange& aRange) 2279 : mRange(aRange) {} 2280 2281 nsresult Init(JS::DecodeOptions& aOptions) { 2282 nsresult rv = InitFrontendContext(); 2283 NS_ENSURE_SUCCESS(rv, rv); 2284 2285 if (!mDecodeOptions.copy(mFrontendContext, aOptions)) { 2286 mIsCancelled = true; 2287 return NS_ERROR_OUT_OF_MEMORY; 2288 } 2289 2290 return NS_OK; 2291 } 2292 2293 TaskResult Run() override { 2294 MutexAutoLock lock(mMutex); 2295 2296 if (IsCancelled(lock)) { 2297 return TaskResult::Complete; 2298 } 2299 2300 RefPtr<JS::Stencil> stencil = Decode(); 2301 2302 JS::OwningCompileOptions compileOptions( 2303 (JS::OwningCompileOptions::ForFrontendContext())); 2304 mOptions.steal(std::move(mDecodeOptions)); 2305 2306 DidRunTask(lock, std::move(stencil)); 2307 return TaskResult::Complete; 2308 } 2309 2310 private: 2311 already_AddRefed<JS::Stencil> Decode() { 2312 // NOTE: JS::DecodeStencil doesn't need the stack quota. 2313 2314 RefPtr<JS::Stencil> stencil; 2315 mResult = JS::DecodeStencil(mFrontendContext, mDecodeOptions, mRange, 2316 getter_AddRefs(stencil)); 2317 return stencil.forget(); 2318 } 2319 2320 public: 2321 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 2322 bool GetName(nsACString& aName) override { 2323 aName.AssignLiteral("ScriptDecodeTask"); 2324 return true; 2325 } 2326 #endif 2327 2328 private: 2329 JS::OwningDecodeOptions mDecodeOptions; 2330 2331 JS::TranscodeRange mRange; 2332 }; 2333 2334 nsresult ScriptLoader::CreateOffThreadTask( 2335 JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions& aOptions, 2336 CompileOrDecodeTask** aCompileOrDecodeTask) { 2337 if (aRequest->IsSerializedStencil()) { 2338 JS::TranscodeRange range = aRequest->SerializedStencil(); 2339 JS::DecodeOptions decodeOptions(aOptions); 2340 RefPtr<ScriptDecodeTask> decodeTask = new ScriptDecodeTask(range); 2341 nsresult rv = decodeTask->Init(decodeOptions); 2342 NS_ENSURE_SUCCESS(rv, rv); 2343 decodeTask.forget(aCompileOrDecodeTask); 2344 return NS_OK; 2345 } 2346 2347 MaybeSourceText maybeSource; 2348 nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource, 2349 aRequest->mLoadContext.get()); 2350 NS_ENSURE_SUCCESS(rv, rv); 2351 2352 if (ShouldApplyDelazifyStrategy(aRequest)) { 2353 ApplyDelazifyStrategy(&aOptions); 2354 mTotalFullParseSize += 2355 aRequest->ScriptTextLength() > 0 2356 ? static_cast<uint32_t>(aRequest->ScriptTextLength()) 2357 : 0; 2358 2359 LOG( 2360 ("ScriptLoadRequest (%p): non-on-demand-only (omt) Parsing Enabled " 2361 "for url=%s mTotalFullParseSize=%u", 2362 aRequest, aRequest->URI()->GetSpecOrDefault().get(), 2363 mTotalFullParseSize)); 2364 } 2365 2366 if (aRequest->IsModuleRequest()) { 2367 RefPtr<ModuleCompileTask> compileTask = 2368 new ModuleCompileTask(std::move(maybeSource)); 2369 rv = compileTask->Init(aOptions); 2370 NS_ENSURE_SUCCESS(rv, rv); 2371 compileTask.forget(aCompileOrDecodeTask); 2372 return NS_OK; 2373 } 2374 2375 if (StaticPrefs::dom_expose_test_interfaces()) { 2376 switch (aOptions.eagerDelazificationStrategy()) { 2377 case JS::DelazificationOption::OnDemandOnly: 2378 TRACE_FOR_TEST(aRequest, "delazification:OnDemandOnly"); 2379 break; 2380 case JS::DelazificationOption::CheckConcurrentWithOnDemand: 2381 case JS::DelazificationOption::ConcurrentDepthFirst: 2382 TRACE_FOR_TEST(aRequest, "delazification:ConcurrentDepthFirst"); 2383 break; 2384 case JS::DelazificationOption::ConcurrentLargeFirst: 2385 TRACE_FOR_TEST(aRequest, "delazification:ConcurrentLargeFirst"); 2386 break; 2387 case JS::DelazificationOption::ParseEverythingEagerly: 2388 TRACE_FOR_TEST(aRequest, "delazification:ParseEverythingEagerly"); 2389 break; 2390 } 2391 } 2392 2393 RefPtr<ScriptCompileTask> compileTask = 2394 new ScriptCompileTask(std::move(maybeSource)); 2395 rv = compileTask->Init(aOptions); 2396 NS_ENSURE_SUCCESS(rv, rv); 2397 compileTask.forget(aCompileOrDecodeTask); 2398 return NS_OK; 2399 } 2400 2401 nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) { 2402 MOZ_ASSERT(aRequest->IsCompiling()); 2403 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT); 2404 2405 if (aRequest->IsCanceled()) { 2406 return NS_OK; 2407 } 2408 2409 aRequest->GetScriptLoadContext()->mWasCompiledOMT = true; 2410 2411 if (aRequest->GetScriptLoadContext()->mInCompilingList) { 2412 mOffThreadCompilingRequests.Remove(aRequest); 2413 aRequest->GetScriptLoadContext()->mInCompilingList = false; 2414 } 2415 2416 if (aRequest->IsModuleRequest()) { 2417 MOZ_ASSERT(aRequest->GetScriptLoadContext()->mCompileOrDecodeTask); 2418 ModuleLoadRequest* request = aRequest->AsModuleRequest(); 2419 return request->OnFetchComplete(NS_OK); 2420 } 2421 2422 // Element may not be ready yet if speculatively compiling, so process the 2423 // request in ProcessPendingRequests when it is available. 2424 MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled(), 2425 aRequest->GetScriptLoadContext()->HasScriptElement()); 2426 if (!aRequest->GetScriptLoadContext()->HasScriptElement()) { 2427 // Unblock onload here in case this request never gets executed. 2428 aRequest->GetScriptLoadContext()->MaybeUnblockOnload(); 2429 return NS_OK; 2430 } 2431 2432 aRequest->SetReady(); 2433 2434 // Move async scripts to mLoadedAsyncRequests and process them by calling 2435 // ProcessPendingRequests. 2436 if (aRequest != mParserBlockingRequest && 2437 (aRequest->GetScriptLoadContext()->IsAsyncScript() || 2438 aRequest->GetScriptLoadContext()->IsBlockingScript()) && 2439 !aRequest->isInList()) { 2440 if (aRequest->GetScriptLoadContext()->IsAsyncScript()) { 2441 // We're adding the request back to async list so that it can be executed 2442 // later. 2443 aRequest->GetScriptLoadContext()->mInAsyncList = false; 2444 AddAsyncRequest(aRequest); 2445 } else { 2446 MOZ_ASSERT( 2447 false, 2448 "This should not run ever with the current default prefs. The " 2449 "request should not run synchronously but added to some queue."); 2450 return ProcessRequest(aRequest); 2451 } 2452 } 2453 2454 // Process other scripts in the proper order. 2455 ProcessPendingRequests(); 2456 return NS_OK; 2457 } 2458 2459 nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) { 2460 LOG(("ScriptLoadRequest (%p): Process request", aRequest)); 2461 2462 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), 2463 "Processing requests when running scripts is unsafe."); 2464 NS_ASSERTION(aRequest->IsFinished(), 2465 "Processing a request that is not ready to run."); 2466 2467 NS_ENSURE_ARG(aRequest); 2468 2469 auto unblockOnload = MakeScopeExit( 2470 [&] { aRequest->GetScriptLoadContext()->MaybeUnblockOnload(); }); 2471 2472 if (aRequest->IsModuleRequest()) { 2473 ModuleLoadRequest* request = aRequest->AsModuleRequest(); 2474 if (request->IsDynamicImport()) { 2475 request->ProcessDynamicImport(); 2476 return NS_OK; 2477 } 2478 2479 if (request->mModuleScript && 2480 !request->mModuleScript->HasErrorToRethrow()) { 2481 if (!request->InstantiateModuleGraph()) { 2482 request->mModuleScript = nullptr; 2483 } 2484 } 2485 2486 if (!request->mModuleScript) { 2487 // There was an error fetching a module script. Nothing to do here. 2488 LOG(("ScriptLoadRequest (%p): Error loading request, firing error", 2489 aRequest)); 2490 FireScriptAvailable(NS_ERROR_FAILURE, aRequest); 2491 return NS_OK; 2492 } 2493 } 2494 2495 nsCOMPtr<nsIScriptElement> oldParserInsertedScript; 2496 uint32_t parserCreated = aRequest->GetScriptLoadContext()->GetParserCreated(); 2497 if (parserCreated) { 2498 oldParserInsertedScript = mCurrentParserInsertedScript; 2499 mCurrentParserInsertedScript = 2500 aRequest->GetScriptLoadContext() 2501 ->GetScriptElementForCurrentParserInsertedScript(); 2502 } 2503 2504 aRequest->GetScriptLoadContext()->BeginEvaluatingTopLevel(); 2505 2506 FireScriptAvailable(NS_OK, aRequest); 2507 2508 { 2509 // Try to perform a microtask checkpoint 2510 nsAutoMicroTask mt; 2511 } 2512 2513 nsresult rv = EvaluateScriptElement(aRequest); 2514 2515 FireScriptEvaluated(rv, aRequest); 2516 2517 aRequest->GetScriptLoadContext()->EndEvaluatingTopLevel(); 2518 2519 if (parserCreated) { 2520 mCurrentParserInsertedScript = oldParserInsertedScript; 2521 } 2522 2523 if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) { 2524 // The request was parsed off-main-thread, but the result of the off 2525 // thread parse was not actually needed to process the request 2526 // (disappearing window, some other error, ...). Finish the 2527 // request to avoid leaks. 2528 MOZ_ASSERT(!aRequest->IsModuleRequest()); 2529 aRequest->GetScriptLoadContext()->MaybeCancelOffThreadScript(); 2530 } 2531 2532 if (aRequest->IsTextSource()) { 2533 // Free text source, but keep the serialized Stencil as we might have to 2534 // save it later. 2535 aRequest->ClearScriptText(); 2536 } else if (aRequest->IsSerializedStencil()) { 2537 // We received serialized Stencil as input, thus we were decoding, and we 2538 // will not be encoding it once more. We can safely clear the content of 2539 // this buffer. 2540 aRequest->DropSRIOrSRIAndSerializedStencil(); 2541 } 2542 2543 return rv; 2544 } 2545 2546 void ScriptLoader::FireScriptAvailable(nsresult aResult, 2547 ScriptLoadRequest* aRequest) { 2548 for (int32_t i = 0; i < mObservers.Count(); i++) { 2549 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i]; 2550 obs->ScriptAvailable( 2551 aResult, 2552 aRequest->GetScriptLoadContext()->GetScriptElementForObserver(), 2553 aRequest->GetScriptLoadContext()->mIsInline, aRequest->URI(), 2554 aRequest->GetScriptLoadContext()->mLineNo); 2555 } 2556 2557 bool isInlineClassicScript = aRequest->GetScriptLoadContext()->mIsInline && 2558 !aRequest->IsModuleRequest(); 2559 RefPtr<nsIScriptElement> scriptElement = 2560 aRequest->GetScriptLoadContext()->GetScriptElementForObserver(); 2561 scriptElement->ScriptAvailable(aResult, scriptElement, isInlineClassicScript, 2562 aRequest->URI(), 2563 aRequest->GetScriptLoadContext()->mLineNo); 2564 } 2565 2566 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) 2567 MOZ_CAN_RUN_SCRIPT_BOUNDARY void ScriptLoader::FireScriptEvaluated( 2568 nsresult aResult, ScriptLoadRequest* aRequest) { 2569 for (int32_t i = 0; i < mObservers.Count(); i++) { 2570 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i]; 2571 RefPtr<nsIScriptElement> scriptElement = 2572 aRequest->GetScriptLoadContext()->GetScriptElementForObserver(); 2573 obs->ScriptEvaluated(aResult, scriptElement, 2574 aRequest->GetScriptLoadContext()->mIsInline); 2575 } 2576 2577 RefPtr<nsIScriptElement> scriptElement = 2578 aRequest->GetScriptLoadContext()->GetScriptElementForObserver(); 2579 scriptElement->ScriptEvaluated(aResult, scriptElement, 2580 aRequest->GetScriptLoadContext()->mIsInline); 2581 } 2582 2583 already_AddRefed<nsIGlobalObject> ScriptLoader::GetGlobalForRequest( 2584 ScriptLoadRequest* aRequest) { 2585 if (aRequest->IsModuleRequest()) { 2586 ModuleLoader* loader = 2587 ModuleLoader::From(aRequest->AsModuleRequest()->mLoader); 2588 nsCOMPtr<nsIGlobalObject> global = loader->GetGlobalObject(); 2589 return global.forget(); 2590 } 2591 2592 return GetScriptGlobalObject(); 2593 } 2594 2595 already_AddRefed<nsIScriptGlobalObject> ScriptLoader::GetScriptGlobalObject() { 2596 if (!mDocument) { 2597 return nullptr; 2598 } 2599 2600 nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow(); 2601 if (!pwin) { 2602 return nullptr; 2603 } 2604 2605 nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin); 2606 NS_ASSERTION(globalObject, "windows must be global objects"); 2607 2608 // and make sure we are setup for this type of script. 2609 nsresult rv = globalObject->EnsureScriptEnvironment(); 2610 if (NS_FAILED(rv)) { 2611 return nullptr; 2612 } 2613 2614 return globalObject.forget(); 2615 } 2616 2617 static void ApplyEagerBaselineStrategy(JS::CompileOptions* aOptions) { 2618 uint32_t strategyIndex = StaticPrefs:: 2619 javascript_options_baselinejit_offthread_compilation_strategy(); 2620 2621 JS::EagerBaselineOption strategy; 2622 switch (strategyIndex) { 2623 // Values of 2 and 3 indicate to eagerly baseline compile, but only 2624 // if JitHints are available. 2625 case 2: 2626 case 3: 2627 strategy = JS::EagerBaselineOption::JitHints; 2628 break; 2629 case 4: 2630 strategy = JS::EagerBaselineOption::Aggressive; 2631 break; 2632 default: 2633 // Value of 0 indicates omt baseline compilation should be disabled. 2634 // Value of 1 indicates omt baseline compilation should be on demand only, 2635 // so set the eager baseline strategy to None. 2636 strategy = JS::EagerBaselineOption::None; 2637 break; 2638 } 2639 2640 aOptions->setEagerBaselineStrategy(strategy); 2641 } 2642 2643 nsresult ScriptLoader::FillCompileOptionsForRequest( 2644 JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions, 2645 JS::MutableHandle<JSScript*> aIntroductionScript) { 2646 // It's very important to use aRequest->URI(), not the final URI of the 2647 // channel aRequest ended up getting script data from, as the script filename. 2648 nsresult rv = aRequest->URI()->GetSpec(aRequest->mURL); 2649 if (NS_WARN_IF(NS_FAILED(rv))) { 2650 return rv; 2651 } 2652 2653 if (mDocument) { 2654 mDocument->NoteScriptTrackingStatus( 2655 aRequest->mURL, 2656 aRequest->GetScriptLoadContext()->GetClassificationFlags()); 2657 } 2658 2659 const char* introductionType; 2660 if (aRequest->IsModuleRequest() && 2661 !aRequest->AsModuleRequest()->IsTopLevel()) { 2662 introductionType = "importedModule"; 2663 } else if (!aRequest->GetScriptLoadContext()->mIsInline) { 2664 introductionType = "srcScript"; 2665 } else if (aRequest->GetScriptLoadContext()->GetParserCreated() == 2666 FROM_PARSER_NETWORK) { 2667 introductionType = "inlineScript"; 2668 } else { 2669 introductionType = "injectedScript"; 2670 } 2671 aOptions->setIntroductionInfoToCaller(aCx, introductionType, 2672 aIntroductionScript); 2673 aOptions->setFileAndLine(aRequest->mURL.get(), 2674 aRequest->GetScriptLoadContext()->mLineNo); 2675 // The column is only relevant for inline scripts in order for SpiderMonkey to 2676 // properly compute offsets relatively to the script position within the HTML 2677 // file. injectedScript are not concerned and are always considered to start 2678 // at column 0. 2679 if (aRequest->GetScriptLoadContext()->mIsInline && 2680 aRequest->GetScriptLoadContext()->GetParserCreated() == 2681 FROM_PARSER_NETWORK) { 2682 aOptions->setColumn(aRequest->GetScriptLoadContext()->mColumnNo); 2683 } 2684 aOptions->setIsRunOnce(true); 2685 aOptions->setNoScriptRval(true); 2686 if (aRequest->HasSourceMapURL()) { 2687 aOptions->setSourceMapURL(aRequest->GetSourceMapURL().get()); 2688 } 2689 if (aRequest->mOriginPrincipal) { 2690 nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalForRequest(aRequest); 2691 nsIPrincipal* scriptPrin = globalObject->PrincipalOrNull(); 2692 MOZ_ASSERT(scriptPrin); 2693 bool subsumes = scriptPrin->Subsumes(aRequest->mOriginPrincipal); 2694 aOptions->setMutedErrors(!subsumes); 2695 } 2696 2697 aOptions->setDeferDebugMetadata(true); 2698 2699 aOptions->borrowBuffer = true; 2700 2701 ApplyEagerBaselineStrategy(aOptions); 2702 2703 return NS_OK; 2704 } 2705 2706 /* static */ 2707 ScriptLoader::DiskCacheStrategy ScriptLoader::GetDiskCacheStrategy() { 2708 int32_t strategyPref = 2709 StaticPrefs::dom_script_loader_bytecode_cache_strategy(); 2710 LOG(("Bytecode-cache: disk cache strategy = %d.", strategyPref)); 2711 2712 DiskCacheStrategy strategy; 2713 switch (strategyPref) { 2714 case -2: { 2715 strategy.mIsDisabled = true; 2716 break; 2717 } 2718 case -1: { 2719 // Eager mode, skip heuristics! 2720 strategy.mHasSourceLengthMin = false; 2721 strategy.mHasFetchCountMin = false; 2722 break; 2723 } 2724 case 1: { 2725 strategy.mHasSourceLengthMin = true; 2726 strategy.mHasFetchCountMin = true; 2727 strategy.mSourceLengthMin = 1024; 2728 // fetchCountMin is optimized for speed in exchange for additional 2729 // memory and cache use. 2730 strategy.mFetchCountMin = 2; 2731 break; 2732 } 2733 default: 2734 case 0: { 2735 strategy.mHasSourceLengthMin = true; 2736 strategy.mHasFetchCountMin = true; 2737 strategy.mSourceLengthMin = 1024; 2738 // If we were to optimize only for speed, without considering the impact 2739 // on memory, we should set this threshold to 2. (Bug 900784 comment 120) 2740 strategy.mFetchCountMin = 4; 2741 break; 2742 } 2743 } 2744 2745 return strategy; 2746 } 2747 2748 void ScriptLoader::CalculateCacheFlag(ScriptLoadRequest* aRequest) { 2749 using mozilla::TimeDuration; 2750 using mozilla::TimeStamp; 2751 2752 if (aRequest->GetScriptLoadContext()->mIsInline) { 2753 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Skip all: Inline script", 2754 aRequest)); 2755 aRequest->MarkNotCacheable(); 2756 MOZ_ASSERT(!aRequest->getLoadedScript()->HasDiskCacheReference()); 2757 // NOTE: An inline script tag can have an SRI, but we don't calculate it 2758 // for this case. 2759 MOZ_ASSERT(aRequest->HasNoSRIOrSRIAndSerializedStencil()); 2760 return; 2761 } 2762 2763 if (!aRequest->URI()->SchemeIs("http") && 2764 !aRequest->URI()->SchemeIs("https")) { 2765 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Skip all: Unsupported scheme", 2766 aRequest)); 2767 // Internal resources can be exposed to the web content, but they don't 2768 // have to be cached. 2769 aRequest->MarkNotCacheable(); 2770 MOZ_ASSERT(!aRequest->getLoadedScript()->HasDiskCacheReference()); 2771 MOZ_ASSERT(aRequest->HasNoSRIOrSRIAndSerializedStencil()); 2772 return; 2773 } 2774 2775 if (aRequest->IsModuleRequest()) { 2776 ModuleLoadRequest* moduleLoadRequest = aRequest->AsModuleRequest(); 2777 if (moduleLoadRequest->mModuleType == JS::ModuleType::JavaScriptOrWasm) { 2778 #ifdef NIGHTLY_BUILD 2779 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1998240 2780 // For now, we don't support caching wasm modules. 2781 if (moduleLoadRequest->HasWasmMimeTypeEssence()) { 2782 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Skip all: wasm module", 2783 aRequest)); 2784 aRequest->MarkNotCacheable(); 2785 // The disk reference is cleared when we do the mime essense check 2786 // in PrepareLoadedRequest. 2787 MOZ_ASSERT(!aRequest->getLoadedScript()->HasDiskCacheReference()); 2788 MOZ_ASSERT_IF(aRequest->IsTextSource(), 2789 aRequest->HasNoSRIOrSRIAndSerializedStencil()); 2790 return; 2791 } 2792 #endif 2793 } else { 2794 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Skip all: synthetic module", 2795 aRequest)); 2796 aRequest->MarkNotCacheable(); 2797 MOZ_ASSERT(!aRequest->getLoadedScript()->HasDiskCacheReference()); 2798 MOZ_ASSERT_IF(aRequest->IsTextSource(), 2799 aRequest->HasNoSRIOrSRIAndSerializedStencil()); 2800 return; 2801 } 2802 } 2803 2804 if (!aRequest->IsCachedStencil() && aRequest->ExpirationTime().IsExpired()) { 2805 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Skip all: Expired", 2806 aRequest)); 2807 // NOTE: The expiration for in-memory-cached case should be handled by 2808 // SharedScriptCache. 2809 aRequest->MarkSkippedAllCaching(); 2810 aRequest->getLoadedScript()->DropDiskCacheReferenceAndSRI(); 2811 return; 2812 } 2813 2814 if (mCache) { 2815 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Mark in-memory: Stencil", 2816 aRequest)); 2817 aRequest->MarkPassedConditionForMemoryCache(); 2818 2819 // Disk cache is handled by SharedScriptCache. 2820 return; 2821 } 2822 2823 aRequest->MarkSkippedMemoryCaching(); 2824 2825 // The following conditions apply only to the disk cache. 2826 2827 if (aRequest->IsSerializedStencil()) { 2828 LOG( 2829 ("ScriptLoadRequest (%p): Bytecode-cache: Skip disk: " 2830 "IsSerializedStencil", 2831 aRequest)); 2832 aRequest->MarkSkippedDiskCaching(); 2833 MOZ_ASSERT(!aRequest->getLoadedScript()->HasDiskCacheReference()); 2834 return; 2835 } 2836 2837 // We need the nsICacheInfoChannel to exist to be able to open the alternate 2838 // data output stream. 2839 if (!aRequest->getLoadedScript()->HasDiskCacheReference()) { 2840 LOG( 2841 ("ScriptLoadRequest (%p): Bytecode-cache: Skip disk: " 2842 "!LoadedScript::HasDiskCacheReference", 2843 aRequest)); 2844 aRequest->MarkSkippedDiskCaching(); 2845 MOZ_ASSERT_IF(aRequest->IsTextSource(), 2846 aRequest->HasNoSRIOrSRIAndSerializedStencil()); 2847 return; 2848 } 2849 2850 auto strategy = GetDiskCacheStrategy(); 2851 2852 if (strategy.mIsDisabled) { 2853 // Reader mode, keep requesting alternate data but no longer save it. 2854 LOG( 2855 ("ScriptLoadRequest (%p): Bytecode-cache: Skip disk: Disabled by " 2856 "pref.", 2857 aRequest)); 2858 aRequest->MarkSkippedDiskCaching(); 2859 2860 aRequest->getLoadedScript()->DropDiskCacheReferenceAndSRI(); 2861 return; 2862 } 2863 2864 // If the script is too small/large, do not attempt at creating a disk 2865 // cache for this script, as the overhead of parsing it might not be worth the 2866 // effort. 2867 if (strategy.mHasSourceLengthMin) { 2868 size_t sourceLength; 2869 if (aRequest->IsCachedStencil()) { 2870 sourceLength = JS::GetScriptSourceLength(aRequest->GetStencil()); 2871 } else { 2872 MOZ_ASSERT(aRequest->IsTextSource()); 2873 sourceLength = aRequest->ReceivedScriptTextLength(); 2874 } 2875 if (sourceLength < strategy.mSourceLengthMin) { 2876 LOG( 2877 ("ScriptLoadRequest (%p): Bytecode-cache: Skip disk: Script is too " 2878 "small.", 2879 aRequest)); 2880 aRequest->MarkSkippedDiskCaching(); 2881 aRequest->getLoadedScript()->DropDiskCacheReferenceAndSRI(); 2882 return; 2883 } 2884 } 2885 2886 // Check that we loaded the cache entry a few times before attempting any 2887 // disk cache optimization, such that we do not waste time on entry which 2888 // are going to be dropped soon. 2889 if (strategy.mHasFetchCountMin) { 2890 uint8_t fetchCount = aRequest->mLoadedScript->mFetchCount; 2891 LOG(("ScriptLoadRequest (%p): Bytecode-cache: fetchCount = %d.", aRequest, 2892 fetchCount)); 2893 if (fetchCount < strategy.mFetchCountMin) { 2894 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Skip disk: fetchCount", 2895 aRequest)); 2896 aRequest->MarkSkippedDiskCaching(); 2897 2898 if (!mCache) { 2899 // If in-memory cache is not enabled, the disk cache reference 2900 // and the SRI data is necessary only when the current request 2901 // reaches the minimum fetch count. And they can be discarded here 2902 // if the fetch count is less than the minimum. 2903 // 2904 // If in-memory cache is enabled, the disk cache reference and the 2905 // SRI data is cached with the LoadedScript, and the LoadedScript 2906 // is reused by the subsequent requests, and the fetch count 2907 // can reach the minimum later. We need to keep the disk cache 2908 // reference and the SRI data until then. 2909 aRequest->getLoadedScript()->DropDiskCacheReferenceAndSRI(); 2910 } 2911 return; 2912 } 2913 } 2914 2915 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Mark disk: Passed condition", 2916 aRequest)); 2917 aRequest->MarkPassedConditionForDiskCache(); 2918 2919 if (aRequest->IsModuleRequest() && 2920 aRequest->AsModuleRequest()->IsStaticImport()) { 2921 MOZ_ASSERT(!aRequest->isInList()); 2922 mDiskCacheableDependencyModules.AppendElement(aRequest); 2923 } 2924 } 2925 2926 class MOZ_RAII AutoSetProcessingScriptTag { 2927 nsCOMPtr<nsIScriptContext> mContext; 2928 bool mOldTag; 2929 2930 public: 2931 explicit AutoSetProcessingScriptTag(nsIScriptContext* aContext) 2932 : mContext(aContext), mOldTag(mContext->GetProcessingScriptTag()) { 2933 mContext->SetProcessingScriptTag(true); 2934 } 2935 2936 ~AutoSetProcessingScriptTag() { mContext->SetProcessingScriptTag(mOldTag); } 2937 }; 2938 2939 static void ExecuteCompiledScript(JSContext* aCx, ClassicScript* aLoaderScript, 2940 JS::Handle<JSScript*> aScript, 2941 ErrorResult& aRv) { 2942 if (!aScript) { 2943 // Compilation succeeds without producing a script if scripting is 2944 // disabled for the global. 2945 return; 2946 } 2947 2948 if (JS::GetScriptPrivate(aScript).isUndefined()) { 2949 aLoaderScript->AssociateWithScript(aScript); 2950 } 2951 2952 if (!JS_ExecuteScript(aCx, aScript)) { 2953 aRv.NoteJSContextException(aCx); 2954 } 2955 } 2956 2957 // https://html.spec.whatwg.org/#execute-the-script-element 2958 nsresult ScriptLoader::EvaluateScriptElement(ScriptLoadRequest* aRequest) { 2959 MOZ_ASSERT(aRequest->IsFinished()); 2960 MOZ_ASSERT(mDocument); 2961 2962 // The window may have gone away by this point, in which case there's no point 2963 // in trying to run the script. 2964 if (!mDocument->GetInnerWindow()) { 2965 return NS_OK; 2966 } 2967 2968 // 2. If el's preparation-time document is not equal to document, then return. 2969 Document* ownerDoc = 2970 aRequest->GetScriptLoadContext()->GetScriptOwnerDocument(); 2971 if (ownerDoc != mDocument) { 2972 return NS_ERROR_FAILURE; 2973 } 2974 2975 nsCOMPtr<nsIGlobalObject> globalObject; 2976 nsCOMPtr<nsIScriptContext> context; 2977 if (!IsWebExtensionRequest(aRequest)) { 2978 // Otherwise we have to ensure that there is a nsIScriptContext. 2979 nsCOMPtr<nsIScriptGlobalObject> scriptGlobal = GetScriptGlobalObject(); 2980 if (!scriptGlobal) { 2981 return NS_ERROR_FAILURE; 2982 } 2983 2984 MOZ_ASSERT_IF( 2985 aRequest->IsModuleRequest(), 2986 aRequest->AsModuleRequest()->GetGlobalObject() == scriptGlobal); 2987 2988 // Make sure context is a strong reference since we access it after 2989 // we've executed a script, which may cause all other references to 2990 // the context to go away. 2991 context = scriptGlobal->GetScriptContext(); 2992 if (!context) { 2993 return NS_ERROR_FAILURE; 2994 } 2995 2996 globalObject = scriptGlobal; 2997 } 2998 2999 // 5. If el's from an external file is true, or el's type is "module", then 3000 // increment document's ignore-destructive-writes counter. 3001 const bool ignoreDestructiveWrites = 3002 !aRequest->GetScriptLoadContext()->mIsInline || 3003 aRequest->IsModuleRequest(); 3004 if (ignoreDestructiveWrites) { 3005 ownerDoc->IncrementIgnoreDestructiveWritesCounter(); 3006 } 3007 3008 auto afterScript = MakeScopeExit([&] { 3009 if (mContinueParsingDocumentAfterCurrentScript) { 3010 // This mechanism is currently only used when the parser returns 3011 // early due to this script loader having a current script. However, 3012 // now that we have this, we could migrate continuing after a 3013 // parser-blocking script to this same mechanism. Not doing it right 3014 // away to reduce risk of introducing bugs. 3015 mContinueParsingDocumentAfterCurrentScript = false; 3016 if (mDocument) { 3017 nsCOMPtr<nsIParser> parser = mDocument->CreatorParserOrNull(); 3018 if (parser) { 3019 parser->ContinueInterruptedParsingAsync(); 3020 } 3021 } 3022 } 3023 // 7. Decrement the ignore-destructive-writes counter of document, if it was 3024 // incremented in the earlier step. 3025 if (ignoreDestructiveWrites) { 3026 ownerDoc->DecrementIgnoreDestructiveWritesCounter(); 3027 } 3028 }); 3029 3030 // Update our current script. 3031 // This must be destroyed after destroying nsAutoMicroTask, see: 3032 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620505#c4 3033 nsIScriptElement* currentScript = 3034 aRequest->IsModuleRequest() ? nullptr 3035 : aRequest->GetScriptLoadContext() 3036 ->GetScriptElementForCurrentScript(); 3037 AutoCurrentScriptUpdater scriptUpdater(this, currentScript); 3038 3039 Maybe<AutoSetProcessingScriptTag> setProcessingScriptTag; 3040 if (context) { 3041 setProcessingScriptTag.emplace(context); 3042 } 3043 3044 // https://wicg.github.io/import-maps/#integration-script-type 3045 // Switch on the script's type for scriptElement: 3046 // "importmap" 3047 // Assert: Never reached. 3048 MOZ_ASSERT(!aRequest->IsImportMapRequest()); 3049 3050 if (aRequest->IsModuleRequest()) { 3051 return aRequest->AsModuleRequest()->EvaluateModule(); 3052 } 3053 3054 return EvaluateScript(globalObject, aRequest); 3055 } 3056 3057 // Decode a script contained in a buffer. 3058 static void Decode(JSContext* aCx, JS::CompileOptions& aCompileOptions, 3059 const JS::TranscodeRange& aRange, 3060 RefPtr<JS::Stencil>& aStencil, ErrorResult& aRv) { 3061 JS::DecodeOptions decodeOptions(aCompileOptions); 3062 decodeOptions.borrowBuffer = true; 3063 3064 MOZ_ASSERT(aCompileOptions.noScriptRval); 3065 JS::TranscodeResult tr = 3066 JS::DecodeStencil(aCx, decodeOptions, aRange, getter_AddRefs(aStencil)); 3067 // These errors are external parameters which should be handled before the 3068 // decoding phase, and which are the only reasons why you might want to 3069 // fallback on decoding failures. 3070 MOZ_ASSERT(tr != JS::TranscodeResult::Failure_BadBuildId); 3071 if (tr != JS::TranscodeResult::Ok) { 3072 aRv = NS_ERROR_DOM_JS_DECODING_ERROR; 3073 return; 3074 } 3075 } 3076 3077 enum class CollectDelazifications : bool { No, Yes }; 3078 enum class IsAlreadyCollecting : bool { No, Yes }; 3079 3080 // Instantiate (on main-thread) a JS::Stencil generated by off-thread or 3081 // main-thread parsing or decoding. 3082 static void InstantiateStencil( 3083 JSContext* aCx, JS::CompileOptions& aCompileOptions, JS::Stencil* aStencil, 3084 JS::MutableHandle<JSScript*> aScript, 3085 JS::Handle<JS::Value> aDebuggerPrivateValue, 3086 JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv, 3087 const nsAutoCString& aProfilerLabelString, 3088 JS::InstantiationStorage* aStorage = nullptr, 3089 CollectDelazifications aCollectDelazifications = 3090 CollectDelazifications::No) { 3091 AUTO_PROFILER_MARKER_TEXT("ScriptInstantiation", JS, 3092 MarkerInnerWindowIdFromJSContext(aCx), 3093 aProfilerLabelString); 3094 3095 JS::InstantiateOptions instantiateOptions(aCompileOptions); 3096 JS::Rooted<JSScript*> script( 3097 aCx, JS::InstantiateGlobalStencil(aCx, instantiateOptions, aStencil, 3098 aStorage)); 3099 if (!script) { 3100 aRv.NoteJSContextException(aCx); 3101 return; 3102 } 3103 3104 if (aCollectDelazifications == CollectDelazifications::Yes) { 3105 bool ignored; 3106 if (!JS::StartCollectingDelazifications(aCx, script, aStencil, ignored)) { 3107 aRv.NoteJSContextException(aCx); 3108 return; 3109 } 3110 } 3111 3112 aScript.set(script); 3113 3114 if (instantiateOptions.deferDebugMetadata) { 3115 if (!JS::UpdateDebugMetadata(aCx, aScript, instantiateOptions, 3116 aDebuggerPrivateValue, nullptr, 3117 aDebuggerIntroductionScript, nullptr)) { 3118 aRv = NS_ERROR_OUT_OF_MEMORY; 3119 } 3120 } 3121 } 3122 3123 void ScriptLoader::InstantiateClassicScriptFromMaybeEncodedSource( 3124 JSContext* aCx, JS::CompileOptions& aCompileOptions, 3125 ScriptLoadRequest* aRequest, JS::MutableHandle<JSScript*> aScript, 3126 JS::Handle<JS::Value> aDebuggerPrivateValue, 3127 JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv) { 3128 nsAutoCString profilerLabelString; 3129 aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); 3130 3131 CalculateCacheFlag(aRequest); 3132 3133 if (aRequest->IsSerializedStencil()) { 3134 if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) { 3135 LOG(("ScriptLoadRequest (%p): Decode & instantiate and Execute", 3136 aRequest)); 3137 RefPtr<JS::Stencil> stencil; 3138 JS::InstantiationStorage storage; 3139 MOZ_ASSERT(aCompileOptions.noScriptRval); 3140 stencil = 3141 aRequest->GetScriptLoadContext()->StealOffThreadResult(aCx, &storage); 3142 if (!stencil) { 3143 aRv.NoteJSContextException(aCx); 3144 return; 3145 } 3146 3147 aRequest->SetStencil(stencil); 3148 3149 InstantiateStencil(aCx, aCompileOptions, stencil, aScript, 3150 aDebuggerPrivateValue, aDebuggerIntroductionScript, 3151 aRv, profilerLabelString, &storage); 3152 } else { 3153 LOG(("ScriptLoadRequest (%p): Decode and Execute", aRequest)); 3154 3155 RefPtr<JS::Stencil> stencil; 3156 { 3157 AUTO_PROFILER_MARKER_TEXT("DecodeStencilMainThread", JS, 3158 MarkerInnerWindowIdFromJSContext(aCx), 3159 profilerLabelString); 3160 Decode(aCx, aCompileOptions, aRequest->SerializedStencil(), stencil, 3161 aRv); 3162 } 3163 3164 if (stencil) { 3165 aRequest->SetStencil(stencil); 3166 3167 InstantiateStencil(aCx, aCompileOptions, stencil, aScript, 3168 aDebuggerPrivateValue, aDebuggerIntroductionScript, 3169 aRv, profilerLabelString); 3170 } 3171 } 3172 3173 // We do not expect to be saving anything when we already have some 3174 // serialized Stencil. 3175 MOZ_ASSERT(!aRequest->getLoadedScript()->HasDiskCacheReference()); 3176 return; 3177 } 3178 3179 MOZ_ASSERT(aRequest->IsTextSource()); 3180 CollectDelazifications collectDelazifications = 3181 aRequest->PassedConditionForEitherCache() ? CollectDelazifications::Yes 3182 : CollectDelazifications::No; 3183 3184 if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) { 3185 // Off-main-thread parsing. 3186 LOG( 3187 ("ScriptLoadRequest (%p): instantiate off-thread result and " 3188 "Execute", 3189 aRequest)); 3190 MOZ_ASSERT(aRequest->IsTextSource()); 3191 RefPtr<JS::Stencil> stencil; 3192 JS::InstantiationStorage storage; 3193 MOZ_ASSERT(aCompileOptions.noScriptRval); 3194 stencil = 3195 aRequest->GetScriptLoadContext()->StealOffThreadResult(aCx, &storage); 3196 if (!stencil) { 3197 aRv.NoteJSContextException(aCx); 3198 return; 3199 } 3200 3201 aRequest->SetStencil(stencil); 3202 3203 InstantiateStencil(aCx, aCompileOptions, stencil, aScript, 3204 aDebuggerPrivateValue, aDebuggerIntroductionScript, aRv, 3205 profilerLabelString, &storage, collectDelazifications); 3206 } else { 3207 // Main thread parsing (inline and small scripts) 3208 LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest)); 3209 MOZ_ASSERT(aRequest->IsTextSource()); 3210 MaybeSourceText maybeSource; 3211 aRv = aRequest->GetScriptSource(aCx, &maybeSource, 3212 aRequest->mLoadContext.get()); 3213 if (!aRv.Failed()) { 3214 RefPtr<JS::Stencil> stencil; 3215 ErrorResult erv; 3216 auto compile = [&](auto& source) { 3217 AUTO_PROFILER_MARKER_TEXT("ScriptCompileMainThread", JS, 3218 MarkerInnerWindowIdFromJSContext(aCx), 3219 profilerLabelString); 3220 3221 stencil = CompileGlobalScriptToStencil(aCx, aCompileOptions, source); 3222 if (!stencil) { 3223 erv.NoteJSContextException(aCx); 3224 } 3225 }; 3226 3227 MOZ_ASSERT(!maybeSource.empty()); 3228 maybeSource.mapNonEmpty(compile); 3229 3230 if (stencil) { 3231 aRequest->SetStencil(stencil); 3232 3233 InstantiateStencil(aCx, aCompileOptions, stencil, aScript, 3234 aDebuggerPrivateValue, aDebuggerIntroductionScript, 3235 erv, profilerLabelString, /* aStorage = */ nullptr, 3236 collectDelazifications); 3237 } 3238 3239 aRv = std::move(erv); 3240 } 3241 } 3242 } 3243 3244 void ScriptLoader::InstantiateClassicScriptFromCachedStencil( 3245 JSContext* aCx, JS::CompileOptions& aCompileOptions, 3246 ScriptLoadRequest* aRequest, JS::Stencil* aStencil, 3247 JS::MutableHandle<JSScript*> aScript, 3248 JS::Handle<JS::Value> aDebuggerPrivateValue, 3249 JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv) { 3250 nsAutoCString profilerLabelString; 3251 aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); 3252 3253 CalculateCacheFlag(aRequest); 3254 3255 MOZ_ASSERT(aRequest->PassedConditionForMemoryCache()); 3256 3257 // For cached stencils, there can be already ongoing work for the in-memory 3258 // cache and the disk cache. 3259 // 3260 // For collecting delazifications, it's detected by 3261 // JS::StartCollectingDelazifications API and it's not a problem. 3262 // 3263 // For disk cache, ScriptLoader::UpdateDiskCache checks the 3264 // HasDiskCacheReference condition, and that filters out any loaded scripts 3265 // queued multiple times. 3266 InstantiateStencil(aCx, aCompileOptions, aStencil, aScript, 3267 aDebuggerPrivateValue, aDebuggerIntroductionScript, aRv, 3268 profilerLabelString, 3269 /* aStorage = */ nullptr, CollectDelazifications::Yes); 3270 } 3271 3272 void ScriptLoader::InstantiateClassicScriptFromAny( 3273 JSContext* aCx, JS::CompileOptions& aCompileOptions, 3274 ScriptLoadRequest* aRequest, JS::MutableHandle<JSScript*> aScript, 3275 JS::Handle<JS::Value> aDebuggerPrivateValue, 3276 JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv) { 3277 if (aRequest->IsCachedStencil()) { 3278 RefPtr<JS::Stencil> stencil = aRequest->GetStencil(); 3279 InstantiateClassicScriptFromCachedStencil( 3280 aCx, aCompileOptions, aRequest, stencil, aScript, aDebuggerPrivateValue, 3281 aDebuggerIntroductionScript, aRv); 3282 return; 3283 } 3284 3285 InstantiateClassicScriptFromMaybeEncodedSource( 3286 aCx, aCompileOptions, aRequest, aScript, aDebuggerPrivateValue, 3287 aDebuggerIntroductionScript, aRv); 3288 if (aRv.Failed()) { 3289 return; 3290 } 3291 3292 TryCacheRequest(aRequest); 3293 } 3294 3295 ScriptLoader::CacheBehavior ScriptLoader::GetCacheBehavior( 3296 ScriptLoadRequest* aRequest) { 3297 if (!mCache) { 3298 return CacheBehavior::DoNothing; 3299 } 3300 3301 if (aRequest->ExpirationTime().IsExpired()) { 3302 return CacheBehavior::Evict; 3303 } 3304 3305 // NOTE: A new response may arrive even if the exiting cache is still valid, 3306 // for example when the request is performed with bypassing the cache. 3307 // 3308 // If the response is cacheable, it should overwrite the existing cache 3309 // if any. If the response is not cacheable, that should just evict the 3310 // existing cache if any, so that the next request will also reach the 3311 // server. 3312 if (ShouldBypassCache()) { 3313 // If the request bypasses the cache, the response should always 3314 // overwrite the cache, regardless of the content. 3315 return CacheBehavior::Insert; 3316 } 3317 3318 ScriptHashKey key(this, aRequest, aRequest->getLoadedScript()); 3319 auto cacheResult = mCache->Lookup(*this, key, 3320 /* aSyncLoad = */ true); 3321 if (cacheResult.mState == CachedSubResourceState::Complete) { 3322 return CacheBehavior::DoNothing; 3323 } 3324 3325 return CacheBehavior::Insert; 3326 } 3327 3328 void ScriptLoader::TryCacheRequest(ScriptLoadRequest* aRequest) { 3329 MOZ_ASSERT(aRequest->HasStencil()); 3330 MOZ_ASSERT(!aRequest->IsCachedStencil()); 3331 3332 if (aRequest->IsMarkedNotCacheable()) { 3333 aRequest->ClearStencil(); 3334 return; 3335 } 3336 3337 CacheBehavior cacheBehavior = GetCacheBehavior(aRequest); 3338 3339 if (cacheBehavior == CacheBehavior::DoNothing) { 3340 if (!aRequest->PassedConditionForEitherCache()) { 3341 aRequest->ClearStencil(); 3342 } 3343 return; 3344 } 3345 3346 MOZ_ASSERT(mCache); 3347 3348 if (!JS::IsStencilCacheable(aRequest->GetStencil())) { 3349 // If the stencil is not compatible with the cache (e.g. contains asm.js), 3350 // this should also evict any the existing cache if any. 3351 cacheBehavior = CacheBehavior::Evict; 3352 } 3353 3354 LoadedScript* loadedScript = aRequest->getLoadedScript(); 3355 if (cacheBehavior == CacheBehavior::Insert) { 3356 auto loadData = MakeRefPtr<ScriptLoadData>(this, aRequest, loadedScript); 3357 loadedScript->ConvertToCachedStencil(); 3358 if (loadedScript->mFetchCount == 0) { 3359 loadedScript->mFetchCount = 1; 3360 } 3361 mCache->Insert(*loadData); 3362 LOG(("ScriptLoader (%p): Inserting in-memory cache for %s.", this, 3363 aRequest->URI()->GetSpecOrDefault().get())); 3364 TRACE_FOR_TEST(aRequest, "memorycache:saved"); 3365 } else { 3366 MOZ_ASSERT(cacheBehavior == CacheBehavior::Evict); 3367 ScriptHashKey key(this, aRequest, loadedScript); 3368 mCache->Evict(key); 3369 LOG(("ScriptLoader (%p): Evicting in-memory cache for %s.", this, 3370 aRequest->URI()->GetSpecOrDefault().get())); 3371 3372 if (!aRequest->PassedConditionForEitherCache()) { 3373 aRequest->ClearStencil(); 3374 } 3375 TRACE_FOR_TEST(aRequest, "memorycache:evict"); 3376 } 3377 } 3378 3379 /* static */ 3380 nsCString& ScriptLoader::BytecodeMimeTypeFor( 3381 const ScriptLoadRequest* aRequest) { 3382 if (aRequest->IsModuleRequest()) { 3383 return nsContentUtils::JSModuleBytecodeMimeType(); 3384 } 3385 return nsContentUtils::JSScriptBytecodeMimeType(); 3386 } 3387 3388 /* static */ 3389 nsCString& ScriptLoader::BytecodeMimeTypeFor( 3390 const JS::loader::LoadedScript* aLoadedScript) { 3391 if (aLoadedScript->IsModuleScript()) { 3392 return nsContentUtils::JSModuleBytecodeMimeType(); 3393 } 3394 return nsContentUtils::JSScriptBytecodeMimeType(); 3395 } 3396 3397 nsresult ScriptLoader::MaybePrepareForDiskCacheAfterExecute( 3398 ScriptLoadRequest* aRequest, nsresult aRv) { 3399 if (mCache) { 3400 // Disk cache is handled by SharedScriptCache. 3401 return NS_OK; 3402 } 3403 3404 if (!aRequest->PassedConditionForDiskCache() || !aRequest->HasStencil()) { 3405 LOG(("ScriptLoadRequest (%p): Bytecode-cache: disabled (rv = %X)", aRequest, 3406 unsigned(aRv))); 3407 TRACE_FOR_TEST(aRequest, "diskcache:disabled"); 3408 3409 // For in-memory cached requests, the disk cache references are necessary 3410 // for later load. 3411 if (aRequest->HasStencil()) { 3412 MOZ_ASSERT_IF(!aRequest->PassedConditionForMemoryCache(), 3413 !aRequest->getLoadedScript()->HasDiskCacheReference()); 3414 } else { 3415 // This hits compile error. 3416 aRequest->getLoadedScript()->DropDiskCacheReferenceAndSRI(); 3417 } 3418 3419 return aRv; 3420 } 3421 3422 TRACE_FOR_TEST(aRequest, "diskcache:register"); 3423 MOZ_ASSERT(aRequest->GetSRILength() == aRequest->SRI().length()); 3424 RegisterForDiskCache(aRequest); 3425 3426 return aRv; 3427 } 3428 3429 nsresult ScriptLoader::MaybePrepareModuleForDiskCacheAfterExecute( 3430 ModuleLoadRequest* aRequest, nsresult aRv) { 3431 MOZ_ASSERT(aRequest->IsTopLevel() || aRequest->IsDynamicImport()); 3432 3433 if (mCache) { 3434 // Disk cache is handled by SharedScriptCache. 3435 return NS_OK; 3436 } 3437 3438 // NOTE: If a module is passed to this multiple times, it can be 3439 // enqueued multiple times. 3440 // This is okay because ScriptLoader::UpdateDiskCache filters out 3441 // any script without the disk cache reference. 3442 3443 aRv = MaybePrepareForDiskCacheAfterExecute(aRequest, aRv); 3444 3445 for (auto* r = mDiskCacheableDependencyModules.getFirst(); r;) { 3446 auto* dep = r->AsModuleRequest(); 3447 MOZ_ASSERT(dep->PassedConditionForDiskCache()); 3448 3449 r = r->getNext(); 3450 3451 if (dep->GetRootModule() != aRequest) { 3452 continue; 3453 } 3454 3455 mDiskCacheableDependencyModules.Remove(dep); 3456 3457 aRv = MaybePrepareForDiskCacheAfterExecute(dep, aRv); 3458 } 3459 3460 return aRv; 3461 } 3462 3463 nsresult ScriptLoader::EvaluateScript(nsIGlobalObject* aGlobalObject, 3464 ScriptLoadRequest* aRequest) { 3465 nsAutoMicroTask mt; 3466 AutoEntryScript aes(aGlobalObject, "EvaluateScript", true); 3467 JSContext* cx = aes.cx(); 3468 3469 nsAutoCString profilerLabelString; 3470 aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); 3471 3472 // Create a ClassicScript object and associate it with the JSScript. 3473 MOZ_ASSERT(aRequest->mLoadedScript->IsClassicScript()); 3474 3475 RefPtr<ClassicScript> classicScript = 3476 aRequest->mLoadedScript->AsClassicScript(); 3477 JS::Rooted<JS::Value> classicScriptValue(cx, JS::PrivateValue(classicScript)); 3478 3479 JS::CompileOptions options(cx); 3480 JS::Rooted<JSScript*> introductionScript(cx); 3481 nsresult rv = 3482 FillCompileOptionsForRequest(cx, aRequest, &options, &introductionScript); 3483 3484 if (NS_FAILED(rv)) { 3485 return rv; 3486 } 3487 3488 // Apply the delazify strategy if the script is small. 3489 if (aRequest->IsTextSource() && 3490 aRequest->ScriptTextLength() < OffThreadMinimumTextLength && 3491 ShouldApplyDelazifyStrategy(aRequest)) { 3492 ApplyDelazifyStrategy(&options); 3493 mTotalFullParseSize += 3494 aRequest->ScriptTextLength() > 0 3495 ? static_cast<uint32_t>(aRequest->ScriptTextLength()) 3496 : 0; 3497 3498 LOG( 3499 ("ScriptLoadRequest (%p): non-on-demand-only (non-omt) Parsing Enabled " 3500 "for url=%s mTotalFullParseSize=%u", 3501 aRequest, aRequest->URI()->GetSpecOrDefault().get(), 3502 mTotalFullParseSize)); 3503 } 3504 3505 JS::Rooted<JSObject*> global(cx, aGlobalObject->GetGlobalJSObject()); 3506 if (MOZ_UNLIKELY(!xpc::Scriptability::Get(global).Allowed())) { 3507 return NS_OK; 3508 } 3509 ErrorResult erv; 3510 mozilla::AutoProfilerLabel autoProfilerLabel("JSExecutionContext", 3511 /* dynamicStr */ nullptr, 3512 JS::ProfilingCategoryPair::JS); 3513 JSAutoRealm autoRealm(cx, global); 3514 JS::Rooted<JSScript*> script(cx); 3515 InstantiateClassicScriptFromAny(cx, options, aRequest, &script, 3516 classicScriptValue, introductionScript, erv); 3517 3518 if (!erv.Failed()) { 3519 LOG(("ScriptLoadRequest (%p): Evaluate Script", aRequest)); 3520 AUTO_PROFILER_MARKER_TEXT("ScriptExecution", JS, 3521 MarkerInnerWindowIdFromJSContext(cx), 3522 profilerLabelString); 3523 3524 MOZ_ASSERT(options.noScriptRval); 3525 TRACE_FOR_TEST(aRequest, "evaluate:classic"); 3526 3527 auto start = TimeStamp::Now(); 3528 3529 ExecuteCompiledScript(cx, classicScript, script, erv); 3530 3531 auto end = TimeStamp::Now(); 3532 auto duration = (end - start).ToMilliseconds(); 3533 3534 static constexpr double LongScriptThresholdInMilliseconds = 1.0; 3535 if (duration > LongScriptThresholdInMilliseconds) { 3536 aRequest->SetTookLongInPreviousRuns(); 3537 } 3538 } 3539 rv = EvaluationExceptionToNSResult(erv); 3540 3541 if (NS_FAILED(rv)) { 3542 return rv; 3543 } 3544 3545 // This must be called also for compilation failure case, in order to 3546 // dispatch test-only event. 3547 rv = MaybePrepareForDiskCacheAfterExecute(aRequest, rv); 3548 3549 // Even if we are not saving the current script to the disk cache, we have 3550 // to trigger the disk cache encoding, as the current script can be blocking 3551 // the other encoding, or the current script can delazify more functions 3552 // which we are recording the disk cache. 3553 LOG(("ScriptLoadRequest (%p): ScriptLoader = %p", aRequest, this)); 3554 MaybeUpdateDiskCache(); 3555 3556 return rv; 3557 } 3558 3559 /* static */ 3560 LoadedScript* ScriptLoader::GetActiveScript(JSContext* aCx) { 3561 JS::Value value = JS::GetScriptedCallerPrivate(aCx); 3562 if (value.isUndefined()) { 3563 return nullptr; 3564 } 3565 3566 return static_cast<LoadedScript*>(value.toPrivate()); 3567 } 3568 3569 void ScriptLoader::RegisterForDiskCache(ScriptLoadRequest* aRequest) { 3570 MOZ_ASSERT(!mCache); 3571 MOZ_ASSERT(aRequest->PassedConditionForDiskCache()); 3572 MOZ_ASSERT(aRequest->HasStencil()); 3573 MOZ_ASSERT(aRequest->getLoadedScript()->HasDiskCacheReference()); 3574 MOZ_DIAGNOSTIC_ASSERT(!aRequest->isInList()); 3575 MOZ_ASSERT(!IsWebExtensionRequest(aRequest), 3576 "Web extension scripts are not compatible with the disk cache"); 3577 mDiskCacheQueue.AppendElement(aRequest->getLoadedScript()); 3578 } 3579 3580 void ScriptLoader::LoadEventFired() { 3581 mLoadEventFired = true; 3582 MaybeUpdateDiskCache(); 3583 } 3584 3585 void ScriptLoader::Destroy() { 3586 if (mShutdownObserver) { 3587 mShutdownObserver->Unregister(); 3588 mShutdownObserver = nullptr; 3589 } 3590 3591 CancelAndClearScriptLoadRequests(); 3592 GiveUpDiskCaching(); 3593 } 3594 3595 void ScriptLoader::MaybeUpdateDiskCache() { 3596 // We wait for the load event to be fired before saving any script to the 3597 // disk cache. It is quite common to have load event listeners trigger more 3598 // JavaScript execution, that we want to save as part of disk cache, to 3599 // improve the load time in subsequent loads. 3600 if (!mLoadEventFired) { 3601 LOG(("ScriptLoader (%p): Wait for the load-end event to fire.", this)); 3602 return; 3603 } 3604 3605 // Wait until all scripts are loaded before saving to the disk cache, such 3606 // that we capture most of the intialization of the page. 3607 if (HasPendingRequests()) { 3608 LOG(("ScriptLoader (%p): Wait for other pending request to finish.", this)); 3609 return; 3610 } 3611 3612 if (mCache) { 3613 if (!mCache->MaybeScheduleUpdateDiskCache()) { 3614 TRACE_FOR_TEST_0("diskcache:noschedule"); 3615 } 3616 return; 3617 } 3618 3619 // If we already gave up, ensure that we are not going to enqueue any script, 3620 // and that we finalize them properly. 3621 if (mGiveUpDiskCaching) { 3622 LOG(("ScriptLoader (%p): Keep giving-up saving to the disk cache.", this)); 3623 GiveUpDiskCaching(); 3624 return; 3625 } 3626 3627 // No need to fire any event if there is no script to be saved. 3628 if (mDiskCacheQueue.IsEmpty()) { 3629 LOG(("ScriptLoader (%p): No script in queue to be saved to the disk.", 3630 this)); 3631 return; 3632 } 3633 3634 // Create a new runnable dedicated to encoding all enqueued scripts when the 3635 // document is idle. In case of failure, we give-up on saving the disk cache. 3636 nsCOMPtr<nsIRunnable> encoder = NewRunnableMethod( 3637 "ScriptLoader::UpdateCache", this, &ScriptLoader::UpdateDiskCache); 3638 if (NS_FAILED(NS_DispatchToCurrentThreadQueue(encoder.forget(), 3639 EventQueuePriority::Idle))) { 3640 GiveUpDiskCaching(); 3641 return; 3642 } 3643 3644 LOG(("ScriptLoader (%p): Schedule the disk cache encoding.", this)); 3645 } 3646 3647 void ScriptLoader::UpdateDiskCache() { 3648 MOZ_ASSERT(!mCache); 3649 LOG(("ScriptLoader (%p): Start the disk cache encoding.", this)); 3650 3651 // If any script got added in the previous loop cycle, wait until all 3652 // remaining script executions are completed, such that we capture most of 3653 // the initialization. 3654 if (HasPendingRequests()) { 3655 return; 3656 } 3657 3658 JS::FrontendContext* fc = JS::NewFrontendContext(); 3659 if (!fc) { 3660 LOG( 3661 ("ScriptLoader (%p): Cannot create FrontendContext for the disk cache " 3662 "encoding.", 3663 this)); 3664 return; 3665 } 3666 3667 for (auto& loadedScript : mDiskCacheQueue) { 3668 // The encoding is performed only when there was no disk cache stored in 3669 // the necko cache. 3670 if (!loadedScript->HasDiskCacheReference()) { 3671 continue; 3672 } 3673 3674 MOZ_ASSERT(loadedScript->HasStencil()); 3675 3676 Vector<uint8_t> compressed; 3677 if (!EncodeAndCompress(fc, loadedScript, loadedScript->GetStencil(), 3678 loadedScript->SRI(), compressed)) { 3679 loadedScript->DropDiskCacheReference(); 3680 loadedScript->DropSRIOrSRIAndSerializedStencil(); 3681 TRACE_FOR_TEST(loadedScript, "diskcache:failed"); 3682 continue; 3683 } 3684 3685 if (!SaveToDiskCache(loadedScript, compressed)) { 3686 loadedScript->DropDiskCacheReference(); 3687 loadedScript->DropSRIOrSRIAndSerializedStencil(); 3688 TRACE_FOR_TEST(loadedScript, "diskcache:failed"); 3689 continue; 3690 } 3691 3692 loadedScript->DropDiskCacheReference(); 3693 loadedScript->DropSRIOrSRIAndSerializedStencil(); 3694 TRACE_FOR_TEST(loadedScript, "diskcache:saved"); 3695 } 3696 mDiskCacheQueue.Clear(); 3697 3698 JS::DestroyFrontendContext(fc); 3699 } 3700 3701 /* static */ 3702 bool ScriptLoader::EncodeAndCompress( 3703 JS::FrontendContext* aFc, const JS::loader::LoadedScript* aLoadedScript, 3704 JS::Stencil* aStencil, const JS::TranscodeBuffer& aSRI, 3705 Vector<uint8_t>& aCompressed) { 3706 size_t SRILength = aSRI.length(); 3707 MOZ_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(SRILength)); 3708 3709 JS::TranscodeBuffer SRIAndSerializedStencil; 3710 if (!SRIAndSerializedStencil.appendAll(aSRI)) { 3711 LOG(("LoadedScript (%p): Cannot allocate buffer", aLoadedScript)); 3712 return false; 3713 } 3714 3715 JS::TranscodeResult result = 3716 JS::EncodeStencil(aFc, aStencil, SRIAndSerializedStencil); 3717 3718 if (result != JS::TranscodeResult::Ok) { 3719 // Encoding can be aborted for non-supported syntax (e.g. asm.js), or 3720 // any other internal error. 3721 // We don't care the error and just give up encoding. 3722 JS::ClearFrontendErrors(aFc); 3723 3724 LOG(("LoadedScript (%p): Cannot encode stencil", aLoadedScript)); 3725 return false; 3726 } 3727 3728 // TODO probably need to move this to a helper thread 3729 if (!ScriptBytecodeCompress(SRIAndSerializedStencil, SRILength, 3730 aCompressed)) { 3731 return false; 3732 } 3733 3734 if (aCompressed.length() >= UINT32_MAX) { 3735 LOG( 3736 ("LoadedScript (%p): Serialized stencil is too large to be decoded " 3737 "correctly.", 3738 aLoadedScript)); 3739 return false; 3740 } 3741 3742 return true; 3743 } 3744 3745 /* static */ 3746 bool ScriptLoader::SaveToDiskCache( 3747 const JS::loader::LoadedScript* aLoadedScript, 3748 const Vector<uint8_t>& aCompressed) { 3749 MOZ_ASSERT(NS_IsMainThread()); 3750 3751 // Open the output stream to the cache entry alternate data storage. This 3752 // might fail if the stream is already open by another request, in which 3753 // case, we just ignore the current one. 3754 nsCOMPtr<nsIAsyncOutputStream> output; 3755 nsresult rv = aLoadedScript->mCacheEntry->OpenAlternativeOutputStream( 3756 BytecodeMimeTypeFor(aLoadedScript), 3757 static_cast<int64_t>(aCompressed.length()), getter_AddRefs(output)); 3758 if (NS_FAILED(rv)) { 3759 LOG( 3760 ("LoadedScript (%p): Cannot open the disk cache (rv = %X, output " 3761 "= %p)", 3762 aLoadedScript, unsigned(rv), output.get())); 3763 return false; 3764 } 3765 MOZ_ASSERT(output); 3766 3767 auto closeOutStream = mozilla::MakeScopeExit([&]() { 3768 rv = output->CloseWithStatus(rv); 3769 LOG(("LoadedScript (%p): Closing (rv = %X)", aLoadedScript, unsigned(rv))); 3770 }); 3771 3772 uint32_t n; 3773 rv = output->Write(reinterpret_cast<const char*>(aCompressed.begin()), 3774 aCompressed.length(), &n); 3775 LOG( 3776 ("LoadedScript (%p): Write the disk cache (rv = %X, length = %u, " 3777 "written = %u)", 3778 aLoadedScript, unsigned(rv), unsigned(aCompressed.length()), n)); 3779 if (NS_FAILED(rv)) { 3780 return false; 3781 } 3782 3783 MOZ_RELEASE_ASSERT(aCompressed.length() == n); 3784 return true; 3785 } 3786 3787 void ScriptLoader::GiveUpDiskCaching() { 3788 if (mCache) { 3789 // Disk cache is handled by SharedScriptCache. 3790 MOZ_ASSERT(mDiskCacheQueue.IsEmpty()); 3791 MOZ_ASSERT(mDiskCacheableDependencyModules.isEmpty()); 3792 return; 3793 } 3794 3795 // If the document went away prematurely, we still want to set this, in order 3796 // to avoid queuing more scripts. 3797 mGiveUpDiskCaching = true; 3798 3799 for (auto& loadedScript : mDiskCacheQueue) { 3800 LOG(("LoadedScript (%p): Giving up encoding the disk cache", 3801 loadedScript.get())); 3802 TRACE_FOR_TEST(loadedScript, "diskcache:giveup"); 3803 3804 loadedScript->DropDiskCacheReference(); 3805 loadedScript->DropSRIOrSRIAndSerializedStencil(); 3806 } 3807 mDiskCacheQueue.Clear(); 3808 3809 while (!mDiskCacheableDependencyModules.isEmpty()) { 3810 RefPtr<ScriptLoadRequest> request = 3811 mDiskCacheableDependencyModules.StealFirst(); 3812 } 3813 } 3814 3815 bool ScriptLoader::HasPendingRequests() const { 3816 return mParserBlockingRequest || !mXSLTRequests.isEmpty() || 3817 !mLoadedAsyncRequests.isEmpty() || 3818 !mNonAsyncExternalScriptInsertedRequests.isEmpty() || 3819 !mDeferRequests.isEmpty() || HasPendingDynamicImports() || 3820 !mPendingChildLoaders.IsEmpty(); 3821 // mOffThreadCompilingRequests are already being processed. 3822 } 3823 3824 bool ScriptLoader::HasPendingDynamicImports() const { 3825 if (mModuleLoader && mModuleLoader->HasPendingDynamicImports()) { 3826 return true; 3827 } 3828 3829 for (ModuleLoader* loader : mWebExtModuleLoaders) { 3830 if (loader->HasPendingDynamicImports()) { 3831 return true; 3832 } 3833 } 3834 3835 for (ModuleLoader* loader : mShadowRealmModuleLoaders) { 3836 if (loader->HasPendingDynamicImports()) { 3837 return true; 3838 } 3839 } 3840 3841 return false; 3842 } 3843 3844 void ScriptLoader::ProcessPendingRequestsAsync() { 3845 if (HasPendingRequests()) { 3846 nsCOMPtr<nsIRunnable> task = NewRunnableMethod<bool>( 3847 "dom::ScriptLoader::ProcessPendingRequests", this, 3848 &ScriptLoader::ProcessPendingRequests, false); 3849 if (mDocument) { 3850 mDocument->Dispatch(task.forget()); 3851 } else { 3852 NS_DispatchToCurrentThread(task.forget()); 3853 } 3854 } 3855 } 3856 3857 void ProcessPendingRequestsCallback(nsITimer* aTimer, void* aClosure) { 3858 RefPtr<ScriptLoader> sl = static_cast<ScriptLoader*>(aClosure); 3859 sl->ProcessPendingRequests(true); 3860 } 3861 3862 void ScriptLoader::ProcessPendingRequestsAsyncBypassParserBlocking() { 3863 MOZ_ASSERT(HasPendingRequests()); 3864 3865 if (!mProcessPendingRequestsAsyncBypassParserBlocking) { 3866 mProcessPendingRequestsAsyncBypassParserBlocking = NS_NewTimer(); 3867 } 3868 3869 // test_bug503481b.html tests the unlikely edge case where loading parser 3870 // blocking script depends on async script to be executed. So don't block 3871 // async scripts forever. 3872 mProcessPendingRequestsAsyncBypassParserBlocking->InitWithNamedFuncCallback( 3873 ProcessPendingRequestsCallback, this, 2500, nsITimer::TYPE_ONE_SHOT, 3874 "ProcessPendingRequestsAsyncBypassParserBlocking"_ns); 3875 } 3876 3877 void ScriptLoader::ProcessPendingRequests(bool aAllowBypassingParserBlocking) { 3878 RefPtr<ScriptLoadRequest> request; 3879 3880 if (mProcessPendingRequestsAsyncBypassParserBlocking) { 3881 mProcessPendingRequestsAsyncBypassParserBlocking->Cancel(); 3882 } 3883 3884 if (mParserBlockingRequest) { 3885 if (mParserBlockingRequest->IsFinished() && 3886 ReadyToExecuteParserBlockingScripts()) { 3887 request.swap(mParserBlockingRequest); 3888 UnblockParser(request); 3889 ProcessRequest(request); 3890 ContinueParserAsync(request); 3891 ProcessPendingRequestsAsync(); 3892 return; 3893 } 3894 3895 if (!aAllowBypassingParserBlocking) { 3896 ProcessPendingRequestsAsyncBypassParserBlocking(); 3897 return; 3898 } 3899 } 3900 3901 while (ReadyToExecuteParserBlockingScripts() && !mXSLTRequests.isEmpty() && 3902 mXSLTRequests.getFirst()->IsFinished()) { 3903 request = mXSLTRequests.StealFirst(); 3904 ProcessRequest(request); 3905 } 3906 3907 while (ReadyToExecuteScripts() && !mLoadedAsyncRequests.isEmpty()) { 3908 request = mLoadedAsyncRequests.StealFirst(); 3909 if (request->IsModuleRequest()) { 3910 ProcessRequest(request); 3911 } else { 3912 CompileOffThreadOrProcessRequest(request); 3913 } 3914 } 3915 3916 while (ReadyToExecuteScripts() && 3917 !mNonAsyncExternalScriptInsertedRequests.isEmpty() && 3918 mNonAsyncExternalScriptInsertedRequests.getFirst()->IsFinished()) { 3919 // Violate the HTML5 spec and execute these in the insertion order in 3920 // order to make LABjs and the "order" plug-in for RequireJS work with 3921 // their Gecko-sniffed code path. See 3922 // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html 3923 request = mNonAsyncExternalScriptInsertedRequests.StealFirst(); 3924 ProcessRequest(request); 3925 } 3926 3927 if (mDeferCheckpointReached && mXSLTRequests.isEmpty()) { 3928 while (ReadyToExecuteScripts() && !mDeferRequests.isEmpty() && 3929 mDeferRequests.getFirst()->IsFinished()) { 3930 if (mDeferRequests.getFirst()->TookLongInPreviousRuns() && 3931 !mDeferRequests.getFirst()->HadPostponed() && IsBeforeFCP()) { 3932 mDeferRequests.getFirst()->SetHadPostponed(); 3933 ProcessPendingRequestsAsync(); 3934 return; 3935 } 3936 3937 request = mDeferRequests.StealFirst(); 3938 ProcessRequest(request); 3939 } 3940 } 3941 3942 while (!mPendingChildLoaders.IsEmpty() && 3943 ReadyToExecuteParserBlockingScripts()) { 3944 RefPtr<ScriptLoader> child = mPendingChildLoaders[0]; 3945 mPendingChildLoaders.RemoveElementAt(0); 3946 child->RemoveParserBlockingScriptExecutionBlocker(); 3947 } 3948 3949 if (mDeferCheckpointReached && mDocument && !mParserBlockingRequest && 3950 mNonAsyncExternalScriptInsertedRequests.isEmpty() && 3951 mXSLTRequests.isEmpty() && mDeferRequests.isEmpty() && 3952 MaybeRemovedDeferRequests()) { 3953 return ProcessPendingRequests(); 3954 } 3955 3956 if (mDeferCheckpointReached && mDocument && !mParserBlockingRequest && 3957 mLoadingAsyncRequests.isEmpty() && mLoadedAsyncRequests.isEmpty() && 3958 mNonAsyncExternalScriptInsertedRequests.isEmpty() && 3959 mXSLTRequests.isEmpty() && mDeferRequests.isEmpty()) { 3960 // No more pending scripts; time to unblock onload. 3961 // OK to unblock onload synchronously here, since callers must be 3962 // prepared for the world changing anyway. 3963 mDeferCheckpointReached = false; 3964 mDocument->UnblockOnload(true); 3965 } 3966 } 3967 3968 bool ScriptLoader::IsBeforeFCP() { 3969 if (mHadFCPDoNotUseDirectly) { 3970 return false; 3971 } 3972 3973 if (mLoadEventFired) { 3974 return false; 3975 } 3976 3977 if (!mDocument) { 3978 return false; 3979 } 3980 3981 nsPresContext* context = mDocument->GetPresContext(); 3982 if (!context) { 3983 return false; 3984 } 3985 3986 if (context->HadFirstContentfulPaint()) { 3987 mHadFCPDoNotUseDirectly = true; 3988 return false; 3989 } 3990 3991 return true; 3992 } 3993 3994 bool ScriptLoader::ReadyToExecuteParserBlockingScripts() { 3995 // Make sure the SelfReadyToExecuteParserBlockingScripts check is first, so 3996 // that we don't block twice on an ancestor. 3997 if (!SelfReadyToExecuteParserBlockingScripts()) { 3998 return false; 3999 } 4000 4001 if (mDocument && mDocument->GetWindowContext()) { 4002 for (WindowContext* wc = 4003 mDocument->GetWindowContext()->GetParentWindowContext(); 4004 wc; wc = wc->GetParentWindowContext()) { 4005 if (Document* doc = wc->GetDocument()) { 4006 ScriptLoader* ancestor = doc->GetScriptLoader(); 4007 if (ancestor && !ancestor->SelfReadyToExecuteParserBlockingScripts() && 4008 ancestor->AddPendingChildLoader(this)) { 4009 AddParserBlockingScriptExecutionBlocker(); 4010 return false; 4011 } 4012 } 4013 } 4014 } 4015 4016 return true; 4017 } 4018 4019 template <typename Unit> 4020 static nsresult ConvertToUnicode(nsIChannel* aChannel, const uint8_t* aData, 4021 uint32_t aLength, 4022 const nsAString& aHintCharset, 4023 Document* aDocument, Unit*& aBufOut, 4024 size_t& aLengthOut) { 4025 if (!aLength) { 4026 aBufOut = nullptr; 4027 aLengthOut = 0; 4028 return NS_OK; 4029 } 4030 4031 auto data = Span(aData, aLength); 4032 4033 // The encoding info precedence is as follows from high to low: 4034 // The BOM 4035 // HTTP Content-Type (if name recognized) 4036 // charset attribute (if name recognized) 4037 // The encoding of the document 4038 4039 UniquePtr<Decoder> unicodeDecoder; 4040 4041 const Encoding* encoding; 4042 std::tie(encoding, std::ignore) = Encoding::ForBOM(data); 4043 if (encoding) { 4044 unicodeDecoder = encoding->NewDecoderWithBOMRemoval(); 4045 } 4046 4047 if (!unicodeDecoder && aChannel) { 4048 nsAutoCString label; 4049 if (NS_SUCCEEDED(aChannel->GetContentCharset(label)) && 4050 (encoding = Encoding::ForLabel(label))) { 4051 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling(); 4052 } 4053 } 4054 4055 if (!unicodeDecoder && (encoding = Encoding::ForLabel(aHintCharset))) { 4056 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling(); 4057 } 4058 4059 if (!unicodeDecoder && aDocument) { 4060 unicodeDecoder = 4061 aDocument->GetDocumentCharacterSet()->NewDecoderWithoutBOMHandling(); 4062 } 4063 4064 if (!unicodeDecoder) { 4065 // Curiously, there are various callers that don't pass aDocument. The 4066 // fallback in the old code was ISO-8859-1, which behaved like 4067 // windows-1252. 4068 unicodeDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling(); 4069 } 4070 4071 auto signalOOM = mozilla::MakeScopeExit([&aBufOut, &aLengthOut]() { 4072 aBufOut = nullptr; 4073 aLengthOut = 0; 4074 }); 4075 4076 CheckedInt<size_t> bufferLength = 4077 ScriptDecoding<Unit>::MaxBufferLength(unicodeDecoder, aLength); 4078 if (!bufferLength.isValid()) { 4079 return NS_ERROR_OUT_OF_MEMORY; 4080 } 4081 4082 CheckedInt<size_t> bufferByteSize = bufferLength * sizeof(Unit); 4083 if (!bufferByteSize.isValid()) { 4084 return NS_ERROR_OUT_OF_MEMORY; 4085 } 4086 4087 aBufOut = static_cast<Unit*>(js_malloc(bufferByteSize.value())); 4088 if (!aBufOut) { 4089 return NS_ERROR_OUT_OF_MEMORY; 4090 } 4091 4092 signalOOM.release(); 4093 aLengthOut = ScriptDecoding<Unit>::DecodeInto( 4094 unicodeDecoder, data, Span(aBufOut, bufferLength.value()), 4095 /* aEndOfSource = */ true); 4096 return NS_OK; 4097 } 4098 4099 /* static */ 4100 nsresult ScriptLoader::ConvertToUTF16( 4101 nsIChannel* aChannel, const uint8_t* aData, uint32_t aLength, 4102 const nsAString& aHintCharset, Document* aDocument, 4103 UniquePtr<char16_t[], JS::FreePolicy>& aBufOut, size_t& aLengthOut) { 4104 char16_t* bufOut; 4105 nsresult rv = ConvertToUnicode(aChannel, aData, aLength, aHintCharset, 4106 aDocument, bufOut, aLengthOut); 4107 if (NS_SUCCEEDED(rv)) { 4108 aBufOut.reset(bufOut); 4109 } 4110 return rv; 4111 } 4112 4113 /* static */ 4114 nsresult ScriptLoader::ConvertToUTF8( 4115 nsIChannel* aChannel, const uint8_t* aData, uint32_t aLength, 4116 const nsAString& aHintCharset, Document* aDocument, 4117 UniquePtr<Utf8Unit[], JS::FreePolicy>& aBufOut, size_t& aLengthOut) { 4118 Utf8Unit* bufOut; 4119 nsresult rv = ConvertToUnicode(aChannel, aData, aLength, aHintCharset, 4120 aDocument, bufOut, aLengthOut); 4121 if (NS_SUCCEEDED(rv)) { 4122 aBufOut.reset(bufOut); 4123 } 4124 return rv; 4125 } 4126 4127 nsresult ScriptLoader::OnStreamComplete( 4128 nsIIncrementalStreamLoader* aLoader, ScriptLoadRequest* aRequest, 4129 nsresult aChannelStatus, nsresult aSRIStatus, 4130 SRICheckDataVerifier* aSRIDataVerifier) { 4131 NS_ASSERTION(aRequest, "null request in stream complete handler"); 4132 NS_ENSURE_TRUE(aRequest, NS_ERROR_FAILURE); 4133 4134 if (aRequest->IsCanceled()) { 4135 return NS_BINDING_ABORTED; 4136 } 4137 4138 nsresult rv = VerifySRI(aRequest, aLoader, aSRIStatus, aSRIDataVerifier); 4139 4140 if (NS_SUCCEEDED(rv)) { 4141 nsCOMPtr<nsIRequest> channelRequest; 4142 aLoader->GetRequest(getter_AddRefs(channelRequest)); 4143 4144 nsCOMPtr<nsICacheInfoChannel> cacheInfo = do_QueryInterface(channelRequest); 4145 nsCOMPtr<nsICacheEntryWriteHandle> cacheEntry; 4146 if (cacheInfo && NS_SUCCEEDED(cacheInfo->GetCacheEntryWriteHandle( 4147 getter_AddRefs(cacheEntry)))) { 4148 uint64_t id; 4149 nsresult rv = cacheInfo->GetCacheEntryId(&id); 4150 if (NS_SUCCEEDED(rv)) { 4151 LOG(("ScriptLoadRequest (%p): cacheEntryId = %zx", aRequest, 4152 size_t(id))); 4153 4154 if (aRequest->HasDirtyCache()) { 4155 // This request found a dirty cache. 4156 // Validate the cache with the response's cache ID. 4157 ScriptHashKey key(this, aRequest, aRequest->ReferrerPolicy(), 4158 aRequest->FetchOptions(), aRequest->URI()); 4159 auto cacheResult = mCache->Lookup(*this, key, /* aSyncLoad = */ true); 4160 if (cacheResult.mState == CachedSubResourceState::Complete && 4161 cacheResult.mCompleteValue->CacheEntryId() == id) { 4162 cacheResult.mCompleteValue->UnsetDirty(); 4163 // This keeps the request as "fetching" state. 4164 // PrepareLoadedRequest below will set it to "ready" state. 4165 // 4166 // Off-thread compilation is skipped for the revived cache. 4167 // See AttemptOffThreadScriptCompile. 4168 // 4169 // Main thread compilation is skipped in the same way as 4170 // non-dirty cache. 4171 aRequest->CacheEntryRevived(cacheResult.mCompleteValue); 4172 4173 cacheResult.mCompleteValue->AddFetchCount(); 4174 4175 TRACE_FOR_TEST(aRequest, "memorycache:dirty:revived"); 4176 } else { 4177 mCache->Evict(key); 4178 TRACE_FOR_TEST(aRequest, "memorycache:dirty:evicted"); 4179 } 4180 } 4181 4182 aRequest->getLoadedScript()->SetCacheEntryId(id); 4183 } 4184 4185 // If we are loading from source, store the cache info channel and 4186 // save the computed SRI hash or a dummy SRI hash in case we are going to 4187 // save the this script in the disk cache. 4188 if (aRequest->IsTextSource() && 4189 StaticPrefs::dom_script_loader_bytecode_cache_enabled()) { 4190 uint32_t fetchCount; 4191 if (NS_SUCCEEDED(cacheInfo->GetCacheTokenFetchCount(&fetchCount))) { 4192 if (fetchCount < UINT8_MAX) { 4193 aRequest->getLoadedScript()->mFetchCount = fetchCount; 4194 } else { 4195 aRequest->getLoadedScript()->mFetchCount = UINT8_MAX; 4196 } 4197 } 4198 4199 aRequest->getLoadedScript()->mCacheEntry = cacheEntry; 4200 LOG(("ScriptLoadRequest (%p): nsICacheEntryWriteHandle = %p", aRequest, 4201 (void*)cacheEntry)); 4202 4203 rv = SaveSRIHash(aRequest, aSRIDataVerifier); 4204 } 4205 } 4206 4207 if (NS_SUCCEEDED(rv)) { 4208 rv = PrepareLoadedRequest(aRequest, aLoader, aChannelStatus); 4209 } 4210 4211 if (NS_FAILED(rv)) { 4212 aRequest->getLoadedScript()->DropDiskCacheReference(); 4213 ReportErrorToConsole(aRequest, rv); 4214 } 4215 } 4216 4217 if (NS_FAILED(rv)) { 4218 // When loading the disk cache, we verify the SRI hash. If it does not match 4219 // the one from the document we restart the load, forcing us to load the 4220 // source instead. If this happens do not remove the current request from 4221 // script loader's data structures or fire any events. 4222 if (aChannelStatus != NS_BINDING_RETARGETED) { 4223 HandleLoadError(aRequest, rv); 4224 } 4225 } 4226 4227 // Process our request and/or any pending ones 4228 ProcessPendingRequests(); 4229 4230 return rv; 4231 } 4232 4233 nsresult ScriptLoader::VerifySRI(ScriptLoadRequest* aRequest, 4234 nsIIncrementalStreamLoader* aLoader, 4235 nsresult aSRIStatus, 4236 SRICheckDataVerifier* aSRIDataVerifier) const { 4237 nsCOMPtr<nsIRequest> channelRequest; 4238 aLoader->GetRequest(getter_AddRefs(channelRequest)); 4239 nsCOMPtr<nsIChannel> channel; 4240 channel = do_QueryInterface(channelRequest); 4241 4242 nsresult rv = NS_OK; 4243 if (!aRequest->mIntegrity.IsEmpty() && NS_SUCCEEDED((rv = aSRIStatus))) { 4244 MOZ_ASSERT(aSRIDataVerifier); 4245 MOZ_ASSERT(mReporter); 4246 rv = aSRIDataVerifier->Verify(aRequest->mIntegrity, channel, mReporter); 4247 if (channelRequest) { 4248 mReporter->FlushReportsToConsole( 4249 nsContentUtils::GetInnerWindowID(channelRequest)); 4250 } else { 4251 mReporter->FlushConsoleReports(mDocument); 4252 } 4253 if (NS_FAILED(rv)) { 4254 rv = NS_ERROR_SRI_CORRUPT; 4255 TRACE_FOR_TEST(aRequest, "sri:corrupt"); 4256 } 4257 } 4258 4259 return rv; 4260 } 4261 4262 nsresult ScriptLoader::SaveSRIHash( 4263 ScriptLoadRequest* aRequest, SRICheckDataVerifier* aSRIDataVerifier) const { 4264 MOZ_ASSERT(aRequest->IsTextSource()); 4265 JS::TranscodeBuffer& sri = aRequest->SRI(); 4266 MOZ_ASSERT(sri.empty()); 4267 4268 uint32_t len = 0; 4269 4270 // If the integrity metadata does not correspond to a valid hash function, 4271 // IsComplete would be false. 4272 if (!aRequest->mIntegrity.IsEmpty() && aSRIDataVerifier->IsComplete()) { 4273 MOZ_ASSERT(sri.length() == 0); 4274 4275 // Encode the SRI computed hash. 4276 len = aSRIDataVerifier->DataSummaryLength(); 4277 4278 if (!sri.resize(len)) { 4279 return NS_ERROR_OUT_OF_MEMORY; 4280 } 4281 4282 DebugOnly<nsresult> res = 4283 aSRIDataVerifier->ExportDataSummary(len, sri.begin()); 4284 MOZ_ASSERT(NS_SUCCEEDED(res)); 4285 } else { 4286 MOZ_ASSERT(sri.length() == 0); 4287 4288 // Encode a dummy SRI hash. 4289 len = SRICheckDataVerifier::EmptyDataSummaryLength(); 4290 4291 if (!sri.resize(len)) { 4292 return NS_ERROR_OUT_OF_MEMORY; 4293 } 4294 4295 DebugOnly<nsresult> res = 4296 SRICheckDataVerifier::ExportEmptyDataSummary(len, sri.begin()); 4297 MOZ_ASSERT(NS_SUCCEEDED(res)); 4298 } 4299 4300 // Verify that the exported and predicted length correspond. 4301 DebugOnly<uint32_t> srilen{}; 4302 MOZ_ASSERT(NS_SUCCEEDED( 4303 SRICheckDataVerifier::DataSummaryLength(len, sri.begin(), &srilen))); 4304 MOZ_ASSERT(srilen == len); 4305 4306 MOZ_ASSERT(sri.length() == len); 4307 aRequest->SetSRILength(len); 4308 4309 if (aRequest->GetSRILength() != len) { 4310 // The serialized stencil is aligned in the buffer, and space might be 4311 // reserved for padding after the SRI hash. 4312 if (!sri.resize(aRequest->GetSRILength())) { 4313 return NS_ERROR_OUT_OF_MEMORY; 4314 } 4315 } 4316 4317 return NS_OK; 4318 } 4319 4320 void ScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest, 4321 nsresult aResult) const { 4322 MOZ_ASSERT(aRequest); 4323 4324 if (aRequest->GetScriptLoadContext()->IsPreload()) { 4325 // Skip reporting errors in preload requests. If the request is actually 4326 // used then we will report the error in ReportPreloadErrorsToConsole below. 4327 aRequest->GetScriptLoadContext()->mUnreportedPreloadError = aResult; 4328 return; 4329 } 4330 4331 if (!mDocument) { 4332 return; 4333 } 4334 4335 bool isScript = !aRequest->IsModuleRequest(); 4336 const char* message; 4337 if (aResult == NS_ERROR_MALFORMED_URI) { 4338 message = isScript ? "ScriptSourceMalformed" : "ModuleSourceMalformed"; 4339 } else if (aResult == NS_ERROR_DOM_BAD_URI) { 4340 message = isScript ? "ScriptSourceNotAllowed" : "ModuleSourceNotAllowed"; 4341 } else if (aResult == NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI) { 4342 MOZ_ASSERT(!isScript); 4343 message = "WebExtContentScriptModuleSourceNotAllowed"; 4344 } else if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( 4345 aResult)) { 4346 // Blocking classifier error codes already show their own console messages. 4347 return; 4348 } else { 4349 message = isScript ? "ScriptSourceLoadFailed" : "ModuleSourceLoadFailed"; 4350 } 4351 4352 AutoTArray<nsString, 1> params; 4353 CopyUTF8toUTF16(aRequest->URI()->GetSpecOrDefault(), *params.AppendElement()); 4354 4355 Maybe<SourceLocation> loc; 4356 if (!isScript && !aRequest->IsTopLevel()) { 4357 MOZ_ASSERT(aRequest->mReferrer); 4358 loc.emplace(aRequest->mReferrer.get()); 4359 } else { 4360 uint32_t lineNo = aRequest->GetScriptLoadContext()->GetScriptLineNumber(); 4361 JS::ColumnNumberOneOrigin columnNo = 4362 aRequest->GetScriptLoadContext()->GetScriptColumnNumber(); 4363 loc.emplace(mDocument->GetDocumentURI(), lineNo, columnNo.oneOriginValue()); 4364 } 4365 4366 nsContentUtils::ReportToConsole( 4367 nsIScriptError::warningFlag, "Script Loader"_ns, mDocument, 4368 nsContentUtils::eDOM_PROPERTIES, message, params, loc.ref()); 4369 } 4370 4371 void ScriptLoader::ReportWarningToConsole( 4372 ScriptLoadRequest* aRequest, const char* aMessageName, 4373 const nsTArray<nsString>& aParams) const { 4374 if (!mDocument) { 4375 return; 4376 } 4377 uint32_t lineNo = aRequest->GetScriptLoadContext()->GetScriptLineNumber(); 4378 JS::ColumnNumberOneOrigin columnNo = 4379 aRequest->GetScriptLoadContext()->GetScriptColumnNumber(); 4380 nsContentUtils::ReportToConsole( 4381 nsIScriptError::warningFlag, "Script Loader"_ns, mDocument, 4382 nsContentUtils::eDOM_PROPERTIES, aMessageName, aParams, 4383 SourceLocation{mDocument->GetDocumentURI(), lineNo, 4384 columnNo.oneOriginValue()}); 4385 } 4386 4387 void ScriptLoader::ReportPreloadErrorsToConsole(ScriptLoadRequest* aRequest) { 4388 if (NS_FAILED(aRequest->GetScriptLoadContext()->mUnreportedPreloadError)) { 4389 ReportErrorToConsole( 4390 aRequest, aRequest->GetScriptLoadContext()->mUnreportedPreloadError); 4391 aRequest->GetScriptLoadContext()->mUnreportedPreloadError = NS_OK; 4392 } 4393 4394 // TODO: 4395 // Bug 1973466, check the child request's error that happened during 4396 // preload is reported. 4397 } 4398 4399 void ScriptLoader::HandleLoadError(ScriptLoadRequest* aRequest, 4400 nsresult aResult) { 4401 /* 4402 * Handle script not loading error because source was an tracking URL (or 4403 * fingerprinting, cryptomining, etc). 4404 * We make a note of this script node by including it in a dedicated 4405 * array of blocked tracking nodes under its parent document. 4406 */ 4407 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( 4408 aResult)) { 4409 nsCOMPtr<nsIContent> cont = do_QueryInterface( 4410 aRequest->GetScriptLoadContext()->GetScriptElementForUrlClassifier()); 4411 mDocument->AddBlockedNodeByClassifier(cont); 4412 } 4413 4414 bool wasHandled = false; 4415 4416 // A ModuleLoadRequest will be stored either in mDeferRequests or 4417 // mLoadingAsyncRequests, but the onerror handler should be triggered later in 4418 // ProcessRequests, so we handle ModuleLoadRequest before mDeferRequestrs and 4419 // mLoadingAsyncRequests. 4420 if (aRequest->IsModuleRequest()) { 4421 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mIsInline); 4422 wasHandled = true; 4423 4424 ModuleLoadRequest* modReq = aRequest->AsModuleRequest(); 4425 modReq->OnFetchComplete(aResult); 4426 4427 MOZ_ASSERT(modReq->IsErrored()); 4428 } else if (aRequest->GetScriptLoadContext()->mInDeferList) { 4429 wasHandled = true; 4430 if (aRequest->isInList()) { 4431 RefPtr<ScriptLoadRequest> req = mDeferRequests.Steal(aRequest); 4432 FireScriptAvailable(aResult, req); 4433 } 4434 } else if (aRequest->GetScriptLoadContext()->mInAsyncList) { 4435 wasHandled = true; 4436 if (aRequest->isInList()) { 4437 RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest); 4438 FireScriptAvailable(aResult, req); 4439 } 4440 } 4441 4442 if (aRequest->GetScriptLoadContext()->mIsNonAsyncScriptInserted) { 4443 if (aRequest->isInList()) { 4444 RefPtr<ScriptLoadRequest> req = 4445 mNonAsyncExternalScriptInsertedRequests.Steal(aRequest); 4446 FireScriptAvailable(aResult, req); 4447 } 4448 } else if (aRequest->GetScriptLoadContext()->mIsXSLT) { 4449 if (aRequest->isInList()) { 4450 RefPtr<ScriptLoadRequest> req = mXSLTRequests.Steal(aRequest); 4451 FireScriptAvailable(aResult, req); 4452 } 4453 } else if (aRequest->GetScriptLoadContext()->IsPreload()) { 4454 if (aRequest->IsTopLevel()) { 4455 // Request may already have been removed by 4456 // CancelAndClearScriptLoadRequests. 4457 mPreloads.RemoveElement(aRequest, PreloadRequestComparator()); 4458 } 4459 MOZ_ASSERT(!aRequest->isInList()); 4460 } else if (mParserBlockingRequest == aRequest) { 4461 MOZ_ASSERT(!aRequest->isInList()); 4462 mParserBlockingRequest = nullptr; 4463 UnblockParser(aRequest); 4464 4465 // Ensure that we treat the script as our current parser-inserted script 4466 // while firing onerror on it. 4467 MOZ_ASSERT(aRequest->GetScriptLoadContext()->GetParserCreated()); 4468 nsCOMPtr<nsIScriptElement> oldParserInsertedScript = 4469 mCurrentParserInsertedScript; 4470 mCurrentParserInsertedScript = 4471 aRequest->GetScriptLoadContext() 4472 ->GetScriptElementForCurrentParserInsertedScript(); 4473 FireScriptAvailable(aResult, aRequest); 4474 ContinueParserAsync(aRequest); 4475 mCurrentParserInsertedScript = oldParserInsertedScript; 4476 } else if (!wasHandled) { 4477 // This happens for blocking requests cancelled by ParsingComplete(). 4478 // Ignore cancellation status for link-preload requests, as cancellation can 4479 // be omitted for them when SRI is stronger on consumer tags. 4480 MOZ_ASSERT(aRequest->IsCanceled() || 4481 aRequest->GetScriptLoadContext()->IsLinkPreloadScript()); 4482 MOZ_ASSERT(!aRequest->isInList()); 4483 } 4484 } 4485 4486 void ScriptLoader::HandleLoadErrorAndProcessPendingRequests( 4487 ScriptLoadRequest* aRequest, nsresult aResult) { 4488 HandleLoadError(aRequest, aResult); 4489 // Process in case some other requests have finished meanwhile. 4490 ProcessPendingRequests(); 4491 } 4492 4493 void ScriptLoader::UnblockParser(ScriptLoadRequest* aParserBlockingRequest) { 4494 aParserBlockingRequest->GetScriptLoadContext()->UnblockParser(); 4495 } 4496 4497 void ScriptLoader::ContinueParserAsync( 4498 ScriptLoadRequest* aParserBlockingRequest) { 4499 aParserBlockingRequest->GetScriptLoadContext()->ContinueParserAsync(); 4500 } 4501 4502 uint32_t ScriptLoader::NumberOfProcessors() { 4503 if (mNumberOfProcessors > 0) { 4504 return mNumberOfProcessors; 4505 } 4506 4507 int32_t numProcs = PR_GetNumberOfProcessors(); 4508 if (numProcs > 0) { 4509 mNumberOfProcessors = numProcs; 4510 } 4511 return mNumberOfProcessors; 4512 } 4513 4514 int32_t ScriptLoader::PhysicalSizeOfMemoryInGB() { 4515 // 0 is a valid result from PR_GetPhysicalMemorySize() which 4516 // means a failure occured. 4517 if (mPhysicalSizeOfMemory >= 0) { 4518 return mPhysicalSizeOfMemory; 4519 } 4520 4521 // Save the size in GB. 4522 mPhysicalSizeOfMemory = 4523 static_cast<int32_t>(PR_GetPhysicalMemorySize() >> 30); 4524 return mPhysicalSizeOfMemory; 4525 } 4526 4527 bool ScriptLoader::ShouldApplyDelazifyStrategy(ScriptLoadRequest* aRequest) { 4528 // Full parse everything if negative. 4529 if (StaticPrefs::dom_script_loader_delazification_max_size() < 0) { 4530 return true; 4531 } 4532 4533 // Be conservative on machines with 2GB or less of memory. 4534 if (PhysicalSizeOfMemoryInGB() <= 4535 StaticPrefs::dom_script_loader_delazification_min_mem()) { 4536 return false; 4537 } 4538 4539 uint32_t max_size = static_cast<uint32_t>( 4540 StaticPrefs::dom_script_loader_delazification_max_size()); 4541 uint32_t script_size = 4542 aRequest->ScriptTextLength() > 0 4543 ? static_cast<uint32_t>(aRequest->ScriptTextLength()) 4544 : 0; 4545 4546 if (mTotalFullParseSize + script_size < max_size) { 4547 return true; 4548 } 4549 4550 if (LOG_ENABLED()) { 4551 nsCString url = aRequest->URI()->GetSpecOrDefault(); 4552 LOG( 4553 ("ScriptLoadRequest (%p): non-on-demand-only Parsing Disabled for (%s) " 4554 "with size=%u because mTotalFullParseSize=%u would exceed max_size=%u", 4555 aRequest, url.get(), script_size, mTotalFullParseSize, max_size)); 4556 } 4557 4558 return false; 4559 } 4560 4561 void ScriptLoader::ApplyDelazifyStrategy(JS::CompileOptions* aOptions) { 4562 JS::DelazificationOption strategy = 4563 JS::DelazificationOption::ParseEverythingEagerly; 4564 uint32_t strategyIndex = 4565 StaticPrefs::dom_script_loader_delazification_strategy(); 4566 4567 // Assert that all enumerated values of DelazificationOption are dense between 4568 // OnDemandOnly and ParseEverythingEagerly. 4569 #ifdef DEBUG 4570 uint32_t count = 0; 4571 uint32_t mask = 0; 4572 # define _COUNT_ENTRIES(Name) count++; 4573 # define _MASK_ENTRIES(Name) \ 4574 mask |= 1 << uint32_t(JS::DelazificationOption::Name); 4575 4576 FOREACH_DELAZIFICATION_STRATEGY(_COUNT_ENTRIES); 4577 MOZ_ASSERT(count == uint32_t(strategy) + 1); 4578 FOREACH_DELAZIFICATION_STRATEGY(_MASK_ENTRIES); 4579 MOZ_ASSERT(((mask + 1) & mask) == 0); 4580 # undef _COUNT_ENTRIES 4581 # undef _MASK_ENTRIES 4582 #endif 4583 4584 // Any strategy index larger than ParseEverythingEagerly would default to 4585 // ParseEverythingEagerly. 4586 if (strategyIndex <= uint32_t(strategy)) { 4587 strategy = JS::DelazificationOption(uint8_t(strategyIndex)); 4588 } 4589 4590 aOptions->setEagerDelazificationStrategy(strategy); 4591 } 4592 4593 bool ScriptLoader::ShouldCompileOffThread(ScriptLoadRequest* aRequest) { 4594 if (NumberOfProcessors() <= 1) { 4595 return false; 4596 } 4597 if (aRequest == mParserBlockingRequest) { 4598 return true; 4599 } 4600 if (SpeculativeOMTParsingEnabled()) { 4601 // Processing non async inserted scripts too early can potentially delay the 4602 // load event from firing so focus on other scripts instead. 4603 if (aRequest->GetScriptLoadContext()->mIsNonAsyncScriptInserted && 4604 !StaticPrefs:: 4605 dom_script_loader_external_scripts_speculate_non_parser_inserted_enabled()) { 4606 return false; 4607 } 4608 4609 // Async and link preload scripts do not need to be parsed right away. 4610 if (aRequest->GetScriptLoadContext()->IsAsyncScript() && 4611 !StaticPrefs:: 4612 dom_script_loader_external_scripts_speculate_async_enabled()) { 4613 return false; 4614 } 4615 4616 if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript() && 4617 !StaticPrefs:: 4618 dom_script_loader_external_scripts_speculate_link_preload_enabled()) { 4619 return false; 4620 } 4621 4622 return true; 4623 } 4624 return false; 4625 } 4626 4627 static bool MimeTypeMatchesExpectedModuleType( 4628 nsIChannel* aChannel, JS::ModuleType expectedModuleType) { 4629 nsAutoCString mimeType; 4630 aChannel->GetContentType(mimeType); 4631 NS_ConvertUTF8toUTF16 typeString(mimeType); 4632 4633 switch (expectedModuleType) { 4634 case JS::ModuleType::JavaScriptOrWasm: 4635 #ifdef NIGHTLY_BUILD 4636 if (StaticPrefs::javascript_options_experimental_wasm_esm_integration()) { 4637 return nsContentUtils::IsJavascriptMIMEType(typeString) || 4638 nsContentUtils::HasWasmMimeTypeEssence(typeString); 4639 } 4640 #endif 4641 return nsContentUtils::IsJavascriptMIMEType(typeString); 4642 case JS::ModuleType::JSON: 4643 return nsContentUtils::IsJsonMimeType(typeString); 4644 case JS::ModuleType::CSS: 4645 return nsContentUtils::HasCssMimeTypeEssence(typeString); 4646 case JS::ModuleType::Unknown: 4647 case JS::ModuleType::Bytes: 4648 break; 4649 } 4650 4651 return false; 4652 } 4653 4654 nsresult ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest, 4655 nsIIncrementalStreamLoader* aLoader, 4656 nsresult aStatus) { 4657 if (NS_FAILED(aStatus)) { 4658 return aStatus; 4659 } 4660 4661 MOZ_ASSERT(aRequest->IsFetching()); 4662 CollectScriptTelemetry(aRequest); 4663 4664 // If we don't have a document, then we need to abort further 4665 // evaluation. 4666 if (!mDocument) { 4667 return NS_ERROR_NOT_AVAILABLE; 4668 } 4669 4670 // If the load returned an error page, then we need to abort 4671 nsCOMPtr<nsIRequest> req; 4672 nsresult rv = aLoader->GetRequest(getter_AddRefs(req)); 4673 NS_ASSERTION(req, "StreamLoader's request went away prematurely"); 4674 NS_ENSURE_SUCCESS(rv, rv); 4675 4676 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req); 4677 if (httpChannel) { 4678 bool requestSucceeded; 4679 rv = httpChannel->GetRequestSucceeded(&requestSucceeded); 4680 if (NS_SUCCEEDED(rv) && !requestSucceeded) { 4681 return NS_ERROR_NOT_AVAILABLE; 4682 } 4683 4684 if (aRequest->IsModuleRequest()) { 4685 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script 4686 // Update script's referrer-policy if there's a Referrer-Policy header in 4687 // the HTTP response. 4688 ReferrerPolicy policy = 4689 nsContentUtils::GetReferrerPolicyFromChannel(httpChannel); 4690 if (policy != ReferrerPolicy::_empty) { 4691 aRequest->AsModuleRequest()->UpdateReferrerPolicy(policy); 4692 } 4693 4694 #ifdef NIGHTLY_BUILD 4695 if (StaticPrefs::javascript_options_experimental_wasm_esm_integration()) { 4696 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script 4697 // Extract the content-type. If its essence is wasm, we'll attempt to 4698 // compile this module as a wasm module. (Steps 13.2, 13.6) 4699 nsAutoCString mimeType; 4700 if (NS_SUCCEEDED(httpChannel->GetContentType(mimeType))) { 4701 if (nsContentUtils::HasWasmMimeTypeEssence( 4702 NS_ConvertUTF8toUTF16(mimeType))) { 4703 aRequest->AsModuleRequest()->SetHasWasmMimeTypeEssence(); 4704 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1998240 4705 // For now, we don't support caching wasm modules. We enable 4706 // caching in ScriptLoader::OnStreamComplete for 4707 // text streams prior to reaching the mime type check. 4708 aRequest->getLoadedScript()->DropDiskCacheReferenceAndSRI(); 4709 } 4710 } 4711 } 4712 #endif 4713 } 4714 4715 nsAutoCString sourceMapURL; 4716 if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) { 4717 aRequest->SetSourceMapURL(NS_ConvertUTF8toUTF16(sourceMapURL)); 4718 } 4719 4720 nsCOMPtr<nsIClassifiedChannel> classifiedChannel = do_QueryInterface(req); 4721 MOZ_ASSERT(classifiedChannel); 4722 if (classifiedChannel && 4723 classifiedChannel->IsThirdPartyTrackingResource()) { 4724 net::ClassificationFlags flags{ 4725 classifiedChannel->GetFirstPartyClassificationFlags(), 4726 classifiedChannel->GetThirdPartyClassificationFlags()}; 4727 aRequest->GetScriptLoadContext()->SetClassificationFlags(flags); 4728 } 4729 } 4730 4731 nsCOMPtr<nsIChannel> channel = do_QueryInterface(req); 4732 // If this load was subject to a CORS check, don't flag it with a separate 4733 // origin principal, so that it will treat our document's principal as the 4734 // origin principal. Module loads always use CORS. 4735 if (!aRequest->IsModuleRequest() && aRequest->CORSMode() == CORS_NONE) { 4736 rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( 4737 channel, getter_AddRefs(aRequest->mOriginPrincipal)); 4738 NS_ENSURE_SUCCESS(rv, rv); 4739 } 4740 4741 // This assertion could fire errorously if we ran out of memory when 4742 // inserting the request in the array. However it's an unlikely case 4743 // so if you see this assertion it is likely something else that is 4744 // wrong, especially if you see it more than once. 4745 NS_ASSERTION(mDeferRequests.Contains(aRequest) || 4746 mLoadingAsyncRequests.Contains(aRequest) || 4747 mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) || 4748 mXSLTRequests.Contains(aRequest) || 4749 (aRequest->IsModuleRequest() && 4750 (aRequest->AsModuleRequest()->IsRegisteredDynamicImport() || 4751 !aRequest->AsModuleRequest()->IsTopLevel())) || 4752 mPreloads.Contains(aRequest, PreloadRequestComparator()) || 4753 mParserBlockingRequest == aRequest, 4754 "aRequest should be pending!"); 4755 4756 nsCOMPtr<nsIURI> uri; 4757 rv = channel->GetOriginalURI(getter_AddRefs(uri)); 4758 NS_ENSURE_SUCCESS(rv, rv); 4759 4760 aRequest->SetBaseURLFromChannelAndOriginalURI(channel, uri); 4761 4762 if (aRequest->IsModuleRequest()) { 4763 ModuleLoadRequest* request = aRequest->AsModuleRequest(); 4764 4765 // When loading a module, only responses with an expected MIME type are 4766 // acceptable. 4767 if (!MimeTypeMatchesExpectedModuleType(channel, request->mModuleType)) { 4768 return NS_ERROR_FAILURE; 4769 } 4770 4771 // Attempt to compile off main thread. 4772 bool couldCompile = false; 4773 rv = AttemptOffThreadScriptCompile(request, &couldCompile); 4774 NS_ENSURE_SUCCESS(rv, rv); 4775 if (couldCompile) { 4776 return NS_OK; 4777 } 4778 4779 // Otherwise compile it right away and start fetching descendents. 4780 return request->OnFetchComplete(NS_OK); 4781 } 4782 4783 // The script is now loaded and ready to run. 4784 aRequest->SetReady(); 4785 4786 // If speculative parsing is enabled attempt to compile all 4787 // external scripts off-main-thread. Otherwise, only omt compile scripts 4788 // blocking the parser. 4789 if (ShouldCompileOffThread(aRequest)) { 4790 MOZ_ASSERT(!aRequest->IsModuleRequest()); 4791 bool couldCompile = false; 4792 nsresult rv = AttemptOffThreadScriptCompile(aRequest, &couldCompile); 4793 NS_ENSURE_SUCCESS(rv, rv); 4794 if (couldCompile) { 4795 MOZ_ASSERT(aRequest->mState == ScriptLoadRequest::State::Compiling, 4796 "Request should be off-thread compiling now."); 4797 return NS_OK; 4798 } 4799 4800 // If off-thread compile was rejected, continue with regular processing. 4801 } 4802 4803 MaybeMoveToLoadedList(aRequest); 4804 4805 return NS_OK; 4806 } 4807 4808 void ScriptLoader::DeferCheckpointReached() { 4809 if (mDeferEnabled) { 4810 // Have to check because we apparently get ParsingComplete 4811 // without BeginDeferringScripts in some cases 4812 mDeferCheckpointReached = true; 4813 } 4814 4815 mDeferEnabled = false; 4816 ProcessPendingRequests(); 4817 } 4818 4819 void ScriptLoader::ParsingComplete(bool aTerminated) { 4820 if (aTerminated) { 4821 CancelAndClearScriptLoadRequests(); 4822 4823 // Have to call this even if aTerminated so we'll correctly unblock onload. 4824 DeferCheckpointReached(); 4825 } 4826 } 4827 4828 void ScriptLoader::PreloadURI( 4829 nsIURI* aURI, const nsAString& aCharset, const nsAString& aType, 4830 const nsAString& aCrossOrigin, const nsAString& aNonce, 4831 const nsAString& aFetchPriority, const nsAString& aIntegrity, 4832 bool aScriptFromHead, bool aAsync, bool aDefer, bool aLinkPreload, 4833 const ReferrerPolicy aReferrerPolicy, uint64_t aEarlyHintPreloaderId) { 4834 NS_ENSURE_TRUE_VOID(mDocument); 4835 // Check to see if scripts has been turned off. 4836 if (!mEnabled || !mDocument->IsScriptEnabled()) { 4837 return; 4838 } 4839 4840 ScriptKind scriptKind = ScriptKind::eClassic; 4841 4842 static const char kASCIIWhitespace[] = "\t\n\f\r "; 4843 4844 nsAutoString type(aType); 4845 type.Trim(kASCIIWhitespace); 4846 if (type.LowerCaseEqualsASCII("module")) { 4847 scriptKind = ScriptKind::eModule; 4848 } 4849 4850 if (scriptKind == ScriptKind::eClassic && !aType.IsEmpty() && 4851 !nsContentUtils::IsJavascriptMIMEType(aType)) { 4852 // Unknown type. Don't load it. 4853 return; 4854 } 4855 4856 SRIMetadata sriMetadata; 4857 GetSRIMetadata(aIntegrity, &sriMetadata); 4858 if (aIntegrity.IsVoid() && scriptKind == ScriptKind::eModule) { 4859 mModuleLoader->GetImportMapSRI(aURI, mDocument->GetDocumentURIAsReferrer(), 4860 mReporter, &sriMetadata); 4861 } 4862 4863 const auto requestPriority = FetchPriorityToRequestPriority( 4864 nsGenericHTMLElement::ToFetchPriority(aFetchPriority)); 4865 4866 // For link type "modulepreload": 4867 // https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload 4868 // Step 11. Let options be a script fetch options whose cryptographic nonce is 4869 // cryptographic nonce, integrity metadata is integrity metadata, parser 4870 // metadata is "not-parser-inserted", credentials mode is credentials mode, 4871 // referrer policy is referrer policy, and fetch priority is fetch priority. 4872 // 4873 // We treat speculative <script> loads as parser-inserted, because they 4874 // come from a parser. This will also match how they should be treated 4875 // as a normal load. 4876 RefPtr<ScriptLoadRequest> request = CreateLoadRequest( 4877 scriptKind, aURI, nullptr, VoidString(), mDocument->NodePrincipal(), 4878 Element::StringToCORSMode(aCrossOrigin), aNonce, requestPriority, 4879 sriMetadata, aReferrerPolicy, 4880 aLinkPreload ? ParserMetadata::NotParserInserted 4881 : ParserMetadata::ParserInserted, 4882 ScriptLoadRequestType::Preload); 4883 request->GetScriptLoadContext()->mIsInline = false; 4884 request->GetScriptLoadContext()->mScriptFromHead = aScriptFromHead; 4885 request->GetScriptLoadContext()->SetScriptMode(aDefer, aAsync, aLinkPreload); 4886 request->GetScriptLoadContext()->SetIsPreloadRequest(); 4887 request->mEarlyHintPreloaderId = aEarlyHintPreloaderId; 4888 4889 if (LOG_ENABLED()) { 4890 nsAutoCString url; 4891 aURI->GetAsciiSpec(url); 4892 LOG(("ScriptLoadRequest (%p): Created preload request for %s", 4893 request.get(), url.get())); 4894 } 4895 4896 nsAutoString charset(aCharset); 4897 nsresult rv = StartLoad(request, Some(charset)); 4898 if (NS_FAILED(rv)) { 4899 return; 4900 } 4901 4902 PreloadInfo* pi = mPreloads.AppendElement(); 4903 pi->mRequest = request; 4904 pi->mCharset = aCharset; 4905 } 4906 4907 void ScriptLoader::AddDeferRequest(ScriptLoadRequest* aRequest) { 4908 MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsDeferredScript()); 4909 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInDeferList && 4910 !aRequest->GetScriptLoadContext()->mInAsyncList); 4911 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInCompilingList); 4912 4913 aRequest->GetScriptLoadContext()->mInDeferList = true; 4914 mDeferRequests.AppendElement(aRequest); 4915 if (mDeferEnabled && aRequest == mDeferRequests.getFirst() && mDocument && 4916 !mBlockingDOMContentLoaded) { 4917 MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING); 4918 mBlockingDOMContentLoaded = true; 4919 mDocument->BlockDOMContentLoaded(); 4920 } 4921 } 4922 4923 void ScriptLoader::AddAsyncRequest(ScriptLoadRequest* aRequest) { 4924 MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsAsyncScript()); 4925 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInDeferList && 4926 !aRequest->GetScriptLoadContext()->mInAsyncList); 4927 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInCompilingList); 4928 4929 aRequest->GetScriptLoadContext()->mInAsyncList = true; 4930 if (aRequest->IsFinished()) { 4931 mLoadedAsyncRequests.AppendElement(aRequest); 4932 } else { 4933 mLoadingAsyncRequests.AppendElement(aRequest); 4934 } 4935 } 4936 4937 void ScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) { 4938 MOZ_ASSERT(aRequest->IsFinished()); 4939 4940 bool isDynamicImport = false; 4941 if (aRequest->IsModuleRequest()) { 4942 ModuleLoadRequest* modReq = aRequest->AsModuleRequest(); 4943 isDynamicImport = modReq->IsDynamicImport(); 4944 } 4945 4946 MOZ_ASSERT(aRequest->IsTopLevel() || isDynamicImport); 4947 4948 // If it's async, move it to the loaded list. 4949 // aRequest->GetScriptLoadContext()->mInAsyncList really _should_ be in a 4950 // list, but the consequences if it's not are bad enough we want to avoid 4951 // trying to move it if it's not. 4952 if (aRequest->GetScriptLoadContext()->mInAsyncList) { 4953 MOZ_ASSERT(aRequest->isInList()); 4954 if (aRequest->isInList()) { 4955 RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest); 4956 mLoadedAsyncRequests.AppendElement(req); 4957 } 4958 } else if (isDynamicImport) { 4959 // Process dynamic imports with async scripts. 4960 MOZ_ASSERT(!aRequest->isInList()); 4961 mLoadedAsyncRequests.AppendElement(aRequest); 4962 } 4963 } 4964 4965 bool ScriptLoader::MaybeRemovedDeferRequests() { 4966 if (mDeferRequests.isEmpty() && mDocument && mBlockingDOMContentLoaded) { 4967 mBlockingDOMContentLoaded = false; 4968 mDocument->UnblockDOMContentLoaded(); 4969 return true; 4970 } 4971 return false; 4972 } 4973 4974 DocGroup* ScriptLoader::GetDocGroup() const { return mDocument->GetDocGroup(); } 4975 4976 void ScriptLoader::BeginDeferringScripts() { 4977 if (mDeferEnabled || mDeferCheckpointReached) { 4978 // We already started loading. Now, document.open() happened and we're doing 4979 // a new parse. 4980 // If mDeferEnabled, we haven't reached the defer checkpoint and if 4981 // mDeferCheckpointReached, we did but still have pending scripts. Either 4982 // way, the load event is still blocked, so we shouldn't block again. 4983 // If set, reset mDeferCheckpointReached. It'll get set again when the 4984 // DeferCheckpointReached call corresponding to this BeginDeferringScripts 4985 // call happens (on document.close()), since we will set mDeferEnabled. 4986 mDeferCheckpointReached = false; 4987 } else if (mDocument) { 4988 mDocument->BlockOnload(); 4989 } 4990 mDeferEnabled = true; 4991 } 4992 4993 nsAutoScriptLoaderDisabler::nsAutoScriptLoaderDisabler(Document* aDoc) { 4994 mLoader = aDoc->GetScriptLoader(); 4995 mWasEnabled = mLoader && mLoader->GetEnabled(); 4996 if (mWasEnabled) { 4997 mLoader->SetEnabled(false); 4998 } 4999 } 5000 5001 nsAutoScriptLoaderDisabler::~nsAutoScriptLoaderDisabler() { 5002 if (mWasEnabled) { 5003 MOZ_ASSERT(mLoader, "mWasEnabled can be true only if we have a loader"); 5004 mLoader->SetEnabled(true); 5005 } 5006 } 5007 5008 #undef LOG 5009 5010 } // namespace mozilla::dom