Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / dom / script / ScriptLoader.cpp
blobfccc4262f7d5f7be2a529f0df9fd6d53f230a1c8
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ScriptLoader.h"
8 #include "ScriptLoadHandler.h"
9 #include "ScriptTrace.h"
10 #include "ModuleLoader.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/dom/FetchPriority.h"
14 #include "mozilla/dom/HTMLScriptElement.h"
15 #include "mozilla/dom/RequestBinding.h"
16 #include "nsIChildChannel.h"
17 #include "zlib.h"
19 #include "prsystem.h"
20 #include "jsapi.h"
21 #include "jsfriendapi.h"
22 #include "js/Array.h" // JS::GetArrayLength
23 #include "js/ColumnNumber.h" // #include "js/CompilationAndEvaluation.h"
25 #include "js/CompilationAndEvaluation.h"
26 #include "js/CompileOptions.h" // JS::CompileOptions, JS::OwningCompileOptions, JS::DecodeOptions, JS::OwningDecodeOptions, JS::DelazificationOption
27 #include "js/ContextOptions.h" // JS::ContextOptionsRef
28 #include "js/experimental/JSStencil.h" // JS::Stencil, JS::InstantiationStorage
29 #include "js/experimental/CompileScript.h" // JS::FrontendContext, JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::CompilationStorage, JS::CompileGlobalScriptToStencil, JS::CompileModuleScriptToStencil, JS::DecodeStencil, JS::PrepareForInstantiate
30 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
31 #include "js/loader/ScriptLoadRequest.h"
32 #include "ScriptCompression.h"
33 #include "js/loader/LoadedScript.h"
34 #include "js/loader/ModuleLoadRequest.h"
35 #include "js/MemoryFunctions.h"
36 #include "js/Modules.h"
37 #include "js/PropertyAndElement.h" // JS_DefineProperty
38 #include "js/Realm.h"
39 #include "js/SourceText.h"
40 #include "js/Transcoding.h" // JS::TranscodeRange, JS::TranscodeResult, JS::IsTranscodeFailureResult
41 #include "js/Utility.h"
42 #include "xpcpublic.h"
43 #include "GeckoProfiler.h"
44 #include "nsContentSecurityManager.h"
45 #include "nsCycleCollectionParticipant.h"
46 #include "nsIContent.h"
47 #include "nsJSUtils.h"
48 #include "mozilla/dom/AutoEntryScript.h"
49 #include "mozilla/dom/DocGroup.h"
50 #include "mozilla/dom/Element.h"
51 #include "mozilla/dom/JSExecutionContext.h"
52 #include "mozilla/dom/ScriptDecoding.h" // mozilla::dom::ScriptDecoding
53 #include "mozilla/dom/ScriptSettings.h"
54 #include "mozilla/dom/SRILogHelper.h"
55 #include "mozilla/dom/WindowContext.h"
56 #include "mozilla/Mutex.h" // mozilla::Mutex
57 #include "mozilla/net/UrlClassifierFeatureFactory.h"
58 #include "mozilla/Preferences.h"
59 #include "mozilla/StaticPrefs_dom.h"
60 #include "mozilla/StaticPrefs_javascript.h"
61 #include "mozilla/StaticPrefs_network.h"
62 #include "nsAboutProtocolUtils.h"
63 #include "nsGkAtoms.h"
64 #include "nsNetUtil.h"
65 #include "nsIScriptGlobalObject.h"
66 #include "nsIScriptContext.h"
67 #include "nsIPrincipal.h"
68 #include "nsJSPrincipals.h"
69 #include "nsContentPolicyUtils.h"
70 #include "nsIClassifiedChannel.h"
71 #include "nsIHttpChannel.h"
72 #include "nsIHttpChannelInternal.h"
73 #include "nsIClassOfService.h"
74 #include "nsICacheInfoChannel.h"
75 #include "nsITimedChannel.h"
76 #include "nsIScriptElement.h"
77 #include "nsISupportsPriority.h"
78 #include "nsIDocShell.h"
79 #include "nsContentUtils.h"
80 #include "nsUnicharUtils.h"
81 #include "nsError.h"
82 #include "nsThreadUtils.h"
83 #include "nsDocShellCID.h"
84 #include "nsIContentSecurityPolicy.h"
85 #include "mozilla/Logging.h"
86 #include "nsCRT.h"
87 #include "nsContentCreatorFunctions.h"
88 #include "nsProxyRelease.h"
89 #include "nsSandboxFlags.h"
90 #include "nsContentTypeParser.h"
91 #include "nsINetworkPredictor.h"
92 #include "nsMimeTypes.h"
93 #include "mozilla/ConsoleReportCollector.h"
94 #include "mozilla/CycleCollectedJSContext.h"
95 #include "mozilla/EventQueue.h"
96 #include "mozilla/LoadInfo.h"
97 #include "ReferrerInfo.h"
99 #include "mozilla/AsyncEventDispatcher.h"
100 #include "mozilla/Attributes.h"
101 #include "mozilla/ScopeExit.h"
102 #include "mozilla/TaskController.h"
103 #include "mozilla/Telemetry.h"
104 #include "mozilla/TimeStamp.h"
105 #include "mozilla/UniquePtr.h"
106 #include "mozilla/Unused.h"
107 #include "mozilla/Utf8.h" // mozilla::Utf8Unit
108 #include "nsIScriptError.h"
109 #include "nsIAsyncOutputStream.h"
110 #include "js/loader/ModuleLoaderBase.h"
111 #include "mozilla/Maybe.h"
113 using JS::SourceText;
114 using namespace JS::loader;
116 using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT;
118 namespace mozilla::dom {
120 LazyLogModule ScriptLoader::gCspPRLog("CSP");
121 LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader");
123 #undef LOG
124 #define LOG(args) \
125 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
127 #define LOG_ENABLED() \
128 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
130 // Alternate Data MIME type used by the ScriptLoader to register that we want to
131 // store bytecode without reading it.
132 static constexpr auto kNullMimeType = "javascript/null"_ns;
134 /////////////////////////////////////////////////////////////
135 // AsyncCompileShutdownObserver
136 /////////////////////////////////////////////////////////////
138 NS_IMPL_ISUPPORTS(AsyncCompileShutdownObserver, nsIObserver)
140 void AsyncCompileShutdownObserver::OnShutdown() {
141 if (mScriptLoader) {
142 mScriptLoader->Destroy();
143 MOZ_ASSERT(!mScriptLoader);
147 void AsyncCompileShutdownObserver::Unregister() {
148 if (mScriptLoader) {
149 mScriptLoader = nullptr;
150 nsContentUtils::UnregisterShutdownObserver(this);
154 NS_IMETHODIMP
155 AsyncCompileShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
156 const char16_t* aData) {
157 OnShutdown();
158 return NS_OK;
161 //////////////////////////////////////////////////////////////
162 // ScriptLoader::PreloadInfo
163 //////////////////////////////////////////////////////////////
165 inline void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField) {
166 ImplCycleCollectionUnlink(aField.mRequest);
169 inline void ImplCycleCollectionTraverse(
170 nsCycleCollectionTraversalCallback& aCallback,
171 ScriptLoader::PreloadInfo& aField, const char* aName, uint32_t aFlags = 0) {
172 ImplCycleCollectionTraverse(aCallback, aField.mRequest, aName, aFlags);
175 //////////////////////////////////////////////////////////////
176 // ScriptLoader
177 //////////////////////////////////////////////////////////////
179 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoader)
180 NS_INTERFACE_MAP_END
182 NS_IMPL_CYCLE_COLLECTION(ScriptLoader, mNonAsyncExternalScriptInsertedRequests,
183 mLoadingAsyncRequests, mLoadedAsyncRequests,
184 mOffThreadCompilingRequests, mDeferRequests,
185 mXSLTRequests, mParserBlockingRequest,
186 mBytecodeEncodingQueue, mPreloads,
187 mPendingChildLoaders, mModuleLoader,
188 mWebExtModuleLoaders, mShadowRealmModuleLoaders)
190 NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader)
191 NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader)
193 ScriptLoader::ScriptLoader(Document* aDocument)
194 : mDocument(aDocument),
195 mParserBlockingBlockerCount(0),
196 mBlockerCount(0),
197 mNumberOfProcessors(0),
198 mTotalFullParseSize(0),
199 mPhysicalSizeOfMemory(-1),
200 mEnabled(true),
201 mDeferEnabled(false),
202 mSpeculativeOMTParsingEnabled(false),
203 mDeferCheckpointReached(false),
204 mBlockingDOMContentLoaded(false),
205 mLoadEventFired(false),
206 mGiveUpEncoding(false),
207 mReporter(new ConsoleReportCollector()) {
208 LOG(("ScriptLoader::ScriptLoader %p", this));
210 mSpeculativeOMTParsingEnabled = StaticPrefs::
211 dom_script_loader_external_scripts_speculative_omt_parse_enabled();
213 mShutdownObserver = new AsyncCompileShutdownObserver(this);
214 nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
217 ScriptLoader::~ScriptLoader() {
218 LOG(("ScriptLoader::~ScriptLoader %p", this));
220 mObservers.Clear();
222 if (mParserBlockingRequest) {
223 FireScriptAvailable(NS_ERROR_ABORT, mParserBlockingRequest);
226 for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
227 req = req->getNext()) {
228 FireScriptAvailable(NS_ERROR_ABORT, req);
231 for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req;
232 req = req->getNext()) {
233 FireScriptAvailable(NS_ERROR_ABORT, req);
236 for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
237 req = req->getNext()) {
238 FireScriptAvailable(NS_ERROR_ABORT, req);
241 for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
242 req = req->getNext()) {
243 FireScriptAvailable(NS_ERROR_ABORT, req);
246 for (ScriptLoadRequest* req =
247 mNonAsyncExternalScriptInsertedRequests.getFirst();
248 req; req = req->getNext()) {
249 FireScriptAvailable(NS_ERROR_ABORT, req);
252 // Unblock the kids, in case any of them moved to a different document
253 // subtree in the meantime and therefore aren't actually going away.
254 for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) {
255 mPendingChildLoaders[j]->RemoveParserBlockingScriptExecutionBlocker();
258 for (size_t i = 0; i < mPreloads.Length(); i++) {
259 AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::NotUsed);
262 if (mShutdownObserver) {
263 mShutdownObserver->Unregister();
264 mShutdownObserver = nullptr;
267 mModuleLoader = nullptr;
270 void ScriptLoader::SetGlobalObject(nsIGlobalObject* aGlobalObject) {
271 if (!aGlobalObject) {
272 // The document is being detached.
273 CancelAndClearScriptLoadRequests();
274 return;
277 MOZ_ASSERT(!HasPendingRequests());
279 if (!mModuleLoader) {
280 // The module loader is associated with a global object, so don't create it
281 // until we have a global set.
282 mModuleLoader = new ModuleLoader(this, aGlobalObject, ModuleLoader::Normal);
285 MOZ_ASSERT(mModuleLoader->GetGlobalObject() == aGlobalObject);
286 MOZ_ASSERT(aGlobalObject->GetModuleLoader(dom::danger::GetJSContext()) ==
287 mModuleLoader);
290 void ScriptLoader::RegisterContentScriptModuleLoader(ModuleLoader* aLoader) {
291 MOZ_ASSERT(aLoader);
292 MOZ_ASSERT(aLoader->GetScriptLoader() == this);
294 mWebExtModuleLoaders.AppendElement(aLoader);
297 void ScriptLoader::RegisterShadowRealmModuleLoader(ModuleLoader* aLoader) {
298 MOZ_ASSERT(aLoader);
299 MOZ_ASSERT(aLoader->GetScriptLoader() == this);
301 mShadowRealmModuleLoaders.AppendElement(aLoader);
304 // Collect telemtry data about the cache information, and the kind of source
305 // which are being loaded, and where it is being loaded from.
306 static void CollectScriptTelemetry(ScriptLoadRequest* aRequest) {
307 using namespace mozilla::Telemetry;
309 MOZ_ASSERT(aRequest->IsFetching());
311 // Skip this function if we are not running telemetry.
312 if (!CanRecordExtended()) {
313 return;
316 // Report the script kind.
317 if (aRequest->IsModuleRequest()) {
318 AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ModuleScript);
319 } else {
320 AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ClassicScript);
323 // Report the type of source. This is used to monitor the status of the
324 // JavaScript Start-up Bytecode Cache, with the expectation of an almost zero
325 // source-fallback and alternate-data being roughtly equal to source loads.
326 if (aRequest->mFetchSourceOnly) {
327 if (aRequest->GetScriptLoadContext()->mIsInline) {
328 AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Inline);
329 } else if (aRequest->IsTextSource()) {
330 AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::SourceFallback);
332 } else {
333 if (aRequest->IsTextSource()) {
334 AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Source);
335 } else if (aRequest->IsBytecode()) {
336 AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::AltData);
341 // Helper method for checking if the script element is an event-handler
342 // This means that it has both a for-attribute and a event-attribute.
343 // Also, if the for-attribute has a value that matches "\s*window\s*",
344 // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
345 // eventhandler. (both matches are case insensitive).
346 // This is how IE seems to filter out a window's onload handler from a
347 // <script for=... event=...> element.
349 static bool IsScriptEventHandler(ScriptKind kind, nsIContent* aScriptElement) {
350 if (kind != ScriptKind::eClassic) {
351 return false;
354 if (!aScriptElement->IsHTMLElement()) {
355 return false;
358 nsAutoString forAttr, eventAttr;
359 if (!aScriptElement->AsElement()->GetAttr(nsGkAtoms::_for, forAttr) ||
360 !aScriptElement->AsElement()->GetAttr(nsGkAtoms::event, eventAttr)) {
361 return false;
364 const nsAString& for_str =
365 nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(forAttr);
366 if (!for_str.LowerCaseEqualsLiteral("window")) {
367 return true;
370 // We found for="window", now check for event="onload".
371 const nsAString& event_str =
372 nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(eventAttr, false);
373 if (!StringBeginsWith(event_str, u"onload"_ns,
374 nsCaseInsensitiveStringComparator)) {
375 // It ain't "onload.*".
377 return true;
380 nsAutoString::const_iterator start, end;
381 event_str.BeginReading(start);
382 event_str.EndReading(end);
384 start.advance(6); // advance past "onload"
386 if (start != end && *start != '(' && *start != ' ') {
387 // We got onload followed by something other than space or
388 // '('. Not good enough.
390 return true;
393 return false;
396 nsContentPolicyType ScriptLoadRequestToContentPolicyType(
397 ScriptLoadRequest* aRequest) {
398 if (aRequest->GetScriptLoadContext()->IsPreload()) {
399 return aRequest->IsModuleRequest()
400 ? nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD
401 : nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD;
404 return aRequest->IsModuleRequest() ? nsIContentPolicy::TYPE_INTERNAL_MODULE
405 : nsIContentPolicy::TYPE_INTERNAL_SCRIPT;
408 nsresult ScriptLoader::CheckContentPolicy(Document* aDocument,
409 nsIScriptElement* aElement,
410 const nsAString& aType,
411 const nsAString& aNonce,
412 ScriptLoadRequest* aRequest) {
413 nsContentPolicyType contentPolicyType =
414 ScriptLoadRequestToContentPolicyType(aRequest);
416 nsCOMPtr<nsINode> requestingNode = do_QueryInterface(aElement);
417 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
418 aDocument->NodePrincipal(), // loading principal
419 aDocument->NodePrincipal(), // triggering principal
420 requestingNode, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
421 contentPolicyType);
422 secCheckLoadInfo->SetParserCreatedScript(aElement->GetParserCreated() !=
423 mozilla::dom::NOT_FROM_PARSER);
424 // Use nonce of the current element, instead of the preload, because those
425 // are allowed to differ.
426 secCheckLoadInfo->SetCspNonce(aNonce);
427 if (aRequest->mIntegrity.IsValid()) {
428 MOZ_ASSERT(!aRequest->mIntegrity.IsEmpty());
429 secCheckLoadInfo->SetIntegrityMetadata(
430 aRequest->mIntegrity.GetIntegrityString());
433 int16_t shouldLoad = nsIContentPolicy::ACCEPT;
434 nsresult rv = NS_CheckContentLoadPolicy(
435 aRequest->mURI, secCheckLoadInfo, NS_LossyConvertUTF16toASCII(aType),
436 &shouldLoad, nsContentUtils::GetContentPolicy());
437 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
438 if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
439 return NS_ERROR_CONTENT_BLOCKED;
441 return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
444 return NS_OK;
447 /* static */
448 bool ScriptLoader::IsAboutPageLoadingChromeURI(ScriptLoadRequest* aRequest,
449 Document* aDocument) {
450 // if the uri to be loaded is not of scheme chrome:, there is nothing to do.
451 if (!aRequest->mURI->SchemeIs("chrome")) {
452 return false;
455 // we can either get here with a regular contentPrincipal or with a
456 // NullPrincipal in case we are showing an error page in a sandboxed iframe.
457 // In either case if the about: page is linkable from content, there is
458 // nothing to do.
459 uint32_t aboutModuleFlags = 0;
460 nsresult rv = NS_OK;
462 nsCOMPtr<nsIPrincipal> triggeringPrincipal = aRequest->TriggeringPrincipal();
463 if (triggeringPrincipal->GetIsContentPrincipal()) {
464 if (!triggeringPrincipal->SchemeIs("about")) {
465 return false;
467 rv = triggeringPrincipal->GetAboutModuleFlags(&aboutModuleFlags);
468 NS_ENSURE_SUCCESS(rv, false);
469 } else if (triggeringPrincipal->GetIsNullPrincipal()) {
470 nsCOMPtr<nsIURI> docURI = aDocument->GetDocumentURI();
471 if (!docURI->SchemeIs("about")) {
472 return false;
475 nsCOMPtr<nsIAboutModule> aboutModule;
476 rv = NS_GetAboutModule(docURI, getter_AddRefs(aboutModule));
477 if (NS_FAILED(rv) || !aboutModule) {
478 return false;
480 rv = aboutModule->GetURIFlags(docURI, &aboutModuleFlags);
481 NS_ENSURE_SUCCESS(rv, false);
482 } else {
483 return false;
486 if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) {
487 return false;
490 // seems like an about page wants to load a chrome URI.
491 return true;
494 nsIURI* ScriptLoader::GetBaseURI() const {
495 MOZ_ASSERT(mDocument);
496 return mDocument->GetDocBaseURI();
499 class ScriptRequestProcessor : public Runnable {
500 private:
501 RefPtr<ScriptLoader> mLoader;
502 RefPtr<ScriptLoadRequest> mRequest;
504 public:
505 ScriptRequestProcessor(ScriptLoader* aLoader, ScriptLoadRequest* aRequest)
506 : Runnable("dom::ScriptRequestProcessor"),
507 mLoader(aLoader),
508 mRequest(aRequest) {}
509 NS_IMETHOD Run() override { return mLoader->ProcessRequest(mRequest); }
512 void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) {
513 auto* runnable = new ScriptRequestProcessor(this, aRequest);
514 nsContentUtils::AddScriptRunner(runnable);
517 nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) {
518 MOZ_ASSERT(aRequest->IsBytecode());
519 aRequest->mScriptBytecode.clearAndFree();
520 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
521 "scriptloader_fallback");
523 // Notify preload restart so that we can register this preload request again.
524 aRequest->GetScriptLoadContext()->NotifyRestart(mDocument);
526 // Start a new channel from which we explicitly request to stream the source
527 // instead of the bytecode.
528 aRequest->mFetchSourceOnly = true;
529 nsresult rv;
530 if (aRequest->IsModuleRequest()) {
531 rv = aRequest->AsModuleRequest()->RestartModuleLoad();
532 } else {
533 rv = StartLoad(aRequest, Nothing());
535 if (NS_FAILED(rv)) {
536 return rv;
539 // Close the current channel and this ScriptLoadHandler as we created a new
540 // one for the same request.
541 return NS_BINDING_RETARGETED;
544 nsresult ScriptLoader::StartLoad(
545 ScriptLoadRequest* aRequest,
546 const Maybe<nsAutoString>& aCharsetForPreload) {
547 if (aRequest->IsModuleRequest()) {
548 return aRequest->AsModuleRequest()->StartModuleLoad();
551 return StartClassicLoad(aRequest, aCharsetForPreload);
554 nsresult ScriptLoader::StartClassicLoad(
555 ScriptLoadRequest* aRequest,
556 const Maybe<nsAutoString>& aCharsetForPreload) {
557 MOZ_ASSERT(aRequest->IsFetching());
558 NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
559 aRequest->SetUnknownDataType();
561 // If this document is sandboxed without 'allow-scripts', abort.
562 if (mDocument->HasScriptsBlockedBySandbox()) {
563 return NS_OK;
566 if (LOG_ENABLED()) {
567 nsAutoCString url;
568 aRequest->mURI->GetAsciiSpec(url);
569 LOG(("ScriptLoadRequest (%p): Start Classic Load (url = %s)", aRequest,
570 url.get()));
573 nsSecurityFlags securityFlags =
574 nsContentSecurityManager::ComputeSecurityFlags(
575 aRequest->CORSMode(), nsContentSecurityManager::CORSSecurityMapping::
576 CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS);
578 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
580 nsresult rv = StartLoadInternal(aRequest, securityFlags, aCharsetForPreload);
582 NS_ENSURE_SUCCESS(rv, rv);
584 return NS_OK;
587 static bool IsWebExtensionRequest(ScriptLoadRequest* aRequest) {
588 if (!aRequest->IsModuleRequest()) {
589 return false;
592 ModuleLoader* loader =
593 ModuleLoader::From(aRequest->AsModuleRequest()->mLoader);
594 return loader->GetKind() == ModuleLoader::WebExtension;
597 static nsresult CreateChannelForScriptLoading(nsIChannel** aOutChannel,
598 Document* aDocument,
599 ScriptLoadRequest* aRequest,
600 nsSecurityFlags aSecurityFlags) {
601 nsContentPolicyType contentPolicyType =
602 ScriptLoadRequestToContentPolicyType(aRequest);
603 nsCOMPtr<nsINode> context;
604 if (aRequest->GetScriptLoadContext()->GetScriptElement()) {
605 context =
606 do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement());
607 } else {
608 context = aDocument;
611 nsCOMPtr<nsILoadGroup> loadGroup = aDocument->GetDocumentLoadGroup();
612 nsCOMPtr<nsPIDOMWindowOuter> window = aDocument->GetWindow();
613 NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER);
614 nsIDocShell* docshell = window->GetDocShell();
615 nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
617 return NS_NewChannelWithTriggeringPrincipal(
618 aOutChannel, aRequest->mURI, context, aRequest->TriggeringPrincipal(),
619 aSecurityFlags, contentPolicyType,
620 nullptr, // aPerformanceStorage
621 loadGroup, prompter);
624 static void PrepareLoadInfoForScriptLoading(nsIChannel* aChannel,
625 const ScriptLoadRequest* aRequest) {
626 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
627 loadInfo->SetParserCreatedScript(aRequest->ParserMetadata() ==
628 ParserMetadata::ParserInserted);
629 loadInfo->SetCspNonce(aRequest->Nonce());
630 if (aRequest->mIntegrity.IsValid()) {
631 MOZ_ASSERT(!aRequest->mIntegrity.IsEmpty());
632 loadInfo->SetIntegrityMetadata(aRequest->mIntegrity.GetIntegrityString());
636 // static
637 void ScriptLoader::PrepareCacheInfoChannel(nsIChannel* aChannel,
638 ScriptLoadRequest* aRequest) {
639 // To avoid decoding issues, the build-id is part of the bytecode MIME type
640 // constant.
641 aRequest->mCacheInfo = nullptr;
642 nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(aChannel));
643 if (cic && StaticPrefs::dom_script_loader_bytecode_cache_enabled()) {
644 MOZ_ASSERT(!IsWebExtensionRequest(aRequest),
645 "Can not bytecode cache WebExt code");
646 if (!aRequest->mFetchSourceOnly) {
647 // Inform the HTTP cache that we prefer to have information coming from
648 // the bytecode cache instead of the sources, if such entry is already
649 // registered.
650 LOG(("ScriptLoadRequest (%p): Maybe request bytecode", aRequest));
651 cic->PreferAlternativeDataType(
652 ScriptLoader::BytecodeMimeTypeFor(aRequest), ""_ns,
653 nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC);
654 } else {
655 // If we are explicitly loading from the sources, such as after a
656 // restarted request, we might still want to save the bytecode after.
658 // The following tell the cache to look for an alternative data type which
659 // does not exist, such that we can later save the bytecode with a
660 // different alternative data type.
661 LOG(("ScriptLoadRequest (%p): Request saving bytecode later", aRequest));
662 cic->PreferAlternativeDataType(
663 kNullMimeType, ""_ns,
664 nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC);
669 static void AdjustPriorityForNonLinkPreloadScripts(
670 nsIChannel* aChannel, ScriptLoadRequest* aRequest) {
671 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->IsLinkPreloadScript());
673 if (!StaticPrefs::network_fetchpriority_enabled()) {
674 return;
677 if (nsCOMPtr<nsISupportsPriority> supportsPriority =
678 do_QueryInterface(aChannel)) {
679 const RequestPriority fetchPriority = aRequest->FetchPriority();
680 // The spec defines the priority to be set in an implementation defined
681 // manner (<https://fetch.spec.whatwg.org/#concept-fetch>, step 15 and
682 // <https://html.spec.whatwg.org/#concept-script-fetch-options-fetch-priority>).
683 // For web-compatibility, the fetch priority mapping from
684 // <https://web.dev/fetch-priority/#browser-priority-and-fetchpriority> is
685 // taken.
686 switch (fetchPriority) {
687 case RequestPriority::Auto:
688 LOG(("ScriptLoader::%s:, fetchpriority=auto", __FUNCTION__));
689 break;
690 case RequestPriority::Low: {
691 LOG(("ScriptLoader::%s:, fetchpriority=low, setting priority",
692 __FUNCTION__));
693 supportsPriority->SetPriority(nsISupportsPriority::PRIORITY_LOW);
694 break;
696 case RequestPriority::High: {
697 LOG(("ScriptLoader::%s:, fetchpriority=high, setting priority",
698 __FUNCTION__));
699 supportsPriority->SetPriority(nsISupportsPriority::PRIORITY_HIGH);
700 break;
702 default: {
703 MOZ_ASSERT_UNREACHABLE();
704 break;
710 // static
711 void ScriptLoader::PrepareRequestPriorityAndRequestDependencies(
712 nsIChannel* aChannel, ScriptLoadRequest* aRequest) {
713 if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) {
714 // This is <link rel="preload" as="script"> or <link rel="modulepreload">
715 // initiated speculative load, put it to the group that is not blocked by
716 // leaders and doesn't block follower at the same time. Giving it a much
717 // higher priority will make this request be processed ahead of other
718 // Unblocked requests, but with the same weight as Leaders. This will make
719 // us behave similar way for both http2 and http1.
720 ScriptLoadContext::PrioritizeAsPreload(aChannel);
721 ScriptLoadContext::AddLoadBackgroundFlag(aChannel);
722 } else if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(aChannel)) {
723 AdjustPriorityForNonLinkPreloadScripts(aChannel, aRequest);
725 if (aRequest->GetScriptLoadContext()->mScriptFromHead &&
726 aRequest->GetScriptLoadContext()->IsBlockingScript()) {
727 // synchronous head scripts block loading of most other non js/css
728 // content such as images, Leader implicitely disallows tailing
729 cos->AddClassFlags(nsIClassOfService::Leader);
730 } else if (aRequest->GetScriptLoadContext()->IsDeferredScript() &&
731 !StaticPrefs::network_http_tailing_enabled()) {
732 // Bug 1395525 and the !StaticPrefs::network_http_tailing_enabled() bit:
733 // We want to make sure that turing tailing off by the pref makes the
734 // browser behave exactly the same way as before landing the tailing
735 // patch.
737 // head/body deferred scripts are blocked by leaders but are not
738 // allowed tailing because they block DOMContentLoaded
739 cos->AddClassFlags(nsIClassOfService::TailForbidden);
740 } else {
741 // other scripts (=body sync or head/body async) are neither blocked
742 // nor prioritized
743 cos->AddClassFlags(nsIClassOfService::Unblocked);
745 if (aRequest->GetScriptLoadContext()->IsAsyncScript()) {
746 // async scripts are allowed tailing, since those and only those
747 // don't block DOMContentLoaded; this flag doesn't enforce tailing,
748 // just overweights the Unblocked flag when the channel is found
749 // to be a thrird-party tracker and thus set the Tail flag to engage
750 // tailing.
751 cos->AddClassFlags(nsIClassOfService::TailAllowed);
757 // static
758 nsresult ScriptLoader::PrepareHttpRequestAndInitiatorType(
759 nsIChannel* aChannel, ScriptLoadRequest* aRequest,
760 const Maybe<nsAutoString>& aCharsetForPreload) {
761 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
762 nsresult rv = NS_OK;
764 if (httpChannel) {
765 // HTTP content negotation has little value in this context.
766 nsAutoCString acceptTypes("*/*");
767 rv = httpChannel->SetRequestHeader("Accept"_ns, acceptTypes, false);
768 MOZ_ASSERT(NS_SUCCEEDED(rv));
770 nsCOMPtr<nsIReferrerInfo> referrerInfo =
771 new ReferrerInfo(aRequest->mReferrer, aRequest->ReferrerPolicy());
772 rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
773 MOZ_ASSERT(NS_SUCCEEDED(rv));
775 nsCOMPtr<nsIHttpChannelInternal> internalChannel(
776 do_QueryInterface(httpChannel));
777 if (internalChannel) {
778 rv = internalChannel->SetIntegrityMetadata(
779 aRequest->mIntegrity.GetIntegrityString());
780 MOZ_ASSERT(NS_SUCCEEDED(rv));
783 nsAutoString hintCharset;
784 if (!aRequest->GetScriptLoadContext()->IsPreload() &&
785 aRequest->GetScriptLoadContext()->GetScriptElement()) {
786 aRequest->GetScriptLoadContext()->GetScriptElement()->GetScriptCharset(
787 hintCharset);
788 } else if (aCharsetForPreload.isSome()) {
789 hintCharset = aCharsetForPreload.ref();
792 rv = httpChannel->SetClassicScriptHintCharset(hintCharset);
793 NS_ENSURE_SUCCESS(rv, rv);
796 // Set the initiator type
797 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
798 if (timedChannel) {
799 if (aRequest->mEarlyHintPreloaderId) {
800 timedChannel->SetInitiatorType(u"early-hints"_ns);
801 } else if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) {
802 timedChannel->SetInitiatorType(u"link"_ns);
803 } else {
804 timedChannel->SetInitiatorType(u"script"_ns);
808 return rv;
811 nsresult ScriptLoader::PrepareIncrementalStreamLoader(
812 nsIIncrementalStreamLoader** aOutLoader, ScriptLoadRequest* aRequest) {
813 UniquePtr<mozilla::dom::SRICheckDataVerifier> sriDataVerifier;
814 if (!aRequest->mIntegrity.IsEmpty()) {
815 nsAutoCString sourceUri;
816 if (mDocument->GetDocumentURI()) {
817 mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
819 sriDataVerifier = MakeUnique<SRICheckDataVerifier>(aRequest->mIntegrity,
820 sourceUri, mReporter);
823 RefPtr<ScriptLoadHandler> handler =
824 new ScriptLoadHandler(this, aRequest, std::move(sriDataVerifier));
826 nsresult rv = NS_NewIncrementalStreamLoader(aOutLoader, handler);
827 NS_ENSURE_SUCCESS(rv, rv);
828 return rv;
831 nsresult ScriptLoader::StartLoadInternal(
832 ScriptLoadRequest* aRequest, nsSecurityFlags securityFlags,
833 const Maybe<nsAutoString>& aCharsetForPreload) {
834 nsCOMPtr<nsIChannel> channel;
835 nsresult rv = CreateChannelForScriptLoading(
836 getter_AddRefs(channel), mDocument, aRequest, securityFlags);
838 NS_ENSURE_SUCCESS(rv, rv);
840 if (aRequest->mEarlyHintPreloaderId) {
841 nsCOMPtr<nsIHttpChannelInternal> channelInternal =
842 do_QueryInterface(channel);
843 NS_ENSURE_TRUE(channelInternal != nullptr, NS_ERROR_FAILURE);
845 rv = channelInternal->SetEarlyHintPreloaderId(
846 aRequest->mEarlyHintPreloaderId);
847 NS_ENSURE_SUCCESS(rv, rv);
850 PrepareLoadInfoForScriptLoading(channel, aRequest);
852 nsCOMPtr<nsIScriptGlobalObject> scriptGlobal = GetScriptGlobalObject();
853 if (!scriptGlobal) {
854 return NS_ERROR_FAILURE;
857 ScriptLoader::PrepareCacheInfoChannel(channel, aRequest);
859 LOG(("ScriptLoadRequest (%p): mode=%u tracking=%d", aRequest,
860 unsigned(aRequest->GetScriptLoadContext()->mScriptMode),
861 aRequest->GetScriptLoadContext()->IsTracking()));
863 PrepareRequestPriorityAndRequestDependencies(channel, aRequest);
865 rv =
866 PrepareHttpRequestAndInitiatorType(channel, aRequest, aCharsetForPreload);
867 NS_ENSURE_SUCCESS(rv, rv);
869 mozilla::net::PredictorLearn(
870 aRequest->mURI, mDocument->GetDocumentURI(),
871 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
872 mDocument->NodePrincipal()->OriginAttributesRef());
874 nsCOMPtr<nsIIncrementalStreamLoader> loader;
875 rv = PrepareIncrementalStreamLoader(getter_AddRefs(loader), aRequest);
876 NS_ENSURE_SUCCESS(rv, rv);
878 auto key = PreloadHashKey::CreateAsScript(
879 aRequest->mURI, aRequest->CORSMode(), aRequest->mKind);
880 aRequest->GetScriptLoadContext()->NotifyOpen(
881 key, channel, mDocument,
882 aRequest->GetScriptLoadContext()->IsLinkPreloadScript(),
883 aRequest->IsModuleRequest());
885 rv = channel->AsyncOpen(loader);
887 if (NS_FAILED(rv)) {
888 // Make sure to inform any <link preload> tags about failure to load the
889 // resource.
890 aRequest->GetScriptLoadContext()->NotifyStart(channel);
891 aRequest->GetScriptLoadContext()->NotifyStop(rv);
894 NS_ENSURE_SUCCESS(rv, rv);
896 return NS_OK;
899 bool ScriptLoader::PreloadURIComparator::Equals(const PreloadInfo& aPi,
900 nsIURI* const& aURI) const {
901 bool same;
902 return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) && same;
905 static bool CSPAllowsInlineScript(nsIScriptElement* aElement,
906 const nsAString& aNonce,
907 Document* aDocument) {
908 nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
909 if (!csp) {
910 // no CSP --> allow
911 return true;
914 bool parserCreated =
915 aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER;
916 nsCOMPtr<Element> element = do_QueryInterface(aElement);
918 bool allowInlineScript = false;
919 nsresult rv = csp->GetAllowsInline(
920 nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE,
921 false /* aHasUnsafeHash */, aNonce, parserCreated, element,
922 nullptr /* nsICSPEventListener */, u""_ns,
923 aElement->GetScriptLineNumber(), aElement->GetScriptColumnNumber(),
924 &allowInlineScript);
925 return NS_SUCCEEDED(rv) && allowInlineScript;
928 namespace {
929 RequestPriority FetchPriorityToRequestPriority(
930 const FetchPriority aFetchPriority) {
931 switch (aFetchPriority) {
932 case FetchPriority::High:
933 return RequestPriority::High;
934 case FetchPriority::Low:
935 return RequestPriority::Low;
936 case FetchPriority::Auto:
937 return RequestPriority::Auto;
940 MOZ_ASSERT_UNREACHABLE();
941 return RequestPriority::Auto;
943 } // namespace
945 already_AddRefed<ScriptLoadRequest> ScriptLoader::CreateLoadRequest(
946 ScriptKind aKind, nsIURI* aURI, nsIScriptElement* aElement,
947 nsIPrincipal* aTriggeringPrincipal, CORSMode aCORSMode,
948 const nsAString& aNonce, RequestPriority aRequestPriority,
949 const SRIMetadata& aIntegrity, ReferrerPolicy aReferrerPolicy,
950 ParserMetadata aParserMetadata) {
951 nsIURI* referrer = mDocument->GetDocumentURIAsReferrer();
952 nsCOMPtr<Element> domElement = do_QueryInterface(aElement);
953 RefPtr<ScriptFetchOptions> fetchOptions =
954 new ScriptFetchOptions(aCORSMode, aNonce, aRequestPriority,
955 aParserMetadata, aTriggeringPrincipal, domElement);
956 RefPtr<ScriptLoadContext> context = new ScriptLoadContext();
958 if (aKind == ScriptKind::eClassic || aKind == ScriptKind::eImportMap) {
959 RefPtr<ScriptLoadRequest> aRequest =
960 new ScriptLoadRequest(aKind, aURI, aReferrerPolicy, fetchOptions,
961 aIntegrity, referrer, context);
963 return aRequest.forget();
966 MOZ_ASSERT(aKind == ScriptKind::eModule);
967 RefPtr<ModuleLoadRequest> aRequest = ModuleLoader::CreateTopLevel(
968 aURI, aReferrerPolicy, fetchOptions, aIntegrity, referrer, this, context);
969 return aRequest.forget();
972 bool ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement,
973 const nsAutoString& aTypeAttr) {
974 // We need a document to evaluate scripts.
975 NS_ENSURE_TRUE(mDocument, false);
977 // Check to see if scripts has been turned off.
978 if (!mEnabled || !mDocument->IsScriptEnabled()) {
979 return false;
982 NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script");
984 nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
986 ScriptKind scriptKind;
987 if (aElement->GetScriptIsModule()) {
988 scriptKind = ScriptKind::eModule;
989 } else if (aElement->GetScriptIsImportMap()) {
990 scriptKind = ScriptKind::eImportMap;
991 } else {
992 scriptKind = ScriptKind::eClassic;
995 // Step 13. Check that the script is not an eventhandler
996 if (IsScriptEventHandler(scriptKind, scriptContent)) {
997 return false;
1000 // "In modern user agents that support module scripts, the script element with
1001 // the nomodule attribute will be ignored".
1002 // "The nomodule attribute must not be specified on module scripts (and will
1003 // be ignored if it is)."
1004 if (scriptKind == ScriptKind::eClassic && scriptContent->IsHTMLElement() &&
1005 scriptContent->AsElement()->HasAttr(nsGkAtoms::nomodule)) {
1006 return false;
1009 // Step 15. and later in the HTML5 spec
1010 if (aElement->GetScriptExternal()) {
1011 return ProcessExternalScript(aElement, scriptKind, aTypeAttr,
1012 scriptContent);
1015 return ProcessInlineScript(aElement, scriptKind);
1018 static ParserMetadata GetParserMetadata(nsIScriptElement* aElement) {
1019 return aElement->GetParserCreated() == mozilla::dom::NOT_FROM_PARSER
1020 ? ParserMetadata::NotParserInserted
1021 : ParserMetadata::ParserInserted;
1024 bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
1025 ScriptKind aScriptKind,
1026 const nsAutoString& aTypeAttr,
1027 nsIContent* aScriptContent) {
1028 LOG(("ScriptLoader (%p): Process external script for element %p", this,
1029 aElement));
1031 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
1032 // Step 30.1. If el's type is "importmap", then queue an element task on the
1033 // DOM manipulation task source given el to fire an event named error at el,
1034 // and return.
1035 if (aScriptKind == ScriptKind::eImportMap) {
1036 NS_DispatchToCurrentThread(
1037 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
1038 &nsIScriptElement::FireErrorEvent));
1039 nsContentUtils::ReportToConsole(
1040 nsIScriptError::warningFlag, "Script Loader"_ns, mDocument,
1041 nsContentUtils::eDOM_PROPERTIES, "ImportMapExternalNotSupported");
1042 return false;
1045 nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
1046 if (!scriptURI) {
1047 // Asynchronously report the failure to create a URI object
1048 NS_DispatchToCurrentThread(
1049 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
1050 &nsIScriptElement::FireErrorEvent));
1051 return false;
1054 nsAutoString nonce;
1055 if (nsString* cspNonce = static_cast<nsString*>(
1056 aScriptContent->GetProperty(nsGkAtoms::nonce))) {
1057 nonce = *cspNonce;
1060 SRIMetadata sriMetadata;
1062 nsAutoString integrity;
1063 aScriptContent->AsElement()->GetAttr(nsGkAtoms::integrity, integrity);
1064 GetSRIMetadata(integrity, &sriMetadata);
1067 RefPtr<ScriptLoadRequest> request =
1068 LookupPreloadRequest(aElement, aScriptKind, sriMetadata);
1070 if (request && NS_FAILED(CheckContentPolicy(mDocument, aElement, aTypeAttr,
1071 nonce, request))) {
1072 LOG(("ScriptLoader (%p): content policy check failed for preload", this));
1074 // Probably plans have changed; even though the preload was allowed seems
1075 // like the actual load is not; let's cancel the preload request.
1076 request->Cancel();
1077 AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RejectedByPolicy);
1078 return false;
1081 // If there are a preloaded request and an import map, we won't use the
1082 // preloaded request and will try to create a new one for this, because the
1083 // import map isn't preloaded, and the preloaded request may have used the
1084 // wrong module specifiers.
1086 // We use IsModuleFetched() to check if the module has been fetched, if it
1087 // hasn't been fetched we can simply just reuse it.
1088 if (request && mModuleLoader->IsModuleFetched(request->mURI) &&
1089 mModuleLoader->HasImportMapRegistered()) {
1090 DebugOnly<bool> removed = mModuleLoader->RemoveFetchedModule(request->mURI);
1091 MOZ_ASSERT(removed);
1092 request = nullptr;
1095 if (request) {
1096 // Use the preload request.
1098 LOG(("ScriptLoadRequest (%p): Using preload request", request.get()));
1100 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-script-tree
1101 // Step 1. Disallow further import maps given settings object.
1102 if (request->IsModuleRequest()) {
1103 LOG(("ScriptLoadRequest (%p): Disallow further import maps.",
1104 request.get()));
1105 mModuleLoader->DisallowImportMaps();
1108 // It's possible these attributes changed since we started the preload so
1109 // update them here.
1110 request->GetScriptLoadContext()->SetScriptMode(
1111 aElement->GetScriptDeferred(), aElement->GetScriptAsync(), false);
1113 // The request will be added to another list or set as
1114 // mParserBlockingRequest below.
1115 if (request->GetScriptLoadContext()->mInCompilingList) {
1116 mOffThreadCompilingRequests.Remove(request);
1117 request->GetScriptLoadContext()->mInCompilingList = false;
1120 AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::Used);
1121 } else {
1122 // No usable preload found.
1124 nsCOMPtr<nsIPrincipal> principal =
1125 aElement->GetScriptURITriggeringPrincipal();
1126 if (!principal) {
1127 principal = aScriptContent->NodePrincipal();
1130 CORSMode ourCORSMode = aElement->GetCORSMode();
1131 const FetchPriority fetchPriority = aElement->GetFetchPriority();
1132 ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement);
1133 ParserMetadata parserMetadata = GetParserMetadata(aElement);
1135 request = CreateLoadRequest(aScriptKind, scriptURI, aElement, principal,
1136 ourCORSMode, nonce,
1137 FetchPriorityToRequestPriority(fetchPriority),
1138 sriMetadata, referrerPolicy, parserMetadata);
1139 request->GetScriptLoadContext()->mIsInline = false;
1140 request->GetScriptLoadContext()->SetScriptMode(
1141 aElement->GetScriptDeferred(), aElement->GetScriptAsync(), false);
1142 // keep request->GetScriptLoadContext()->mScriptFromHead to false so we
1143 // don't treat non preloaded scripts as blockers for full page load. See bug
1144 // 792438.
1146 LOG(("ScriptLoadRequest (%p): Created request for external script",
1147 request.get()));
1149 nsresult rv = StartLoad(request, Nothing());
1150 if (NS_FAILED(rv)) {
1151 ReportErrorToConsole(request, rv);
1153 // Asynchronously report the load failure
1154 nsCOMPtr<nsIRunnable> runnable =
1155 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
1156 &nsIScriptElement::FireErrorEvent);
1157 if (mDocument) {
1158 mDocument->Dispatch(runnable.forget());
1159 } else {
1160 NS_DispatchToCurrentThread(runnable.forget());
1162 return false;
1166 // We should still be in loading stage of script unless we're loading a
1167 // module or speculatively off-main-thread parsing a script.
1168 NS_ASSERTION(SpeculativeOMTParsingEnabled() ||
1169 !request->GetScriptLoadContext()->CompileStarted() ||
1170 request->IsModuleRequest(),
1171 "Request should not yet be in compiling stage.");
1173 if (request->GetScriptLoadContext()->IsAsyncScript()) {
1174 AddAsyncRequest(request);
1175 if (request->IsFinished()) {
1176 // The script is available already. Run it ASAP when the event
1177 // loop gets a chance to spin.
1179 // KVKV TODO: Instead of processing immediately, try off-thread-parsing
1180 // it and only schedule a pending ProcessRequest if that fails.
1181 ProcessPendingRequestsAsync();
1183 return false;
1185 if (!aElement->GetParserCreated()) {
1186 // Violate the HTML5 spec in order to make LABjs and the "order" plug-in
1187 // for RequireJS work with their Gecko-sniffed code path. See
1188 // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
1189 request->GetScriptLoadContext()->mIsNonAsyncScriptInserted = true;
1190 mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
1191 if (request->IsFinished()) {
1192 // The script is available already. Run it ASAP when the event
1193 // loop gets a chance to spin.
1194 ProcessPendingRequestsAsync();
1196 return false;
1198 // we now have a parser-inserted request that may or may not be still
1199 // loading
1200 if (request->GetScriptLoadContext()->IsDeferredScript()) {
1201 // We don't want to run this yet.
1202 // If we come here, the script is a parser-created script and it has
1203 // the defer attribute but not the async attribute OR it is a module
1204 // script without the async attribute. Since a
1205 // a parser-inserted script is being run, we came here by the parser
1206 // running the script, which means the parser is still alive and the
1207 // parse is ongoing.
1208 NS_ASSERTION(mDocument->GetCurrentContentSink() ||
1209 aElement->GetParserCreated() == FROM_PARSER_XSLT,
1210 "Non-XSLT Defer script on a document without an active "
1211 "parser; bug 592366.");
1212 AddDeferRequest(request);
1213 return false;
1216 if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
1217 // Need to maintain order for XSLT-inserted scripts
1218 NS_ASSERTION(!mParserBlockingRequest,
1219 "Parser-blocking scripts and XSLT scripts in the same doc!");
1220 request->GetScriptLoadContext()->mIsXSLT = true;
1221 mXSLTRequests.AppendElement(request);
1222 if (request->IsFinished()) {
1223 // The script is available already. Run it ASAP when the event
1224 // loop gets a chance to spin.
1225 ProcessPendingRequestsAsync();
1227 return true;
1230 if (request->IsFinished() && ReadyToExecuteParserBlockingScripts()) {
1231 // The request has already been loaded and there are no pending style
1232 // sheets. If the script comes from the network stream, cheat for
1233 // performance reasons and avoid a trip through the event loop.
1234 if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) {
1235 return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
1237 // Otherwise, we've got a document.written script, make a trip through
1238 // the event loop to hide the preload effects from the scripts on the
1239 // Web page.
1240 NS_ASSERTION(!mParserBlockingRequest,
1241 "There can be only one parser-blocking script at a time");
1242 NS_ASSERTION(mXSLTRequests.isEmpty(),
1243 "Parser-blocking scripts and XSLT scripts in the same doc!");
1244 mParserBlockingRequest = request;
1245 ProcessPendingRequestsAsync();
1246 return true;
1249 // The script hasn't loaded yet or there's a style sheet blocking it.
1250 // The script will be run when it loads or the style sheet loads.
1251 NS_ASSERTION(!mParserBlockingRequest,
1252 "There can be only one parser-blocking script at a time");
1253 NS_ASSERTION(mXSLTRequests.isEmpty(),
1254 "Parser-blocking scripts and XSLT scripts in the same doc!");
1255 mParserBlockingRequest = request;
1256 return true;
1259 bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement,
1260 ScriptKind aScriptKind) {
1261 // Is this document sandboxed without 'allow-scripts'?
1262 if (mDocument->HasScriptsBlockedBySandbox()) {
1263 return false;
1266 nsCOMPtr<nsINode> node = do_QueryInterface(aElement);
1267 nsAutoString nonce;
1268 if (nsString* cspNonce =
1269 static_cast<nsString*>(node->GetProperty(nsGkAtoms::nonce))) {
1270 nonce = *cspNonce;
1273 // Does CSP allow this inline script to run?
1274 if (!CSPAllowsInlineScript(aElement, nonce, mDocument)) {
1275 return false;
1278 // Check if adding an import map script is allowed. If not, we bail out
1279 // early to prevent creating a load request.
1280 if (aScriptKind == ScriptKind::eImportMap) {
1281 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
1282 // Step 31.2 type is "importmap":
1283 // Step 1. If el's relevant global object's import maps allowed is false,
1284 // then queue an element task on the DOM manipulation task source given el
1285 // to fire an event named error at el, and return.
1286 if (!mModuleLoader->IsImportMapAllowed()) {
1287 NS_WARNING("ScriptLoader: import maps allowed is false.");
1288 const char* msg = mModuleLoader->HasImportMapRegistered()
1289 ? "ImportMapNotAllowedMultiple"
1290 : "ImportMapNotAllowedAfterModuleLoad";
1291 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
1292 "Script Loader"_ns, mDocument,
1293 nsContentUtils::eDOM_PROPERTIES, msg);
1294 NS_DispatchToCurrentThread(
1295 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
1296 &nsIScriptElement::FireErrorEvent));
1297 return false;
1301 // Inline classic scripts ignore their CORS mode and are always CORS_NONE.
1302 CORSMode corsMode = CORS_NONE;
1303 if (aScriptKind == ScriptKind::eModule) {
1304 corsMode = aElement->GetCORSMode();
1306 // <https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element>
1307 // step 29 specifies to use the fetch priority. Presumably it has no effect
1308 // for inline scripts.
1309 const auto fetchPriority = aElement->GetFetchPriority();
1311 ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement);
1312 ParserMetadata parserMetadata = GetParserMetadata(aElement);
1314 RefPtr<ScriptLoadRequest> request =
1315 CreateLoadRequest(aScriptKind, mDocument->GetDocumentURI(), aElement,
1316 mDocument->NodePrincipal(), corsMode, nonce,
1317 FetchPriorityToRequestPriority(fetchPriority),
1318 SRIMetadata(), // SRI doesn't apply
1319 referrerPolicy, parserMetadata);
1320 request->GetScriptLoadContext()->mIsInline = true;
1321 request->GetScriptLoadContext()->mLineNo = aElement->GetScriptLineNumber();
1322 request->GetScriptLoadContext()->mColumnNo =
1323 aElement->GetScriptColumnNumber();
1324 request->mFetchSourceOnly = true;
1325 request->SetTextSource();
1326 TRACE_FOR_TEST_BOOL(request->GetScriptLoadContext()->GetScriptElement(),
1327 "scriptloader_load_source");
1328 CollectScriptTelemetry(request);
1330 // Only the 'async' attribute is heeded on an inline module script and
1331 // inline classic scripts ignore both these attributes.
1332 MOZ_ASSERT(!aElement->GetScriptDeferred());
1333 MOZ_ASSERT_IF(!request->IsModuleRequest(), !aElement->GetScriptAsync());
1334 request->GetScriptLoadContext()->SetScriptMode(
1335 false, aElement->GetScriptAsync(), false);
1337 LOG(("ScriptLoadRequest (%p): Created request for inline script",
1338 request.get()));
1340 request->mBaseURL = mDocument->GetDocBaseURI();
1342 if (request->IsModuleRequest()) {
1343 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph
1344 // Step 1. Disallow further import maps given settings object.
1345 mModuleLoader->DisallowImportMaps();
1347 ModuleLoadRequest* modReq = request->AsModuleRequest();
1348 if (aElement->GetParserCreated() != NOT_FROM_PARSER) {
1349 if (aElement->GetScriptAsync()) {
1350 AddAsyncRequest(modReq);
1351 } else {
1352 AddDeferRequest(modReq);
1356 // This calls OnFetchComplete directly since there's no need to start
1357 // fetching an inline script.
1358 nsresult rv = modReq->OnFetchComplete(NS_OK);
1359 if (NS_FAILED(rv)) {
1360 ReportErrorToConsole(modReq, rv);
1361 HandleLoadError(modReq, rv);
1364 return false;
1367 if (request->IsImportMapRequest()) {
1368 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
1369 // Step 31.2 type is "importmap":
1370 // Impl note: Step 1 is done above before creating a ScriptLoadRequest.
1371 MOZ_ASSERT(mModuleLoader->IsImportMapAllowed());
1373 // Step 2. Set el's relevant global object's import maps allowed to false.
1374 mModuleLoader->DisallowImportMaps();
1376 // Step 3. Let result be the result of creating an import map parse result
1377 // given source text and base URL.
1378 UniquePtr<ImportMap> importMap = mModuleLoader->ParseImportMap(request);
1379 if (!importMap) {
1380 // If parsing import maps fails, the exception will be reported in
1381 // ModuleLoaderBase::ParseImportMap, and the registration of the import
1382 // map will bail out early.
1383 return false;
1386 // TODO: Bug 1781758: Move RegisterImportMap into EvaluateScriptElement.
1388 // https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-element
1389 // The spec defines 'register an import map' should be done in
1390 // 'execute the script element', because inside 'execute the script element'
1391 // it will perform a 'preparation-time document check'.
1392 // However, as import maps could be only inline scripts by now, the
1393 // 'preparation-time document check' will never fail for import maps.
1394 // So we simply call 'register an import map' here.
1395 mModuleLoader->RegisterImportMap(std::move(importMap));
1396 return false;
1399 request->mState = ScriptLoadRequest::State::Ready;
1400 if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
1401 (!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) {
1402 // Need to maintain order for XSLT-inserted scripts
1403 NS_ASSERTION(!mParserBlockingRequest,
1404 "Parser-blocking scripts and XSLT scripts in the same doc!");
1405 mXSLTRequests.AppendElement(request);
1406 return true;
1408 if (aElement->GetParserCreated() == NOT_FROM_PARSER) {
1409 NS_ASSERTION(
1410 !nsContentUtils::IsSafeToRunScript(),
1411 "A script-inserted script is inserted without an update batch?");
1412 RunScriptWhenSafe(request);
1413 return false;
1415 if (aElement->GetParserCreated() == FROM_PARSER_NETWORK &&
1416 !ReadyToExecuteParserBlockingScripts()) {
1417 NS_ASSERTION(!mParserBlockingRequest,
1418 "There can be only one parser-blocking script at a time");
1419 mParserBlockingRequest = request;
1420 NS_ASSERTION(mXSLTRequests.isEmpty(),
1421 "Parser-blocking scripts and XSLT scripts in the same doc!");
1422 return true;
1424 // We now have a document.written inline script or we have an inline script
1425 // from the network but there is no style sheet that is blocking scripts.
1426 // Don't check for style sheets blocking scripts in the document.write
1427 // case to avoid style sheet network activity affecting when
1428 // document.write returns. It's not really necessary to do this if
1429 // there's no document.write currently on the call stack. However,
1430 // this way matches IE more closely than checking if document.write
1431 // is on the call stack.
1432 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
1433 "Not safe to run a parser-inserted script?");
1434 return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
1437 ScriptLoadRequest* ScriptLoader::LookupPreloadRequest(
1438 nsIScriptElement* aElement, ScriptKind aScriptKind,
1439 const SRIMetadata& aSRIMetadata) {
1440 MOZ_ASSERT(aElement);
1442 nsTArray<PreloadInfo>::index_type i =
1443 mPreloads.IndexOf(aElement->GetScriptURI(), 0, PreloadURIComparator());
1444 if (i == nsTArray<PreloadInfo>::NoIndex) {
1445 return nullptr;
1447 RefPtr<ScriptLoadRequest> request = mPreloads[i].mRequest;
1448 if (aScriptKind != request->mKind) {
1449 return nullptr;
1452 // Found preloaded request. Note that a script-inserted script can steal a
1453 // preload!
1454 request->GetScriptLoadContext()->SetIsLoadRequest(aElement);
1456 if (request->GetScriptLoadContext()->mWasCompiledOMT &&
1457 !request->IsModuleRequest()) {
1458 request->SetReady();
1461 nsString preloadCharset(mPreloads[i].mCharset);
1462 mPreloads.RemoveElementAt(i);
1464 // Double-check that the charset the preload used is the same as the charset
1465 // we have now.
1466 nsAutoString elementCharset;
1467 aElement->GetScriptCharset(elementCharset);
1469 // Bug 1832361: charset and crossorigin attributes shouldn't affect matching
1470 // of module scripts and modulepreload
1471 if (!request->IsModuleRequest() &&
1472 (!elementCharset.Equals(preloadCharset) ||
1473 aElement->GetCORSMode() != request->CORSMode())) {
1474 // Drop the preload.
1475 request->Cancel();
1476 AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RequestMismatch);
1477 return nullptr;
1480 if (!aSRIMetadata.CanTrustBeDelegatedTo(request->mIntegrity)) {
1481 // Don't cancel link preload requests, we want to deliver onload according
1482 // the result of the load, cancellation would unexpectedly lead to error
1483 // notification.
1484 if (!request->GetScriptLoadContext()->IsLinkPreloadScript()) {
1485 request->Cancel();
1487 return nullptr;
1490 // Report any errors that we skipped while preloading.
1491 ReportPreloadErrorsToConsole(request);
1493 // This makes sure the pending preload (if exists) for this resource is
1494 // properly marked as used and thus not notified in the console as unused.
1495 request->GetScriptLoadContext()->NotifyUsage(mDocument);
1496 // A used preload must no longer be found in the Document's hash table. Any
1497 // <link preload> tag after the <script> tag will start a new request, that
1498 // can be satisfied from a different cache, but not from the preload cache.
1499 request->GetScriptLoadContext()->RemoveSelf(mDocument);
1501 return request;
1504 void ScriptLoader::GetSRIMetadata(const nsAString& aIntegrityAttr,
1505 SRIMetadata* aMetadataOut) {
1506 MOZ_ASSERT(aMetadataOut->IsEmpty());
1508 if (aIntegrityAttr.IsEmpty()) {
1509 return;
1512 MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
1513 ("ScriptLoader::GetSRIMetadata, integrity=%s",
1514 NS_ConvertUTF16toUTF8(aIntegrityAttr).get()));
1516 nsAutoCString sourceUri;
1517 if (mDocument->GetDocumentURI()) {
1518 mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
1520 SRICheck::IntegrityMetadata(aIntegrityAttr, sourceUri, mReporter,
1521 aMetadataOut);
1524 ReferrerPolicy ScriptLoader::GetReferrerPolicy(nsIScriptElement* aElement) {
1525 ReferrerPolicy scriptReferrerPolicy = aElement->GetReferrerPolicy();
1526 if (scriptReferrerPolicy != ReferrerPolicy::_empty) {
1527 return scriptReferrerPolicy;
1529 return mDocument->GetReferrerPolicy();
1532 void ScriptLoader::CancelAndClearScriptLoadRequests() {
1533 // Cancel all requests that have not been executed and remove them.
1535 if (mParserBlockingRequest) {
1536 mParserBlockingRequest->Cancel();
1537 mParserBlockingRequest = nullptr;
1540 mDeferRequests.CancelRequestsAndClear();
1541 mLoadingAsyncRequests.CancelRequestsAndClear();
1542 mLoadedAsyncRequests.CancelRequestsAndClear();
1543 mNonAsyncExternalScriptInsertedRequests.CancelRequestsAndClear();
1544 mXSLTRequests.CancelRequestsAndClear();
1545 mOffThreadCompilingRequests.CancelRequestsAndClear();
1547 if (mModuleLoader) {
1548 mModuleLoader->CancelAndClearDynamicImports();
1551 for (ModuleLoader* loader : mWebExtModuleLoaders) {
1552 loader->CancelAndClearDynamicImports();
1555 for (ModuleLoader* loader : mShadowRealmModuleLoaders) {
1556 loader->CancelAndClearDynamicImports();
1559 for (size_t i = 0; i < mPreloads.Length(); i++) {
1560 mPreloads[i].mRequest->Cancel();
1562 mPreloads.Clear();
1565 nsresult ScriptLoader::CompileOffThreadOrProcessRequest(
1566 ScriptLoadRequest* aRequest) {
1567 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
1568 "Processing requests when running scripts is unsafe.");
1570 if (!aRequest->GetScriptLoadContext()->mCompileOrDecodeTask &&
1571 !aRequest->GetScriptLoadContext()->CompileStarted()) {
1572 bool couldCompile = false;
1573 nsresult rv = AttemptOffThreadScriptCompile(aRequest, &couldCompile);
1574 if (NS_FAILED(rv)) {
1575 HandleLoadError(aRequest, rv);
1576 return rv;
1579 if (couldCompile) {
1580 return NS_OK;
1584 return ProcessRequest(aRequest);
1587 namespace {
1589 class OffThreadCompilationCompleteTask : public Task {
1590 public:
1591 OffThreadCompilationCompleteTask(ScriptLoadRequest* aRequest,
1592 ScriptLoader* aLoader)
1593 : Task(Kind::MainThreadOnly, EventQueuePriority::Normal),
1594 mRequest(aRequest),
1595 mLoader(aLoader) {
1596 MOZ_ASSERT(NS_IsMainThread());
1599 void RecordStartTime() { mStartTime = TimeStamp::Now(); }
1600 void RecordStopTime() { mStopTime = TimeStamp::Now(); }
1602 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
1603 bool GetName(nsACString& aName) override {
1604 aName.AssignLiteral("dom::OffThreadCompilationCompleteTask");
1605 return true;
1607 #endif
1609 bool Run() override {
1610 MOZ_ASSERT(NS_IsMainThread());
1612 RefPtr<ScriptLoadContext> context = mRequest->GetScriptLoadContext();
1614 if (!context->mCompileOrDecodeTask) {
1615 // Request has been cancelled by MaybeCancelOffThreadScript.
1616 return true;
1619 RecordStopTime();
1621 if (profiler_is_active()) {
1622 ProfilerString8View scriptSourceString;
1623 if (mRequest->IsTextSource()) {
1624 scriptSourceString = "ScriptCompileOffThread";
1625 } else {
1626 MOZ_ASSERT(mRequest->IsBytecode());
1627 scriptSourceString = "BytecodeDecodeOffThread";
1630 nsAutoCString profilerLabelString;
1631 mRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString);
1632 PROFILER_MARKER_TEXT(scriptSourceString, JS,
1633 MarkerTiming::Interval(mStartTime, mStopTime),
1634 profilerLabelString);
1637 (void)mLoader->ProcessOffThreadRequest(mRequest);
1639 mRequest = nullptr;
1640 mLoader = nullptr;
1641 return true;
1644 private:
1645 // NOTE:
1646 // These fields are main-thread only, and this task shouldn't be freed off
1647 // main thread.
1649 // This is guaranteed by not having off-thread tasks which depends on this
1650 // task, because otherwise the off-thread task's mDependencies can be the
1651 // last reference, which results in freeing this task off main thread.
1653 // If such task is added, these fields must be moved to separate storage.
1654 RefPtr<ScriptLoadRequest> mRequest;
1655 RefPtr<ScriptLoader> mLoader;
1657 TimeStamp mStartTime;
1658 TimeStamp mStopTime;
1661 } /* anonymous namespace */
1663 nsresult ScriptLoader::AttemptOffThreadScriptCompile(
1664 ScriptLoadRequest* aRequest, bool* aCouldCompileOut) {
1665 // If speculative parsing is enabled, the request may not be ready to run if
1666 // the element is not yet available.
1667 MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled() && !aRequest->IsModuleRequest(),
1668 aRequest->IsFinished());
1669 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT);
1670 MOZ_ASSERT(aCouldCompileOut && !*aCouldCompileOut);
1672 // Don't off-thread compile inline scripts.
1673 if (aRequest->GetScriptLoadContext()->mIsInline) {
1674 return NS_OK;
1677 nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalForRequest(aRequest);
1678 if (!globalObject) {
1679 return NS_ERROR_FAILURE;
1682 AutoJSAPI jsapi;
1683 if (!jsapi.Init(globalObject)) {
1684 return NS_ERROR_FAILURE;
1687 JSContext* cx = jsapi.cx();
1688 JS::CompileOptions options(cx);
1690 // Introduction script will actually be computed and set when the script is
1691 // collected from offthread
1692 JS::Rooted<JSScript*> dummyIntroductionScript(cx);
1693 nsresult rv = FillCompileOptionsForRequest(cx, aRequest, &options,
1694 &dummyIntroductionScript);
1695 if (NS_WARN_IF(NS_FAILED(rv))) {
1696 return rv;
1699 // TODO: This uses the same heuristics and the same threshold as the
1700 // JS::CanCompileOffThread / JS::CanDecodeOffThread APIs, but the
1701 // heuristics needs to be updated to reflect the change regarding the
1702 // Stencil API, and also the thread management on the consumer side
1703 // (bug 1846160).
1704 static constexpr size_t OffThreadMinimumTextLength = 5 * 1000;
1705 static constexpr size_t OffThreadMinimumBytecodeLength = 5 * 1000;
1707 if (aRequest->IsTextSource()) {
1708 if (!StaticPrefs::javascript_options_parallel_parsing() ||
1709 aRequest->ScriptTextLength() < OffThreadMinimumTextLength) {
1710 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
1711 "scriptloader_main_thread_compile");
1712 return NS_OK;
1714 } else {
1715 MOZ_ASSERT(aRequest->IsBytecode());
1717 size_t length =
1718 aRequest->mScriptBytecode.length() - aRequest->mBytecodeOffset;
1719 if (!StaticPrefs::javascript_options_parallel_parsing() ||
1720 length < OffThreadMinimumBytecodeLength) {
1721 return NS_OK;
1725 RefPtr<CompileOrDecodeTask> compileOrDecodeTask;
1726 rv = CreateOffThreadTask(cx, aRequest, options,
1727 getter_AddRefs(compileOrDecodeTask));
1728 NS_ENSURE_SUCCESS(rv, rv);
1730 RefPtr<OffThreadCompilationCompleteTask> completeTask =
1731 new OffThreadCompilationCompleteTask(aRequest, this);
1733 completeTask->RecordStartTime();
1735 aRequest->GetScriptLoadContext()->mCompileOrDecodeTask = compileOrDecodeTask;
1736 completeTask->AddDependency(compileOrDecodeTask);
1738 TaskController::Get()->AddTask(compileOrDecodeTask.forget());
1739 TaskController::Get()->AddTask(completeTask.forget());
1741 aRequest->GetScriptLoadContext()->BlockOnload(mDocument);
1743 // Once the compilation is finished, the completeTask will be run on
1744 // the main thread to call ScriptLoader::ProcessOffThreadRequest for the
1745 // request.
1746 aRequest->mState = ScriptLoadRequest::State::Compiling;
1748 // Requests that are not tracked elsewhere are added to a list while they are
1749 // being compiled off-thread, so we can cancel the compilation later if
1750 // necessary.
1752 // Non-top-level modules not tracked because these are cancelled from their
1753 // importing module.
1754 if (aRequest->IsTopLevel() && !aRequest->isInList()) {
1755 mOffThreadCompilingRequests.AppendElement(aRequest);
1756 aRequest->GetScriptLoadContext()->mInCompilingList = true;
1759 *aCouldCompileOut = true;
1761 return NS_OK;
1764 CompileOrDecodeTask::CompileOrDecodeTask()
1765 : Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal),
1766 mMutex("CompileOrDecodeTask"),
1767 mOptions(JS::OwningCompileOptions::ForFrontendContext()) {}
1769 CompileOrDecodeTask::~CompileOrDecodeTask() {
1770 if (mFrontendContext) {
1771 JS::DestroyFrontendContext(mFrontendContext);
1772 mFrontendContext = nullptr;
1776 nsresult CompileOrDecodeTask::InitFrontendContext() {
1777 mFrontendContext = JS::NewFrontendContext();
1778 if (!mFrontendContext) {
1779 mIsCancelled = true;
1780 return NS_ERROR_OUT_OF_MEMORY;
1782 return NS_OK;
1785 void CompileOrDecodeTask::DidRunTask(const MutexAutoLock& aProofOfLock,
1786 RefPtr<JS::Stencil>&& aStencil) {
1787 if (aStencil) {
1788 if (!JS::PrepareForInstantiate(mFrontendContext, *aStencil,
1789 mInstantiationStorage)) {
1790 aStencil = nullptr;
1794 mStencil = std::move(aStencil);
1797 already_AddRefed<JS::Stencil> CompileOrDecodeTask::StealResult(
1798 JSContext* aCx, JS::InstantiationStorage* aInstantiationStorage) {
1799 JS::FrontendContext* fc = mFrontendContext;
1800 mFrontendContext = nullptr;
1801 auto destroyFrontendContext =
1802 mozilla::MakeScopeExit([&]() { JS::DestroyFrontendContext(fc); });
1804 MOZ_ASSERT(fc);
1806 if (JS::HadFrontendErrors(fc)) {
1807 (void)JS::ConvertFrontendErrorsToRuntimeErrors(aCx, fc, mOptions);
1808 return nullptr;
1811 if (!mStencil && JS::IsTranscodeFailureResult(mResult)) {
1812 // Decode failure with bad content isn't reported as error.
1813 JS_ReportErrorASCII(aCx, "failed to decode cache");
1814 return nullptr;
1817 // Report warnings.
1818 if (!JS::ConvertFrontendErrorsToRuntimeErrors(aCx, fc, mOptions)) {
1819 return nullptr;
1822 MOZ_ASSERT(mStencil,
1823 "If this task is cancelled, StealResult shouldn't be called");
1825 // This task is started and finished successfully.
1826 *aInstantiationStorage = std::move(mInstantiationStorage);
1828 return mStencil.forget();
1831 void CompileOrDecodeTask::Cancel() {
1832 MOZ_ASSERT(NS_IsMainThread());
1834 MutexAutoLock lock(mMutex);
1836 mIsCancelled = true;
1839 enum class CompilationTarget { Script, Module };
1841 template <CompilationTarget target>
1842 class ScriptOrModuleCompileTask final : public CompileOrDecodeTask {
1843 public:
1844 explicit ScriptOrModuleCompileTask(
1845 ScriptLoader::MaybeSourceText&& aMaybeSource)
1846 : CompileOrDecodeTask(), mMaybeSource(std::move(aMaybeSource)) {}
1848 nsresult Init(JS::CompileOptions& aOptions) {
1849 nsresult rv = InitFrontendContext();
1850 NS_ENSURE_SUCCESS(rv, rv);
1852 if (!mOptions.copy(mFrontendContext, aOptions)) {
1853 mIsCancelled = true;
1854 return NS_ERROR_OUT_OF_MEMORY;
1857 return NS_OK;
1860 bool Run() override {
1861 MutexAutoLock lock(mMutex);
1863 if (IsCancelled(lock)) {
1864 return true;
1867 RefPtr<JS::Stencil> stencil = Compile();
1869 DidRunTask(lock, std::move(stencil));
1870 return true;
1873 private:
1874 static size_t ThreadStackQuotaForSize(size_t size) {
1875 // Set the stack quota to 10% less that the actual size.
1876 // NOTE: This follows what JS helper thread does.
1877 return size_t(double(size) * 0.9);
1880 already_AddRefed<JS::Stencil> Compile() {
1881 size_t stackSize = TaskController::GetThreadStackSize();
1882 JS::SetNativeStackQuota(mFrontendContext,
1883 ThreadStackQuotaForSize(stackSize));
1885 JS::CompilationStorage compileStorage;
1886 auto compile = [&](auto& source) {
1887 if constexpr (target == CompilationTarget::Script) {
1888 return JS::CompileGlobalScriptToStencil(mFrontendContext, mOptions,
1889 source, compileStorage);
1891 return JS::CompileModuleScriptToStencil(mFrontendContext, mOptions,
1892 source, compileStorage);
1894 return mMaybeSource.mapNonEmpty(compile);
1897 public:
1898 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
1899 bool GetName(nsACString& aName) override {
1900 if constexpr (target == CompilationTarget::Script) {
1901 aName.AssignLiteral("ScriptCompileTask");
1902 } else {
1903 aName.AssignLiteral("ModuleCompileTask");
1905 return true;
1907 #endif
1909 private:
1910 ScriptLoader::MaybeSourceText mMaybeSource;
1913 using ScriptCompileTask =
1914 class ScriptOrModuleCompileTask<CompilationTarget::Script>;
1915 using ModuleCompileTask =
1916 class ScriptOrModuleCompileTask<CompilationTarget::Module>;
1918 class ScriptDecodeTask final : public CompileOrDecodeTask {
1919 public:
1920 explicit ScriptDecodeTask(const JS::TranscodeRange& aRange)
1921 : CompileOrDecodeTask(), mRange(aRange) {}
1923 nsresult Init(JS::DecodeOptions& aOptions) {
1924 nsresult rv = InitFrontendContext();
1925 NS_ENSURE_SUCCESS(rv, rv);
1927 if (!mDecodeOptions.copy(mFrontendContext, aOptions)) {
1928 mIsCancelled = true;
1929 return NS_ERROR_OUT_OF_MEMORY;
1932 return NS_OK;
1935 bool Run() override {
1936 MutexAutoLock lock(mMutex);
1938 if (IsCancelled(lock)) {
1939 return true;
1942 RefPtr<JS::Stencil> stencil = Decode();
1944 JS::OwningCompileOptions compileOptions(
1945 (JS::OwningCompileOptions::ForFrontendContext()));
1946 mOptions.steal(std::move(mDecodeOptions));
1948 DidRunTask(lock, std::move(stencil));
1949 return true;
1952 private:
1953 already_AddRefed<JS::Stencil> Decode() {
1954 // NOTE: JS::DecodeStencil doesn't need the stack quota.
1956 JS::CompilationStorage compileStorage;
1957 RefPtr<JS::Stencil> stencil;
1958 mResult = JS::DecodeStencil(mFrontendContext, mDecodeOptions, mRange,
1959 getter_AddRefs(stencil));
1960 return stencil.forget();
1963 public:
1964 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
1965 bool GetName(nsACString& aName) override {
1966 aName.AssignLiteral("ScriptDecodeTask");
1967 return true;
1969 #endif
1971 private:
1972 JS::OwningDecodeOptions mDecodeOptions;
1974 JS::TranscodeRange mRange;
1977 nsresult ScriptLoader::CreateOffThreadTask(
1978 JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions& aOptions,
1979 CompileOrDecodeTask** aCompileOrDecodeTask) {
1980 if (aRequest->IsBytecode()) {
1981 JS::DecodeOptions decodeOptions(aOptions);
1982 JS::TranscodeRange range(
1983 aRequest->mScriptBytecode.begin() + aRequest->mBytecodeOffset,
1984 aRequest->mScriptBytecode.length() - aRequest->mBytecodeOffset);
1985 RefPtr<ScriptDecodeTask> decodeTask = new ScriptDecodeTask(range);
1986 nsresult rv = decodeTask->Init(decodeOptions);
1987 NS_ENSURE_SUCCESS(rv, rv);
1988 decodeTask.forget(aCompileOrDecodeTask);
1989 return NS_OK;
1992 MaybeSourceText maybeSource;
1993 nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource);
1994 NS_ENSURE_SUCCESS(rv, rv);
1996 if (ShouldApplyDelazifyStrategy(aRequest)) {
1997 ApplyDelazifyStrategy(&aOptions);
1998 mTotalFullParseSize +=
1999 aRequest->ScriptTextLength() > 0
2000 ? static_cast<uint32_t>(aRequest->ScriptTextLength())
2001 : 0;
2003 LOG(
2004 ("ScriptLoadRequest (%p): non-on-demand-only Parsing Enabled for "
2005 "url=%s mTotalFullParseSize=%u",
2006 aRequest, aRequest->mURI->GetSpecOrDefault().get(),
2007 mTotalFullParseSize));
2010 if (aRequest->IsModuleRequest()) {
2011 RefPtr<ModuleCompileTask> compileTask =
2012 new ModuleCompileTask(std::move(maybeSource));
2013 rv = compileTask->Init(aOptions);
2014 NS_ENSURE_SUCCESS(rv, rv);
2015 compileTask.forget(aCompileOrDecodeTask);
2016 return NS_OK;
2019 if (StaticPrefs::dom_expose_test_interfaces()) {
2020 switch (aOptions.eagerDelazificationStrategy()) {
2021 case JS::DelazificationOption::OnDemandOnly:
2022 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2023 "delazification_on_demand_only");
2024 break;
2025 case JS::DelazificationOption::CheckConcurrentWithOnDemand:
2026 case JS::DelazificationOption::ConcurrentDepthFirst:
2027 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2028 "delazification_concurrent_depth_first");
2029 break;
2030 case JS::DelazificationOption::ConcurrentLargeFirst:
2031 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2032 "delazification_concurrent_large_first");
2033 break;
2034 case JS::DelazificationOption::ParseEverythingEagerly:
2035 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2036 "delazification_parse_everything_eagerly");
2037 break;
2041 RefPtr<ScriptCompileTask> compileTask =
2042 new ScriptCompileTask(std::move(maybeSource));
2043 rv = compileTask->Init(aOptions);
2044 NS_ENSURE_SUCCESS(rv, rv);
2045 compileTask.forget(aCompileOrDecodeTask);
2046 return NS_OK;
2049 nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) {
2050 MOZ_ASSERT(aRequest->IsCompiling());
2051 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT);
2053 if (aRequest->IsCanceled()) {
2054 return NS_OK;
2057 aRequest->GetScriptLoadContext()->mWasCompiledOMT = true;
2059 if (aRequest->GetScriptLoadContext()->mInCompilingList) {
2060 mOffThreadCompilingRequests.Remove(aRequest);
2061 aRequest->GetScriptLoadContext()->mInCompilingList = false;
2064 if (aRequest->IsModuleRequest()) {
2065 MOZ_ASSERT(aRequest->GetScriptLoadContext()->mCompileOrDecodeTask);
2066 ModuleLoadRequest* request = aRequest->AsModuleRequest();
2067 return request->OnFetchComplete(NS_OK);
2070 // Element may not be ready yet if speculatively compiling, so process the
2071 // request in ProcessPendingRequests when it is available.
2072 MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled(),
2073 aRequest->GetScriptLoadContext()->GetScriptElement());
2074 if (!aRequest->GetScriptLoadContext()->GetScriptElement()) {
2075 // Unblock onload here in case this request never gets executed.
2076 aRequest->GetScriptLoadContext()->MaybeUnblockOnload();
2077 return NS_OK;
2080 aRequest->SetReady();
2082 if (aRequest == mParserBlockingRequest) {
2083 if (!ReadyToExecuteParserBlockingScripts()) {
2084 // If not ready to execute scripts, schedule an async call to
2085 // ProcessPendingRequests to handle it.
2086 ProcessPendingRequestsAsync();
2087 return NS_OK;
2090 // Same logic as in top of ProcessPendingRequests.
2091 mParserBlockingRequest = nullptr;
2092 UnblockParser(aRequest);
2093 ProcessRequest(aRequest);
2094 ContinueParserAsync(aRequest);
2095 return NS_OK;
2098 // Async scripts and blocking scripts can be executed right away.
2099 if ((aRequest->GetScriptLoadContext()->IsAsyncScript() ||
2100 aRequest->GetScriptLoadContext()->IsBlockingScript()) &&
2101 !aRequest->isInList()) {
2102 return ProcessRequest(aRequest);
2105 // Process other scripts in the proper order.
2106 ProcessPendingRequests();
2107 return NS_OK;
2110 nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) {
2111 LOG(("ScriptLoadRequest (%p): Process request", aRequest));
2113 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
2114 "Processing requests when running scripts is unsafe.");
2115 NS_ASSERTION(aRequest->IsFinished(),
2116 "Processing a request that is not ready to run.");
2118 NS_ENSURE_ARG(aRequest);
2120 auto unblockOnload = MakeScopeExit(
2121 [&] { aRequest->GetScriptLoadContext()->MaybeUnblockOnload(); });
2123 if (aRequest->IsModuleRequest()) {
2124 ModuleLoadRequest* request = aRequest->AsModuleRequest();
2125 if (request->IsDynamicImport()) {
2126 request->ProcessDynamicImport();
2127 return NS_OK;
2130 if (request->mModuleScript) {
2131 if (!request->InstantiateModuleGraph()) {
2132 request->mModuleScript = nullptr;
2136 if (!request->mModuleScript) {
2137 // There was an error fetching a module script. Nothing to do here.
2138 LOG(("ScriptLoadRequest (%p): Error loading request, firing error",
2139 aRequest));
2140 FireScriptAvailable(NS_ERROR_FAILURE, aRequest);
2141 return NS_OK;
2145 nsCOMPtr<nsINode> scriptElem =
2146 do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement());
2148 nsCOMPtr<Document> doc;
2149 if (!aRequest->GetScriptLoadContext()->mIsInline ||
2150 aRequest->IsModuleRequest()) {
2151 doc = scriptElem->OwnerDoc();
2154 nsCOMPtr<nsIScriptElement> oldParserInsertedScript;
2155 uint32_t parserCreated = aRequest->GetScriptLoadContext()->GetParserCreated();
2156 if (parserCreated) {
2157 oldParserInsertedScript = mCurrentParserInsertedScript;
2158 mCurrentParserInsertedScript =
2159 aRequest->GetScriptLoadContext()->GetScriptElement();
2162 aRequest->GetScriptLoadContext()->GetScriptElement()->BeginEvaluating();
2164 FireScriptAvailable(NS_OK, aRequest);
2166 // The window may have gone away by this point, in which case there's no point
2167 // in trying to run the script.
2170 // Try to perform a microtask checkpoint
2171 nsAutoMicroTask mt;
2174 nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow();
2175 bool runScript = !!pwin;
2176 if (runScript) {
2177 nsContentUtils::DispatchTrustedEvent(
2178 scriptElem->OwnerDoc(), scriptElem, u"beforescriptexecute"_ns,
2179 CanBubble::eYes, Cancelable::eYes, &runScript);
2182 // Inner window could have gone away after firing beforescriptexecute
2183 pwin = mDocument->GetInnerWindow();
2184 if (!pwin) {
2185 runScript = false;
2188 nsresult rv = NS_OK;
2189 if (runScript) {
2190 if (doc) {
2191 doc->IncrementIgnoreDestructiveWritesCounter();
2193 rv = EvaluateScriptElement(aRequest);
2194 if (doc) {
2195 doc->DecrementIgnoreDestructiveWritesCounter();
2198 nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(), scriptElem,
2199 u"afterscriptexecute"_ns,
2200 CanBubble::eYes, Cancelable::eNo);
2203 FireScriptEvaluated(rv, aRequest);
2205 aRequest->GetScriptLoadContext()->GetScriptElement()->EndEvaluating();
2207 if (parserCreated) {
2208 mCurrentParserInsertedScript = oldParserInsertedScript;
2211 if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) {
2212 // The request was parsed off-main-thread, but the result of the off
2213 // thread parse was not actually needed to process the request
2214 // (disappearing window, some other error, ...). Finish the
2215 // request to avoid leaks.
2216 MOZ_ASSERT(!aRequest->IsModuleRequest());
2217 aRequest->GetScriptLoadContext()->MaybeCancelOffThreadScript();
2220 // Free any source data, but keep the bytecode content as we might have to
2221 // save it later.
2222 aRequest->ClearScriptSource();
2223 if (aRequest->IsBytecode()) {
2224 // We received bytecode as input, thus we were decoding, and we will not be
2225 // encoding the bytecode once more. We can safely clear the content of this
2226 // buffer.
2227 aRequest->mScriptBytecode.clearAndFree();
2230 return rv;
2233 void ScriptLoader::FireScriptAvailable(nsresult aResult,
2234 ScriptLoadRequest* aRequest) {
2235 for (int32_t i = 0; i < mObservers.Count(); i++) {
2236 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
2237 obs->ScriptAvailable(
2238 aResult, aRequest->GetScriptLoadContext()->GetScriptElement(),
2239 aRequest->GetScriptLoadContext()->mIsInline, aRequest->mURI,
2240 aRequest->GetScriptLoadContext()->mLineNo);
2243 bool isInlineClassicScript = aRequest->GetScriptLoadContext()->mIsInline &&
2244 !aRequest->IsModuleRequest();
2245 RefPtr<nsIScriptElement> scriptElement =
2246 aRequest->GetScriptLoadContext()->GetScriptElement();
2247 scriptElement->ScriptAvailable(aResult, scriptElement, isInlineClassicScript,
2248 aRequest->mURI,
2249 aRequest->GetScriptLoadContext()->mLineNo);
2252 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
2253 MOZ_CAN_RUN_SCRIPT_BOUNDARY void ScriptLoader::FireScriptEvaluated(
2254 nsresult aResult, ScriptLoadRequest* aRequest) {
2255 for (int32_t i = 0; i < mObservers.Count(); i++) {
2256 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
2257 RefPtr<nsIScriptElement> scriptElement =
2258 aRequest->GetScriptLoadContext()->GetScriptElement();
2259 obs->ScriptEvaluated(aResult, scriptElement,
2260 aRequest->GetScriptLoadContext()->mIsInline);
2263 RefPtr<nsIScriptElement> scriptElement =
2264 aRequest->GetScriptLoadContext()->GetScriptElement();
2265 scriptElement->ScriptEvaluated(aResult, scriptElement,
2266 aRequest->GetScriptLoadContext()->mIsInline);
2269 already_AddRefed<nsIGlobalObject> ScriptLoader::GetGlobalForRequest(
2270 ScriptLoadRequest* aRequest) {
2271 if (aRequest->IsModuleRequest()) {
2272 ModuleLoader* loader =
2273 ModuleLoader::From(aRequest->AsModuleRequest()->mLoader);
2274 nsCOMPtr<nsIGlobalObject> global = loader->GetGlobalObject();
2275 return global.forget();
2278 return GetScriptGlobalObject();
2281 already_AddRefed<nsIScriptGlobalObject> ScriptLoader::GetScriptGlobalObject() {
2282 if (!mDocument) {
2283 return nullptr;
2286 nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow();
2287 if (!pwin) {
2288 return nullptr;
2291 nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
2292 NS_ASSERTION(globalObject, "windows must be global objects");
2294 // and make sure we are setup for this type of script.
2295 nsresult rv = globalObject->EnsureScriptEnvironment();
2296 if (NS_FAILED(rv)) {
2297 return nullptr;
2300 return globalObject.forget();
2303 nsresult ScriptLoader::FillCompileOptionsForRequest(
2304 JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
2305 JS::MutableHandle<JSScript*> aIntroductionScript) {
2306 // It's very important to use aRequest->mURI, not the final URI of the channel
2307 // aRequest ended up getting script data from, as the script filename.
2308 nsresult rv = aRequest->mURI->GetSpec(aRequest->mURL);
2309 if (NS_WARN_IF(NS_FAILED(rv))) {
2310 return rv;
2313 if (mDocument) {
2314 mDocument->NoteScriptTrackingStatus(
2315 aRequest->mURL, aRequest->GetScriptLoadContext()->IsTracking());
2318 const char* introductionType;
2319 if (aRequest->IsModuleRequest() &&
2320 !aRequest->AsModuleRequest()->IsTopLevel()) {
2321 introductionType = "importedModule";
2322 } else if (!aRequest->GetScriptLoadContext()->mIsInline) {
2323 introductionType = "srcScript";
2324 } else if (aRequest->GetScriptLoadContext()->GetParserCreated() ==
2325 FROM_PARSER_NETWORK) {
2326 introductionType = "inlineScript";
2327 } else {
2328 introductionType = "injectedScript";
2330 aOptions->setIntroductionInfoToCaller(aCx, introductionType,
2331 aIntroductionScript);
2332 aOptions->setFileAndLine(aRequest->mURL.get(),
2333 aRequest->GetScriptLoadContext()->mLineNo);
2334 // The column is only relevant for inline scripts in order for SpiderMonkey to
2335 // properly compute offsets relatively to the script position within the HTML
2336 // file. injectedScript are not concerned and are always considered to start
2337 // at column 0.
2338 if (aRequest->GetScriptLoadContext()->mIsInline &&
2339 aRequest->GetScriptLoadContext()->GetParserCreated() ==
2340 FROM_PARSER_NETWORK) {
2341 aOptions->setColumn(JS::ColumnNumberZeroOrigin(
2342 aRequest->GetScriptLoadContext()->mColumnNo));
2344 aOptions->setIsRunOnce(true);
2345 aOptions->setNoScriptRval(true);
2346 if (aRequest->mSourceMapURL) {
2347 aOptions->setSourceMapURL(aRequest->mSourceMapURL->get());
2349 if (aRequest->mOriginPrincipal) {
2350 nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalForRequest(aRequest);
2351 nsIPrincipal* scriptPrin = globalObject->PrincipalOrNull();
2352 MOZ_ASSERT(scriptPrin);
2353 bool subsumes = scriptPrin->Subsumes(aRequest->mOriginPrincipal);
2354 aOptions->setMutedErrors(!subsumes);
2357 if (aRequest->IsModuleRequest()) {
2358 aOptions->setHideScriptFromDebugger(true);
2361 aOptions->setDeferDebugMetadata(true);
2363 aOptions->borrowBuffer = true;
2365 return NS_OK;
2368 /* static */
2369 bool ScriptLoader::ShouldCacheBytecode(ScriptLoadRequest* aRequest) {
2370 using mozilla::TimeDuration;
2371 using mozilla::TimeStamp;
2373 // We need the nsICacheInfoChannel to exist to be able to open the alternate
2374 // data output stream. This pointer would only be non-null if the bytecode was
2375 // activated at the time the channel got created in StartLoad.
2376 if (!aRequest->mCacheInfo) {
2377 LOG(("ScriptLoadRequest (%p): Cannot cache anything (cacheInfo = %p)",
2378 aRequest, aRequest->mCacheInfo.get()));
2379 return false;
2382 // Look at the preference to know which strategy (parameters) should be used
2383 // when the bytecode cache is enabled.
2384 int32_t strategy = StaticPrefs::dom_script_loader_bytecode_cache_strategy();
2386 // List of parameters used by the strategies.
2387 bool hasSourceLengthMin = false;
2388 bool hasFetchCountMin = false;
2389 size_t sourceLengthMin = 100;
2390 uint32_t fetchCountMin = 4;
2392 LOG(("ScriptLoadRequest (%p): Bytecode-cache: strategy = %d.", aRequest,
2393 strategy));
2394 switch (strategy) {
2395 case -2: {
2396 // Reader mode, keep requesting alternate data but no longer save it.
2397 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Encoding disabled.",
2398 aRequest));
2399 return false;
2401 case -1: {
2402 // Eager mode, skip heuristics!
2403 hasSourceLengthMin = false;
2404 hasFetchCountMin = false;
2405 break;
2407 default:
2408 case 0: {
2409 hasSourceLengthMin = true;
2410 hasFetchCountMin = true;
2411 sourceLengthMin = 1024;
2412 // If we were to optimize only for speed, without considering the impact
2413 // on memory, we should set this threshold to 2. (Bug 900784 comment 120)
2414 fetchCountMin = 4;
2415 break;
2419 // If the script is too small/large, do not attempt at creating a bytecode
2420 // cache for this script, as the overhead of parsing it might not be worth the
2421 // effort.
2422 if (hasSourceLengthMin) {
2423 size_t sourceLength;
2424 size_t minLength;
2425 MOZ_ASSERT(aRequest->IsTextSource());
2426 sourceLength = aRequest->mScriptTextLength;
2427 minLength = sourceLengthMin;
2428 if (sourceLength < minLength) {
2429 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Script is too small.",
2430 aRequest));
2431 return false;
2435 // Check that we loaded the cache entry a few times before attempting any
2436 // bytecode-cache optimization, such that we do not waste time on entry which
2437 // are going to be dropped soon.
2438 if (hasFetchCountMin) {
2439 uint32_t fetchCount = 0;
2440 if (NS_FAILED(aRequest->mCacheInfo->GetCacheTokenFetchCount(&fetchCount))) {
2441 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Cannot get fetchCount.",
2442 aRequest));
2443 return false;
2445 LOG(("ScriptLoadRequest (%p): Bytecode-cache: fetchCount = %d.", aRequest,
2446 fetchCount));
2447 if (fetchCount < fetchCountMin) {
2448 return false;
2452 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Trigger encoding.", aRequest));
2453 return true;
2456 class MOZ_RAII AutoSetProcessingScriptTag {
2457 nsCOMPtr<nsIScriptContext> mContext;
2458 bool mOldTag;
2460 public:
2461 explicit AutoSetProcessingScriptTag(nsIScriptContext* aContext)
2462 : mContext(aContext), mOldTag(mContext->GetProcessingScriptTag()) {
2463 mContext->SetProcessingScriptTag(true);
2466 ~AutoSetProcessingScriptTag() { mContext->SetProcessingScriptTag(mOldTag); }
2469 static nsresult ExecuteCompiledScript(JSContext* aCx, JSExecutionContext& aExec,
2470 ClassicScript* aLoaderScript) {
2471 JS::Rooted<JSScript*> script(aCx, aExec.GetScript());
2472 if (!script) {
2473 // Compilation succeeds without producing a script if scripting is
2474 // disabled for the global.
2475 return NS_OK;
2478 if (JS::GetScriptPrivate(script).isUndefined()) {
2479 aLoaderScript->AssociateWithScript(script);
2482 return aExec.ExecScript();
2485 nsresult ScriptLoader::EvaluateScriptElement(ScriptLoadRequest* aRequest) {
2486 using namespace mozilla::Telemetry;
2487 MOZ_ASSERT(aRequest->IsFinished());
2489 // We need a document to evaluate scripts.
2490 if (!mDocument) {
2491 return NS_ERROR_FAILURE;
2494 nsCOMPtr<nsIContent> scriptContent(
2495 do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement()));
2496 MOZ_ASSERT(scriptContent);
2497 Document* ownerDoc = scriptContent->OwnerDoc();
2498 if (ownerDoc != mDocument) {
2499 // Willful violation of HTML5 as of 2010-12-01
2500 return NS_ERROR_FAILURE;
2503 nsCOMPtr<nsIGlobalObject> globalObject;
2504 nsCOMPtr<nsIScriptContext> context;
2505 if (!IsWebExtensionRequest(aRequest)) {
2506 // Otherwise we have to ensure that there is a nsIScriptContext.
2507 nsCOMPtr<nsIScriptGlobalObject> scriptGlobal = GetScriptGlobalObject();
2508 if (!scriptGlobal) {
2509 return NS_ERROR_FAILURE;
2512 MOZ_ASSERT_IF(
2513 aRequest->IsModuleRequest(),
2514 aRequest->AsModuleRequest()->GetGlobalObject() == scriptGlobal);
2516 // Make sure context is a strong reference since we access it after
2517 // we've executed a script, which may cause all other references to
2518 // the context to go away.
2519 context = scriptGlobal->GetScriptContext();
2520 if (!context) {
2521 return NS_ERROR_FAILURE;
2524 globalObject = scriptGlobal;
2527 // Update our current script.
2528 // This must be destroyed after destroying nsAutoMicroTask, see:
2529 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620505#c4
2530 nsIScriptElement* currentScript =
2531 aRequest->IsModuleRequest()
2532 ? nullptr
2533 : aRequest->GetScriptLoadContext()->GetScriptElement();
2534 AutoCurrentScriptUpdater scriptUpdater(this, currentScript);
2536 Maybe<AutoSetProcessingScriptTag> setProcessingScriptTag;
2537 if (context) {
2538 setProcessingScriptTag.emplace(context);
2541 // https://wicg.github.io/import-maps/#integration-script-type
2542 // Switch on the script's type for scriptElement:
2543 // "importmap"
2544 // Assert: Never reached.
2545 MOZ_ASSERT(!aRequest->IsImportMapRequest());
2547 if (aRequest->IsModuleRequest()) {
2548 return aRequest->AsModuleRequest()->EvaluateModule();
2551 return EvaluateScript(globalObject, aRequest);
2554 nsresult ScriptLoader::CompileOrDecodeClassicScript(
2555 JSContext* aCx, JSExecutionContext& aExec, ScriptLoadRequest* aRequest) {
2556 nsAutoCString profilerLabelString;
2557 aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString);
2559 nsresult rv;
2560 if (aRequest->IsBytecode()) {
2561 if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) {
2562 LOG(("ScriptLoadRequest (%p): Decode Bytecode & instantiate and Execute",
2563 aRequest));
2564 rv = aExec.JoinOffThread(aRequest->GetScriptLoadContext());
2565 } else {
2566 LOG(("ScriptLoadRequest (%p): Decode Bytecode and Execute", aRequest));
2567 AUTO_PROFILER_MARKER_TEXT("BytecodeDecodeMainThread", JS,
2568 MarkerInnerWindowIdFromJSContext(aCx),
2569 profilerLabelString);
2571 rv = aExec.Decode(aRequest->mScriptBytecode, aRequest->mBytecodeOffset);
2574 // We do not expect to be saving anything when we already have some
2575 // bytecode.
2576 MOZ_ASSERT(!aRequest->mCacheInfo);
2577 return rv;
2580 MOZ_ASSERT(aRequest->IsSource());
2581 bool encodeBytecode = ShouldCacheBytecode(aRequest);
2582 aExec.SetEncodeBytecode(encodeBytecode);
2584 if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) {
2585 // Off-main-thread parsing.
2586 LOG(
2587 ("ScriptLoadRequest (%p): instantiate off-thread result and "
2588 "Execute",
2589 aRequest));
2590 MOZ_ASSERT(aRequest->IsTextSource());
2591 rv = aExec.JoinOffThread(aRequest->GetScriptLoadContext());
2592 } else {
2593 // Main thread parsing (inline and small scripts)
2594 LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest));
2595 MOZ_ASSERT(aRequest->IsTextSource());
2596 MaybeSourceText maybeSource;
2597 rv = aRequest->GetScriptSource(aCx, &maybeSource);
2598 if (NS_SUCCEEDED(rv)) {
2599 AUTO_PROFILER_MARKER_TEXT("ScriptCompileMainThread", JS,
2600 MarkerInnerWindowIdFromJSContext(aCx),
2601 profilerLabelString);
2603 auto compile = [&](auto& source) { return aExec.Compile(source); };
2605 MOZ_ASSERT(!maybeSource.empty());
2606 TimeStamp startTime = TimeStamp::Now();
2607 rv = maybeSource.mapNonEmpty(compile);
2608 mMainThreadParseTime += TimeStamp::Now() - startTime;
2611 return rv;
2614 /* static */
2615 nsCString& ScriptLoader::BytecodeMimeTypeFor(ScriptLoadRequest* aRequest) {
2616 if (aRequest->IsModuleRequest()) {
2617 return nsContentUtils::JSModuleBytecodeMimeType();
2619 return nsContentUtils::JSScriptBytecodeMimeType();
2622 void ScriptLoader::MaybePrepareForBytecodeEncodingBeforeExecute(
2623 ScriptLoadRequest* aRequest, JS::Handle<JSScript*> aScript) {
2624 if (!ShouldCacheBytecode(aRequest)) {
2625 return;
2628 aRequest->MarkForBytecodeEncoding(aScript);
2631 nsresult ScriptLoader::MaybePrepareForBytecodeEncodingAfterExecute(
2632 ScriptLoadRequest* aRequest, nsresult aRv) {
2633 if (aRequest->IsMarkedForBytecodeEncoding()) {
2634 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2635 "scriptloader_encode");
2636 // NOTE: This assertion will fail once we start encoding more data after the
2637 // first encode.
2638 MOZ_ASSERT(aRequest->mBytecodeOffset == aRequest->mScriptBytecode.length());
2639 RegisterForBytecodeEncoding(aRequest);
2640 MOZ_ASSERT(IsAlreadyHandledForBytecodeEncodingPreparation(aRequest));
2642 return aRv;
2645 LOG(("ScriptLoadRequest (%p): Bytecode-cache: disabled (rv = %X)", aRequest,
2646 unsigned(aRv)));
2647 TRACE_FOR_TEST_NONE(aRequest->GetScriptLoadContext()->GetScriptElement(),
2648 "scriptloader_no_encode");
2649 aRequest->mCacheInfo = nullptr;
2650 MOZ_ASSERT(IsAlreadyHandledForBytecodeEncodingPreparation(aRequest));
2652 return aRv;
2655 bool ScriptLoader::IsAlreadyHandledForBytecodeEncodingPreparation(
2656 ScriptLoadRequest* aRequest) {
2657 return aRequest->isInList() || !aRequest->mCacheInfo;
2660 void ScriptLoader::MaybePrepareModuleForBytecodeEncodingBeforeExecute(
2661 JSContext* aCx, ModuleLoadRequest* aRequest) {
2663 ModuleScript* moduleScript = aRequest->mModuleScript;
2664 JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
2666 if (aRequest->IsMarkedForBytecodeEncoding()) {
2667 // This module is imported multiple times, and already marked.
2668 return;
2671 if (ShouldCacheBytecode(aRequest)) {
2672 aRequest->MarkModuleForBytecodeEncoding();
2676 for (ModuleLoadRequest* childRequest : aRequest->mImports) {
2677 MaybePrepareModuleForBytecodeEncodingBeforeExecute(aCx, childRequest);
2681 nsresult ScriptLoader::MaybePrepareModuleForBytecodeEncodingAfterExecute(
2682 ModuleLoadRequest* aRequest, nsresult aRv) {
2683 if (IsAlreadyHandledForBytecodeEncodingPreparation(aRequest)) {
2684 // This module is imported multiple times and already handled.
2685 return aRv;
2688 aRv = MaybePrepareForBytecodeEncodingAfterExecute(aRequest, aRv);
2690 for (ModuleLoadRequest* childRequest : aRequest->mImports) {
2691 aRv = MaybePrepareModuleForBytecodeEncodingAfterExecute(childRequest, aRv);
2694 return aRv;
2697 nsresult ScriptLoader::EvaluateScript(nsIGlobalObject* aGlobalObject,
2698 ScriptLoadRequest* aRequest) {
2699 nsAutoMicroTask mt;
2700 AutoEntryScript aes(aGlobalObject, "EvaluateScript", true);
2701 JSContext* cx = aes.cx();
2703 nsAutoCString profilerLabelString;
2704 aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString);
2706 // Create a ClassicScript object and associate it with the JSScript.
2707 RefPtr<ClassicScript> classicScript = new ClassicScript(
2708 aRequest->ReferrerPolicy(), aRequest->mFetchOptions, aRequest->mBaseURL);
2709 JS::Rooted<JS::Value> classicScriptValue(cx, JS::PrivateValue(classicScript));
2711 JS::CompileOptions options(cx);
2712 JS::Rooted<JSScript*> introductionScript(cx);
2713 nsresult rv =
2714 FillCompileOptionsForRequest(cx, aRequest, &options, &introductionScript);
2716 if (NS_FAILED(rv)) {
2717 return rv;
2720 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2721 "scriptloader_execute");
2722 JS::Rooted<JSObject*> global(cx, aGlobalObject->GetGlobalJSObject());
2723 JSExecutionContext exec(cx, global, options, classicScriptValue,
2724 introductionScript);
2726 rv = CompileOrDecodeClassicScript(cx, exec, aRequest);
2728 if (NS_FAILED(rv)) {
2729 return rv;
2732 // TODO (yulia): rewrite this section. rv can be a failing pattern other than
2733 // NS_OK which will pass the NS_FAILED check above. If we call exec.GetScript
2734 // in that case, it will crash.
2735 if (rv == NS_OK) {
2736 JS::Rooted<JSScript*> script(cx, exec.GetScript());
2737 MaybePrepareForBytecodeEncodingBeforeExecute(aRequest, script);
2740 LOG(("ScriptLoadRequest (%p): Evaluate Script", aRequest));
2741 AUTO_PROFILER_MARKER_TEXT("ScriptExecution", JS,
2742 MarkerInnerWindowIdFromJSContext(cx),
2743 profilerLabelString);
2745 rv = ExecuteCompiledScript(cx, exec, classicScript);
2749 // This must be called also for compilation failure case, in order to
2750 // dispatch test-only event.
2751 rv = MaybePrepareForBytecodeEncodingAfterExecute(aRequest, rv);
2753 // Even if we are not saving the bytecode of the current script, we have
2754 // to trigger the encoding of the bytecode, as the current script can
2755 // call functions of a script for which we are recording the bytecode.
2756 LOG(("ScriptLoadRequest (%p): ScriptLoader = %p", aRequest, this));
2757 MaybeTriggerBytecodeEncoding();
2759 return rv;
2762 /* static */
2763 LoadedScript* ScriptLoader::GetActiveScript(JSContext* aCx) {
2764 JS::Value value = JS::GetScriptedCallerPrivate(aCx);
2765 if (value.isUndefined()) {
2766 return nullptr;
2769 return static_cast<LoadedScript*>(value.toPrivate());
2772 void ScriptLoader::RegisterForBytecodeEncoding(ScriptLoadRequest* aRequest) {
2773 MOZ_ASSERT(aRequest->mCacheInfo);
2774 MOZ_ASSERT(aRequest->IsMarkedForBytecodeEncoding());
2775 MOZ_DIAGNOSTIC_ASSERT(!aRequest->isInList());
2776 mBytecodeEncodingQueue.AppendElement(aRequest);
2779 void ScriptLoader::LoadEventFired() {
2780 mLoadEventFired = true;
2781 MaybeTriggerBytecodeEncoding();
2783 if (!mMainThreadParseTime.IsZero()) {
2784 Telemetry::Accumulate(
2785 Telemetry::JS_PAGELOAD_PARSE_MS,
2786 static_cast<uint32_t>(mMainThreadParseTime.ToMilliseconds()));
2790 void ScriptLoader::Destroy() {
2791 if (mShutdownObserver) {
2792 mShutdownObserver->Unregister();
2793 mShutdownObserver = nullptr;
2796 CancelAndClearScriptLoadRequests();
2797 GiveUpBytecodeEncoding();
2800 void ScriptLoader::MaybeTriggerBytecodeEncoding() {
2801 // If we already gave up, ensure that we are not going to enqueue any script,
2802 // and that we finalize them properly.
2803 if (mGiveUpEncoding) {
2804 LOG(("ScriptLoader (%p): Keep giving-up bytecode encoding.", this));
2805 GiveUpBytecodeEncoding();
2806 return;
2809 // We wait for the load event to be fired before saving the bytecode of
2810 // any script to the cache. It is quite common to have load event
2811 // listeners trigger more JavaScript execution, that we want to save as
2812 // part of this start-up bytecode cache.
2813 if (!mLoadEventFired) {
2814 LOG(("ScriptLoader (%p): Wait for the load-end event to fire.", this));
2815 return;
2818 // No need to fire any event if there is no bytecode to be saved.
2819 if (mBytecodeEncodingQueue.isEmpty()) {
2820 LOG(("ScriptLoader (%p): No script in queue to be encoded.", this));
2821 return;
2824 // Wait until all scripts are loaded before saving the bytecode, such that
2825 // we capture most of the intialization of the page.
2826 if (HasPendingRequests()) {
2827 LOG(("ScriptLoader (%p): Wait for other pending request to finish.", this));
2828 return;
2831 // Create a new runnable dedicated to encoding the content of the bytecode of
2832 // all enqueued scripts when the document is idle. In case of failure, we
2833 // give-up on encoding the bytecode.
2834 nsCOMPtr<nsIRunnable> encoder = NewRunnableMethod(
2835 "ScriptLoader::EncodeBytecode", this, &ScriptLoader::EncodeBytecode);
2836 if (NS_FAILED(NS_DispatchToCurrentThreadQueue(encoder.forget(),
2837 EventQueuePriority::Idle))) {
2838 GiveUpBytecodeEncoding();
2839 return;
2842 LOG(("ScriptLoader (%p): Schedule bytecode encoding.", this));
2845 void ScriptLoader::EncodeBytecode() {
2846 LOG(("ScriptLoader (%p): Start bytecode encoding.", this));
2848 // If any script got added in the previous loop cycle, wait until all
2849 // remaining script executions are completed, such that we capture most of
2850 // the initialization.
2851 if (HasPendingRequests()) {
2852 return;
2855 // Should not be encoding modules at all.
2856 nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
2857 if (!globalObject) {
2858 GiveUpBytecodeEncoding();
2859 return;
2862 nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
2863 if (!context) {
2864 GiveUpBytecodeEncoding();
2865 return;
2868 AutoEntryScript aes(globalObject, "encode bytecode", true);
2869 RefPtr<ScriptLoadRequest> request;
2870 while (!mBytecodeEncodingQueue.isEmpty()) {
2871 request = mBytecodeEncodingQueue.StealFirst();
2872 MOZ_ASSERT(!IsWebExtensionRequest(request),
2873 "Bytecode for web extension content scrips is not cached");
2874 EncodeRequestBytecode(aes.cx(), request);
2875 request->mScriptBytecode.clearAndFree();
2876 request->DropBytecodeCacheReferences();
2880 void ScriptLoader::EncodeRequestBytecode(JSContext* aCx,
2881 ScriptLoadRequest* aRequest) {
2882 using namespace mozilla::Telemetry;
2883 nsresult rv = NS_OK;
2884 MOZ_ASSERT(aRequest->mCacheInfo);
2885 auto bytecodeFailed = mozilla::MakeScopeExit([&]() {
2886 TRACE_FOR_TEST_NONE(aRequest->GetScriptLoadContext()->GetScriptElement(),
2887 "scriptloader_bytecode_failed");
2890 bool result;
2891 if (aRequest->IsModuleRequest()) {
2892 ModuleScript* moduleScript = aRequest->AsModuleRequest()->mModuleScript;
2893 JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
2894 result =
2895 JS::FinishIncrementalEncoding(aCx, module, aRequest->mScriptBytecode);
2896 } else {
2897 JS::Rooted<JSScript*> script(aCx, aRequest->mScriptForBytecodeEncoding);
2898 result =
2899 JS::FinishIncrementalEncoding(aCx, script, aRequest->mScriptBytecode);
2901 if (!result) {
2902 // Encoding can be aborted for non-supported syntax (e.g. asm.js), or
2903 // any other internal error.
2904 // We don't care the error and just give up encoding.
2905 JS_ClearPendingException(aCx);
2907 LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", aRequest));
2908 return;
2911 Vector<uint8_t> compressedBytecode;
2912 // TODO probably need to move this to a helper thread
2913 if (!ScriptBytecodeCompress(aRequest->mScriptBytecode,
2914 aRequest->mBytecodeOffset, compressedBytecode)) {
2915 return;
2918 if (compressedBytecode.length() >= UINT32_MAX) {
2919 LOG(
2920 ("ScriptLoadRequest (%p): Bytecode cache is too large to be decoded "
2921 "correctly.",
2922 aRequest));
2923 return;
2926 // Open the output stream to the cache entry alternate data storage. This
2927 // might fail if the stream is already open by another request, in which
2928 // case, we just ignore the current one.
2929 nsCOMPtr<nsIAsyncOutputStream> output;
2930 rv = aRequest->mCacheInfo->OpenAlternativeOutputStream(
2931 BytecodeMimeTypeFor(aRequest),
2932 static_cast<int64_t>(compressedBytecode.length()),
2933 getter_AddRefs(output));
2934 if (NS_FAILED(rv)) {
2935 LOG(
2936 ("ScriptLoadRequest (%p): Cannot open bytecode cache (rv = %X, output "
2937 "= %p)",
2938 aRequest, unsigned(rv), output.get()));
2939 return;
2941 MOZ_ASSERT(output);
2943 auto closeOutStream = mozilla::MakeScopeExit([&]() {
2944 rv = output->CloseWithStatus(rv);
2945 LOG(("ScriptLoadRequest (%p): Closing (rv = %X)", aRequest, unsigned(rv)));
2948 uint32_t n;
2949 rv = output->Write(reinterpret_cast<char*>(compressedBytecode.begin()),
2950 compressedBytecode.length(), &n);
2951 LOG(
2952 ("ScriptLoadRequest (%p): Write bytecode cache (rv = %X, length = %u, "
2953 "written = %u)",
2954 aRequest, unsigned(rv), unsigned(compressedBytecode.length()), n));
2955 if (NS_FAILED(rv)) {
2956 return;
2959 MOZ_RELEASE_ASSERT(compressedBytecode.length() == n);
2961 bytecodeFailed.release();
2962 TRACE_FOR_TEST_NONE(aRequest->GetScriptLoadContext()->GetScriptElement(),
2963 "scriptloader_bytecode_saved");
2966 void ScriptLoader::GiveUpBytecodeEncoding() {
2967 // If the document went away prematurely, we still want to set this, in order
2968 // to avoid queuing more scripts.
2969 mGiveUpEncoding = true;
2971 // Ideally we prefer to properly end the incremental encoder, such that we
2972 // would not keep a large buffer around. If we cannot, we fallback on the
2973 // removal of all request from the current list and these large buffers would
2974 // be removed at the same time as the source object.
2975 nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
2976 AutoAllowLegacyScriptExecution exemption;
2977 Maybe<AutoEntryScript> aes;
2979 if (globalObject) {
2980 nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
2981 if (context) {
2982 aes.emplace(globalObject, "give-up bytecode encoding", true);
2986 while (!mBytecodeEncodingQueue.isEmpty()) {
2987 RefPtr<ScriptLoadRequest> request = mBytecodeEncodingQueue.StealFirst();
2988 LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", request.get()));
2989 TRACE_FOR_TEST_NONE(request->GetScriptLoadContext()->GetScriptElement(),
2990 "scriptloader_bytecode_failed");
2991 MOZ_ASSERT(!IsWebExtensionRequest(request));
2993 if (aes.isSome()) {
2994 if (request->IsModuleRequest()) {
2995 ModuleScript* moduleScript = request->AsModuleRequest()->mModuleScript;
2996 JS::Rooted<JSObject*> module(aes->cx(), moduleScript->ModuleRecord());
2997 JS::AbortIncrementalEncoding(module);
2998 } else {
2999 JS::Rooted<JSScript*> script(aes->cx(),
3000 request->mScriptForBytecodeEncoding);
3001 JS::AbortIncrementalEncoding(script);
3005 request->mScriptBytecode.clearAndFree();
3006 request->DropBytecodeCacheReferences();
3010 bool ScriptLoader::HasPendingRequests() const {
3011 return mParserBlockingRequest || !mXSLTRequests.isEmpty() ||
3012 !mLoadedAsyncRequests.isEmpty() ||
3013 !mNonAsyncExternalScriptInsertedRequests.isEmpty() ||
3014 !mDeferRequests.isEmpty() || HasPendingDynamicImports() ||
3015 !mPendingChildLoaders.IsEmpty();
3016 // mOffThreadCompilingRequests are already being processed.
3019 bool ScriptLoader::HasPendingDynamicImports() const {
3020 if (mModuleLoader && mModuleLoader->HasPendingDynamicImports()) {
3021 return true;
3024 for (ModuleLoader* loader : mWebExtModuleLoaders) {
3025 if (loader->HasPendingDynamicImports()) {
3026 return true;
3030 for (ModuleLoader* loader : mShadowRealmModuleLoaders) {
3031 if (loader->HasPendingDynamicImports()) {
3032 return true;
3036 return false;
3039 void ScriptLoader::ProcessPendingRequestsAsync() {
3040 if (HasPendingRequests()) {
3041 nsCOMPtr<nsIRunnable> task =
3042 NewRunnableMethod("dom::ScriptLoader::ProcessPendingRequests", this,
3043 &ScriptLoader::ProcessPendingRequests);
3044 if (mDocument) {
3045 mDocument->Dispatch(task.forget());
3046 } else {
3047 NS_DispatchToCurrentThread(task.forget());
3052 void ScriptLoader::ProcessPendingRequests() {
3053 RefPtr<ScriptLoadRequest> request;
3055 if (mParserBlockingRequest && mParserBlockingRequest->IsFinished() &&
3056 ReadyToExecuteParserBlockingScripts()) {
3057 request.swap(mParserBlockingRequest);
3058 UnblockParser(request);
3059 ProcessRequest(request);
3060 ContinueParserAsync(request);
3063 while (ReadyToExecuteParserBlockingScripts() && !mXSLTRequests.isEmpty() &&
3064 mXSLTRequests.getFirst()->IsFinished()) {
3065 request = mXSLTRequests.StealFirst();
3066 ProcessRequest(request);
3069 while (ReadyToExecuteScripts() && !mLoadedAsyncRequests.isEmpty()) {
3070 request = mLoadedAsyncRequests.StealFirst();
3071 if (request->IsModuleRequest()) {
3072 ProcessRequest(request);
3073 } else {
3074 CompileOffThreadOrProcessRequest(request);
3078 while (ReadyToExecuteScripts() &&
3079 !mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
3080 mNonAsyncExternalScriptInsertedRequests.getFirst()->IsFinished()) {
3081 // Violate the HTML5 spec and execute these in the insertion order in
3082 // order to make LABjs and the "order" plug-in for RequireJS work with
3083 // their Gecko-sniffed code path. See
3084 // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
3085 request = mNonAsyncExternalScriptInsertedRequests.StealFirst();
3086 ProcessRequest(request);
3089 if (mDeferCheckpointReached && mXSLTRequests.isEmpty()) {
3090 while (ReadyToExecuteScripts() && !mDeferRequests.isEmpty() &&
3091 mDeferRequests.getFirst()->IsFinished()) {
3092 request = mDeferRequests.StealFirst();
3093 ProcessRequest(request);
3097 while (!mPendingChildLoaders.IsEmpty() &&
3098 ReadyToExecuteParserBlockingScripts()) {
3099 RefPtr<ScriptLoader> child = mPendingChildLoaders[0];
3100 mPendingChildLoaders.RemoveElementAt(0);
3101 child->RemoveParserBlockingScriptExecutionBlocker();
3104 if (mDeferCheckpointReached && mDocument && !mParserBlockingRequest &&
3105 mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
3106 mXSLTRequests.isEmpty() && mDeferRequests.isEmpty() &&
3107 MaybeRemovedDeferRequests()) {
3108 return ProcessPendingRequests();
3111 if (mDeferCheckpointReached && mDocument && !mParserBlockingRequest &&
3112 mLoadingAsyncRequests.isEmpty() && mLoadedAsyncRequests.isEmpty() &&
3113 mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
3114 mXSLTRequests.isEmpty() && mDeferRequests.isEmpty()) {
3115 // No more pending scripts; time to unblock onload.
3116 // OK to unblock onload synchronously here, since callers must be
3117 // prepared for the world changing anyway.
3118 mDeferCheckpointReached = false;
3119 mDocument->UnblockOnload(true);
3123 bool ScriptLoader::ReadyToExecuteParserBlockingScripts() {
3124 // Make sure the SelfReadyToExecuteParserBlockingScripts check is first, so
3125 // that we don't block twice on an ancestor.
3126 if (!SelfReadyToExecuteParserBlockingScripts()) {
3127 return false;
3130 if (mDocument && mDocument->GetWindowContext()) {
3131 for (WindowContext* wc =
3132 mDocument->GetWindowContext()->GetParentWindowContext();
3133 wc; wc = wc->GetParentWindowContext()) {
3134 if (Document* doc = wc->GetDocument()) {
3135 ScriptLoader* ancestor = doc->ScriptLoader();
3136 if (!ancestor->SelfReadyToExecuteParserBlockingScripts() &&
3137 ancestor->AddPendingChildLoader(this)) {
3138 AddParserBlockingScriptExecutionBlocker();
3139 return false;
3145 return true;
3148 template <typename Unit>
3149 static nsresult ConvertToUnicode(nsIChannel* aChannel, const uint8_t* aData,
3150 uint32_t aLength,
3151 const nsAString& aHintCharset,
3152 Document* aDocument, Unit*& aBufOut,
3153 size_t& aLengthOut) {
3154 if (!aLength) {
3155 aBufOut = nullptr;
3156 aLengthOut = 0;
3157 return NS_OK;
3160 auto data = Span(aData, aLength);
3162 // The encoding info precedence is as follows from high to low:
3163 // The BOM
3164 // HTTP Content-Type (if name recognized)
3165 // charset attribute (if name recognized)
3166 // The encoding of the document
3168 UniquePtr<Decoder> unicodeDecoder;
3170 const Encoding* encoding;
3171 std::tie(encoding, std::ignore) = Encoding::ForBOM(data);
3172 if (encoding) {
3173 unicodeDecoder = encoding->NewDecoderWithBOMRemoval();
3176 if (!unicodeDecoder && aChannel) {
3177 nsAutoCString label;
3178 if (NS_SUCCEEDED(aChannel->GetContentCharset(label)) &&
3179 (encoding = Encoding::ForLabel(label))) {
3180 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
3184 if (!unicodeDecoder && (encoding = Encoding::ForLabel(aHintCharset))) {
3185 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
3188 if (!unicodeDecoder && aDocument) {
3189 unicodeDecoder =
3190 aDocument->GetDocumentCharacterSet()->NewDecoderWithoutBOMHandling();
3193 if (!unicodeDecoder) {
3194 // Curiously, there are various callers that don't pass aDocument. The
3195 // fallback in the old code was ISO-8859-1, which behaved like
3196 // windows-1252.
3197 unicodeDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
3200 auto signalOOM = mozilla::MakeScopeExit([&aBufOut, &aLengthOut]() {
3201 aBufOut = nullptr;
3202 aLengthOut = 0;
3205 CheckedInt<size_t> bufferLength =
3206 ScriptDecoding<Unit>::MaxBufferLength(unicodeDecoder, aLength);
3207 if (!bufferLength.isValid()) {
3208 return NS_ERROR_OUT_OF_MEMORY;
3211 CheckedInt<size_t> bufferByteSize = bufferLength * sizeof(Unit);
3212 if (!bufferByteSize.isValid()) {
3213 return NS_ERROR_OUT_OF_MEMORY;
3216 aBufOut = static_cast<Unit*>(js_malloc(bufferByteSize.value()));
3217 if (!aBufOut) {
3218 return NS_ERROR_OUT_OF_MEMORY;
3221 signalOOM.release();
3222 aLengthOut = ScriptDecoding<Unit>::DecodeInto(
3223 unicodeDecoder, data, Span(aBufOut, bufferLength.value()),
3224 /* aEndOfSource = */ true);
3225 return NS_OK;
3228 /* static */
3229 nsresult ScriptLoader::ConvertToUTF16(
3230 nsIChannel* aChannel, const uint8_t* aData, uint32_t aLength,
3231 const nsAString& aHintCharset, Document* aDocument,
3232 UniquePtr<char16_t[], JS::FreePolicy>& aBufOut, size_t& aLengthOut) {
3233 char16_t* bufOut;
3234 nsresult rv = ConvertToUnicode(aChannel, aData, aLength, aHintCharset,
3235 aDocument, bufOut, aLengthOut);
3236 if (NS_SUCCEEDED(rv)) {
3237 aBufOut.reset(bufOut);
3239 return rv;
3242 /* static */
3243 nsresult ScriptLoader::ConvertToUTF8(
3244 nsIChannel* aChannel, const uint8_t* aData, uint32_t aLength,
3245 const nsAString& aHintCharset, Document* aDocument,
3246 UniquePtr<Utf8Unit[], JS::FreePolicy>& aBufOut, size_t& aLengthOut) {
3247 Utf8Unit* bufOut;
3248 nsresult rv = ConvertToUnicode(aChannel, aData, aLength, aHintCharset,
3249 aDocument, bufOut, aLengthOut);
3250 if (NS_SUCCEEDED(rv)) {
3251 aBufOut.reset(bufOut);
3253 return rv;
3256 nsresult ScriptLoader::OnStreamComplete(
3257 nsIIncrementalStreamLoader* aLoader, ScriptLoadRequest* aRequest,
3258 nsresult aChannelStatus, nsresult aSRIStatus,
3259 SRICheckDataVerifier* aSRIDataVerifier) {
3260 NS_ASSERTION(aRequest, "null request in stream complete handler");
3261 NS_ENSURE_TRUE(aRequest, NS_ERROR_FAILURE);
3263 nsresult rv = VerifySRI(aRequest, aLoader, aSRIStatus, aSRIDataVerifier);
3265 if (NS_SUCCEEDED(rv)) {
3266 // If we are loading from source, save the computed SRI hash or a dummy SRI
3267 // hash in case we are going to save the bytecode of this script in the
3268 // cache.
3269 if (aRequest->IsSource()) {
3270 uint32_t sriLength = 0;
3271 rv = SaveSRIHash(aRequest, aSRIDataVerifier, &sriLength);
3272 MOZ_ASSERT_IF(NS_SUCCEEDED(rv),
3273 aRequest->mScriptBytecode.length() == sriLength);
3275 aRequest->mBytecodeOffset = JS::AlignTranscodingBytecodeOffset(sriLength);
3276 if (aRequest->mBytecodeOffset != sriLength) {
3277 // We need extra padding after SRI hash.
3278 if (!aRequest->mScriptBytecode.resize(aRequest->mBytecodeOffset)) {
3279 return NS_ERROR_OUT_OF_MEMORY;
3284 if (NS_SUCCEEDED(rv)) {
3285 rv = PrepareLoadedRequest(aRequest, aLoader, aChannelStatus);
3288 if (NS_FAILED(rv)) {
3289 ReportErrorToConsole(aRequest, rv);
3293 if (NS_FAILED(rv)) {
3294 // When loading bytecode, we verify the SRI hash. If it does not match the
3295 // one from the document we restart the load, forcing us to load the source
3296 // instead. If this happens do not remove the current request from script
3297 // loader's data structures or fire any events.
3298 if (aChannelStatus != NS_BINDING_RETARGETED) {
3299 HandleLoadError(aRequest, rv);
3303 // Process our request and/or any pending ones
3304 ProcessPendingRequests();
3306 return rv;
3309 nsresult ScriptLoader::VerifySRI(ScriptLoadRequest* aRequest,
3310 nsIIncrementalStreamLoader* aLoader,
3311 nsresult aSRIStatus,
3312 SRICheckDataVerifier* aSRIDataVerifier) const {
3313 nsCOMPtr<nsIRequest> channelRequest;
3314 aLoader->GetRequest(getter_AddRefs(channelRequest));
3315 nsCOMPtr<nsIChannel> channel;
3316 channel = do_QueryInterface(channelRequest);
3318 nsresult rv = NS_OK;
3319 if (!aRequest->mIntegrity.IsEmpty() && NS_SUCCEEDED((rv = aSRIStatus))) {
3320 MOZ_ASSERT(aSRIDataVerifier);
3321 MOZ_ASSERT(mReporter);
3323 nsAutoCString sourceUri;
3324 if (mDocument && mDocument->GetDocumentURI()) {
3325 mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
3327 rv = aSRIDataVerifier->Verify(aRequest->mIntegrity, channel, sourceUri,
3328 mReporter);
3329 if (channelRequest) {
3330 mReporter->FlushReportsToConsole(
3331 nsContentUtils::GetInnerWindowID(channelRequest));
3332 } else {
3333 mReporter->FlushConsoleReports(mDocument);
3335 if (NS_FAILED(rv)) {
3336 rv = NS_ERROR_SRI_CORRUPT;
3340 return rv;
3343 nsresult ScriptLoader::SaveSRIHash(ScriptLoadRequest* aRequest,
3344 SRICheckDataVerifier* aSRIDataVerifier,
3345 uint32_t* sriLength) const {
3346 MOZ_ASSERT(aRequest->IsSource());
3347 MOZ_ASSERT(aRequest->mScriptBytecode.empty());
3349 uint32_t len;
3351 // If the integrity metadata does not correspond to a valid hash function,
3352 // IsComplete would be false.
3353 if (!aRequest->mIntegrity.IsEmpty() && aSRIDataVerifier->IsComplete()) {
3354 MOZ_ASSERT(aRequest->mScriptBytecode.length() == 0);
3356 // Encode the SRI computed hash.
3357 len = aSRIDataVerifier->DataSummaryLength();
3359 if (!aRequest->mScriptBytecode.resize(len)) {
3360 return NS_ERROR_OUT_OF_MEMORY;
3363 DebugOnly<nsresult> res = aSRIDataVerifier->ExportDataSummary(
3364 len, aRequest->mScriptBytecode.begin());
3365 MOZ_ASSERT(NS_SUCCEEDED(res));
3366 } else {
3367 MOZ_ASSERT(aRequest->mScriptBytecode.length() == 0);
3369 // Encode a dummy SRI hash.
3370 len = SRICheckDataVerifier::EmptyDataSummaryLength();
3372 if (!aRequest->mScriptBytecode.resize(len)) {
3373 return NS_ERROR_OUT_OF_MEMORY;
3376 DebugOnly<nsresult> res = SRICheckDataVerifier::ExportEmptyDataSummary(
3377 len, aRequest->mScriptBytecode.begin());
3378 MOZ_ASSERT(NS_SUCCEEDED(res));
3381 // Verify that the exported and predicted length correspond.
3382 DebugOnly<uint32_t> srilen{};
3383 MOZ_ASSERT(NS_SUCCEEDED(SRICheckDataVerifier::DataSummaryLength(
3384 len, aRequest->mScriptBytecode.begin(), &srilen)));
3385 MOZ_ASSERT(srilen == len);
3387 *sriLength = len;
3389 return NS_OK;
3392 void ScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest,
3393 nsresult aResult) const {
3394 MOZ_ASSERT(aRequest);
3396 if (aRequest->GetScriptLoadContext()->IsPreload()) {
3397 // Skip reporting errors in preload requests. If the request is actually
3398 // used then we will report the error in ReportPreloadErrorsToConsole below.
3399 aRequest->GetScriptLoadContext()->mUnreportedPreloadError = aResult;
3400 return;
3403 bool isScript = !aRequest->IsModuleRequest();
3404 const char* message;
3405 if (aResult == NS_ERROR_MALFORMED_URI) {
3406 message = isScript ? "ScriptSourceMalformed" : "ModuleSourceMalformed";
3407 } else if (aResult == NS_ERROR_DOM_BAD_URI) {
3408 message = isScript ? "ScriptSourceNotAllowed" : "ModuleSourceNotAllowed";
3409 } else if (aResult == NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI) {
3410 MOZ_ASSERT(!isScript);
3411 message = "WebExtContentScriptModuleSourceNotAllowed";
3412 } else if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
3413 aResult)) {
3414 // Blocking classifier error codes already show their own console messages.
3415 return;
3416 } else {
3417 message = isScript ? "ScriptSourceLoadFailed" : "ModuleSourceLoadFailed";
3420 AutoTArray<nsString, 1> params;
3421 CopyUTF8toUTF16(aRequest->mURI->GetSpecOrDefault(), *params.AppendElement());
3423 nsIScriptElement* element =
3424 aRequest->GetScriptLoadContext()->GetScriptElement();
3425 uint32_t lineNo = element ? element->GetScriptLineNumber() : 0;
3426 uint32_t columnNo = element ? element->GetScriptColumnNumber() : 0;
3428 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3429 "Script Loader"_ns, mDocument,
3430 nsContentUtils::eDOM_PROPERTIES, message,
3431 params, nullptr, u""_ns, lineNo, columnNo);
3434 void ScriptLoader::ReportWarningToConsole(
3435 ScriptLoadRequest* aRequest, const char* aMessageName,
3436 const nsTArray<nsString>& aParams) const {
3437 nsIScriptElement* element =
3438 aRequest->GetScriptLoadContext()->GetScriptElement();
3439 uint32_t lineNo = element ? element->GetScriptLineNumber() : 0;
3440 uint32_t columnNo = element ? element->GetScriptColumnNumber() : 0;
3441 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3442 "Script Loader"_ns, mDocument,
3443 nsContentUtils::eDOM_PROPERTIES, aMessageName,
3444 aParams, nullptr, u""_ns, lineNo, columnNo);
3447 void ScriptLoader::ReportPreloadErrorsToConsole(ScriptLoadRequest* aRequest) {
3448 if (NS_FAILED(aRequest->GetScriptLoadContext()->mUnreportedPreloadError)) {
3449 ReportErrorToConsole(
3450 aRequest, aRequest->GetScriptLoadContext()->mUnreportedPreloadError);
3451 aRequest->GetScriptLoadContext()->mUnreportedPreloadError = NS_OK;
3454 if (aRequest->IsModuleRequest()) {
3455 for (const auto& childRequest : aRequest->AsModuleRequest()->mImports) {
3456 ReportPreloadErrorsToConsole(childRequest);
3461 void ScriptLoader::HandleLoadError(ScriptLoadRequest* aRequest,
3462 nsresult aResult) {
3464 * Handle script not loading error because source was an tracking URL (or
3465 * fingerprinting, cryptomining, etc).
3466 * We make a note of this script node by including it in a dedicated
3467 * array of blocked tracking nodes under its parent document.
3469 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
3470 aResult)) {
3471 nsCOMPtr<nsIContent> cont =
3472 do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement());
3473 mDocument->AddBlockedNodeByClassifier(cont);
3476 if (aRequest->IsModuleRequest()) {
3477 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mIsInline);
3478 aRequest->AsModuleRequest()->OnFetchComplete(aResult);
3481 if (aRequest->GetScriptLoadContext()->mInDeferList) {
3482 MOZ_ASSERT_IF(aRequest->IsModuleRequest(),
3483 aRequest->AsModuleRequest()->IsTopLevel());
3484 if (aRequest->isInList()) {
3485 RefPtr<ScriptLoadRequest> req = mDeferRequests.Steal(aRequest);
3486 FireScriptAvailable(aResult, req);
3488 } else if (aRequest->GetScriptLoadContext()->mInAsyncList) {
3489 MOZ_ASSERT_IF(aRequest->IsModuleRequest(),
3490 aRequest->AsModuleRequest()->IsTopLevel());
3491 if (aRequest->isInList()) {
3492 RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
3493 FireScriptAvailable(aResult, req);
3495 } else if (aRequest->GetScriptLoadContext()->mIsNonAsyncScriptInserted) {
3496 if (aRequest->isInList()) {
3497 RefPtr<ScriptLoadRequest> req =
3498 mNonAsyncExternalScriptInsertedRequests.Steal(aRequest);
3499 FireScriptAvailable(aResult, req);
3501 } else if (aRequest->GetScriptLoadContext()->mIsXSLT) {
3502 if (aRequest->isInList()) {
3503 RefPtr<ScriptLoadRequest> req = mXSLTRequests.Steal(aRequest);
3504 FireScriptAvailable(aResult, req);
3506 } else if (aRequest->GetScriptLoadContext()->IsPreload()) {
3507 if (aRequest->IsModuleRequest()) {
3508 aRequest->Cancel();
3510 if (aRequest->IsTopLevel()) {
3511 // Request may already have been removed by
3512 // CancelAndClearScriptLoadRequests.
3513 mPreloads.RemoveElement(aRequest, PreloadRequestComparator());
3515 MOZ_ASSERT(!aRequest->isInList());
3516 AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::LoadError);
3517 } else if (aRequest->IsModuleRequest()) {
3518 ModuleLoadRequest* modReq = aRequest->AsModuleRequest();
3519 if (modReq->IsDynamicImport()) {
3520 MOZ_ASSERT(modReq->IsTopLevel());
3521 if (aRequest->isInList()) {
3522 modReq->CancelDynamicImport(aResult);
3524 } else {
3525 MOZ_ASSERT(!modReq->IsTopLevel());
3526 MOZ_ASSERT(!modReq->isInList());
3527 modReq->Cancel();
3528 // The error is handled for the top level module.
3530 } else if (mParserBlockingRequest == aRequest) {
3531 MOZ_ASSERT(!aRequest->isInList());
3532 mParserBlockingRequest = nullptr;
3533 UnblockParser(aRequest);
3535 // Ensure that we treat aRequest->GetScriptLoadContext()->GetScriptElement()
3536 // as our current parser-inserted script while firing onerror on it.
3537 MOZ_ASSERT(aRequest->GetScriptLoadContext()
3538 ->GetScriptElement()
3539 ->GetParserCreated());
3540 nsCOMPtr<nsIScriptElement> oldParserInsertedScript =
3541 mCurrentParserInsertedScript;
3542 mCurrentParserInsertedScript =
3543 aRequest->GetScriptLoadContext()->GetScriptElement();
3544 FireScriptAvailable(aResult, aRequest);
3545 ContinueParserAsync(aRequest);
3546 mCurrentParserInsertedScript = oldParserInsertedScript;
3547 } else {
3548 // This happens for blocking requests cancelled by ParsingComplete().
3549 // Ignore cancellation status for link-preload requests, as cancellation can
3550 // be omitted for them when SRI is stronger on consumer tags.
3551 MOZ_ASSERT(aRequest->IsCanceled() ||
3552 aRequest->GetScriptLoadContext()->IsLinkPreloadScript());
3553 MOZ_ASSERT(!aRequest->isInList());
3557 void ScriptLoader::UnblockParser(ScriptLoadRequest* aParserBlockingRequest) {
3558 aParserBlockingRequest->GetScriptLoadContext()
3559 ->GetScriptElement()
3560 ->UnblockParser();
3563 void ScriptLoader::ContinueParserAsync(
3564 ScriptLoadRequest* aParserBlockingRequest) {
3565 aParserBlockingRequest->GetScriptLoadContext()
3566 ->GetScriptElement()
3567 ->ContinueParserAsync();
3570 uint32_t ScriptLoader::NumberOfProcessors() {
3571 if (mNumberOfProcessors > 0) {
3572 return mNumberOfProcessors;
3575 int32_t numProcs = PR_GetNumberOfProcessors();
3576 if (numProcs > 0) {
3577 mNumberOfProcessors = numProcs;
3579 return mNumberOfProcessors;
3582 int32_t ScriptLoader::PhysicalSizeOfMemoryInGB() {
3583 // 0 is a valid result from PR_GetPhysicalMemorySize() which
3584 // means a failure occured.
3585 if (mPhysicalSizeOfMemory >= 0) {
3586 return mPhysicalSizeOfMemory;
3589 // Save the size in GB.
3590 mPhysicalSizeOfMemory =
3591 static_cast<int32_t>(PR_GetPhysicalMemorySize() >> 30);
3592 return mPhysicalSizeOfMemory;
3595 static bool IsInternalURIScheme(nsIURI* uri) {
3596 return uri->SchemeIs("moz-extension") || uri->SchemeIs("resource") ||
3597 uri->SchemeIs("chrome");
3600 bool ScriptLoader::ShouldApplyDelazifyStrategy(ScriptLoadRequest* aRequest) {
3601 // Full parse everything if negative.
3602 if (StaticPrefs::dom_script_loader_delazification_max_size() < 0) {
3603 return true;
3606 // Be conservative on machines with 2GB or less of memory.
3607 if (PhysicalSizeOfMemoryInGB() <=
3608 StaticPrefs::dom_script_loader_delazification_min_mem()) {
3609 return false;
3612 uint32_t max_size = static_cast<uint32_t>(
3613 StaticPrefs::dom_script_loader_delazification_max_size());
3614 uint32_t script_size =
3615 aRequest->ScriptTextLength() > 0
3616 ? static_cast<uint32_t>(aRequest->ScriptTextLength())
3617 : 0;
3619 if (mTotalFullParseSize + script_size < max_size) {
3620 return true;
3623 if (LOG_ENABLED()) {
3624 nsCString url = aRequest->mURI->GetSpecOrDefault();
3625 LOG(
3626 ("ScriptLoadRequest (%p): non-on-demand-only Parsing Disabled for (%s) "
3627 "with size=%u because mTotalFullParseSize=%u would exceed max_size=%u",
3628 aRequest, url.get(), script_size, mTotalFullParseSize, max_size));
3631 return false;
3634 void ScriptLoader::ApplyDelazifyStrategy(JS::CompileOptions* aOptions) {
3635 JS::DelazificationOption strategy =
3636 JS::DelazificationOption::ParseEverythingEagerly;
3637 uint32_t strategyIndex =
3638 StaticPrefs::dom_script_loader_delazification_strategy();
3640 // Assert that all enumerated values of DelazificationOption are dense between
3641 // OnDemandOnly and ParseEverythingEagerly.
3642 #ifdef DEBUG
3643 uint32_t count = 0;
3644 uint32_t mask = 0;
3645 # define _COUNT_ENTRIES(Name) count++;
3646 # define _MASK_ENTRIES(Name) \
3647 mask |= 1 << uint32_t(JS::DelazificationOption::Name);
3649 FOREACH_DELAZIFICATION_STRATEGY(_COUNT_ENTRIES);
3650 MOZ_ASSERT(count == uint32_t(strategy) + 1);
3651 FOREACH_DELAZIFICATION_STRATEGY(_MASK_ENTRIES);
3652 MOZ_ASSERT(((mask + 1) & mask) == 0);
3653 # undef _COUNT_ENTRIES
3654 # undef _MASK_ENTRIES
3655 #endif
3657 // Any strategy index larger than ParseEverythingEagerly would default to
3658 // ParseEverythingEagerly.
3659 if (strategyIndex <= uint32_t(strategy)) {
3660 strategy = JS::DelazificationOption(uint8_t(strategyIndex));
3663 aOptions->setEagerDelazificationStrategy(strategy);
3666 bool ScriptLoader::ShouldCompileOffThread(ScriptLoadRequest* aRequest) {
3667 if (NumberOfProcessors() <= 1) {
3668 return false;
3670 if (aRequest == mParserBlockingRequest) {
3671 return true;
3673 if (SpeculativeOMTParsingEnabled()) {
3674 // Processing non async inserted scripts too early can potentially delay the
3675 // load event from firing so focus on other scripts instead.
3676 if (aRequest->GetScriptLoadContext()->mIsNonAsyncScriptInserted &&
3677 !StaticPrefs::
3678 dom_script_loader_external_scripts_speculate_non_parser_inserted_enabled()) {
3679 return false;
3682 // Async and link preload scripts do not need to be parsed right away.
3683 if (aRequest->GetScriptLoadContext()->IsAsyncScript() &&
3684 !StaticPrefs::
3685 dom_script_loader_external_scripts_speculate_async_enabled()) {
3686 return false;
3689 if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript() &&
3690 !StaticPrefs::
3691 dom_script_loader_external_scripts_speculate_link_preload_enabled()) {
3692 return false;
3695 return true;
3697 return false;
3700 nsresult ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest,
3701 nsIIncrementalStreamLoader* aLoader,
3702 nsresult aStatus) {
3703 if (NS_FAILED(aStatus)) {
3704 return aStatus;
3707 if (aRequest->IsCanceled()) {
3708 return NS_BINDING_ABORTED;
3710 MOZ_ASSERT(aRequest->IsFetching());
3711 CollectScriptTelemetry(aRequest);
3713 // If we don't have a document, then we need to abort further
3714 // evaluation.
3715 if (!mDocument) {
3716 return NS_ERROR_NOT_AVAILABLE;
3719 // If the load returned an error page, then we need to abort
3720 nsCOMPtr<nsIRequest> req;
3721 nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
3722 NS_ASSERTION(req, "StreamLoader's request went away prematurely");
3723 NS_ENSURE_SUCCESS(rv, rv);
3725 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
3726 if (httpChannel) {
3727 bool requestSucceeded;
3728 rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
3729 if (NS_SUCCEEDED(rv) && !requestSucceeded) {
3730 return NS_ERROR_NOT_AVAILABLE;
3733 if (aRequest->IsModuleRequest()) {
3734 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
3735 // Update script's referrer-policy if there's a Referrer-Policy header in
3736 // the HTTP response.
3737 ReferrerPolicy policy =
3738 nsContentUtils::GetReferrerPolicyFromChannel(httpChannel);
3739 if (policy != ReferrerPolicy::_empty) {
3740 aRequest->UpdateReferrerPolicy(policy);
3744 nsAutoCString sourceMapURL;
3745 if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
3746 aRequest->mSourceMapURL = Some(NS_ConvertUTF8toUTF16(sourceMapURL));
3749 nsCOMPtr<nsIClassifiedChannel> classifiedChannel = do_QueryInterface(req);
3750 MOZ_ASSERT(classifiedChannel);
3751 if (classifiedChannel &&
3752 classifiedChannel->IsThirdPartyTrackingResource()) {
3753 aRequest->GetScriptLoadContext()->SetIsTracking();
3757 nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
3758 // If this load was subject to a CORS check, don't flag it with a separate
3759 // origin principal, so that it will treat our document's principal as the
3760 // origin principal. Module loads always use CORS.
3761 if (!aRequest->IsModuleRequest() && aRequest->CORSMode() == CORS_NONE) {
3762 rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
3763 channel, getter_AddRefs(aRequest->mOriginPrincipal));
3764 NS_ENSURE_SUCCESS(rv, rv);
3767 // This assertion could fire errorously if we ran out of memory when
3768 // inserting the request in the array. However it's an unlikely case
3769 // so if you see this assertion it is likely something else that is
3770 // wrong, especially if you see it more than once.
3771 NS_ASSERTION(mDeferRequests.Contains(aRequest) ||
3772 mLoadingAsyncRequests.Contains(aRequest) ||
3773 mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) ||
3774 mXSLTRequests.Contains(aRequest) ||
3775 (aRequest->IsModuleRequest() &&
3776 (aRequest->AsModuleRequest()->IsRegisteredDynamicImport() ||
3777 !aRequest->AsModuleRequest()->IsTopLevel())) ||
3778 mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
3779 mParserBlockingRequest == aRequest,
3780 "aRequest should be pending!");
3782 nsCOMPtr<nsIURI> uri;
3783 rv = channel->GetOriginalURI(getter_AddRefs(uri));
3784 NS_ENSURE_SUCCESS(rv, rv);
3786 // Fixup moz-extension: and resource: URIs, because the channel URI will
3787 // point to file:, which won't be allowed to load.
3788 if (uri && IsInternalURIScheme(uri)) {
3789 aRequest->mBaseURL = uri;
3790 } else {
3791 channel->GetURI(getter_AddRefs(aRequest->mBaseURL));
3794 if (aRequest->IsModuleRequest()) {
3795 ModuleLoadRequest* request = aRequest->AsModuleRequest();
3797 // When loading a module, only responses with a JavaScript MIME type are
3798 // acceptable.
3799 nsAutoCString mimeType;
3800 channel->GetContentType(mimeType);
3801 NS_ConvertUTF8toUTF16 typeString(mimeType);
3802 if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
3803 return NS_ERROR_FAILURE;
3806 // Attempt to compile off main thread.
3807 bool couldCompile = false;
3808 rv = AttemptOffThreadScriptCompile(request, &couldCompile);
3809 NS_ENSURE_SUCCESS(rv, rv);
3810 if (couldCompile) {
3811 return NS_OK;
3814 // Otherwise compile it right away and start fetching descendents.
3815 return request->OnFetchComplete(NS_OK);
3818 // The script is now loaded and ready to run.
3819 aRequest->SetReady();
3821 // If speculative parsing is enabled attempt to compile all
3822 // external scripts off-main-thread. Otherwise, only omt compile scripts
3823 // blocking the parser.
3824 if (ShouldCompileOffThread(aRequest)) {
3825 MOZ_ASSERT(!aRequest->IsModuleRequest());
3826 bool couldCompile = false;
3827 nsresult rv = AttemptOffThreadScriptCompile(aRequest, &couldCompile);
3828 NS_ENSURE_SUCCESS(rv, rv);
3829 if (couldCompile) {
3830 MOZ_ASSERT(aRequest->mState == ScriptLoadRequest::State::Compiling,
3831 "Request should be off-thread compiling now.");
3832 return NS_OK;
3835 // If off-thread compile was rejected, continue with regular processing.
3838 MaybeMoveToLoadedList(aRequest);
3840 return NS_OK;
3843 void ScriptLoader::DeferCheckpointReached() {
3844 if (mDeferEnabled) {
3845 // Have to check because we apparently get ParsingComplete
3846 // without BeginDeferringScripts in some cases
3847 mDeferCheckpointReached = true;
3850 mDeferEnabled = false;
3851 ProcessPendingRequests();
3854 void ScriptLoader::ParsingComplete(bool aTerminated) {
3855 if (aTerminated) {
3856 CancelAndClearScriptLoadRequests();
3858 // Have to call this even if aTerminated so we'll correctly unblock onload.
3859 DeferCheckpointReached();
3863 void ScriptLoader::PreloadURI(
3864 nsIURI* aURI, const nsAString& aCharset, const nsAString& aType,
3865 const nsAString& aCrossOrigin, const nsAString& aNonce,
3866 const nsAString& aFetchPriority, const nsAString& aIntegrity,
3867 bool aScriptFromHead, bool aAsync, bool aDefer, bool aLinkPreload,
3868 const ReferrerPolicy aReferrerPolicy, uint64_t aEarlyHintPreloaderId) {
3869 NS_ENSURE_TRUE_VOID(mDocument);
3870 // Check to see if scripts has been turned off.
3871 if (!mEnabled || !mDocument->IsScriptEnabled()) {
3872 return;
3875 ScriptKind scriptKind = ScriptKind::eClassic;
3877 static const char kASCIIWhitespace[] = "\t\n\f\r ";
3879 nsAutoString type(aType);
3880 type.Trim(kASCIIWhitespace);
3881 if (type.LowerCaseEqualsASCII("module")) {
3882 scriptKind = ScriptKind::eModule;
3885 if (scriptKind == ScriptKind::eClassic && !aType.IsEmpty() &&
3886 !nsContentUtils::IsJavascriptMIMEType(aType)) {
3887 // Unknown type. Don't load it.
3888 return;
3891 SRIMetadata sriMetadata;
3892 GetSRIMetadata(aIntegrity, &sriMetadata);
3894 const auto requestPriority = FetchPriorityToRequestPriority(
3895 HTMLScriptElement::ToFetchPriority(aFetchPriority));
3897 // For link type "modulepreload":
3898 // https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload
3899 // Step 11. Let options be a script fetch options whose cryptographic nonce is
3900 // cryptographic nonce, integrity metadata is integrity metadata, parser
3901 // metadata is "not-parser-inserted", credentials mode is credentials mode,
3902 // referrer policy is referrer policy, and fetch priority is fetch priority.
3904 // We treat speculative <script> loads as parser-inserted, because they
3905 // come from a parser. This will also match how they should be treated
3906 // as a normal load.
3907 RefPtr<ScriptLoadRequest> request =
3908 CreateLoadRequest(scriptKind, aURI, nullptr, mDocument->NodePrincipal(),
3909 Element::StringToCORSMode(aCrossOrigin), aNonce,
3910 requestPriority, sriMetadata, aReferrerPolicy,
3911 aLinkPreload ? ParserMetadata::NotParserInserted
3912 : ParserMetadata::ParserInserted);
3913 request->GetScriptLoadContext()->mIsInline = false;
3914 request->GetScriptLoadContext()->mScriptFromHead = aScriptFromHead;
3915 request->GetScriptLoadContext()->SetScriptMode(aDefer, aAsync, aLinkPreload);
3916 request->GetScriptLoadContext()->SetIsPreloadRequest();
3917 request->mEarlyHintPreloaderId = aEarlyHintPreloaderId;
3919 if (LOG_ENABLED()) {
3920 nsAutoCString url;
3921 aURI->GetAsciiSpec(url);
3922 LOG(("ScriptLoadRequest (%p): Created preload request for %s",
3923 request.get(), url.get()));
3926 nsAutoString charset(aCharset);
3927 nsresult rv = StartLoad(request, Some(charset));
3928 if (NS_FAILED(rv)) {
3929 return;
3932 PreloadInfo* pi = mPreloads.AppendElement();
3933 pi->mRequest = request;
3934 pi->mCharset = aCharset;
3937 void ScriptLoader::AddDeferRequest(ScriptLoadRequest* aRequest) {
3938 MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsDeferredScript());
3939 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInDeferList &&
3940 !aRequest->GetScriptLoadContext()->mInAsyncList);
3941 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInCompilingList);
3943 aRequest->GetScriptLoadContext()->mInDeferList = true;
3944 mDeferRequests.AppendElement(aRequest);
3945 if (mDeferEnabled && aRequest == mDeferRequests.getFirst() && mDocument &&
3946 !mBlockingDOMContentLoaded) {
3947 MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING);
3948 mBlockingDOMContentLoaded = true;
3949 mDocument->BlockDOMContentLoaded();
3953 void ScriptLoader::AddAsyncRequest(ScriptLoadRequest* aRequest) {
3954 MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsAsyncScript());
3955 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInDeferList &&
3956 !aRequest->GetScriptLoadContext()->mInAsyncList);
3957 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInCompilingList);
3959 aRequest->GetScriptLoadContext()->mInAsyncList = true;
3960 if (aRequest->IsFinished()) {
3961 mLoadedAsyncRequests.AppendElement(aRequest);
3962 } else {
3963 mLoadingAsyncRequests.AppendElement(aRequest);
3967 void ScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) {
3968 MOZ_ASSERT(aRequest->IsFinished());
3969 MOZ_ASSERT(aRequest->IsTopLevel());
3971 // If it's async, move it to the loaded list.
3972 // aRequest->GetScriptLoadContext()->mInAsyncList really _should_ be in a
3973 // list, but the consequences if it's not are bad enough we want to avoid
3974 // trying to move it if it's not.
3975 if (aRequest->GetScriptLoadContext()->mInAsyncList) {
3976 MOZ_ASSERT(aRequest->isInList());
3977 if (aRequest->isInList()) {
3978 RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
3979 mLoadedAsyncRequests.AppendElement(req);
3981 } else if (aRequest->IsModuleRequest() &&
3982 aRequest->AsModuleRequest()->IsDynamicImport()) {
3983 // Process dynamic imports with async scripts.
3984 MOZ_ASSERT(!aRequest->isInList());
3985 mLoadedAsyncRequests.AppendElement(aRequest);
3989 bool ScriptLoader::MaybeRemovedDeferRequests() {
3990 if (mDeferRequests.isEmpty() && mDocument && mBlockingDOMContentLoaded) {
3991 mBlockingDOMContentLoaded = false;
3992 mDocument->UnblockDOMContentLoaded();
3993 return true;
3995 return false;
3998 DocGroup* ScriptLoader::GetDocGroup() const { return mDocument->GetDocGroup(); }
4000 void ScriptLoader::BeginDeferringScripts() {
4001 mDeferEnabled = true;
4002 if (mDeferCheckpointReached) {
4003 // We already completed a parse and were just waiting for some async
4004 // scripts to load (and were already blocking the load event waiting for
4005 // that to happen), when document.open() happened and now we're doing a
4006 // new parse. We shouldn't block the load event again, but _should_ reset
4007 // mDeferCheckpointReached to false. It'll get set to true again when the
4008 // DeferCheckpointReached call that corresponds to this
4009 // BeginDeferringScripts call happens (on document.close()), since we just
4010 // set mDeferEnabled to true.
4011 mDeferCheckpointReached = false;
4012 } else {
4013 if (mDocument) {
4014 mDocument->BlockOnload();
4019 nsAutoScriptLoaderDisabler::nsAutoScriptLoaderDisabler(Document* aDoc) {
4020 mLoader = aDoc->ScriptLoader();
4021 mWasEnabled = mLoader->GetEnabled();
4022 if (mWasEnabled) {
4023 mLoader->SetEnabled(false);
4027 nsAutoScriptLoaderDisabler::~nsAutoScriptLoaderDisabler() {
4028 if (mWasEnabled) {
4029 mLoader->SetEnabled(true);
4033 #undef TRACE_FOR_TEST
4034 #undef TRACE_FOR_TEST_BOOL
4035 #undef TRACE_FOR_TEST_NONE
4037 #undef LOG
4039 } // namespace mozilla::dom