/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ScriptLoader.h" #include "ScriptLoadHandler.h" #include "ScriptTrace.h" #include "ModuleLoader.h" #include "nsIChildChannel.h" #include "zlib.h" #include "prsystem.h" #include "jsapi.h" #include "jsfriendapi.h" #include "js/Array.h" // JS::GetArrayLength #include "js/CompilationAndEvaluation.h" #include "js/ContextOptions.h" // JS::ContextOptionsRef #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/loader/ScriptLoadRequest.h" #include "ScriptCompression.h" #include "js/loader/LoadedScript.h" #include "js/loader/ModuleLoadRequest.h" #include "js/MemoryFunctions.h" #include "js/Modules.h" #include "js/OffThreadScriptCompilation.h" #include "js/PropertyAndElement.h" // JS_DefineProperty #include "js/Realm.h" #include "js/SourceText.h" #include "js/Transcoding.h" #include "js/Utility.h" #include "xpcpublic.h" #include "GeckoProfiler.h" #include "nsContentSecurityManager.h" #include "nsCycleCollectionParticipant.h" #include "nsIContent.h" #include "nsJSUtils.h" #include "mozilla/dom/AutoEntryScript.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/JSExecutionContext.h" #include "mozilla/dom/ScriptDecoding.h" // mozilla::dom::ScriptDecoding #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/SRILogHelper.h" #include "mozilla/dom/WindowContext.h" #include "mozilla/net/UrlClassifierFeatureFactory.h" #include "mozilla/Preferences.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/StaticPrefs_javascript.h" #include "mozilla/StaticPrefs_network.h" #include "nsAboutProtocolUtils.h" #include "nsGkAtoms.h" #include "nsNetUtil.h" #include "nsGlobalWindowInner.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptContext.h" #include "nsIPrincipal.h" #include "nsJSPrincipals.h" #include "nsContentPolicyUtils.h" #include "nsIClassifiedChannel.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIClassOfService.h" #include "nsICacheInfoChannel.h" #include "nsITimedChannel.h" #include "nsIScriptElement.h" #include "nsISupportsPriority.h" #include "nsIDocShell.h" #include "nsContentUtils.h" #include "nsUnicharUtils.h" #include "nsError.h" #include "nsThreadUtils.h" #include "nsDocShellCID.h" #include "nsIContentSecurityPolicy.h" #include "mozilla/Logging.h" #include "nsCRT.h" #include "nsContentCreatorFunctions.h" #include "nsProxyRelease.h" #include "nsSandboxFlags.h" #include "nsContentTypeParser.h" #include "nsINetworkPredictor.h" #include "nsMimeTypes.h" #include "mozilla/ConsoleReportCollector.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/LoadInfo.h" #include "ReferrerInfo.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/Attributes.h" #include "mozilla/ScopeExit.h" #include "mozilla/Telemetry.h" #include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" #include "mozilla/Unused.h" #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include "nsIScriptError.h" #include "nsIAsyncOutputStream.h" using JS::SourceText; using namespace JS::loader; using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT; namespace mozilla::dom { LazyLogModule ScriptLoader::gCspPRLog("CSP"); LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader"); #undef LOG #define LOG(args) \ MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args) #define LOG_ENABLED() \ MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug) // Alternate Data MIME type used by the ScriptLoader to register that we want to // store bytecode without reading it. static constexpr auto kNullMimeType = "javascript/null"_ns; ///////////////////////////////////////////////////////////// // AsyncCompileShutdownObserver ///////////////////////////////////////////////////////////// NS_IMPL_ISUPPORTS(AsyncCompileShutdownObserver, nsIObserver) void AsyncCompileShutdownObserver::OnShutdown() { if (mScriptLoader) { mScriptLoader->Destroy(); MOZ_ASSERT(!mScriptLoader); } } void AsyncCompileShutdownObserver::Unregister() { if (mScriptLoader) { mScriptLoader = nullptr; nsContentUtils::UnregisterShutdownObserver(this); } } NS_IMETHODIMP AsyncCompileShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { OnShutdown(); return NS_OK; } ////////////////////////////////////////////////////////////// // ScriptLoader::PreloadInfo ////////////////////////////////////////////////////////////// inline void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField) { ImplCycleCollectionUnlink(aField.mRequest); } inline void ImplCycleCollectionTraverse( nsCycleCollectionTraversalCallback& aCallback, ScriptLoader::PreloadInfo& aField, const char* aName, uint32_t aFlags = 0) { ImplCycleCollectionTraverse(aCallback, aField.mRequest, aName, aFlags); } ////////////////////////////////////////////////////////////// // ScriptLoader ////////////////////////////////////////////////////////////// NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoader) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION(ScriptLoader, mNonAsyncExternalScriptInsertedRequests, mLoadingAsyncRequests, mLoadedAsyncRequests, mOffThreadCompilingRequests, mDeferRequests, mXSLTRequests, mParserBlockingRequest, mBytecodeEncodingQueue, mPreloads, mPendingChildLoaders, mModuleLoader, mWebExtModuleLoaders, mShadowRealmModuleLoaders) NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader) NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader) ScriptLoader::ScriptLoader(Document* aDocument) : mDocument(aDocument), mParserBlockingBlockerCount(0), mBlockerCount(0), mNumberOfProcessors(0), mTotalFullParseSize(0), mPhysicalSizeOfMemory(-1), mEnabled(true), mDeferEnabled(false), mSpeculativeOMTParsingEnabled(false), mDeferCheckpointReached(false), mBlockingDOMContentLoaded(false), mLoadEventFired(false), mGiveUpEncoding(false), mReporter(new ConsoleReportCollector()) { LOG(("ScriptLoader::ScriptLoader %p", this)); mSpeculativeOMTParsingEnabled = StaticPrefs:: dom_script_loader_external_scripts_speculative_omt_parse_enabled(); mShutdownObserver = new AsyncCompileShutdownObserver(this); nsContentUtils::RegisterShutdownObserver(mShutdownObserver); } ScriptLoader::~ScriptLoader() { LOG(("ScriptLoader::~ScriptLoader %p", this)); mObservers.Clear(); if (mParserBlockingRequest) { FireScriptAvailable(NS_ERROR_ABORT, mParserBlockingRequest); } for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req; req = req->getNext()) { FireScriptAvailable(NS_ERROR_ABORT, req); } for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req; req = req->getNext()) { FireScriptAvailable(NS_ERROR_ABORT, req); } for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req; req = req->getNext()) { FireScriptAvailable(NS_ERROR_ABORT, req); } for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req; req = req->getNext()) { FireScriptAvailable(NS_ERROR_ABORT, req); } for (ScriptLoadRequest* req = mNonAsyncExternalScriptInsertedRequests.getFirst(); req; req = req->getNext()) { FireScriptAvailable(NS_ERROR_ABORT, req); } // Unblock the kids, in case any of them moved to a different document // subtree in the meantime and therefore aren't actually going away. for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) { mPendingChildLoaders[j]->RemoveParserBlockingScriptExecutionBlocker(); } for (size_t i = 0; i < mPreloads.Length(); i++) { AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::NotUsed); } if (mShutdownObserver) { mShutdownObserver->Unregister(); mShutdownObserver = nullptr; } mModuleLoader = nullptr; } void ScriptLoader::SetGlobalObject(nsIGlobalObject* aGlobalObject) { if (!aGlobalObject) { // The document is being detached. CancelAndClearScriptLoadRequests(); return; } MOZ_ASSERT(!HasPendingRequests()); if (!mModuleLoader) { // The module loader is associated with a global object, so don't create it // until we have a global set. mModuleLoader = new ModuleLoader(this, aGlobalObject, ModuleLoader::Normal); } MOZ_ASSERT(mModuleLoader->GetGlobalObject() == aGlobalObject); MOZ_ASSERT(aGlobalObject->GetModuleLoader(dom::danger::GetJSContext()) == mModuleLoader); } void ScriptLoader::RegisterContentScriptModuleLoader(ModuleLoader* aLoader) { MOZ_ASSERT(aLoader); MOZ_ASSERT(aLoader->GetScriptLoader() == this); mWebExtModuleLoaders.AppendElement(aLoader); } void ScriptLoader::RegisterShadowRealmModuleLoader(ModuleLoader* aLoader) { MOZ_ASSERT(aLoader); MOZ_ASSERT(aLoader->GetScriptLoader() == this); mShadowRealmModuleLoaders.AppendElement(aLoader); } // Collect telemtry data about the cache information, and the kind of source // which are being loaded, and where it is being loaded from. static void CollectScriptTelemetry(ScriptLoadRequest* aRequest) { using namespace mozilla::Telemetry; MOZ_ASSERT(aRequest->IsFetching()); // Skip this function if we are not running telemetry. if (!CanRecordExtended()) { return; } // Report the script kind. if (aRequest->IsModuleRequest()) { AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ModuleScript); } else { AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ClassicScript); } // Report the type of source. This is used to monitor the status of the // JavaScript Start-up Bytecode Cache, with the expectation of an almost zero // source-fallback and alternate-data being roughtly equal to source loads. if (aRequest->mFetchSourceOnly) { if (aRequest->GetScriptLoadContext()->mIsInline) { AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Inline); } else if (aRequest->IsTextSource()) { AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::SourceFallback); } } else { if (aRequest->IsTextSource()) { AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Source); } else if (aRequest->IsBytecode()) { AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::AltData); } } } // Helper method for checking if the script element is an event-handler // This means that it has both a for-attribute and a event-attribute. // Also, if the for-attribute has a value that matches "\s*window\s*", // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an // eventhandler. (both matches are case insensitive). // This is how IE seems to filter out a window's onload handler from a // <script for=... event=...> element. static bool IsScriptEventHandler(ScriptKind kind, nsIContent* aScriptElement) { if (kind != ScriptKind::eClassic) { return false; } if (!aScriptElement->IsHTMLElement()) { return false; } nsAutoString forAttr, eventAttr; if (!aScriptElement->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, forAttr) || !aScriptElement->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::event, eventAttr)) { return false; } const nsAString& for_str = nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(forAttr); if (!for_str.LowerCaseEqualsLiteral("window")) { return true; } // We found for="window", now check for event="onload". const nsAString& event_str = nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(eventAttr, false); if (!StringBeginsWith(event_str, u"onload"_ns, nsCaseInsensitiveStringComparator)) { // It ain't "onload.*". return true; } nsAutoString::const_iterator start, end; event_str.BeginReading(start); event_str.EndReading(end); start.advance(6); // advance past "onload" if (start != end && *start != '(' && *start != ' ') { // We got onload followed by something other than space or // '('. Not good enough. return true; } return false; } nsContentPolicyType ScriptLoadRequestToContentPolicyType( ScriptLoadRequest* aRequest) { if (aRequest->GetScriptLoadContext()->IsPreload()) { return aRequest->IsModuleRequest() ? nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD : nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD; } return aRequest->IsModuleRequest() ? nsIContentPolicy::TYPE_INTERNAL_MODULE : nsIContentPolicy::TYPE_INTERNAL_SCRIPT; } nsresult ScriptLoader::CheckContentPolicy(Document* aDocument, nsISupports* aContext, const nsAString& aType, ScriptLoadRequest* aRequest) { nsContentPolicyType contentPolicyType = ScriptLoadRequestToContentPolicyType(aRequest); nsCOMPtr<nsINode> requestingNode = do_QueryInterface(aContext); nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo( aDocument->NodePrincipal(), // loading principal aDocument->NodePrincipal(), // triggering principal requestingNode, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType); // snapshot the nonce at load start time for performing CSP checks if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT || contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_MODULE) { nsCOMPtr<nsINode> node = do_QueryInterface(aContext); if (node) { nsString* cspNonce = static_cast<nsString*>(node->GetProperty(nsGkAtoms::nonce)); if (cspNonce) { secCheckLoadInfo->SetCspNonce(*cspNonce); } } } int16_t shouldLoad = nsIContentPolicy::ACCEPT; nsresult rv = NS_CheckContentLoadPolicy( aRequest->mURI, secCheckLoadInfo, NS_LossyConvertUTF16toASCII(aType), &shouldLoad, nsContentUtils::GetContentPolicy()); if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) { return NS_ERROR_CONTENT_BLOCKED; } return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; } return NS_OK; } /* static */ bool ScriptLoader::IsAboutPageLoadingChromeURI(ScriptLoadRequest* aRequest, Document* aDocument) { // if the uri to be loaded is not of scheme chrome:, there is nothing to do. if (!aRequest->mURI->SchemeIs("chrome")) { return false; } // we can either get here with a regular contentPrincipal or with a // NullPrincipal in case we are showing an error page in a sandboxed iframe. // In either case if the about: page is linkable from content, there is // nothing to do. uint32_t aboutModuleFlags = 0; nsresult rv = NS_OK; nsCOMPtr<nsIPrincipal> triggeringPrincipal = aRequest->TriggeringPrincipal(); if (triggeringPrincipal->GetIsContentPrincipal()) { if (!triggeringPrincipal->SchemeIs("about")) { return false; } rv = triggeringPrincipal->GetAboutModuleFlags(&aboutModuleFlags); NS_ENSURE_SUCCESS(rv, false); } else if (triggeringPrincipal->GetIsNullPrincipal()) { nsCOMPtr<nsIURI> docURI = aDocument->GetDocumentURI(); if (!docURI->SchemeIs("about")) { return false; } nsCOMPtr<nsIAboutModule> aboutModule; rv = NS_GetAboutModule(docURI, getter_AddRefs(aboutModule)); if (NS_FAILED(rv) || !aboutModule) { return false; } rv = aboutModule->GetURIFlags(docURI, &aboutModuleFlags); NS_ENSURE_SUCCESS(rv, false); } else { return false; } if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) { return false; } // seems like an about page wants to load a chrome URI. return true; } nsIURI* ScriptLoader::GetBaseURI() const { MOZ_ASSERT(mDocument); return mDocument->GetDocBaseURI(); } class ScriptRequestProcessor : public Runnable { private: RefPtr<ScriptLoader> mLoader; RefPtr<ScriptLoadRequest> mRequest; public: ScriptRequestProcessor(ScriptLoader* aLoader, ScriptLoadRequest* aRequest) : Runnable("dom::ScriptRequestProcessor"), mLoader(aLoader), mRequest(aRequest) {} NS_IMETHOD Run() override { return mLoader->ProcessRequest(mRequest); } }; void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) { auto* runnable = new ScriptRequestProcessor(this, aRequest); nsContentUtils::AddScriptRunner(runnable); } nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) { MOZ_ASSERT(aRequest->IsBytecode()); aRequest->mScriptBytecode.clearAndFree(); TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), "scriptloader_fallback"); // Notify preload restart so that we can register this preload request again. aRequest->GetScriptLoadContext()->NotifyRestart(mDocument); // Start a new channel from which we explicitly request to stream the source // instead of the bytecode. aRequest->mFetchSourceOnly = true; nsresult rv; if (aRequest->IsModuleRequest()) { rv = aRequest->AsModuleRequest()->RestartModuleLoad(); } else { rv = StartLoad(aRequest, 0, Nothing()); } if (NS_FAILED(rv)) { return rv; } // Close the current channel and this ScriptLoadHandler as we created a new // one for the same request. return NS_BINDING_RETARGETED; } nsresult ScriptLoader::StartLoad( ScriptLoadRequest* aRequest, uint64_t aEarlyHintPreloaderId, const Maybe<nsAutoString>& aCharsetForPreload) { if (aRequest->IsModuleRequest()) { return aRequest->AsModuleRequest()->StartModuleLoad(); } return StartClassicLoad(aRequest, aEarlyHintPreloaderId, aCharsetForPreload); } nsresult ScriptLoader::StartClassicLoad( ScriptLoadRequest* aRequest, uint64_t aEarlyHintPreloaderId, const Maybe<nsAutoString>& aCharsetForPreload) { MOZ_ASSERT(aRequest->IsFetching()); NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER); aRequest->SetUnknownDataType(); // If this document is sandboxed without 'allow-scripts', abort. if (mDocument->HasScriptsBlockedBySandbox()) { return NS_OK; } if (LOG_ENABLED()) { nsAutoCString url; aRequest->mURI->GetAsciiSpec(url); LOG(("ScriptLoadRequest (%p): Start Classic Load (url = %s)", aRequest, url.get())); } nsSecurityFlags securityFlags = nsContentSecurityManager::ComputeSecurityFlags( aRequest->CORSMode(), nsContentSecurityManager::CORSSecurityMapping:: CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS); securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME; nsresult rv = StartLoadInternal(aRequest, securityFlags, aEarlyHintPreloaderId, aCharsetForPreload); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } static bool IsWebExtensionRequest(ScriptLoadRequest* aRequest) { if (!aRequest->IsModuleRequest()) { return false; } ModuleLoader* loader = ModuleLoader::From(aRequest->AsModuleRequest()->mLoader); return loader->GetKind() == ModuleLoader::WebExtension; } nsresult ScriptLoader::StartLoadInternal( ScriptLoadRequest* aRequest, nsSecurityFlags securityFlags, uint64_t aEarlyHintPreloaderId, const Maybe<nsAutoString>& aCharsetForPreload) { nsContentPolicyType contentPolicyType = ScriptLoadRequestToContentPolicyType(aRequest); nsCOMPtr<nsINode> context; if (aRequest->GetScriptLoadContext()->GetScriptElement()) { context = do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement()); } else { context = mDocument; } nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup(); nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow(); NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER); nsIDocShell* docshell = window->GetDocShell(); nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell)); nsCOMPtr<nsIChannel> channel; nsresult rv = NS_NewChannelWithTriggeringPrincipal( getter_AddRefs(channel), aRequest->mURI, context, aRequest->TriggeringPrincipal(), securityFlags, contentPolicyType, nullptr, // aPerformanceStorage loadGroup, prompter); NS_ENSURE_SUCCESS(rv, rv); if (aEarlyHintPreloaderId) { nsCOMPtr<nsIHttpChannelInternal> channelInternal = do_QueryInterface(channel); NS_ENSURE_TRUE(channelInternal != nullptr, NS_ERROR_FAILURE); rv = channelInternal->SetEarlyHintPreloaderId(aEarlyHintPreloaderId); NS_ENSURE_SUCCESS(rv, rv); } // snapshot the nonce at load start time for performing CSP checks if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT || contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_MODULE) { if (context) { nsString* cspNonce = static_cast<nsString*>(context->GetProperty(nsGkAtoms::nonce)); if (cspNonce) { nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); loadInfo->SetCspNonce(*cspNonce); } } } nsCOMPtr<nsIScriptGlobalObject> scriptGlobal = GetScriptGlobalObject(); if (!scriptGlobal) { return NS_ERROR_FAILURE; } // To avoid decoding issues, the build-id is part of the bytecode MIME type // constant. aRequest->mCacheInfo = nullptr; nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(channel)); if (cic && StaticPrefs::dom_script_loader_bytecode_cache_enabled()) { MOZ_ASSERT(!IsWebExtensionRequest(aRequest), "Can not bytecode cache WebExt code"); if (!aRequest->mFetchSourceOnly) { // Inform the HTTP cache that we prefer to have information coming from // the bytecode cache instead of the sources, if such entry is already // registered. LOG(("ScriptLoadRequest (%p): Maybe request bytecode", aRequest)); cic->PreferAlternativeDataType( BytecodeMimeTypeFor(aRequest), ""_ns, nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC); } else { // If we are explicitly loading from the sources, such as after a // restarted request, we might still want to save the bytecode after. // // The following tell the cache to look for an alternative data type which // does not exist, such that we can later save the bytecode with a // different alternative data type. LOG(("ScriptLoadRequest (%p): Request saving bytecode later", aRequest)); cic->PreferAlternativeDataType( kNullMimeType, ""_ns, nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC); } } LOG(("ScriptLoadRequest (%p): mode=%u tracking=%d", aRequest, unsigned(aRequest->GetScriptLoadContext()->mScriptMode), aRequest->GetScriptLoadContext()->IsTracking())); if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) { // This is <link rel="preload" as="script"> or <link rel="modulepreload"> // initiated speculative load, put it to the group that is not blocked by // leaders and doesn't block follower at the same time. Giving it a much // higher priority will make this request be processed ahead of other // Unblocked requests, but with the same weight as Leaders. This will make // us behave similar way for both http2 and http1. ScriptLoadContext::PrioritizeAsPreload(channel); ScriptLoadContext::AddLoadBackgroundFlag(channel); } else if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) { if (aRequest->GetScriptLoadContext()->mScriptFromHead && aRequest->GetScriptLoadContext()->IsBlockingScript()) { // synchronous head scripts block loading of most other non js/css // content such as images, Leader implicitely disallows tailing cos->AddClassFlags(nsIClassOfService::Leader); } else if (aRequest->GetScriptLoadContext()->IsDeferredScript() && !StaticPrefs::network_http_tailing_enabled()) { // Bug 1395525 and the !StaticPrefs::network_http_tailing_enabled() bit: // We want to make sure that turing tailing off by the pref makes the // browser behave exactly the same way as before landing the tailing // patch. // head/body deferred scripts are blocked by leaders but are not // allowed tailing because they block DOMContentLoaded cos->AddClassFlags(nsIClassOfService::TailForbidden); } else { // other scripts (=body sync or head/body async) are neither blocked // nor prioritized cos->AddClassFlags(nsIClassOfService::Unblocked); if (aRequest->GetScriptLoadContext()->IsAsyncScript()) { // async scripts are allowed tailing, since those and only those // don't block DOMContentLoaded; this flag doesn't enforce tailing, // just overweights the Unblocked flag when the channel is found // to be a thrird-party tracker and thus set the Tail flag to engage // tailing. cos->AddClassFlags(nsIClassOfService::TailAllowed); } } } nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); if (httpChannel) { // HTTP content negotation has little value in this context. nsAutoCString acceptTypes("*/*"); rv = httpChannel->SetRequestHeader("Accept"_ns, acceptTypes, false); MOZ_ASSERT(NS_SUCCEEDED(rv)); nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(aRequest->mReferrer, aRequest->ReferrerPolicy()); rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo); MOZ_ASSERT(NS_SUCCEEDED(rv)); nsCOMPtr<nsIHttpChannelInternal> internalChannel( do_QueryInterface(httpChannel)); if (internalChannel) { rv = internalChannel->SetIntegrityMetadata( aRequest->mIntegrity.GetIntegrityString()); MOZ_ASSERT(NS_SUCCEEDED(rv)); } nsAutoString hintCharset; if (!aRequest->GetScriptLoadContext()->IsPreload() && aRequest->GetScriptLoadContext()->GetScriptElement()) { aRequest->GetScriptLoadContext()->GetScriptElement()->GetScriptCharset( hintCharset); } else if (aCharsetForPreload.isSome()) { hintCharset = aCharsetForPreload.ref(); } rv = httpChannel->SetClassicScriptHintCharset(hintCharset); NS_ENSURE_SUCCESS(rv, rv); } mozilla::net::PredictorLearn( aRequest->mURI, mDocument->GetDocumentURI(), nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, mDocument->NodePrincipal()->OriginAttributesRef()); // Set the initiator type nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel)); if (timedChannel) { if (aEarlyHintPreloaderId) { timedChannel->SetInitiatorType(u"early-hints"_ns); } else if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) { timedChannel->SetInitiatorType(u"link"_ns); } else { timedChannel->SetInitiatorType(u"script"_ns); } } UniquePtr<mozilla::dom::SRICheckDataVerifier> sriDataVerifier; if (!aRequest->mIntegrity.IsEmpty()) { nsAutoCString sourceUri; if (mDocument->GetDocumentURI()) { mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri); } sriDataVerifier = MakeUnique<SRICheckDataVerifier>(aRequest->mIntegrity, sourceUri, mReporter); } RefPtr<ScriptLoadHandler> handler = new ScriptLoadHandler(this, aRequest, std::move(sriDataVerifier)); nsCOMPtr<nsIIncrementalStreamLoader> loader; rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), handler); NS_ENSURE_SUCCESS(rv, rv); auto key = PreloadHashKey::CreateAsScript( aRequest->mURI, aRequest->CORSMode(), aRequest->mKind); aRequest->GetScriptLoadContext()->NotifyOpen( key, channel, mDocument, aRequest->GetScriptLoadContext()->IsLinkPreloadScript(), aRequest->IsModuleRequest()); if (aEarlyHintPreloaderId) { nsCOMPtr<nsIHttpChannelInternal> channelInternal = do_QueryInterface(channel); NS_ENSURE_TRUE(channelInternal != nullptr, NS_ERROR_FAILURE); rv = channelInternal->SetEarlyHintPreloaderId(aEarlyHintPreloaderId); NS_ENSURE_SUCCESS(rv, rv); } rv = channel->AsyncOpen(loader); if (NS_FAILED(rv)) { // Make sure to inform any <link preload> tags about failure to load the // resource. aRequest->GetScriptLoadContext()->NotifyStart(channel); aRequest->GetScriptLoadContext()->NotifyStop(rv); } NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } bool ScriptLoader::PreloadURIComparator::Equals(const PreloadInfo& aPi, nsIURI* const& aURI) const { bool same; return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) && same; } static bool CSPAllowsInlineScript(nsIScriptElement* aElement, Document* aDocument) { nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp(); if (!csp) { // no CSP --> allow return true; } // query the nonce nsCOMPtr<Element> scriptContent = do_QueryInterface(aElement); nsAutoString nonce; if (scriptContent) { nsString* cspNonce = static_cast<nsString*>(scriptContent->GetProperty(nsGkAtoms::nonce)); if (cspNonce) { nonce = *cspNonce; } } bool parserCreated = aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER; bool allowInlineScript = false; nsresult rv = csp->GetAllowsInline( nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE, false /* aHasUnsafeHash */, nonce, parserCreated, scriptContent, nullptr /* nsICSPEventListener */, u""_ns, aElement->GetScriptLineNumber(), aElement->GetScriptColumnNumber(), &allowInlineScript); return NS_SUCCEEDED(rv) && allowInlineScript; } already_AddRefed<ScriptLoadRequest> ScriptLoader::CreateLoadRequest( ScriptKind aKind, nsIURI* aURI, nsIScriptElement* aElement, nsIPrincipal* aTriggeringPrincipal, CORSMode aCORSMode, const SRIMetadata& aIntegrity, ReferrerPolicy aReferrerPolicy) { nsIURI* referrer = mDocument->GetDocumentURIAsReferrer(); nsCOMPtr<Element> domElement = do_QueryInterface(aElement); RefPtr<ScriptFetchOptions> fetchOptions = new ScriptFetchOptions( aCORSMode, aReferrerPolicy, aTriggeringPrincipal, domElement); RefPtr<ScriptLoadContext> context = new ScriptLoadContext(); if (aKind == ScriptKind::eClassic || aKind == ScriptKind::eImportMap) { RefPtr<ScriptLoadRequest> aRequest = new ScriptLoadRequest( aKind, aURI, fetchOptions, aIntegrity, referrer, context); return aRequest.forget(); } MOZ_ASSERT(aKind == ScriptKind::eModule); RefPtr<ModuleLoadRequest> aRequest = ModuleLoader::CreateTopLevel( aURI, fetchOptions, aIntegrity, referrer, this, context); return aRequest.forget(); } bool ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement, const nsAutoString& aTypeAttr) { // We need a document to evaluate scripts. NS_ENSURE_TRUE(mDocument, false); // Check to see if scripts has been turned off. if (!mEnabled || !mDocument->IsScriptEnabled()) { return false; } NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script"); nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement); ScriptKind scriptKind; if (aElement->GetScriptIsModule()) { scriptKind = ScriptKind::eModule; } else if (aElement->GetScriptIsImportMap()) { scriptKind = ScriptKind::eImportMap; } else { scriptKind = ScriptKind::eClassic; } // Step 13. Check that the script is not an eventhandler if (IsScriptEventHandler(scriptKind, scriptContent)) { return false; } // "In modern user agents that support module scripts, the script element with // the nomodule attribute will be ignored". // "The nomodule attribute must not be specified on module scripts (and will // be ignored if it is)." if (mDocument->ModuleScriptsEnabled() && scriptKind == ScriptKind::eClassic && scriptContent->IsHTMLElement() && scriptContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::nomodule)) { return false; } // Step 15. and later in the HTML5 spec if (aElement->GetScriptExternal()) { return ProcessExternalScript(aElement, scriptKind, aTypeAttr, scriptContent); } return ProcessInlineScript(aElement, scriptKind); } bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement, ScriptKind aScriptKind, const nsAutoString& aTypeAttr, nsIContent* aScriptContent) { LOG(("ScriptLoader (%p): Process external script for element %p", this, aElement)); // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element // Step 30.1. If el's type is "importmap", then queue an element task on the // DOM manipulation task source given el to fire an event named error at el, // and return. if (aScriptKind == ScriptKind::eImportMap) { NS_DispatchToCurrentThread( NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement, &nsIScriptElement::FireErrorEvent)); nsContentUtils::ReportToConsole( nsIScriptError::warningFlag, "Script Loader"_ns, mDocument, nsContentUtils::eDOM_PROPERTIES, "ImportMapExternalNotSupported"); return false; } nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI(); if (!scriptURI) { // Asynchronously report the failure to create a URI object NS_DispatchToCurrentThread( NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement, &nsIScriptElement::FireErrorEvent)); return false; } SRIMetadata sriMetadata; { nsAutoString integrity; aScriptContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity); GetSRIMetadata(integrity, &sriMetadata); } RefPtr<ScriptLoadRequest> request = LookupPreloadRequest(aElement, aScriptKind, sriMetadata); if (request && NS_FAILED(CheckContentPolicy(mDocument, aElement, aTypeAttr, request))) { LOG(("ScriptLoader (%p): content policy check failed for preload", this)); // Probably plans have changed; even though the preload was allowed seems // like the actual load is not; let's cancel the preload request. request->Cancel(); AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RejectedByPolicy); return false; } if (request) { // Use the preload request. LOG(("ScriptLoadRequest (%p): Using preload request", request.get())); // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-script-tree // Step 1. Disallow further import maps given settings object. if (request->IsModuleRequest()) { LOG(("ScriptLoadRequest (%p): Disallow further import maps.", request.get())); mModuleLoader->DisallowImportMaps(); } // It's possible these attributes changed since we started the preload so // update them here. request->GetScriptLoadContext()->SetScriptMode( aElement->GetScriptDeferred(), aElement->GetScriptAsync(), false); // The request will be added to another list or set as // mParserBlockingRequest below. if (request->GetScriptLoadContext()->mInCompilingList) { mOffThreadCompilingRequests.Remove(request); request->GetScriptLoadContext()->mInCompilingList = false; } AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::Used); } else { // No usable preload found. nsCOMPtr<nsIPrincipal> principal = aElement->GetScriptURITriggeringPrincipal(); if (!principal) { principal = aScriptContent->NodePrincipal(); } CORSMode ourCORSMode = aElement->GetCORSMode(); ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement); request = CreateLoadRequest(aScriptKind, scriptURI, aElement, principal, ourCORSMode, sriMetadata, referrerPolicy); request->GetScriptLoadContext()->mIsInline = false; request->GetScriptLoadContext()->SetScriptMode( aElement->GetScriptDeferred(), aElement->GetScriptAsync(), false); // keep request->GetScriptLoadContext()->mScriptFromHead to false so we // don't treat non preloaded scripts as blockers for full page load. See bug // 792438. LOG(("ScriptLoadRequest (%p): Created request for external script", request.get())); nsresult rv = StartLoad(request, 0, Nothing()); if (NS_FAILED(rv)) { ReportErrorToConsole(request, rv); // Asynchronously report the load failure nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement, &nsIScriptElement::FireErrorEvent); if (mDocument) { mDocument->Dispatch(TaskCategory::Other, runnable.forget()); } else { NS_DispatchToCurrentThread(runnable); } return false; } } // We should still be in loading stage of script unless we're loading a // module or speculatively off-main-thread parsing a script. NS_ASSERTION(SpeculativeOMTParsingEnabled() || !request->GetScriptLoadContext()->CompileStarted() || request->IsModuleRequest(), "Request should not yet be in compiling stage."); if (request->GetScriptLoadContext()->IsAsyncScript()) { AddAsyncRequest(request); if (request->IsReadyToRun()) { // The script is available already. Run it ASAP when the event // loop gets a chance to spin. // KVKV TODO: Instead of processing immediately, try off-thread-parsing // it and only schedule a pending ProcessRequest if that fails. ProcessPendingRequestsAsync(); } return false; } if (!aElement->GetParserCreated()) { // Violate the HTML5 spec in order to make LABjs and the "order" plug-in // for RequireJS work with their Gecko-sniffed code path. See // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html request->GetScriptLoadContext()->mIsNonAsyncScriptInserted = true; mNonAsyncExternalScriptInsertedRequests.AppendElement(request); if (request->IsReadyToRun()) { // The script is available already. Run it ASAP when the event // loop gets a chance to spin. ProcessPendingRequestsAsync(); } return false; } // we now have a parser-inserted request that may or may not be still // loading if (request->GetScriptLoadContext()->IsDeferredScript()) { // We don't want to run this yet. // If we come here, the script is a parser-created script and it has // the defer attribute but not the async attribute. Since a // a parser-inserted script is being run, we came here by the parser // running the script, which means the parser is still alive and the // parse is ongoing. NS_ASSERTION(mDocument->GetCurrentContentSink() || aElement->GetParserCreated() == FROM_PARSER_XSLT, "Non-XSLT Defer script on a document without an active " "parser; bug 592366."); AddDeferRequest(request); return false; } if (aElement->GetParserCreated() == FROM_PARSER_XSLT) { // Need to maintain order for XSLT-inserted scripts NS_ASSERTION(!mParserBlockingRequest, "Parser-blocking scripts and XSLT scripts in the same doc!"); request->GetScriptLoadContext()->mIsXSLT = true; mXSLTRequests.AppendElement(request); if (request->IsReadyToRun()) { // The script is available already. Run it ASAP when the event // loop gets a chance to spin. ProcessPendingRequestsAsync(); } return true; } if (request->IsReadyToRun() && ReadyToExecuteParserBlockingScripts()) { // The request has already been loaded and there are no pending style // sheets. If the script comes from the network stream, cheat for // performance reasons and avoid a trip through the event loop. if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) { return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK; } // Otherwise, we've got a document.written script, make a trip through // the event loop to hide the preload effects from the scripts on the // Web page. NS_ASSERTION(!mParserBlockingRequest, "There can be only one parser-blocking script at a time"); NS_ASSERTION(mXSLTRequests.isEmpty(), "Parser-blocking scripts and XSLT scripts in the same doc!"); mParserBlockingRequest = request; ProcessPendingRequestsAsync(); return true; } // The script hasn't loaded yet or there's a style sheet blocking it. // The script will be run when it loads or the style sheet loads. NS_ASSERTION(!mParserBlockingRequest, "There can be only one parser-blocking script at a time"); NS_ASSERTION(mXSLTRequests.isEmpty(), "Parser-blocking scripts and XSLT scripts in the same doc!"); mParserBlockingRequest = request; return true; } bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement, ScriptKind aScriptKind) { // Is this document sandboxed without 'allow-scripts'? if (mDocument->HasScriptsBlockedBySandbox()) { return false; } // Does CSP allow this inline script to run? if (!CSPAllowsInlineScript(aElement, mDocument)) { return false; } // Check if adding an import map script is allowed. If not, we bail out // early to prevent creating a load request. if (aScriptKind == ScriptKind::eImportMap) { // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element // Step 31.2 type is "importmap": // Step 1. If el's relevant global object's import maps allowed is false, // then queue an element task on the DOM manipulation task source given el // to fire an event named error at el, and return. if (!mModuleLoader->IsImportMapAllowed()) { NS_WARNING("ScriptLoader: import maps allowed is false."); const char* msg = mModuleLoader->HasImportMapRegistered() ? "ImportMapNotAllowedMultiple" : "ImportMapNotAllowedAfterModuleLoad"; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Script Loader"_ns, mDocument, nsContentUtils::eDOM_PROPERTIES, msg); NS_DispatchToCurrentThread( NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement, &nsIScriptElement::FireErrorEvent)); return false; } } // Inline classic scripts ignore their CORS mode and are always CORS_NONE. CORSMode corsMode = CORS_NONE; if (aScriptKind == ScriptKind::eModule) { corsMode = aElement->GetCORSMode(); } ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement); RefPtr<ScriptLoadRequest> request = CreateLoadRequest(aScriptKind, mDocument->GetDocumentURI(), aElement, mDocument->NodePrincipal(), corsMode, SRIMetadata(), // SRI doesn't apply referrerPolicy); request->GetScriptLoadContext()->mIsInline = true; request->GetScriptLoadContext()->mLineNo = aElement->GetScriptLineNumber(); request->GetScriptLoadContext()->mColumnNo = aElement->GetScriptColumnNumber(); request->mFetchSourceOnly = true; request->SetTextSource(); TRACE_FOR_TEST_BOOL(request->GetScriptLoadContext()->GetScriptElement(), "scriptloader_load_source"); CollectScriptTelemetry(request); // Only the 'async' attribute is heeded on an inline module script and // inline classic scripts ignore both these attributes. MOZ_ASSERT(!aElement->GetScriptDeferred()); MOZ_ASSERT_IF(!request->IsModuleRequest(), !aElement->GetScriptAsync()); request->GetScriptLoadContext()->SetScriptMode( false, aElement->GetScriptAsync(), false); LOG(("ScriptLoadRequest (%p): Created request for inline script", request.get())); request->mBaseURL = mDocument->GetDocBaseURI(); if (request->IsModuleRequest()) { // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph // Step 1. Disallow further import maps given settings object. mModuleLoader->DisallowImportMaps(); ModuleLoadRequest* modReq = request->AsModuleRequest(); if (aElement->GetParserCreated() != NOT_FROM_PARSER) { if (aElement->GetScriptAsync()) { AddAsyncRequest(modReq); } else { AddDeferRequest(modReq); } } // This calls OnFetchComplete directly since there's no need to start // fetching an inline script. nsresult rv = modReq->OnFetchComplete(NS_OK); if (NS_FAILED(rv)) { ReportErrorToConsole(modReq, rv); HandleLoadError(modReq, rv); } return false; } if (request->IsImportMapRequest()) { // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element // Step 31.2 type is "importmap": // Impl note: Step 1 is done above before creating a ScriptLoadRequest. MOZ_ASSERT(mModuleLoader->IsImportMapAllowed()); // Step 2. Set el's relevant global object's import maps allowed to false. mModuleLoader->DisallowImportMaps(); // Step 3. Let result be the result of creating an import map parse result // given source text and base URL. UniquePtr<ImportMap> importMap = mModuleLoader->ParseImportMap(request); if (!importMap) { // If parsing import maps fails, the exception will be reported in // ModuleLoaderBase::ParseImportMap, and the registration of the import // map will bail out early. return false; } // TODO: Bug 1781758: Move RegisterImportMap into EvaluateScriptElement. // // https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-element // The spec defines 'register an import map' should be done in // 'execute the script element', because inside 'execute the script element' // it will perform a 'preparation-time document check'. // However, as import maps could be only inline scripts by now, the // 'preparation-time document check' will never fail for import maps. // So we simply call 'register an import map' here. mModuleLoader->RegisterImportMap(std::move(importMap)); return false; } request->mState = ScriptLoadRequest::State::Ready; if (aElement->GetParserCreated() == FROM_PARSER_XSLT && (!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) { // Need to maintain order for XSLT-inserted scripts NS_ASSERTION(!mParserBlockingRequest, "Parser-blocking scripts and XSLT scripts in the same doc!"); mXSLTRequests.AppendElement(request); return true; } if (aElement->GetParserCreated() == NOT_FROM_PARSER) { NS_ASSERTION( !nsContentUtils::IsSafeToRunScript(), "A script-inserted script is inserted without an update batch?"); RunScriptWhenSafe(request); return false; } if (aElement->GetParserCreated() == FROM_PARSER_NETWORK && !ReadyToExecuteParserBlockingScripts()) { NS_ASSERTION(!mParserBlockingRequest, "There can be only one parser-blocking script at a time"); mParserBlockingRequest = request; NS_ASSERTION(mXSLTRequests.isEmpty(), "Parser-blocking scripts and XSLT scripts in the same doc!"); return true; } // We now have a document.written inline script or we have an inline script // from the network but there is no style sheet that is blocking scripts. // Don't check for style sheets blocking scripts in the document.write // case to avoid style sheet network activity affecting when // document.write returns. It's not really necessary to do this if // there's no document.write currently on the call stack. However, // this way matches IE more closely than checking if document.write // is on the call stack. NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "Not safe to run a parser-inserted script?"); return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK; } ScriptLoadRequest* ScriptLoader::LookupPreloadRequest( nsIScriptElement* aElement, ScriptKind aScriptKind, const SRIMetadata& aSRIMetadata) { MOZ_ASSERT(aElement); nsTArray<PreloadInfo>::index_type i = mPreloads.IndexOf(aElement->GetScriptURI(), 0, PreloadURIComparator()); if (i == nsTArray<PreloadInfo>::NoIndex) { return nullptr; } RefPtr<ScriptLoadRequest> request = mPreloads[i].mRequest; if (aScriptKind != request->mKind) { return nullptr; } // Found preloaded request. Note that a script-inserted script can steal a // preload! request->GetScriptLoadContext()->SetIsLoadRequest(aElement); if (request->GetScriptLoadContext()->mWasCompiledOMT && !request->IsModuleRequest()) { request->SetReady(); } nsString preloadCharset(mPreloads[i].mCharset); mPreloads.RemoveElementAt(i); // Double-check that the charset the preload used is the same as the charset // we have now. nsAutoString elementCharset; aElement->GetScriptCharset(elementCharset); // Bug 1832361: charset and crossorigin attributes shouldn't affect matching // of module scripts and modulepreload if (!request->IsModuleRequest() && (!elementCharset.Equals(preloadCharset) || aElement->GetCORSMode() != request->CORSMode())) { // Drop the preload. request->Cancel(); AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RequestMismatch); return nullptr; } if (!aSRIMetadata.CanTrustBeDelegatedTo(request->mIntegrity)) { // Don't cancel link preload requests, we want to deliver onload according // the result of the load, cancellation would unexpectedly lead to error // notification. if (!request->GetScriptLoadContext()->IsLinkPreloadScript()) { request->Cancel(); } return nullptr; } // Report any errors that we skipped while preloading. ReportPreloadErrorsToConsole(request); // This makes sure the pending preload (if exists) for this resource is // properly marked as used and thus not notified in the console as unused. request->GetScriptLoadContext()->NotifyUsage(mDocument); // A used preload must no longer be found in the Document's hash table. Any // <link preload> tag after the <script> tag will start a new request, that // can be satisfied from a different cache, but not from the preload cache. request->GetScriptLoadContext()->RemoveSelf(mDocument); return request; } void ScriptLoader::GetSRIMetadata(const nsAString& aIntegrityAttr, SRIMetadata* aMetadataOut) { MOZ_ASSERT(aMetadataOut->IsEmpty()); if (aIntegrityAttr.IsEmpty()) { return; } MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, ("ScriptLoader::GetSRIMetadata, integrity=%s", NS_ConvertUTF16toUTF8(aIntegrityAttr).get())); nsAutoCString sourceUri; if (mDocument->GetDocumentURI()) { mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri); } SRICheck::IntegrityMetadata(aIntegrityAttr, sourceUri, mReporter, aMetadataOut); } ReferrerPolicy ScriptLoader::GetReferrerPolicy(nsIScriptElement* aElement) { ReferrerPolicy scriptReferrerPolicy = aElement->GetReferrerPolicy(); if (scriptReferrerPolicy != ReferrerPolicy::_empty) { return scriptReferrerPolicy; } return mDocument->GetReferrerPolicy(); } void ScriptLoader::CancelAndClearScriptLoadRequests() { // Cancel all requests that have not been executed and remove them. if (mParserBlockingRequest) { mParserBlockingRequest->Cancel(); mParserBlockingRequest = nullptr; } mDeferRequests.CancelRequestsAndClear(); mLoadingAsyncRequests.CancelRequestsAndClear(); mLoadedAsyncRequests.CancelRequestsAndClear(); mNonAsyncExternalScriptInsertedRequests.CancelRequestsAndClear(); mXSLTRequests.CancelRequestsAndClear(); mOffThreadCompilingRequests.CancelRequestsAndClear(); if (mModuleLoader) { mModuleLoader->CancelAndClearDynamicImports(); } for (ModuleLoader* loader : mWebExtModuleLoaders) { loader->CancelAndClearDynamicImports(); } for (ModuleLoader* loader : mShadowRealmModuleLoaders) { loader->CancelAndClearDynamicImports(); } for (size_t i = 0; i < mPreloads.Length(); i++) { mPreloads[i].mRequest->Cancel(); } mPreloads.Clear(); } nsresult ScriptLoader::CompileOffThreadOrProcessRequest( ScriptLoadRequest* aRequest) { NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "Processing requests when running scripts is unsafe."); if (!aRequest->GetScriptLoadContext()->mOffThreadToken && !aRequest->GetScriptLoadContext()->CompileStarted()) { bool couldCompile = false; nsresult rv = AttemptOffThreadScriptCompile(aRequest, &couldCompile); if (NS_FAILED(rv)) { HandleLoadError(aRequest, rv); return rv; } if (couldCompile) { return NS_OK; } } return ProcessRequest(aRequest); } namespace { class OffThreadCompilationCompleteRunnable : public Runnable { nsMainThreadPtrHandle<ScriptLoadRequest> mRequest; nsMainThreadPtrHandle<ScriptLoader> mLoader; nsCOMPtr<nsISerialEventTarget> mEventTarget; JS::OffThreadToken* mToken; TimeStamp mStartTime; TimeStamp mStopTime; public: OffThreadCompilationCompleteRunnable(ScriptLoadRequest* aRequest, ScriptLoader* aLoader) : Runnable("dom::OffThreadCompilationCompleteRunnable"), mRequest( new nsMainThreadPtrHolder<ScriptLoadRequest>("mRequest", aRequest)), mLoader(new nsMainThreadPtrHolder<ScriptLoader>("mLoader", aLoader)), mToken(nullptr) { MOZ_ASSERT(NS_IsMainThread()); if (DocGroup* docGroup = aLoader->GetDocGroup()) { mEventTarget = docGroup->EventTargetFor(TaskCategory::Other); } } void RecordStartTime() { mStartTime = TimeStamp::Now(); } void RecordStopTime() { mStopTime = TimeStamp::Now(); } void SetToken(JS::OffThreadToken* aToken) { MOZ_ASSERT(aToken && !mToken); mToken = aToken; } static void Dispatch( already_AddRefed<OffThreadCompilationCompleteRunnable>&& aSelf) { RefPtr<OffThreadCompilationCompleteRunnable> self = aSelf; nsCOMPtr<nsISerialEventTarget> eventTarget = self->mEventTarget; eventTarget->Dispatch(self.forget()); } NS_DECL_NSIRUNNABLE }; } /* anonymous namespace */ nsresult ScriptLoader::AttemptOffThreadScriptCompile( ScriptLoadRequest* aRequest, bool* aCouldCompileOut) { // If speculative parsing is enabled, the request may not be ready to run if // the element is not yet available. MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled() && !aRequest->IsModuleRequest(), aRequest->IsReadyToRun()); MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT); MOZ_ASSERT(aCouldCompileOut && !*aCouldCompileOut); // Don't off-thread compile inline scripts. if (aRequest->GetScriptLoadContext()->mIsInline) { return NS_OK; } nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalForRequest(aRequest); if (!globalObject) { return NS_ERROR_FAILURE; } AutoJSAPI jsapi; if (!jsapi.Init(globalObject)) { return NS_ERROR_FAILURE; } JSContext* cx = jsapi.cx(); JS::CompileOptions options(cx); // Introduction script will actually be computed and set when the script is // collected from offthread JS::Rooted<JSScript*> dummyIntroductionScript(cx); nsresult rv = FillCompileOptionsForRequest(cx, aRequest, &options, &dummyIntroductionScript); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (aRequest->IsTextSource()) { if (!JS::CanCompileOffThread(cx, options, aRequest->ScriptTextLength())) { TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), "scriptloader_main_thread_compile"); return NS_OK; } } else { MOZ_ASSERT(aRequest->IsBytecode()); size_t length = aRequest->mScriptBytecode.length() - aRequest->mBytecodeOffset; JS::DecodeOptions decodeOptions(options); if (!JS::CanDecodeOffThread(cx, decodeOptions, length)) { return NS_OK; } } RefPtr<OffThreadCompilationCompleteRunnable> runnable = new OffThreadCompilationCompleteRunnable(aRequest, this); // Emulate dispatch. CompileOffThreadModule will call // OffThreadCompilationCompleteCallback were we will emulate run. LogRunnable::LogDispatch(runnable); runnable->RecordStartTime(); JS::OffThreadToken* token = nullptr; rv = StartOffThreadCompilation(cx, aRequest, options, runnable, &token); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(token); aRequest->GetScriptLoadContext()->mOffThreadToken = token; aRequest->GetScriptLoadContext()->mRunnable = runnable; aRequest->GetScriptLoadContext()->BlockOnload(mDocument); // Once the compilation is finished, a callback will dispatch the runnable to // the main thread to call ScriptLoader::ProcessOffThreadRequest for the // request. aRequest->mState = ScriptLoadRequest::State::Compiling; // Requests that are not tracked elsewhere are added to a list while they are // being compiled off-thread, so we can cancel the compilation later if // necessary. // // Non-top-level modules not tracked because these are cancelled from their // importing module. if (aRequest->IsTopLevel() && !aRequest->isInList()) { mOffThreadCompilingRequests.AppendElement(aRequest); aRequest->GetScriptLoadContext()->mInCompilingList = true; } *aCouldCompileOut = true; return NS_OK; } static inline nsresult CompileResultForToken(void* aToken) { return aToken ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } nsresult ScriptLoader::StartOffThreadCompilation( JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions& aOptions, Runnable* aRunnable, JS::OffThreadToken** aTokenOut) { const JS::OffThreadCompileCallback callback = OffThreadCompilationCompleteCallback; if (aRequest->IsBytecode()) { JS::DecodeOptions decodeOptions(aOptions); *aTokenOut = JS::DecodeStencilOffThread( aCx, decodeOptions, aRequest->mScriptBytecode, aRequest->mBytecodeOffset, callback, aRunnable); return CompileResultForToken(*aTokenOut); } MaybeSourceText maybeSource; nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource); NS_ENSURE_SUCCESS(rv, rv); if (aRequest->IsModuleRequest()) { auto compile = [&](auto& source) { return JS::CompileModuleToStencilOffThread(aCx, aOptions, source, callback, aRunnable); }; MOZ_ASSERT(!maybeSource.empty()); *aTokenOut = maybeSource.mapNonEmpty(compile); return CompileResultForToken(*aTokenOut); } if (ShouldApplyDelazifyStrategy(aRequest)) { ApplyDelazifyStrategy(&aOptions); mTotalFullParseSize += aRequest->ScriptTextLength() > 0 ? static_cast<uint32_t>(aRequest->ScriptTextLength()) : 0; LOG( ("ScriptLoadRequest (%p): non-on-demand-only Parsing Enabled for " "url=%s mTotalFullParseSize=%u", aRequest, aRequest->mURI->GetSpecOrDefault().get(), mTotalFullParseSize)); } if (StaticPrefs::dom_expose_test_interfaces()) { switch (aOptions.eagerDelazificationStrategy()) { case JS::DelazificationOption::OnDemandOnly: TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), "delazification_on_demand_only"); break; case JS::DelazificationOption::CheckConcurrentWithOnDemand: case JS::DelazificationOption::ConcurrentDepthFirst: TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), "delazification_concurrent_depth_first"); break; case JS::DelazificationOption::ConcurrentLargeFirst: TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), "delazification_concurrent_large_first"); break; case JS::DelazificationOption::ParseEverythingEagerly: TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), "delazification_parse_everything_eagerly"); break; } } auto compile = [&](auto& source) { return JS::CompileToStencilOffThread(aCx, aOptions, source, callback, aRunnable); }; MOZ_ASSERT(!maybeSource.empty()); *aTokenOut = maybeSource.mapNonEmpty(compile); return CompileResultForToken(*aTokenOut); } void ScriptLoader::OffThreadCompilationCompleteCallback( JS::OffThreadToken* aToken, void* aCallbackData) { RefPtr<OffThreadCompilationCompleteRunnable> aRunnable = static_cast<OffThreadCompilationCompleteRunnable*>(aCallbackData); LogRunnable::Run run(aRunnable); aRunnable->RecordStopTime(); aRunnable->SetToken(aToken); OffThreadCompilationCompleteRunnable::Dispatch(aRunnable.forget()); } NS_IMETHODIMP OffThreadCompilationCompleteRunnable::Run() { MOZ_ASSERT(NS_IsMainThread()); RefPtr<ScriptLoadContext> context = mRequest->GetScriptLoadContext(); MOZ_ASSERT_IF(context->mRunnable, context->mRunnable == this); MOZ_ASSERT_IF(context->mOffThreadToken, context->mOffThreadToken == mToken); // Clear the pointer to the runnable. The final reference will be released // when this method returns. context->mRunnable = nullptr; if (!context->mOffThreadToken) { // Request has been cancelled by MaybeCancelOffThreadScript. return NS_OK; } if (profiler_is_active()) { ProfilerString8View scriptSourceString; if (mRequest->IsTextSource()) { scriptSourceString = "ScriptCompileOffThread"; } else { MOZ_ASSERT(mRequest->IsBytecode()); scriptSourceString = "BytecodeDecodeOffThread"; } nsAutoCString profilerLabelString; mRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); PROFILER_MARKER_TEXT(scriptSourceString, JS, MarkerTiming::Interval(mStartTime, mStopTime), profilerLabelString); } nsresult rv = mLoader->ProcessOffThreadRequest(mRequest); mRequest = nullptr; mLoader = nullptr; return rv; } nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) { MOZ_ASSERT(aRequest->IsCompiling()); MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT); if (aRequest->IsCanceled()) { return NS_OK; } aRequest->GetScriptLoadContext()->mWasCompiledOMT = true; if (aRequest->GetScriptLoadContext()->mInCompilingList) { mOffThreadCompilingRequests.Remove(aRequest); aRequest->GetScriptLoadContext()->mInCompilingList = false; } if (aRequest->IsModuleRequest()) { MOZ_ASSERT(aRequest->GetScriptLoadContext()->mOffThreadToken); ModuleLoadRequest* request = aRequest->AsModuleRequest(); return request->OnFetchComplete(NS_OK); } // Element may not be ready yet if speculatively compiling, so process the // request in ProcessPendingRequests when it is available. MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled(), aRequest->GetScriptLoadContext()->GetScriptElement()); if (!aRequest->GetScriptLoadContext()->GetScriptElement()) { // Unblock onload here in case this request never gets executed. aRequest->GetScriptLoadContext()->MaybeUnblockOnload(); return NS_OK; } aRequest->SetReady(); if (aRequest == mParserBlockingRequest) { if (!ReadyToExecuteParserBlockingScripts()) { // If not ready to execute scripts, schedule an async call to // ProcessPendingRequests to handle it. ProcessPendingRequestsAsync(); return NS_OK; } // Same logic as in top of ProcessPendingRequests. mParserBlockingRequest = nullptr; UnblockParser(aRequest); ProcessRequest(aRequest); ContinueParserAsync(aRequest); return NS_OK; } // Async scripts and blocking scripts can be executed right away. if ((aRequest->GetScriptLoadContext()->IsAsyncScript() || aRequest->GetScriptLoadContext()->IsBlockingScript()) && !aRequest->isInList()) { return ProcessRequest(aRequest); } // Process other scripts in the proper order. ProcessPendingRequests(); return NS_OK; } nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) { LOG(("ScriptLoadRequest (%p): Process request", aRequest)); NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "Processing requests when running scripts is unsafe."); NS_ASSERTION(aRequest->IsReadyToRun(), "Processing a request that is not ready to run."); NS_ENSURE_ARG(aRequest); auto unblockOnload = MakeScopeExit( [&] { aRequest->GetScriptLoadContext()->MaybeUnblockOnload(); }); if (aRequest->IsModuleRequest()) { ModuleLoadRequest* request = aRequest->AsModuleRequest(); if (request->IsDynamicImport()) { request->ProcessDynamicImport(); return NS_OK; } if (request->mModuleScript) { if (!request->InstantiateModuleGraph()) { request->mModuleScript = nullptr; } } if (!request->mModuleScript) { // There was an error fetching a module script. Nothing to do here. LOG(("ScriptLoadRequest (%p): Error loading request, firing error", aRequest)); FireScriptAvailable(NS_ERROR_FAILURE, aRequest); return NS_OK; } } nsCOMPtr<nsINode> scriptElem = do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement()); nsCOMPtr<Document> doc; if (!aRequest->GetScriptLoadContext()->mIsInline || aRequest->IsModuleRequest()) { doc = scriptElem->OwnerDoc(); } nsCOMPtr<nsIScriptElement> oldParserInsertedScript; uint32_t parserCreated = aRequest->GetScriptLoadContext()->GetParserCreated(); if (parserCreated) { oldParserInsertedScript = mCurrentParserInsertedScript; mCurrentParserInsertedScript = aRequest->GetScriptLoadContext()->GetScriptElement(); } aRequest->GetScriptLoadContext()->GetScriptElement()->BeginEvaluating(); FireScriptAvailable(NS_OK, aRequest); // The window may have gone away by this point, in which case there's no point // in trying to run the script. { // Try to perform a microtask checkpoint nsAutoMicroTask mt; } nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow(); bool runScript = !!pwin; if (runScript) { nsContentUtils::DispatchTrustedEvent( scriptElem->OwnerDoc(), scriptElem, u"beforescriptexecute"_ns, CanBubble::eYes, Cancelable::eYes, &runScript); } // Inner window could have gone away after firing beforescriptexecute pwin = mDocument->GetInnerWindow(); if (!pwin) { runScript = false; } nsresult rv = NS_OK; if (runScript) { if (doc) { doc->IncrementIgnoreDestructiveWritesCounter(); } rv = EvaluateScriptElement(aRequest); if (doc) { doc->DecrementIgnoreDestructiveWritesCounter(); } nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(), scriptElem, u"afterscriptexecute"_ns, CanBubble::eYes, Cancelable::eNo); } FireScriptEvaluated(rv, aRequest); aRequest->GetScriptLoadContext()->GetScriptElement()->EndEvaluating(); if (parserCreated) { mCurrentParserInsertedScript = oldParserInsertedScript; } if (aRequest->GetScriptLoadContext()->mOffThreadToken) { // The request was parsed off-main-thread, but the result of the off // thread parse was not actually needed to process the request // (disappearing window, some other error, ...). Finish the // request to avoid leaks in the JS engine. MOZ_ASSERT(!aRequest->IsModuleRequest()); aRequest->GetScriptLoadContext()->MaybeCancelOffThreadScript(); } // Free any source data, but keep the bytecode content as we might have to // save it later. aRequest->ClearScriptSource(); if (aRequest->IsBytecode()) { // We received bytecode as input, thus we were decoding, and we will not be // encoding the bytecode once more. We can safely clear the content of this // buffer. aRequest->mScriptBytecode.clearAndFree(); } return rv; } void ScriptLoader::FireScriptAvailable(nsresult aResult, ScriptLoadRequest* aRequest) { for (int32_t i = 0; i < mObservers.Count(); i++) { nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i]; obs->ScriptAvailable( aResult, aRequest->GetScriptLoadContext()->GetScriptElement(), aRequest->GetScriptLoadContext()->mIsInline, aRequest->mURI, aRequest->GetScriptLoadContext()->mLineNo); } bool isInlineClassicScript = aRequest->GetScriptLoadContext()->mIsInline && !aRequest->IsModuleRequest(); RefPtr<nsIScriptElement> scriptElement = aRequest->GetScriptLoadContext()->GetScriptElement(); scriptElement->ScriptAvailable(aResult, scriptElement, isInlineClassicScript, aRequest->mURI, aRequest->GetScriptLoadContext()->mLineNo); } // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) MOZ_CAN_RUN_SCRIPT_BOUNDARY void ScriptLoader::FireScriptEvaluated( nsresult aResult, ScriptLoadRequest* aRequest) { for (int32_t i = 0; i < mObservers.Count(); i++) { nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i]; RefPtr<nsIScriptElement> scriptElement = aRequest->GetScriptLoadContext()->GetScriptElement(); obs->ScriptEvaluated(aResult, scriptElement, aRequest->GetScriptLoadContext()->mIsInline); } RefPtr<nsIScriptElement> scriptElement = aRequest->GetScriptLoadContext()->GetScriptElement(); scriptElement->ScriptEvaluated(aResult, scriptElement, aRequest->GetScriptLoadContext()->mIsInline); } already_AddRefed<nsIGlobalObject> ScriptLoader::GetGlobalForRequest( ScriptLoadRequest* aRequest) { if (aRequest->IsModuleRequest()) { ModuleLoader* loader = ModuleLoader::From(aRequest->AsModuleRequest()->mLoader); nsCOMPtr<nsIGlobalObject> global = loader->GetGlobalObject(); return global.forget(); } return GetScriptGlobalObject(); } already_AddRefed<nsIScriptGlobalObject> ScriptLoader::GetScriptGlobalObject() { if (!mDocument) { return nullptr; } nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow(); if (!pwin) { return nullptr; } nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin); NS_ASSERTION(globalObject, "windows must be global objects"); // and make sure we are setup for this type of script. nsresult rv = globalObject->EnsureScriptEnvironment(); if (NS_FAILED(rv)) { return nullptr; } return globalObject.forget(); } nsresult ScriptLoader::FillCompileOptionsForRequest( JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions, JS::MutableHandle<JSScript*> aIntroductionScript) { // It's very important to use aRequest->mURI, not the final URI of the channel // aRequest ended up getting script data from, as the script filename. nsresult rv = aRequest->mURI->GetSpec(aRequest->mURL); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (mDocument) { mDocument->NoteScriptTrackingStatus( aRequest->mURL, aRequest->GetScriptLoadContext()->IsTracking()); } const char* introductionType; if (aRequest->IsModuleRequest() && !aRequest->AsModuleRequest()->IsTopLevel()) { introductionType = "importedModule"; } else if (!aRequest->GetScriptLoadContext()->mIsInline) { introductionType = "srcScript"; } else if (aRequest->GetScriptLoadContext()->GetParserCreated() == FROM_PARSER_NETWORK) { introductionType = "inlineScript"; } else { introductionType = "injectedScript"; } aOptions->setIntroductionInfoToCaller(aCx, introductionType, aIntroductionScript); aOptions->setFileAndLine(aRequest->mURL.get(), aRequest->GetScriptLoadContext()->mLineNo); // The column is only relevant for inline scripts in order for SpiderMonkey to // properly compute offsets relatively to the script position within the HTML // file. injectedScript are not concerned and are always considered to start // at column 0. if (aRequest->GetScriptLoadContext()->mIsInline && aRequest->GetScriptLoadContext()->GetParserCreated() == FROM_PARSER_NETWORK) { aOptions->setColumn(aRequest->GetScriptLoadContext()->mColumnNo); } aOptions->setIsRunOnce(true); aOptions->setNoScriptRval(true); if (aRequest->mSourceMapURL) { aOptions->setSourceMapURL(aRequest->mSourceMapURL->get()); } if (aRequest->mOriginPrincipal) { nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalForRequest(aRequest); nsIPrincipal* scriptPrin = globalObject->PrincipalOrNull(); MOZ_ASSERT(scriptPrin); bool subsumes = scriptPrin->Subsumes(aRequest->mOriginPrincipal); aOptions->setMutedErrors(!subsumes); } if (aRequest->IsModuleRequest()) { aOptions->setHideScriptFromDebugger(true); } aOptions->setDeferDebugMetadata(true); aOptions->borrowBuffer = true; aOptions->allocateInstantiationStorage = true; return NS_OK; } /* static */ bool ScriptLoader::ShouldCacheBytecode(ScriptLoadRequest* aRequest) { using mozilla::TimeDuration; using mozilla::TimeStamp; // We need the nsICacheInfoChannel to exist to be able to open the alternate // data output stream. This pointer would only be non-null if the bytecode was // activated at the time the channel got created in StartLoad. if (!aRequest->mCacheInfo) { LOG(("ScriptLoadRequest (%p): Cannot cache anything (cacheInfo = %p)", aRequest, aRequest->mCacheInfo.get())); return false; } // Look at the preference to know which strategy (parameters) should be used // when the bytecode cache is enabled. int32_t strategy = StaticPrefs::dom_script_loader_bytecode_cache_strategy(); // List of parameters used by the strategies. bool hasSourceLengthMin = false; bool hasFetchCountMin = false; size_t sourceLengthMin = 100; uint32_t fetchCountMin = 4; LOG(("ScriptLoadRequest (%p): Bytecode-cache: strategy = %d.", aRequest, strategy)); switch (strategy) { case -2: { // Reader mode, keep requesting alternate data but no longer save it. LOG(("ScriptLoadRequest (%p): Bytecode-cache: Encoding disabled.", aRequest)); return false; } case -1: { // Eager mode, skip heuristics! hasSourceLengthMin = false; hasFetchCountMin = false; break; } default: case 0: { hasSourceLengthMin = true; hasFetchCountMin = true; sourceLengthMin = 1024; // If we were to optimize only for speed, without considering the impact // on memory, we should set this threshold to 2. (Bug 900784 comment 120) fetchCountMin = 4; break; } } // If the script is too small/large, do not attempt at creating a bytecode // cache for this script, as the overhead of parsing it might not be worth the // effort. if (hasSourceLengthMin) { size_t sourceLength; size_t minLength; MOZ_ASSERT(aRequest->IsTextSource()); sourceLength = aRequest->mScriptTextLength; minLength = sourceLengthMin; if (sourceLength < minLength) { LOG(("ScriptLoadRequest (%p): Bytecode-cache: Script is too small.", aRequest)); return false; } } // Check that we loaded the cache entry a few times before attempting any // bytecode-cache optimization, such that we do not waste time on entry which // are going to be dropped soon. if (hasFetchCountMin) { uint32_t fetchCount = 0; if (NS_FAILED(aRequest->mCacheInfo->GetCacheTokenFetchCount(&fetchCount))) { LOG(("ScriptLoadRequest (%p): Bytecode-cache: Cannot get fetchCount.", aRequest)); return false; } LOG(("ScriptLoadRequest (%p): Bytecode-cache: fetchCount = %d.", aRequest, fetchCount)); if (fetchCount < fetchCountMin) { return false; } } LOG(("ScriptLoadRequest (%p): Bytecode-cache: Trigger encoding.", aRequest)); return true; } class MOZ_RAII AutoSetProcessingScriptTag { nsCOMPtr<nsIScriptContext> mContext; bool mOldTag; public: explicit AutoSetProcessingScriptTag(nsIScriptContext* aContext) : mContext(aContext), mOldTag(mContext->GetProcessingScriptTag()) { mContext->SetProcessingScriptTag(true); } ~AutoSetProcessingScriptTag() { mContext->SetProcessingScriptTag(mOldTag); } }; static nsresult ExecuteCompiledScript(JSContext* aCx, JSExecutionContext& aExec, ClassicScript* aLoaderScript) { JS::Rooted<JSScript*> script(aCx, aExec.GetScript()); if (!script) { // Compilation succeeds without producing a script if scripting is // disabled for the global. return NS_OK; } if (JS::GetScriptPrivate(script).isUndefined()) { aLoaderScript->AssociateWithScript(script); } return aExec.ExecScript(); } nsresult ScriptLoader::EvaluateScriptElement(ScriptLoadRequest* aRequest) { using namespace mozilla::Telemetry; MOZ_ASSERT(aRequest->IsReadyToRun()); // We need a document to evaluate scripts. if (!mDocument) { return NS_ERROR_FAILURE; } nsCOMPtr<nsIContent> scriptContent( do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement())); MOZ_ASSERT(scriptContent); Document* ownerDoc = scriptContent->OwnerDoc(); if (ownerDoc != mDocument) { // Willful violation of HTML5 as of 2010-12-01 return NS_ERROR_FAILURE; } nsCOMPtr<nsIGlobalObject> globalObject; nsCOMPtr<nsIScriptContext> context; if (!IsWebExtensionRequest(aRequest)) { // Otherwise we have to ensure that there is a nsIScriptContext. nsCOMPtr<nsIScriptGlobalObject> scriptGlobal = GetScriptGlobalObject(); if (!scriptGlobal) { return NS_ERROR_FAILURE; } MOZ_ASSERT_IF( aRequest->IsModuleRequest(), aRequest->AsModuleRequest()->GetGlobalObject() == scriptGlobal); // Make sure context is a strong reference since we access it after // we've executed a script, which may cause all other references to // the context to go away. context = scriptGlobal->GetScriptContext(); if (!context) { return NS_ERROR_FAILURE; } globalObject = scriptGlobal; } // Update our current script. // This must be destroyed after destroying nsAutoMicroTask, see: // https://bugzilla.mozilla.org/show_bug.cgi?id=1620505#c4 nsIScriptElement* currentScript = aRequest->IsModuleRequest() ? nullptr : aRequest->GetScriptLoadContext()->GetScriptElement(); AutoCurrentScriptUpdater scriptUpdater(this, currentScript); Maybe<AutoSetProcessingScriptTag> setProcessingScriptTag; if (context) { setProcessingScriptTag.emplace(context); } // https://wicg.github.io/import-maps/#integration-script-type // Switch on the script's type for scriptElement: // "importmap" // Assert: Never reached. MOZ_ASSERT(!aRequest->IsImportMapRequest()); if (aRequest->IsModuleRequest()) { return aRequest->AsModuleRequest()->EvaluateModule(); } return EvaluateScript(globalObject, aRequest); } nsresult ScriptLoader::CompileOrDecodeClassicScript( JSContext* aCx, JSExecutionContext& aExec, ScriptLoadRequest* aRequest) { nsAutoCString profilerLabelString; aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); nsresult rv; if (aRequest->IsBytecode()) { if (aRequest->GetScriptLoadContext()->mOffThreadToken) { LOG(("ScriptLoadRequest (%p): Decode Bytecode & Join and Execute", aRequest)); rv = aExec.JoinOffThread( &aRequest->GetScriptLoadContext()->mOffThreadToken); } else { LOG(("ScriptLoadRequest (%p): Decode Bytecode and Execute", aRequest)); AUTO_PROFILER_MARKER_TEXT("BytecodeDecodeMainThread", JS, MarkerInnerWindowIdFromJSContext(aCx), profilerLabelString); rv = aExec.Decode(aRequest->mScriptBytecode, aRequest->mBytecodeOffset); } // We do not expect to be saving anything when we already have some // bytecode. MOZ_ASSERT(!aRequest->mCacheInfo); return rv; } MOZ_ASSERT(aRequest->IsSource()); bool encodeBytecode = ShouldCacheBytecode(aRequest); aExec.SetEncodeBytecode(encodeBytecode); if (aRequest->GetScriptLoadContext()->mOffThreadToken) { // Off-main-thread parsing. LOG( ("ScriptLoadRequest (%p): Join (off-thread parsing) and " "Execute", aRequest)); MOZ_ASSERT(aRequest->IsTextSource()); rv = aExec.JoinOffThread(&aRequest->GetScriptLoadContext()->mOffThreadToken); } else { // Main thread parsing (inline and small scripts) LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest)); MOZ_ASSERT(aRequest->IsTextSource()); MaybeSourceText maybeSource; rv = aRequest->GetScriptSource(aCx, &maybeSource); if (NS_SUCCEEDED(rv)) { AUTO_PROFILER_MARKER_TEXT("ScriptCompileMainThread", JS, MarkerInnerWindowIdFromJSContext(aCx), profilerLabelString); auto compile = [&](auto& source) { return aExec.Compile(source); }; MOZ_ASSERT(!maybeSource.empty()); TimeStamp startTime = TimeStamp::Now(); rv = maybeSource.mapNonEmpty(compile); mMainThreadParseTime += TimeStamp::Now() - startTime; } } return rv; } /* static */ nsCString& ScriptLoader::BytecodeMimeTypeFor(ScriptLoadRequest* aRequest) { if (aRequest->IsModuleRequest()) { return nsContentUtils::JSModuleBytecodeMimeType(); } return nsContentUtils::JSScriptBytecodeMimeType(); } void ScriptLoader::MaybePrepareForBytecodeEncodingBeforeExecute( ScriptLoadRequest* aRequest, JS::Handle<JSScript*> aScript) { if (!ShouldCacheBytecode(aRequest)) { return; } aRequest->MarkForBytecodeEncoding(aScript); } nsresult ScriptLoader::MaybePrepareForBytecodeEncodingAfterExecute( ScriptLoadRequest* aRequest, nsresult aRv) { if (aRequest->IsMarkedForBytecodeEncoding()) { TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), "scriptloader_encode"); // NOTE: This assertion will fail once we start encoding more data after the // first encode. MOZ_ASSERT(aRequest->mBytecodeOffset == aRequest->mScriptBytecode.length()); RegisterForBytecodeEncoding(aRequest); MOZ_ASSERT(IsAlreadyHandledForBytecodeEncodingPreparation(aRequest)); return aRv; } LOG(("ScriptLoadRequest (%p): Bytecode-cache: disabled (rv = %X)", aRequest, unsigned(aRv))); TRACE_FOR_TEST_NONE(aRequest->GetScriptLoadContext()->GetScriptElement(), "scriptloader_no_encode"); aRequest->mCacheInfo = nullptr; MOZ_ASSERT(IsAlreadyHandledForBytecodeEncodingPreparation(aRequest)); return aRv; } bool ScriptLoader::IsAlreadyHandledForBytecodeEncodingPreparation( ScriptLoadRequest* aRequest) { return aRequest->isInList() || !aRequest->mCacheInfo; } void ScriptLoader::MaybePrepareModuleForBytecodeEncodingBeforeExecute( JSContext* aCx, ModuleLoadRequest* aRequest) { { ModuleScript* moduleScript = aRequest->mModuleScript; JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord()); if (aRequest->IsMarkedForBytecodeEncoding()) { // This module is imported multiple times, and already marked. return; } if (ShouldCacheBytecode(aRequest)) { aRequest->MarkModuleForBytecodeEncoding(); } } for (ModuleLoadRequest* childRequest : aRequest->mImports) { MaybePrepareModuleForBytecodeEncodingBeforeExecute(aCx, childRequest); } } nsresult ScriptLoader::MaybePrepareModuleForBytecodeEncodingAfterExecute( ModuleLoadRequest* aRequest, nsresult aRv) { if (IsAlreadyHandledForBytecodeEncodingPreparation(aRequest)) { // This module is imported multiple times and already handled. return aRv; } aRv = MaybePrepareForBytecodeEncodingAfterExecute(aRequest, aRv); for (ModuleLoadRequest* childRequest : aRequest->mImports) { aRv = MaybePrepareModuleForBytecodeEncodingAfterExecute(childRequest, aRv); } return aRv; } nsresult ScriptLoader::EvaluateScript(nsIGlobalObject* aGlobalObject, ScriptLoadRequest* aRequest) { nsAutoMicroTask mt; AutoEntryScript aes(aGlobalObject, "EvaluateScript", true); JSContext* cx = aes.cx(); nsAutoCString profilerLabelString; aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); // Create a ClassicScript object and associate it with the JSScript. RefPtr<ClassicScript> classicScript = new ClassicScript(aRequest->mFetchOptions, aRequest->mBaseURL); JS::Rooted<JS::Value> classicScriptValue(cx, JS::PrivateValue(classicScript)); JS::CompileOptions options(cx); JS::Rooted<JSScript*> introductionScript(cx); nsresult rv = FillCompileOptionsForRequest(cx, aRequest, &options, &introductionScript); if (NS_FAILED(rv)) { return rv; } TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), "scriptloader_execute"); JS::Rooted<JSObject*> global(cx, aGlobalObject->GetGlobalJSObject()); JSExecutionContext exec(cx, global, options, classicScriptValue, introductionScript); rv = CompileOrDecodeClassicScript(cx, exec, aRequest); if (NS_FAILED(rv)) { return rv; } // TODO (yulia): rewrite this section. rv can be a failing pattern other than // NS_OK which will pass the NS_FAILED check above. If we call exec.GetScript // in that case, it will crash. if (rv == NS_OK) { JS::Rooted<JSScript*> script(cx, exec.GetScript()); MaybePrepareForBytecodeEncodingBeforeExecute(aRequest, script); { LOG(("ScriptLoadRequest (%p): Evaluate Script", aRequest)); AUTO_PROFILER_MARKER_TEXT("ScriptExecution", JS, MarkerInnerWindowIdFromJSContext(cx), profilerLabelString); rv = ExecuteCompiledScript(cx, exec, classicScript); } } // This must be called also for compilation failure case, in order to // dispatch test-only event. rv = MaybePrepareForBytecodeEncodingAfterExecute(aRequest, rv); // Even if we are not saving the bytecode of the current script, we have // to trigger the encoding of the bytecode, as the current script can // call functions of a script for which we are recording the bytecode. LOG(("ScriptLoadRequest (%p): ScriptLoader = %p", aRequest, this)); MaybeTriggerBytecodeEncoding(); return rv; } /* static */ LoadedScript* ScriptLoader::GetActiveScript(JSContext* aCx) { JS::Value value = JS::GetScriptedCallerPrivate(aCx); if (value.isUndefined()) { return nullptr; } return static_cast<LoadedScript*>(value.toPrivate()); } void ScriptLoader::RegisterForBytecodeEncoding(ScriptLoadRequest* aRequest) { MOZ_ASSERT(aRequest->mCacheInfo); MOZ_ASSERT(aRequest->IsMarkedForBytecodeEncoding()); MOZ_DIAGNOSTIC_ASSERT(!aRequest->isInList()); mBytecodeEncodingQueue.AppendElement(aRequest); } void ScriptLoader::LoadEventFired() { mLoadEventFired = true; MaybeTriggerBytecodeEncoding(); if (!mMainThreadParseTime.IsZero()) { Telemetry::Accumulate( Telemetry::JS_PAGELOAD_PARSE_MS, static_cast<uint32_t>(mMainThreadParseTime.ToMilliseconds())); } } void ScriptLoader::Destroy() { if (mShutdownObserver) { mShutdownObserver->Unregister(); mShutdownObserver = nullptr; } CancelAndClearScriptLoadRequests(); GiveUpBytecodeEncoding(); } void ScriptLoader::MaybeTriggerBytecodeEncoding() { // If we already gave up, ensure that we are not going to enqueue any script, // and that we finalize them properly. if (mGiveUpEncoding) { LOG(("ScriptLoader (%p): Keep giving-up bytecode encoding.", this)); GiveUpBytecodeEncoding(); return; } // We wait for the load event to be fired before saving the bytecode of // any script to the cache. It is quite common to have load event // listeners trigger more JavaScript execution, that we want to save as // part of this start-up bytecode cache. if (!mLoadEventFired) { LOG(("ScriptLoader (%p): Wait for the load-end event to fire.", this)); return; } // No need to fire any event if there is no bytecode to be saved. if (mBytecodeEncodingQueue.isEmpty()) { LOG(("ScriptLoader (%p): No script in queue to be encoded.", this)); return; } // Wait until all scripts are loaded before saving the bytecode, such that // we capture most of the intialization of the page. if (HasPendingRequests()) { LOG(("ScriptLoader (%p): Wait for other pending request to finish.", this)); return; } // Create a new runnable dedicated to encoding the content of the bytecode of // all enqueued scripts when the document is idle. In case of failure, we // give-up on encoding the bytecode. nsCOMPtr<nsIRunnable> encoder = NewRunnableMethod( "ScriptLoader::EncodeBytecode", this, &ScriptLoader::EncodeBytecode); if (NS_FAILED(NS_DispatchToCurrentThreadQueue(encoder.forget(), EventQueuePriority::Idle))) { GiveUpBytecodeEncoding(); return; } LOG(("ScriptLoader (%p): Schedule bytecode encoding.", this)); } void ScriptLoader::EncodeBytecode() { LOG(("ScriptLoader (%p): Start bytecode encoding.", this)); // If any script got added in the previous loop cycle, wait until all // remaining script executions are completed, such that we capture most of // the initialization. if (HasPendingRequests()) { return; } // Should not be encoding modules at all. nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject(); if (!globalObject) { GiveUpBytecodeEncoding(); return; } nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext(); if (!context) { GiveUpBytecodeEncoding(); return; } AutoEntryScript aes(globalObject, "encode bytecode", true); RefPtr<ScriptLoadRequest> request; while (!mBytecodeEncodingQueue.isEmpty()) { request = mBytecodeEncodingQueue.StealFirst(); MOZ_ASSERT(!IsWebExtensionRequest(request), "Bytecode for web extension content scrips is not cached"); EncodeRequestBytecode(aes.cx(), request); request->mScriptBytecode.clearAndFree(); request->DropBytecodeCacheReferences(); } } void ScriptLoader::EncodeRequestBytecode(JSContext* aCx, ScriptLoadRequest* aRequest) { using namespace mozilla::Telemetry; nsresult rv = NS_OK; MOZ_ASSERT(aRequest->mCacheInfo); auto bytecodeFailed = mozilla::MakeScopeExit([&]() { TRACE_FOR_TEST_NONE(aRequest->GetScriptLoadContext()->GetScriptElement(), "scriptloader_bytecode_failed"); }); bool result; if (aRequest->IsModuleRequest()) { ModuleScript* moduleScript = aRequest->AsModuleRequest()->mModuleScript; JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord()); result = JS::FinishIncrementalEncoding(aCx, module, aRequest->mScriptBytecode); } else { JS::Rooted<JSScript*> script(aCx, aRequest->mScriptForBytecodeEncoding); result = JS::FinishIncrementalEncoding(aCx, script, aRequest->mScriptBytecode); } if (!result) { // Encoding can be aborted for non-supported syntax (e.g. asm.js), or // any other internal error. // We don't care the error and just give up encoding. JS_ClearPendingException(aCx); LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", aRequest)); return; } Vector<uint8_t> compressedBytecode; // TODO probably need to move this to a helper thread if (!ScriptBytecodeCompress(aRequest->mScriptBytecode, aRequest->mBytecodeOffset, compressedBytecode)) { return; } if (compressedBytecode.length() >= UINT32_MAX) { LOG( ("ScriptLoadRequest (%p): Bytecode cache is too large to be decoded " "correctly.", aRequest)); return; } // Open the output stream to the cache entry alternate data storage. This // might fail if the stream is already open by another request, in which // case, we just ignore the current one. nsCOMPtr<nsIAsyncOutputStream> output; rv = aRequest->mCacheInfo->OpenAlternativeOutputStream( BytecodeMimeTypeFor(aRequest), static_cast<int64_t>(compressedBytecode.length()), getter_AddRefs(output)); if (NS_FAILED(rv)) { LOG( ("ScriptLoadRequest (%p): Cannot open bytecode cache (rv = %X, output " "= %p)", aRequest, unsigned(rv), output.get())); return; } MOZ_ASSERT(output); auto closeOutStream = mozilla::MakeScopeExit([&]() { rv = output->CloseWithStatus(rv); LOG(("ScriptLoadRequest (%p): Closing (rv = %X)", aRequest, unsigned(rv))); }); uint32_t n; rv = output->Write(reinterpret_cast<char*>(compressedBytecode.begin()), compressedBytecode.length(), &n); LOG( ("ScriptLoadRequest (%p): Write bytecode cache (rv = %X, length = %u, " "written = %u)", aRequest, unsigned(rv), unsigned(compressedBytecode.length()), n)); if (NS_FAILED(rv)) { return; } MOZ_RELEASE_ASSERT(compressedBytecode.length() == n); bytecodeFailed.release(); TRACE_FOR_TEST_NONE(aRequest->GetScriptLoadContext()->GetScriptElement(), "scriptloader_bytecode_saved"); } void ScriptLoader::GiveUpBytecodeEncoding() { // If the document went away prematurely, we still want to set this, in order // to avoid queuing more scripts. mGiveUpEncoding = true; // Ideally we prefer to properly end the incremental encoder, such that we // would not keep a large buffer around. If we cannot, we fallback on the // removal of all request from the current list and these large buffers would // be removed at the same time as the source object. nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject(); AutoAllowLegacyScriptExecution exemption; Maybe<AutoEntryScript> aes; if (globalObject) { nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext(); if (context) { aes.emplace(globalObject, "give-up bytecode encoding", true); } } while (!mBytecodeEncodingQueue.isEmpty()) { RefPtr<ScriptLoadRequest> request = mBytecodeEncodingQueue.StealFirst(); LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", request.get())); TRACE_FOR_TEST_NONE(request->GetScriptLoadContext()->GetScriptElement(), "scriptloader_bytecode_failed"); MOZ_ASSERT(!IsWebExtensionRequest(request)); if (aes.isSome()) { if (request->IsModuleRequest()) { ModuleScript* moduleScript = request->AsModuleRequest()->mModuleScript; JS::Rooted<JSObject*> module(aes->cx(), moduleScript->ModuleRecord()); JS::AbortIncrementalEncoding(module); } else { JS::Rooted<JSScript*> script(aes->cx(), request->mScriptForBytecodeEncoding); JS::AbortIncrementalEncoding(script); } } request->mScriptBytecode.clearAndFree(); request->DropBytecodeCacheReferences(); } } bool ScriptLoader::HasPendingRequests() const { return mParserBlockingRequest || !mXSLTRequests.isEmpty() || !mLoadedAsyncRequests.isEmpty() || !mNonAsyncExternalScriptInsertedRequests.isEmpty() || !mDeferRequests.isEmpty() || HasPendingDynamicImports() || !mPendingChildLoaders.IsEmpty(); // mOffThreadCompilingRequests are already being processed. } bool ScriptLoader::HasPendingDynamicImports() const { if (mModuleLoader && mModuleLoader->HasPendingDynamicImports()) { return true; } for (ModuleLoader* loader : mWebExtModuleLoaders) { if (loader->HasPendingDynamicImports()) { return true; } } for (ModuleLoader* loader : mShadowRealmModuleLoaders) { if (loader->HasPendingDynamicImports()) { return true; } } return false; } void ScriptLoader::ProcessPendingRequestsAsync() { if (HasPendingRequests()) { nsCOMPtr<nsIRunnable> task = NewRunnableMethod("dom::ScriptLoader::ProcessPendingRequests", this, &ScriptLoader::ProcessPendingRequests); if (mDocument) { mDocument->Dispatch(TaskCategory::Other, task.forget()); } else { NS_DispatchToCurrentThread(task.forget()); } } } void ScriptLoader::ProcessPendingRequests() { RefPtr<ScriptLoadRequest> request; if (mParserBlockingRequest && mParserBlockingRequest->IsReadyToRun() && ReadyToExecuteParserBlockingScripts()) { request.swap(mParserBlockingRequest); UnblockParser(request); ProcessRequest(request); ContinueParserAsync(request); } while (ReadyToExecuteParserBlockingScripts() && !mXSLTRequests.isEmpty() && mXSLTRequests.getFirst()->IsReadyToRun()) { request = mXSLTRequests.StealFirst(); ProcessRequest(request); } while (ReadyToExecuteScripts() && !mLoadedAsyncRequests.isEmpty()) { request = mLoadedAsyncRequests.StealFirst(); if (request->IsModuleRequest()) { ProcessRequest(request); } else { CompileOffThreadOrProcessRequest(request); } } while (ReadyToExecuteScripts() && !mNonAsyncExternalScriptInsertedRequests.isEmpty() && mNonAsyncExternalScriptInsertedRequests.getFirst()->IsReadyToRun()) { // Violate the HTML5 spec and execute these in the insertion order in // order to make LABjs and the "order" plug-in for RequireJS work with // their Gecko-sniffed code path. See // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html request = mNonAsyncExternalScriptInsertedRequests.StealFirst(); ProcessRequest(request); } if (mDeferCheckpointReached && mXSLTRequests.isEmpty()) { while (ReadyToExecuteScripts() && !mDeferRequests.isEmpty() && mDeferRequests.getFirst()->IsReadyToRun()) { request = mDeferRequests.StealFirst(); ProcessRequest(request); } } while (!mPendingChildLoaders.IsEmpty() && ReadyToExecuteParserBlockingScripts()) { RefPtr<ScriptLoader> child = mPendingChildLoaders[0]; mPendingChildLoaders.RemoveElementAt(0); child->RemoveParserBlockingScriptExecutionBlocker(); } if (mDeferCheckpointReached && mDocument && !mParserBlockingRequest && mNonAsyncExternalScriptInsertedRequests.isEmpty() && mXSLTRequests.isEmpty() && mDeferRequests.isEmpty() && MaybeRemovedDeferRequests()) { return ProcessPendingRequests(); } if (mDeferCheckpointReached && mDocument && !mParserBlockingRequest && mLoadingAsyncRequests.isEmpty() && mLoadedAsyncRequests.isEmpty() && mNonAsyncExternalScriptInsertedRequests.isEmpty() && mXSLTRequests.isEmpty() && mDeferRequests.isEmpty()) { // No more pending scripts; time to unblock onload. // OK to unblock onload synchronously here, since callers must be // prepared for the world changing anyway. mDeferCheckpointReached = false; mDocument->UnblockOnload(true); } } bool ScriptLoader::ReadyToExecuteParserBlockingScripts() { // Make sure the SelfReadyToExecuteParserBlockingScripts check is first, so // that we don't block twice on an ancestor. if (!SelfReadyToExecuteParserBlockingScripts()) { return false; } if (mDocument && mDocument->GetWindowContext()) { for (WindowContext* wc = mDocument->GetWindowContext()->GetParentWindowContext(); wc; wc = wc->GetParentWindowContext()) { if (Document* doc = wc->GetDocument()) { ScriptLoader* ancestor = doc->ScriptLoader(); if (!ancestor->SelfReadyToExecuteParserBlockingScripts() && ancestor->AddPendingChildLoader(this)) { AddParserBlockingScriptExecutionBlocker(); return false; } } } } return true; } template <typename Unit> static nsresult ConvertToUnicode(nsIChannel* aChannel, const uint8_t* aData, uint32_t aLength, const nsAString& aHintCharset, Document* aDocument, Unit*& aBufOut, size_t& aLengthOut) { if (!aLength) { aBufOut = nullptr; aLengthOut = 0; return NS_OK; } auto data = Span(aData, aLength); // The encoding info precedence is as follows from high to low: // The BOM // HTTP Content-Type (if name recognized) // charset attribute (if name recognized) // The encoding of the document UniquePtr<Decoder> unicodeDecoder; const Encoding* encoding; std::tie(encoding, std::ignore) = Encoding::ForBOM(data); if (encoding) { unicodeDecoder = encoding->NewDecoderWithBOMRemoval(); } if (!unicodeDecoder && aChannel) { nsAutoCString label; if (NS_SUCCEEDED(aChannel->GetContentCharset(label)) && (encoding = Encoding::ForLabel(label))) { unicodeDecoder = encoding->NewDecoderWithoutBOMHandling(); } } if (!unicodeDecoder && (encoding = Encoding::ForLabel(aHintCharset))) { unicodeDecoder = encoding->NewDecoderWithoutBOMHandling(); } if (!unicodeDecoder && aDocument) { unicodeDecoder = aDocument->GetDocumentCharacterSet()->NewDecoderWithoutBOMHandling(); } if (!unicodeDecoder) { // Curiously, there are various callers that don't pass aDocument. The // fallback in the old code was ISO-8859-1, which behaved like // windows-1252. unicodeDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling(); } auto signalOOM = mozilla::MakeScopeExit([&aBufOut, &aLengthOut]() { aBufOut = nullptr; aLengthOut = 0; }); CheckedInt<size_t> bufferLength = ScriptDecoding<Unit>::MaxBufferLength(unicodeDecoder, aLength); if (!bufferLength.isValid()) { return NS_ERROR_OUT_OF_MEMORY; } CheckedInt<size_t> bufferByteSize = bufferLength * sizeof(Unit); if (!bufferByteSize.isValid()) { return NS_ERROR_OUT_OF_MEMORY; } aBufOut = static_cast<Unit*>(js_malloc(bufferByteSize.value())); if (!aBufOut) { return NS_ERROR_OUT_OF_MEMORY; } signalOOM.release(); aLengthOut = ScriptDecoding<Unit>::DecodeInto( unicodeDecoder, data, Span(aBufOut, bufferLength.value()), /* aEndOfSource = */ true); return NS_OK; } /* static */ nsresult ScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData, uint32_t aLength, const nsAString& aHintCharset, Document* aDocument, char16_t*& aBufOut, size_t& aLengthOut) { return ConvertToUnicode(aChannel, aData, aLength, aHintCharset, aDocument, aBufOut, aLengthOut); } /* static */ nsresult ScriptLoader::ConvertToUTF8(nsIChannel* aChannel, const uint8_t* aData, uint32_t aLength, const nsAString& aHintCharset, Document* aDocument, Utf8Unit*& aBufOut, size_t& aLengthOut) { return ConvertToUnicode(aChannel, aData, aLength, aHintCharset, aDocument, aBufOut, aLengthOut); } nsresult ScriptLoader::OnStreamComplete( nsIIncrementalStreamLoader* aLoader, ScriptLoadRequest* aRequest, nsresult aChannelStatus, nsresult aSRIStatus, SRICheckDataVerifier* aSRIDataVerifier) { NS_ASSERTION(aRequest, "null request in stream complete handler"); NS_ENSURE_TRUE(aRequest, NS_ERROR_FAILURE); nsresult rv = VerifySRI(aRequest, aLoader, aSRIStatus, aSRIDataVerifier); if (NS_SUCCEEDED(rv)) { // If we are loading from source, save the computed SRI hash or a dummy SRI // hash in case we are going to save the bytecode of this script in the // cache. if (aRequest->IsSource()) { uint32_t sriLength = 0; rv = SaveSRIHash(aRequest, aSRIDataVerifier, &sriLength); MOZ_ASSERT_IF(NS_SUCCEEDED(rv), aRequest->mScriptBytecode.length() == sriLength); aRequest->mBytecodeOffset = JS::AlignTranscodingBytecodeOffset(sriLength); if (aRequest->mBytecodeOffset != sriLength) { // We need extra padding after SRI hash. if (!aRequest->mScriptBytecode.resize(aRequest->mBytecodeOffset)) { return NS_ERROR_OUT_OF_MEMORY; } } } if (NS_SUCCEEDED(rv)) { rv = PrepareLoadedRequest(aRequest, aLoader, aChannelStatus); } if (NS_FAILED(rv)) { ReportErrorToConsole(aRequest, rv); } } if (NS_FAILED(rv)) { // When loading bytecode, we verify the SRI hash. If it does not match the // one from the document we restart the load, forcing us to load the source // instead. If this happens do not remove the current request from script // loader's data structures or fire any events. if (aChannelStatus != NS_BINDING_RETARGETED) { HandleLoadError(aRequest, rv); } } // Process our request and/or any pending ones ProcessPendingRequests(); return rv; } nsresult ScriptLoader::VerifySRI(ScriptLoadRequest* aRequest, nsIIncrementalStreamLoader* aLoader, nsresult aSRIStatus, SRICheckDataVerifier* aSRIDataVerifier) const { nsCOMPtr<nsIRequest> channelRequest; aLoader->GetRequest(getter_AddRefs(channelRequest)); nsCOMPtr<nsIChannel> channel; channel = do_QueryInterface(channelRequest); nsresult rv = NS_OK; if (!aRequest->mIntegrity.IsEmpty() && NS_SUCCEEDED((rv = aSRIStatus))) { MOZ_ASSERT(aSRIDataVerifier); MOZ_ASSERT(mReporter); nsAutoCString sourceUri; if (mDocument && mDocument->GetDocumentURI()) { mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri); } rv = aSRIDataVerifier->Verify(aRequest->mIntegrity, channel, sourceUri, mReporter); if (channelRequest) { mReporter->FlushReportsToConsole( nsContentUtils::GetInnerWindowID(channelRequest)); } else { mReporter->FlushConsoleReports(mDocument); } if (NS_FAILED(rv)) { rv = NS_ERROR_SRI_CORRUPT; } } return rv; } nsresult ScriptLoader::SaveSRIHash(ScriptLoadRequest* aRequest, SRICheckDataVerifier* aSRIDataVerifier, uint32_t* sriLength) const { MOZ_ASSERT(aRequest->IsSource()); MOZ_ASSERT(aRequest->mScriptBytecode.empty()); uint32_t len; // If the integrity metadata does not correspond to a valid hash function, // IsComplete would be false. if (!aRequest->mIntegrity.IsEmpty() && aSRIDataVerifier->IsComplete()) { MOZ_ASSERT(aRequest->mScriptBytecode.length() == 0); // Encode the SRI computed hash. len = aSRIDataVerifier->DataSummaryLength(); if (!aRequest->mScriptBytecode.resize(len)) { return NS_ERROR_OUT_OF_MEMORY; } DebugOnly<nsresult> res = aSRIDataVerifier->ExportDataSummary( len, aRequest->mScriptBytecode.begin()); MOZ_ASSERT(NS_SUCCEEDED(res)); } else { MOZ_ASSERT(aRequest->mScriptBytecode.length() == 0); // Encode a dummy SRI hash. len = SRICheckDataVerifier::EmptyDataSummaryLength(); if (!aRequest->mScriptBytecode.resize(len)) { return NS_ERROR_OUT_OF_MEMORY; } DebugOnly<nsresult> res = SRICheckDataVerifier::ExportEmptyDataSummary( len, aRequest->mScriptBytecode.begin()); MOZ_ASSERT(NS_SUCCEEDED(res)); } // Verify that the exported and predicted length correspond. DebugOnly<uint32_t> srilen{}; MOZ_ASSERT(NS_SUCCEEDED(SRICheckDataVerifier::DataSummaryLength( len, aRequest->mScriptBytecode.begin(), &srilen))); MOZ_ASSERT(srilen == len); *sriLength = len; return NS_OK; } void ScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest, nsresult aResult) const { MOZ_ASSERT(aRequest); if (aRequest->GetScriptLoadContext()->IsPreload()) { // Skip reporting errors in preload requests. If the request is actually // used then we will report the error in ReportPreloadErrorsToConsole below. aRequest->GetScriptLoadContext()->mUnreportedPreloadError = aResult; return; } bool isScript = !aRequest->IsModuleRequest(); const char* message; if (aResult == NS_ERROR_MALFORMED_URI) { message = isScript ? "ScriptSourceMalformed" : "ModuleSourceMalformed"; } else if (aResult == NS_ERROR_DOM_BAD_URI) { message = isScript ? "ScriptSourceNotAllowed" : "ModuleSourceNotAllowed"; } else if (aResult == NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI) { MOZ_ASSERT(!isScript); message = "WebExtContentScriptModuleSourceNotAllowed"; } else if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( aResult)) { // Blocking classifier error codes already show their own console messages. return; } else { message = isScript ? "ScriptSourceLoadFailed" : "ModuleSourceLoadFailed"; } AutoTArray<nsString, 1> params; CopyUTF8toUTF16(aRequest->mURI->GetSpecOrDefault(), *params.AppendElement()); nsIScriptElement* element = aRequest->GetScriptLoadContext()->GetScriptElement(); uint32_t lineNo = element ? element->GetScriptLineNumber() : 0; uint32_t columnNo = element ? element->GetScriptColumnNumber() : 0; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Script Loader"_ns, mDocument, nsContentUtils::eDOM_PROPERTIES, message, params, nullptr, u""_ns, lineNo, columnNo); } void ScriptLoader::ReportWarningToConsole( ScriptLoadRequest* aRequest, const char* aMessageName, const nsTArray<nsString>& aParams) const { nsIScriptElement* element = aRequest->GetScriptLoadContext()->GetScriptElement(); uint32_t lineNo = element ? element->GetScriptLineNumber() : 0; uint32_t columnNo = element ? element->GetScriptColumnNumber() : 0; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Script Loader"_ns, mDocument, nsContentUtils::eDOM_PROPERTIES, aMessageName, aParams, nullptr, u""_ns, lineNo, columnNo); } void ScriptLoader::ReportPreloadErrorsToConsole(ScriptLoadRequest* aRequest) { if (NS_FAILED(aRequest->GetScriptLoadContext()->mUnreportedPreloadError)) { ReportErrorToConsole( aRequest, aRequest->GetScriptLoadContext()->mUnreportedPreloadError); aRequest->GetScriptLoadContext()->mUnreportedPreloadError = NS_OK; } if (aRequest->IsModuleRequest()) { for (const auto& childRequest : aRequest->AsModuleRequest()->mImports) { ReportPreloadErrorsToConsole(childRequest); } } } void ScriptLoader::HandleLoadError(ScriptLoadRequest* aRequest, nsresult aResult) { /* * Handle script not loading error because source was an tracking URL (or * fingerprinting, cryptomining, etc). * We make a note of this script node by including it in a dedicated * array of blocked tracking nodes under its parent document. */ if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( aResult)) { nsCOMPtr<nsIContent> cont = do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement()); mDocument->AddBlockedNodeByClassifier(cont); } if (aRequest->IsModuleRequest()) { MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mIsInline); aRequest->AsModuleRequest()->OnFetchComplete(aResult); } if (aRequest->GetScriptLoadContext()->mInDeferList) { MOZ_ASSERT_IF(aRequest->IsModuleRequest(), aRequest->AsModuleRequest()->IsTopLevel()); if (aRequest->isInList()) { RefPtr<ScriptLoadRequest> req = mDeferRequests.Steal(aRequest); FireScriptAvailable(aResult, req); } } else if (aRequest->GetScriptLoadContext()->mInAsyncList) { MOZ_ASSERT_IF(aRequest->IsModuleRequest(), aRequest->AsModuleRequest()->IsTopLevel()); if (aRequest->isInList()) { RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest); FireScriptAvailable(aResult, req); } } else if (aRequest->GetScriptLoadContext()->mIsNonAsyncScriptInserted) { if (aRequest->isInList()) { RefPtr<ScriptLoadRequest> req = mNonAsyncExternalScriptInsertedRequests.Steal(aRequest); FireScriptAvailable(aResult, req); } } else if (aRequest->GetScriptLoadContext()->mIsXSLT) { if (aRequest->isInList()) { RefPtr<ScriptLoadRequest> req = mXSLTRequests.Steal(aRequest); FireScriptAvailable(aResult, req); } } else if (aRequest->GetScriptLoadContext()->IsPreload()) { if (aRequest->IsModuleRequest()) { aRequest->Cancel(); } if (aRequest->IsTopLevel()) { // Request may already have been removed by // CancelAndClearScriptLoadRequests. mPreloads.RemoveElement(aRequest, PreloadRequestComparator()); } MOZ_ASSERT(!aRequest->isInList()); AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::LoadError); } else if (aRequest->IsModuleRequest()) { ModuleLoadRequest* modReq = aRequest->AsModuleRequest(); if (modReq->IsDynamicImport()) { MOZ_ASSERT(modReq->IsTopLevel()); if (aRequest->isInList()) { modReq->CancelDynamicImport(aResult); } } else { MOZ_ASSERT(!modReq->IsTopLevel()); MOZ_ASSERT(!modReq->isInList()); modReq->Cancel(); // The error is handled for the top level module. } } else if (mParserBlockingRequest == aRequest) { MOZ_ASSERT(!aRequest->isInList()); mParserBlockingRequest = nullptr; UnblockParser(aRequest); // Ensure that we treat aRequest->GetScriptLoadContext()->GetScriptElement() // as our current parser-inserted script while firing onerror on it. MOZ_ASSERT(aRequest->GetScriptLoadContext() ->GetScriptElement() ->GetParserCreated()); nsCOMPtr<nsIScriptElement> oldParserInsertedScript = mCurrentParserInsertedScript; mCurrentParserInsertedScript = aRequest->GetScriptLoadContext()->GetScriptElement(); FireScriptAvailable(aResult, aRequest); ContinueParserAsync(aRequest); mCurrentParserInsertedScript = oldParserInsertedScript; } else { // This happens for blocking requests cancelled by ParsingComplete(). // Ignore cancellation status for link-preload requests, as cancellation can // be omitted for them when SRI is stronger on consumer tags. MOZ_ASSERT(aRequest->IsCanceled() || aRequest->GetScriptLoadContext()->IsLinkPreloadScript()); MOZ_ASSERT(!aRequest->isInList()); } } void ScriptLoader::UnblockParser(ScriptLoadRequest* aParserBlockingRequest) { aParserBlockingRequest->GetScriptLoadContext() ->GetScriptElement() ->UnblockParser(); } void ScriptLoader::ContinueParserAsync( ScriptLoadRequest* aParserBlockingRequest) { aParserBlockingRequest->GetScriptLoadContext() ->GetScriptElement() ->ContinueParserAsync(); } uint32_t ScriptLoader::NumberOfProcessors() { if (mNumberOfProcessors > 0) { return mNumberOfProcessors; } int32_t numProcs = PR_GetNumberOfProcessors(); if (numProcs > 0) { mNumberOfProcessors = numProcs; } return mNumberOfProcessors; } int32_t ScriptLoader::PhysicalSizeOfMemoryInGB() { // 0 is a valid result from PR_GetPhysicalMemorySize() which // means a failure occured. if (mPhysicalSizeOfMemory >= 0) { return mPhysicalSizeOfMemory; } // Save the size in GB. mPhysicalSizeOfMemory = static_cast<int32_t>(PR_GetPhysicalMemorySize() >> 30); return mPhysicalSizeOfMemory; } static bool IsInternalURIScheme(nsIURI* uri) { return uri->SchemeIs("moz-extension") || uri->SchemeIs("resource") || uri->SchemeIs("chrome"); } bool ScriptLoader::ShouldApplyDelazifyStrategy(ScriptLoadRequest* aRequest) { // Full parse everything if negative. if (StaticPrefs::dom_script_loader_delazification_max_size() < 0) { return true; } // Be conservative on machines with 2GB or less of memory. if (PhysicalSizeOfMemoryInGB() <= StaticPrefs::dom_script_loader_delazification_min_mem()) { return false; } uint32_t max_size = static_cast<uint32_t>( StaticPrefs::dom_script_loader_delazification_max_size()); uint32_t script_size = aRequest->ScriptTextLength() > 0 ? static_cast<uint32_t>(aRequest->ScriptTextLength()) : 0; if (mTotalFullParseSize + script_size < max_size) { return true; } if (LOG_ENABLED()) { nsCString url = aRequest->mURI->GetSpecOrDefault(); LOG( ("ScriptLoadRequest (%p): non-on-demand-only Parsing Disabled for (%s) " "with size=%u because mTotalFullParseSize=%u would exceed max_size=%u", aRequest, url.get(), script_size, mTotalFullParseSize, max_size)); } return false; } void ScriptLoader::ApplyDelazifyStrategy(JS::CompileOptions* aOptions) { JS::DelazificationOption strategy = JS::DelazificationOption::ParseEverythingEagerly; uint32_t strategyIndex = StaticPrefs::dom_script_loader_delazification_strategy(); // Assert that all enumerated values of DelazificationOption are dense between // OnDemandOnly and ParseEverythingEagerly. #ifdef DEBUG uint32_t count = 0; uint32_t mask = 0; # define _COUNT_ENTRIES(Name) count++; # define _MASK_ENTRIES(Name) \ mask |= 1 << uint32_t(JS::DelazificationOption::Name); FOREACH_DELAZIFICATION_STRATEGY(_COUNT_ENTRIES); MOZ_ASSERT(count == uint32_t(strategy) + 1); FOREACH_DELAZIFICATION_STRATEGY(_MASK_ENTRIES); MOZ_ASSERT(((mask + 1) & mask) == 0); # undef _COUNT_ENTRIES # undef _MASK_ENTRIES #endif // Any strategy index larger than ParseEverythingEagerly would default to // ParseEverythingEagerly. if (strategyIndex <= uint32_t(strategy)) { strategy = JS::DelazificationOption(uint8_t(strategyIndex)); } aOptions->setEagerDelazificationStrategy(strategy); } bool ScriptLoader::ShouldCompileOffThread(ScriptLoadRequest* aRequest) { if (NumberOfProcessors() <= 1) { return false; } if (aRequest == mParserBlockingRequest) { return true; } if (SpeculativeOMTParsingEnabled()) { // Processing non async inserted scripts too early can potentially delay the // load event from firing so focus on other scripts instead. if (aRequest->GetScriptLoadContext()->mIsNonAsyncScriptInserted && !StaticPrefs:: dom_script_loader_external_scripts_speculate_non_parser_inserted_enabled()) { return false; } // Async and link preload scripts do not need to be parsed right away. if (aRequest->GetScriptLoadContext()->IsAsyncScript() && !StaticPrefs:: dom_script_loader_external_scripts_speculate_async_enabled()) { return false; } if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript() && !StaticPrefs:: dom_script_loader_external_scripts_speculate_link_preload_enabled()) { return false; } return true; } return false; } nsresult ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest, nsIIncrementalStreamLoader* aLoader, nsresult aStatus) { if (NS_FAILED(aStatus)) { return aStatus; } if (aRequest->IsCanceled()) { return NS_BINDING_ABORTED; } MOZ_ASSERT(aRequest->IsFetching()); CollectScriptTelemetry(aRequest); // If we don't have a document, then we need to abort further // evaluation. if (!mDocument) { return NS_ERROR_NOT_AVAILABLE; } // If the load returned an error page, then we need to abort nsCOMPtr<nsIRequest> req; nsresult rv = aLoader->GetRequest(getter_AddRefs(req)); NS_ASSERTION(req, "StreamLoader's request went away prematurely"); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req); if (httpChannel) { bool requestSucceeded; rv = httpChannel->GetRequestSucceeded(&requestSucceeded); if (NS_SUCCEEDED(rv) && !requestSucceeded) { return NS_ERROR_NOT_AVAILABLE; } nsAutoCString sourceMapURL; if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) { aRequest->mSourceMapURL = Some(NS_ConvertUTF8toUTF16(sourceMapURL)); } nsCOMPtr<nsIClassifiedChannel> classifiedChannel = do_QueryInterface(req); MOZ_ASSERT(classifiedChannel); if (classifiedChannel && classifiedChannel->IsThirdPartyTrackingResource()) { aRequest->GetScriptLoadContext()->SetIsTracking(); } } nsCOMPtr<nsIChannel> channel = do_QueryInterface(req); // If this load was subject to a CORS check, don't flag it with a separate // origin principal, so that it will treat our document's principal as the // origin principal. Module loads always use CORS. if (!aRequest->IsModuleRequest() && aRequest->CORSMode() == CORS_NONE) { rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( channel, getter_AddRefs(aRequest->mOriginPrincipal)); NS_ENSURE_SUCCESS(rv, rv); } // This assertion could fire errorously if we ran out of memory when // inserting the request in the array. However it's an unlikely case // so if you see this assertion it is likely something else that is // wrong, especially if you see it more than once. NS_ASSERTION(mDeferRequests.Contains(aRequest) || mLoadingAsyncRequests.Contains(aRequest) || mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) || mXSLTRequests.Contains(aRequest) || (aRequest->IsModuleRequest() && (aRequest->AsModuleRequest()->IsRegisteredDynamicImport() || !aRequest->AsModuleRequest()->IsTopLevel())) || mPreloads.Contains(aRequest, PreloadRequestComparator()) || mParserBlockingRequest == aRequest, "aRequest should be pending!"); nsCOMPtr<nsIURI> uri; rv = channel->GetOriginalURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); // Fixup moz-extension: and resource: URIs, because the channel URI will // point to file:, which won't be allowed to load. if (uri && IsInternalURIScheme(uri)) { aRequest->mBaseURL = uri; } else { channel->GetURI(getter_AddRefs(aRequest->mBaseURL)); } if (aRequest->IsModuleRequest()) { ModuleLoadRequest* request = aRequest->AsModuleRequest(); // When loading a module, only responses with a JavaScript MIME type are // acceptable. nsAutoCString mimeType; channel->GetContentType(mimeType); NS_ConvertUTF8toUTF16 typeString(mimeType); if (!nsContentUtils::IsJavascriptMIMEType(typeString)) { return NS_ERROR_FAILURE; } // Attempt to compile off main thread. bool couldCompile = false; rv = AttemptOffThreadScriptCompile(request, &couldCompile); NS_ENSURE_SUCCESS(rv, rv); if (couldCompile) { return NS_OK; } // Otherwise compile it right away and start fetching descendents. return request->OnFetchComplete(NS_OK); } // The script is now loaded and ready to run. aRequest->SetReady(); // If speculative parsing is enabled attempt to compile all // external scripts off-main-thread. Otherwise, only omt compile scripts // blocking the parser. if (ShouldCompileOffThread(aRequest)) { MOZ_ASSERT(!aRequest->IsModuleRequest()); bool couldCompile = false; nsresult rv = AttemptOffThreadScriptCompile(aRequest, &couldCompile); NS_ENSURE_SUCCESS(rv, rv); if (couldCompile) { MOZ_ASSERT(aRequest->mState == ScriptLoadRequest::State::Compiling, "Request should be off-thread compiling now."); return NS_OK; } // If off-thread compile was rejected, continue with regular processing. } MaybeMoveToLoadedList(aRequest); return NS_OK; } void ScriptLoader::DeferCheckpointReached() { if (mDeferEnabled) { // Have to check because we apparently get ParsingComplete // without BeginDeferringScripts in some cases mDeferCheckpointReached = true; } mDeferEnabled = false; ProcessPendingRequests(); } void ScriptLoader::ParsingComplete(bool aTerminated) { if (aTerminated) { CancelAndClearScriptLoadRequests(); // Have to call this even if aTerminated so we'll correctly unblock onload. DeferCheckpointReached(); } } void ScriptLoader::PreloadURI(nsIURI* aURI, const nsAString& aCharset, const nsAString& aType, const nsAString& aCrossOrigin, const nsAString& aIntegrity, bool aScriptFromHead, bool aAsync, bool aDefer, bool aNoModule, bool aLinkPreload, const ReferrerPolicy aReferrerPolicy, uint64_t aEarlyHintPreloaderId) { NS_ENSURE_TRUE_VOID(mDocument); // Check to see if scripts has been turned off. if (!mEnabled || !mDocument->IsScriptEnabled()) { return; } ScriptKind scriptKind = ScriptKind::eClassic; if (mDocument->ModuleScriptsEnabled()) { // Don't load nomodule scripts. if (aNoModule) { return; } static const char kASCIIWhitespace[] = "\t\n\f\r "; nsAutoString type(aType); type.Trim(kASCIIWhitespace); if (type.LowerCaseEqualsASCII("module")) { scriptKind = ScriptKind::eModule; } } if (scriptKind == ScriptKind::eClassic && !aType.IsEmpty() && !nsContentUtils::IsJavascriptMIMEType(aType)) { // Unknown type. Don't load it. return; } SRIMetadata sriMetadata; GetSRIMetadata(aIntegrity, &sriMetadata); RefPtr<ScriptLoadRequest> request = CreateLoadRequest( scriptKind, aURI, nullptr, mDocument->NodePrincipal(), Element::StringToCORSMode(aCrossOrigin), sriMetadata, aReferrerPolicy); request->GetScriptLoadContext()->mIsInline = false; request->GetScriptLoadContext()->mScriptFromHead = aScriptFromHead; request->GetScriptLoadContext()->SetScriptMode(aDefer, aAsync, aLinkPreload); request->GetScriptLoadContext()->SetIsPreloadRequest(); if (LOG_ENABLED()) { nsAutoCString url; aURI->GetAsciiSpec(url); LOG(("ScriptLoadRequest (%p): Created preload request for %s", request.get(), url.get())); } nsAutoString charset(aCharset); nsresult rv = StartLoad(request, aEarlyHintPreloaderId, Some(charset)); if (NS_FAILED(rv)) { return; } PreloadInfo* pi = mPreloads.AppendElement(); pi->mRequest = request; pi->mCharset = aCharset; } void ScriptLoader::AddDeferRequest(ScriptLoadRequest* aRequest) { MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsDeferredScript()); MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInDeferList && !aRequest->GetScriptLoadContext()->mInAsyncList); MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInCompilingList); aRequest->GetScriptLoadContext()->mInDeferList = true; mDeferRequests.AppendElement(aRequest); if (mDeferEnabled && aRequest == mDeferRequests.getFirst() && mDocument && !mBlockingDOMContentLoaded) { MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING); mBlockingDOMContentLoaded = true; mDocument->BlockDOMContentLoaded(); } } void ScriptLoader::AddAsyncRequest(ScriptLoadRequest* aRequest) { MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsAsyncScript()); MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInDeferList && !aRequest->GetScriptLoadContext()->mInAsyncList); MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInCompilingList); aRequest->GetScriptLoadContext()->mInAsyncList = true; if (aRequest->IsReadyToRun()) { mLoadedAsyncRequests.AppendElement(aRequest); } else { mLoadingAsyncRequests.AppendElement(aRequest); } } void ScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) { MOZ_ASSERT(aRequest->IsReadyToRun()); MOZ_ASSERT(aRequest->IsTopLevel()); // If it's async, move it to the loaded list. // aRequest->GetScriptLoadContext()->mInAsyncList really _should_ be in a // list, but the consequences if it's not are bad enough we want to avoid // trying to move it if it's not. if (aRequest->GetScriptLoadContext()->mInAsyncList) { MOZ_ASSERT(aRequest->isInList()); if (aRequest->isInList()) { RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest); mLoadedAsyncRequests.AppendElement(req); } } else if (aRequest->IsModuleRequest() && aRequest->AsModuleRequest()->IsDynamicImport()) { // Process dynamic imports with async scripts. MOZ_ASSERT(!aRequest->isInList()); mLoadedAsyncRequests.AppendElement(aRequest); } } bool ScriptLoader::MaybeRemovedDeferRequests() { if (mDeferRequests.isEmpty() && mDocument && mBlockingDOMContentLoaded) { mBlockingDOMContentLoaded = false; mDocument->UnblockDOMContentLoaded(); return true; } return false; } DocGroup* ScriptLoader::GetDocGroup() const { return mDocument->GetDocGroup(); } void ScriptLoader::BeginDeferringScripts() { mDeferEnabled = true; if (mDeferCheckpointReached) { // We already completed a parse and were just waiting for some async // scripts to load (and were already blocking the load event waiting for // that to happen), when document.open() happened and now we're doing a // new parse. We shouldn't block the load event again, but _should_ reset // mDeferCheckpointReached to false. It'll get set to true again when the // DeferCheckpointReached call that corresponds to this // BeginDeferringScripts call happens (on document.close()), since we just // set mDeferEnabled to true. mDeferCheckpointReached = false; } else { if (mDocument) { mDocument->BlockOnload(); } } } nsAutoScriptLoaderDisabler::nsAutoScriptLoaderDisabler(Document* aDoc) { mLoader = aDoc->ScriptLoader(); mWasEnabled = mLoader->GetEnabled(); if (mWasEnabled) { mLoader->SetEnabled(false); } } nsAutoScriptLoaderDisabler::~nsAutoScriptLoaderDisabler() { if (mWasEnabled) { mLoader->SetEnabled(true); } } #undef TRACE_FOR_TEST #undef TRACE_FOR_TEST_BOOL #undef TRACE_FOR_TEST_NONE #undef LOG } // namespace mozilla::dom