Bug 1874684 - Part 6: Limit day length calculations to safe integers. r=mgaudet
[gecko.git] / dom / script / ScriptLoader.cpp
blob5297a11f4a3d8f6712a63a518f37ade03ee925ac
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"
11 #include "nsGenericHTMLElement.h"
13 #include "mozilla/Assertions.h"
14 #include "mozilla/dom/FetchPriority.h"
15 #include "mozilla/glean/GleanMetrics.h"
16 #include "mozilla/dom/RequestBinding.h"
18 #include "prsystem.h"
19 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
20 #include "js/CompilationAndEvaluation.h"
21 #include "js/CompileOptions.h" // JS::CompileOptions, JS::OwningCompileOptions, JS::DecodeOptions, JS::OwningDecodeOptions, JS::DelazificationOption
22 #include "js/ContextOptions.h" // JS::ContextOptionsRef
23 #include "js/experimental/JSStencil.h" // JS::Stencil, JS::InstantiationStorage
24 #include "js/experimental/CompileScript.h" // JS::FrontendContext, JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::ThreadStackQuotaForSize, JS::CompilationStorage, JS::CompileGlobalScriptToStencil, JS::CompileModuleScriptToStencil, JS::DecodeStencil, JS::PrepareForInstantiate
25 #include "js/loader/ScriptLoadRequest.h"
26 #include "ScriptCompression.h"
27 #include "js/loader/LoadedScript.h"
28 #include "js/loader/ModuleLoadRequest.h"
29 #include "js/MemoryFunctions.h"
30 #include "js/Modules.h"
31 #include "js/PropertyAndElement.h" // JS_DefineProperty
32 #include "js/Transcoding.h" // JS::TranscodeRange, JS::TranscodeResult, JS::IsTranscodeFailureResult
33 #include "js/Utility.h"
34 #include "xpcpublic.h"
35 #include "GeckoProfiler.h"
36 #include "nsContentSecurityManager.h"
37 #include "nsCycleCollectionParticipant.h"
38 #include "nsIContent.h"
39 #include "nsJSUtils.h"
40 #include "mozilla/dom/AutoEntryScript.h"
41 #include "mozilla/dom/DocGroup.h"
42 #include "mozilla/dom/Element.h"
43 #include "mozilla/dom/JSExecutionContext.h"
44 #include "mozilla/dom/ScriptDecoding.h" // mozilla::dom::ScriptDecoding
45 #include "mozilla/dom/ScriptSettings.h"
46 #include "mozilla/dom/SRILogHelper.h"
47 #include "mozilla/dom/WindowContext.h"
48 #include "mozilla/Mutex.h" // mozilla::Mutex
49 #include "mozilla/net/UrlClassifierFeatureFactory.h"
50 #include "mozilla/StaticPrefs_dom.h"
51 #include "mozilla/StaticPrefs_javascript.h"
52 #include "mozilla/StaticPrefs_network.h"
53 #include "nsAboutProtocolUtils.h"
54 #include "nsGkAtoms.h"
55 #include "nsNetUtil.h"
56 #include "nsIScriptGlobalObject.h"
57 #include "nsIScriptContext.h"
58 #include "nsIPrincipal.h"
59 #include "nsJSPrincipals.h"
60 #include "nsContentPolicyUtils.h"
61 #include "nsContentSecurityUtils.h"
62 #include "nsIClassifiedChannel.h"
63 #include "nsIHttpChannel.h"
64 #include "nsIHttpChannelInternal.h"
65 #include "nsIClassOfService.h"
66 #include "nsICacheInfoChannel.h"
67 #include "nsITimedChannel.h"
68 #include "nsIScriptElement.h"
69 #include "nsISupportsPriority.h"
70 #include "nsIDocShell.h"
71 #include "nsContentUtils.h"
72 #include "nsUnicharUtils.h"
73 #include "nsError.h"
74 #include "nsThreadUtils.h"
75 #include "nsIContentSecurityPolicy.h"
76 #include "mozilla/Logging.h"
77 #include "nsCRT.h"
78 #include "nsContentCreatorFunctions.h"
79 #include "nsProxyRelease.h"
80 #include "nsINetworkPredictor.h"
81 #include "mozilla/ConsoleReportCollector.h"
82 #include "mozilla/CycleCollectedJSContext.h"
83 #include "mozilla/EventQueue.h"
84 #include "mozilla/LoadInfo.h"
85 #include "ReferrerInfo.h"
87 #include "mozilla/AsyncEventDispatcher.h"
88 #include "mozilla/Attributes.h"
89 #include "mozilla/ScopeExit.h"
90 #include "mozilla/TaskController.h"
91 #include "mozilla/Telemetry.h"
92 #include "mozilla/TimeStamp.h"
93 #include "mozilla/UniquePtr.h"
94 #include "mozilla/Utf8.h" // mozilla::Utf8Unit
95 #include "nsIScriptError.h"
96 #include "nsIAsyncOutputStream.h"
97 #include "js/loader/ModuleLoaderBase.h"
98 #include "mozilla/Maybe.h"
100 using namespace JS::loader;
102 using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT;
104 namespace mozilla::dom {
106 LazyLogModule ScriptLoader::gCspPRLog("CSP");
107 LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader");
109 #undef LOG
110 #define LOG(args) \
111 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
113 #define LOG_ENABLED() \
114 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
116 // Alternate Data MIME type used by the ScriptLoader to register that we want to
117 // store bytecode without reading it.
118 static constexpr auto kNullMimeType = "javascript/null"_ns;
120 /////////////////////////////////////////////////////////////
121 // AsyncCompileShutdownObserver
122 /////////////////////////////////////////////////////////////
124 NS_IMPL_ISUPPORTS(AsyncCompileShutdownObserver, nsIObserver)
126 void AsyncCompileShutdownObserver::OnShutdown() {
127 if (mScriptLoader) {
128 mScriptLoader->Destroy();
129 MOZ_ASSERT(!mScriptLoader);
133 void AsyncCompileShutdownObserver::Unregister() {
134 if (mScriptLoader) {
135 mScriptLoader = nullptr;
136 nsContentUtils::UnregisterShutdownObserver(this);
140 NS_IMETHODIMP
141 AsyncCompileShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
142 const char16_t* aData) {
143 OnShutdown();
144 return NS_OK;
147 //////////////////////////////////////////////////////////////
148 // ScriptLoader::PreloadInfo
149 //////////////////////////////////////////////////////////////
151 inline void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField) {
152 ImplCycleCollectionUnlink(aField.mRequest);
155 inline void ImplCycleCollectionTraverse(
156 nsCycleCollectionTraversalCallback& aCallback,
157 ScriptLoader::PreloadInfo& aField, const char* aName, uint32_t aFlags = 0) {
158 ImplCycleCollectionTraverse(aCallback, aField.mRequest, aName, aFlags);
161 //////////////////////////////////////////////////////////////
162 // ScriptLoader
163 //////////////////////////////////////////////////////////////
165 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoader)
166 NS_INTERFACE_MAP_END
168 NS_IMPL_CYCLE_COLLECTION(ScriptLoader, mNonAsyncExternalScriptInsertedRequests,
169 mLoadingAsyncRequests, mLoadedAsyncRequests,
170 mOffThreadCompilingRequests, mDeferRequests,
171 mXSLTRequests, mParserBlockingRequest,
172 mBytecodeEncodingQueue, mPreloads,
173 mPendingChildLoaders, mModuleLoader,
174 mWebExtModuleLoaders, mShadowRealmModuleLoaders)
176 NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader)
177 NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader)
179 ScriptLoader::ScriptLoader(Document* aDocument)
180 : mDocument(aDocument),
181 mParserBlockingBlockerCount(0),
182 mBlockerCount(0),
183 mNumberOfProcessors(0),
184 mTotalFullParseSize(0),
185 mPhysicalSizeOfMemory(-1),
186 mEnabled(true),
187 mDeferEnabled(false),
188 mSpeculativeOMTParsingEnabled(false),
189 mDeferCheckpointReached(false),
190 mBlockingDOMContentLoaded(false),
191 mLoadEventFired(false),
192 mGiveUpEncoding(false),
193 mReporter(new ConsoleReportCollector()) {
194 LOG(("ScriptLoader::ScriptLoader %p", this));
196 mSpeculativeOMTParsingEnabled = StaticPrefs::
197 dom_script_loader_external_scripts_speculative_omt_parse_enabled();
199 mShutdownObserver = new AsyncCompileShutdownObserver(this);
200 nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
203 ScriptLoader::~ScriptLoader() {
204 LOG(("ScriptLoader::~ScriptLoader %p", this));
206 mObservers.Clear();
208 if (mParserBlockingRequest) {
209 FireScriptAvailable(NS_ERROR_ABORT, mParserBlockingRequest);
212 for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
213 req = req->getNext()) {
214 FireScriptAvailable(NS_ERROR_ABORT, req);
217 for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req;
218 req = req->getNext()) {
219 FireScriptAvailable(NS_ERROR_ABORT, req);
222 for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
223 req = req->getNext()) {
224 FireScriptAvailable(NS_ERROR_ABORT, req);
227 for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
228 req = req->getNext()) {
229 FireScriptAvailable(NS_ERROR_ABORT, req);
232 for (ScriptLoadRequest* req =
233 mNonAsyncExternalScriptInsertedRequests.getFirst();
234 req; req = req->getNext()) {
235 FireScriptAvailable(NS_ERROR_ABORT, req);
238 // Unblock the kids, in case any of them moved to a different document
239 // subtree in the meantime and therefore aren't actually going away.
240 for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) {
241 mPendingChildLoaders[j]->RemoveParserBlockingScriptExecutionBlocker();
244 for (size_t i = 0; i < mPreloads.Length(); i++) {
245 AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::NotUsed);
248 if (mShutdownObserver) {
249 mShutdownObserver->Unregister();
250 mShutdownObserver = nullptr;
253 mModuleLoader = nullptr;
256 void ScriptLoader::SetGlobalObject(nsIGlobalObject* aGlobalObject) {
257 if (!aGlobalObject) {
258 // The document is being detached.
259 CancelAndClearScriptLoadRequests();
260 return;
263 MOZ_ASSERT(!HasPendingRequests());
265 if (!mModuleLoader) {
266 // The module loader is associated with a global object, so don't create it
267 // until we have a global set.
268 mModuleLoader = new ModuleLoader(this, aGlobalObject, ModuleLoader::Normal);
271 MOZ_ASSERT(mModuleLoader->GetGlobalObject() == aGlobalObject);
272 MOZ_ASSERT(aGlobalObject->GetModuleLoader(dom::danger::GetJSContext()) ==
273 mModuleLoader);
276 void ScriptLoader::RegisterContentScriptModuleLoader(ModuleLoader* aLoader) {
277 MOZ_ASSERT(aLoader);
278 MOZ_ASSERT(aLoader->GetScriptLoader() == this);
280 mWebExtModuleLoaders.AppendElement(aLoader);
283 void ScriptLoader::RegisterShadowRealmModuleLoader(ModuleLoader* aLoader) {
284 MOZ_ASSERT(aLoader);
285 MOZ_ASSERT(aLoader->GetScriptLoader() == this);
287 mShadowRealmModuleLoaders.AppendElement(aLoader);
290 // Collect telemtry data about the cache information, and the kind of source
291 // which are being loaded, and where it is being loaded from.
292 static void CollectScriptTelemetry(ScriptLoadRequest* aRequest) {
293 using namespace mozilla::Telemetry;
295 MOZ_ASSERT(aRequest->IsFetching());
297 // Skip this function if we are not running telemetry.
298 if (!CanRecordExtended()) {
299 return;
302 // Report the script kind.
303 if (aRequest->IsModuleRequest()) {
304 AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ModuleScript);
305 } else {
306 AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ClassicScript);
309 // Report the type of source. This is used to monitor the status of the
310 // JavaScript Start-up Bytecode Cache, with the expectation of an almost zero
311 // source-fallback and alternate-data being roughtly equal to source loads.
312 if (aRequest->mFetchSourceOnly) {
313 if (aRequest->GetScriptLoadContext()->mIsInline) {
314 AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Inline);
315 } else if (aRequest->IsTextSource()) {
316 AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::SourceFallback);
318 } else {
319 if (aRequest->IsTextSource()) {
320 AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Source);
321 } else if (aRequest->IsBytecode()) {
322 AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::AltData);
327 // Helper method for checking if the script element is an event-handler
328 // This means that it has both a for-attribute and a event-attribute.
329 // Also, if the for-attribute has a value that matches "\s*window\s*",
330 // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
331 // eventhandler. (both matches are case insensitive).
332 // This is how IE seems to filter out a window's onload handler from a
333 // <script for=... event=...> element.
335 static bool IsScriptEventHandler(ScriptKind kind, nsIContent* aScriptElement) {
336 if (kind != ScriptKind::eClassic) {
337 return false;
340 if (!aScriptElement->IsHTMLElement()) {
341 return false;
344 nsAutoString forAttr, eventAttr;
345 if (!aScriptElement->AsElement()->GetAttr(nsGkAtoms::_for, forAttr) ||
346 !aScriptElement->AsElement()->GetAttr(nsGkAtoms::event, eventAttr)) {
347 return false;
350 const nsAString& for_str =
351 nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(forAttr);
352 if (!for_str.LowerCaseEqualsLiteral("window")) {
353 return true;
356 // We found for="window", now check for event="onload".
357 const nsAString& event_str =
358 nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(eventAttr, false);
359 if (!StringBeginsWith(event_str, u"onload"_ns,
360 nsCaseInsensitiveStringComparator)) {
361 // It ain't "onload.*".
363 return true;
366 nsAutoString::const_iterator start, end;
367 event_str.BeginReading(start);
368 event_str.EndReading(end);
370 start.advance(6); // advance past "onload"
372 if (start != end && *start != '(' && *start != ' ') {
373 // We got onload followed by something other than space or
374 // '('. Not good enough.
376 return true;
379 return false;
382 nsContentPolicyType ScriptLoadRequestToContentPolicyType(
383 ScriptLoadRequest* aRequest) {
384 if (aRequest->GetScriptLoadContext()->IsPreload()) {
385 return aRequest->IsModuleRequest()
386 ? nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD
387 : nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD;
390 return aRequest->IsModuleRequest() ? nsIContentPolicy::TYPE_INTERNAL_MODULE
391 : nsIContentPolicy::TYPE_INTERNAL_SCRIPT;
394 nsresult ScriptLoader::CheckContentPolicy(Document* aDocument,
395 nsIScriptElement* aElement,
396 const nsAString& aNonce,
397 ScriptLoadRequest* aRequest) {
398 nsContentPolicyType contentPolicyType =
399 ScriptLoadRequestToContentPolicyType(aRequest);
401 nsCOMPtr<nsINode> requestingNode = do_QueryInterface(aElement);
402 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
403 aDocument->NodePrincipal(), // loading principal
404 aDocument->NodePrincipal(), // triggering principal
405 requestingNode, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
406 contentPolicyType);
407 secCheckLoadInfo->SetParserCreatedScript(aElement->GetParserCreated() !=
408 mozilla::dom::NOT_FROM_PARSER);
409 // Use nonce of the current element, instead of the preload, because those
410 // are allowed to differ.
411 secCheckLoadInfo->SetCspNonce(aNonce);
412 if (aRequest->mIntegrity.IsValid()) {
413 MOZ_ASSERT(!aRequest->mIntegrity.IsEmpty());
414 secCheckLoadInfo->SetIntegrityMetadata(
415 aRequest->mIntegrity.GetIntegrityString());
418 int16_t shouldLoad = nsIContentPolicy::ACCEPT;
419 nsresult rv =
420 NS_CheckContentLoadPolicy(aRequest->mURI, secCheckLoadInfo, &shouldLoad,
421 nsContentUtils::GetContentPolicy());
422 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
423 if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
424 return NS_ERROR_CONTENT_BLOCKED;
426 return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
429 return NS_OK;
432 /* static */
433 bool ScriptLoader::IsAboutPageLoadingChromeURI(ScriptLoadRequest* aRequest,
434 Document* aDocument) {
435 // if the uri to be loaded is not of scheme chrome:, there is nothing to do.
436 if (!aRequest->mURI->SchemeIs("chrome")) {
437 return false;
440 // we can either get here with a regular contentPrincipal or with a
441 // NullPrincipal in case we are showing an error page in a sandboxed iframe.
442 // In either case if the about: page is linkable from content, there is
443 // nothing to do.
444 uint32_t aboutModuleFlags = 0;
445 nsresult rv = NS_OK;
447 nsCOMPtr<nsIPrincipal> triggeringPrincipal = aRequest->TriggeringPrincipal();
448 if (triggeringPrincipal->GetIsContentPrincipal()) {
449 if (!triggeringPrincipal->SchemeIs("about")) {
450 return false;
452 rv = triggeringPrincipal->GetAboutModuleFlags(&aboutModuleFlags);
453 NS_ENSURE_SUCCESS(rv, false);
454 } else if (triggeringPrincipal->GetIsNullPrincipal()) {
455 nsCOMPtr<nsIURI> docURI = aDocument->GetDocumentURI();
456 if (!docURI->SchemeIs("about")) {
457 return false;
460 nsCOMPtr<nsIAboutModule> aboutModule;
461 rv = NS_GetAboutModule(docURI, getter_AddRefs(aboutModule));
462 if (NS_FAILED(rv) || !aboutModule) {
463 return false;
465 rv = aboutModule->GetURIFlags(docURI, &aboutModuleFlags);
466 NS_ENSURE_SUCCESS(rv, false);
467 } else {
468 return false;
471 if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) {
472 return false;
475 // seems like an about page wants to load a chrome URI.
476 return true;
479 nsIURI* ScriptLoader::GetBaseURI() const {
480 MOZ_ASSERT(mDocument);
481 return mDocument->GetDocBaseURI();
484 class ScriptRequestProcessor : public Runnable {
485 private:
486 RefPtr<ScriptLoader> mLoader;
487 RefPtr<ScriptLoadRequest> mRequest;
489 public:
490 ScriptRequestProcessor(ScriptLoader* aLoader, ScriptLoadRequest* aRequest)
491 : Runnable("dom::ScriptRequestProcessor"),
492 mLoader(aLoader),
493 mRequest(aRequest) {}
494 NS_IMETHOD Run() override { return mLoader->ProcessRequest(mRequest); }
497 void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) {
498 auto* runnable = new ScriptRequestProcessor(this, aRequest);
499 nsContentUtils::AddScriptRunner(runnable);
502 nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) {
503 aRequest->DropBytecode();
504 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
505 "scriptloader_fallback");
507 // Notify preload restart so that we can register this preload request again.
508 aRequest->GetScriptLoadContext()->NotifyRestart(mDocument);
510 // Start a new channel from which we explicitly request to stream the source
511 // instead of the bytecode.
512 aRequest->mFetchSourceOnly = true;
513 nsresult rv;
514 if (aRequest->IsModuleRequest()) {
515 rv = aRequest->AsModuleRequest()->RestartModuleLoad();
516 } else {
517 rv = StartLoad(aRequest, Nothing());
519 if (NS_FAILED(rv)) {
520 return rv;
523 // Close the current channel and this ScriptLoadHandler as we created a new
524 // one for the same request.
525 return NS_BINDING_RETARGETED;
528 nsresult ScriptLoader::StartLoad(
529 ScriptLoadRequest* aRequest,
530 const Maybe<nsAutoString>& aCharsetForPreload) {
531 if (aRequest->IsModuleRequest()) {
532 return aRequest->AsModuleRequest()->StartModuleLoad();
535 return StartClassicLoad(aRequest, aCharsetForPreload);
538 nsresult ScriptLoader::StartClassicLoad(
539 ScriptLoadRequest* aRequest,
540 const Maybe<nsAutoString>& aCharsetForPreload) {
541 MOZ_ASSERT(aRequest->IsFetching());
542 NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
543 aRequest->SetUnknownDataType();
545 // If this document is sandboxed without 'allow-scripts', abort.
546 if (mDocument->HasScriptsBlockedBySandbox()) {
547 return NS_OK;
550 if (LOG_ENABLED()) {
551 nsAutoCString url;
552 aRequest->mURI->GetAsciiSpec(url);
553 LOG(("ScriptLoadRequest (%p): Start Classic Load (url = %s)", aRequest,
554 url.get()));
557 nsSecurityFlags securityFlags =
558 nsContentSecurityManager::ComputeSecurityFlags(
559 aRequest->CORSMode(), nsContentSecurityManager::CORSSecurityMapping::
560 CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS);
562 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
564 nsresult rv = StartLoadInternal(aRequest, securityFlags, aCharsetForPreload);
566 NS_ENSURE_SUCCESS(rv, rv);
568 return NS_OK;
571 static bool IsWebExtensionRequest(ScriptLoadRequest* aRequest) {
572 if (!aRequest->IsModuleRequest()) {
573 return false;
576 ModuleLoader* loader =
577 ModuleLoader::From(aRequest->AsModuleRequest()->mLoader);
578 return loader->GetKind() == ModuleLoader::WebExtension;
581 static nsresult CreateChannelForScriptLoading(nsIChannel** aOutChannel,
582 Document* aDocument,
583 ScriptLoadRequest* aRequest,
584 nsSecurityFlags aSecurityFlags) {
585 nsContentPolicyType contentPolicyType =
586 ScriptLoadRequestToContentPolicyType(aRequest);
587 nsCOMPtr<nsINode> context;
588 if (aRequest->GetScriptLoadContext()->GetScriptElement()) {
589 context =
590 do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement());
591 } else {
592 context = aDocument;
595 nsCOMPtr<nsILoadGroup> loadGroup = aDocument->GetDocumentLoadGroup();
596 nsCOMPtr<nsPIDOMWindowOuter> window = aDocument->GetWindow();
597 NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER);
598 nsIDocShell* docshell = window->GetDocShell();
599 nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
601 return NS_NewChannelWithTriggeringPrincipal(
602 aOutChannel, aRequest->mURI, context, aRequest->TriggeringPrincipal(),
603 aSecurityFlags, contentPolicyType,
604 nullptr, // aPerformanceStorage
605 loadGroup, prompter);
608 static void PrepareLoadInfoForScriptLoading(nsIChannel* aChannel,
609 const ScriptLoadRequest* aRequest) {
610 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
611 loadInfo->SetParserCreatedScript(aRequest->ParserMetadata() ==
612 ParserMetadata::ParserInserted);
613 loadInfo->SetCspNonce(aRequest->Nonce());
614 if (aRequest->mIntegrity.IsValid()) {
615 MOZ_ASSERT(!aRequest->mIntegrity.IsEmpty());
616 loadInfo->SetIntegrityMetadata(aRequest->mIntegrity.GetIntegrityString());
620 // static
621 void ScriptLoader::PrepareCacheInfoChannel(nsIChannel* aChannel,
622 ScriptLoadRequest* aRequest) {
623 // To avoid decoding issues, the build-id is part of the bytecode MIME type
624 // constant.
625 aRequest->mCacheInfo = nullptr;
626 nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(aChannel));
627 if (cic && StaticPrefs::dom_script_loader_bytecode_cache_enabled()) {
628 MOZ_ASSERT(!IsWebExtensionRequest(aRequest),
629 "Can not bytecode cache WebExt code");
630 if (!aRequest->mFetchSourceOnly) {
631 // Inform the HTTP cache that we prefer to have information coming from
632 // the bytecode cache instead of the sources, if such entry is already
633 // registered.
634 LOG(("ScriptLoadRequest (%p): Maybe request bytecode", aRequest));
635 cic->PreferAlternativeDataType(
636 ScriptLoader::BytecodeMimeTypeFor(aRequest), ""_ns,
637 nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC);
638 } else {
639 // If we are explicitly loading from the sources, such as after a
640 // restarted request, we might still want to save the bytecode after.
642 // The following tell the cache to look for an alternative data type which
643 // does not exist, such that we can later save the bytecode with a
644 // different alternative data type.
645 LOG(("ScriptLoadRequest (%p): Request saving bytecode later", aRequest));
646 cic->PreferAlternativeDataType(
647 kNullMimeType, ""_ns,
648 nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC);
653 static void AdjustPriorityAndClassOfServiceForLinkPreloadScripts(
654 nsIChannel* aChannel, ScriptLoadRequest* aRequest) {
655 MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsLinkPreloadScript());
657 // Put it to the group that is not blocked by leaders and doesn't block
658 // follower at the same time.
659 // Giving it a much higher priority will make this request be processed
660 // ahead of other Unblocked requests, but with the same weight as
661 // Leaders. This will make us behave similar way for both http2 and http1.
662 ScriptLoadContext::PrioritizeAsPreload(aChannel);
664 if (!StaticPrefs::network_fetchpriority_enabled()) {
665 return;
668 if (nsCOMPtr<nsISupportsPriority> supportsPriority =
669 do_QueryInterface(aChannel)) {
670 LOG(("Is <link rel=[module]preload"));
672 const auto fetchPriority = ToFetchPriority(aRequest->FetchPriority());
673 // The spec defines the priority to be set in an implementation defined
674 // manner (<https://fetch.spec.whatwg.org/#concept-fetch>, step 15 and
675 // <https://html.spec.whatwg.org/#concept-script-fetch-options-fetch-priority>).
676 // See corresponding preferences in StaticPrefList.yaml for more context.
677 const int32_t supportsPriorityDelta =
678 FETCH_PRIORITY_ADJUSTMENT_FOR(link_preload_script, fetchPriority);
679 supportsPriority->AdjustPriority(supportsPriorityDelta);
680 #ifdef DEBUG
681 int32_t adjustedPriority;
682 supportsPriority->GetPriority(&adjustedPriority);
683 LogPriorityMapping(ScriptLoader::gScriptLoaderLog, fetchPriority,
684 adjustedPriority);
685 #endif
689 void AdjustPriorityForNonLinkPreloadScripts(nsIChannel* aChannel,
690 ScriptLoadRequest* aRequest) {
691 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->IsLinkPreloadScript());
693 if (!StaticPrefs::network_fetchpriority_enabled()) {
694 return;
697 if (nsCOMPtr<nsISupportsPriority> supportsPriority =
698 do_QueryInterface(aChannel)) {
699 LOG(("Is not <link rel=[module]preload"));
700 const auto fetchPriority = ToFetchPriority(aRequest->FetchPriority());
702 // The spec defines the priority to be set in an implementation defined
703 // manner (<https://fetch.spec.whatwg.org/#concept-fetch>, step 15 and
704 // <https://html.spec.whatwg.org/#concept-script-fetch-options-fetch-priority>).
705 // See corresponding preferences in StaticPrefList.yaml for more context.
706 const int32_t supportsPriorityDelta = [&]() {
707 const ScriptLoadContext* scriptLoadContext =
708 aRequest->GetScriptLoadContext();
709 if (aRequest->IsModuleRequest()) {
710 return FETCH_PRIORITY_ADJUSTMENT_FOR(module_script, fetchPriority);
713 if (scriptLoadContext->IsAsyncScript() ||
714 scriptLoadContext->IsDeferredScript()) {
715 return FETCH_PRIORITY_ADJUSTMENT_FOR(async_or_defer_script,
716 fetchPriority);
719 if (scriptLoadContext->mScriptFromHead) {
720 return FETCH_PRIORITY_ADJUSTMENT_FOR(script_in_head, fetchPriority);
723 return FETCH_PRIORITY_ADJUSTMENT_FOR(other_script, fetchPriority);
724 }();
726 if (supportsPriorityDelta) {
727 supportsPriority->AdjustPriority(supportsPriorityDelta);
728 #ifdef DEBUG
729 int32_t adjustedPriority;
730 supportsPriority->GetPriority(&adjustedPriority);
731 LogPriorityMapping(ScriptLoader::gScriptLoaderLog, fetchPriority,
732 adjustedPriority);
733 #endif
738 // static
739 void ScriptLoader::PrepareRequestPriorityAndRequestDependencies(
740 nsIChannel* aChannel, ScriptLoadRequest* aRequest) {
741 if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) {
742 // This is <link rel="preload" as="script"> or <link rel="modulepreload">
743 // initiated speculative load
744 // (https://developer.mozilla.org/en-US/docs/Web/Performance/Speculative_loading).
745 AdjustPriorityAndClassOfServiceForLinkPreloadScripts(aChannel, aRequest);
746 ScriptLoadContext::AddLoadBackgroundFlag(aChannel);
747 } else if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(aChannel)) {
748 AdjustPriorityForNonLinkPreloadScripts(aChannel, aRequest);
750 if (aRequest->GetScriptLoadContext()->mScriptFromHead &&
751 aRequest->GetScriptLoadContext()->IsBlockingScript()) {
752 // synchronous head scripts block loading of most other non js/css
753 // content such as images, Leader implicitely disallows tailing
754 cos->AddClassFlags(nsIClassOfService::Leader);
755 } else if (aRequest->GetScriptLoadContext()->IsDeferredScript() &&
756 !StaticPrefs::network_http_tailing_enabled()) {
757 // Bug 1395525 and the !StaticPrefs::network_http_tailing_enabled() bit:
758 // We want to make sure that turing tailing off by the pref makes the
759 // browser behave exactly the same way as before landing the tailing
760 // patch.
762 // head/body deferred scripts are blocked by leaders but are not
763 // allowed tailing because they block DOMContentLoaded
764 cos->AddClassFlags(nsIClassOfService::TailForbidden);
765 } else {
766 // other scripts (=body sync or head/body async) are neither blocked
767 // nor prioritized
768 cos->AddClassFlags(nsIClassOfService::Unblocked);
770 if (aRequest->GetScriptLoadContext()->IsAsyncScript()) {
771 // async scripts are allowed tailing, since those and only those
772 // don't block DOMContentLoaded; this flag doesn't enforce tailing,
773 // just overweights the Unblocked flag when the channel is found
774 // to be a thrird-party tracker and thus set the Tail flag to engage
775 // tailing.
776 cos->AddClassFlags(nsIClassOfService::TailAllowed);
782 // static
783 nsresult ScriptLoader::PrepareHttpRequestAndInitiatorType(
784 nsIChannel* aChannel, ScriptLoadRequest* aRequest,
785 const Maybe<nsAutoString>& aCharsetForPreload) {
786 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
787 nsresult rv = NS_OK;
789 if (httpChannel) {
790 // HTTP content negotation has little value in this context.
791 nsAutoCString acceptTypes("*/*");
792 rv = httpChannel->SetRequestHeader("Accept"_ns, acceptTypes, false);
793 MOZ_ASSERT(NS_SUCCEEDED(rv));
795 nsCOMPtr<nsIReferrerInfo> referrerInfo =
796 new ReferrerInfo(aRequest->mReferrer, aRequest->ReferrerPolicy());
797 rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
798 MOZ_ASSERT(NS_SUCCEEDED(rv));
800 nsCOMPtr<nsIHttpChannelInternal> internalChannel(
801 do_QueryInterface(httpChannel));
802 if (internalChannel) {
803 rv = internalChannel->SetIntegrityMetadata(
804 aRequest->mIntegrity.GetIntegrityString());
805 MOZ_ASSERT(NS_SUCCEEDED(rv));
808 nsAutoString hintCharset;
809 if (!aRequest->GetScriptLoadContext()->IsPreload() &&
810 aRequest->GetScriptLoadContext()->GetScriptElement()) {
811 aRequest->GetScriptLoadContext()->GetScriptElement()->GetScriptCharset(
812 hintCharset);
813 } else if (aCharsetForPreload.isSome()) {
814 hintCharset = aCharsetForPreload.ref();
817 rv = httpChannel->SetClassicScriptHintCharset(hintCharset);
818 NS_ENSURE_SUCCESS(rv, rv);
821 // Set the initiator type
822 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
823 if (timedChannel) {
824 if (aRequest->mEarlyHintPreloaderId) {
825 timedChannel->SetInitiatorType(u"early-hints"_ns);
826 } else if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) {
827 timedChannel->SetInitiatorType(u"link"_ns);
828 } else {
829 timedChannel->SetInitiatorType(u"script"_ns);
833 return rv;
836 nsresult ScriptLoader::PrepareIncrementalStreamLoader(
837 nsIIncrementalStreamLoader** aOutLoader, ScriptLoadRequest* aRequest) {
838 UniquePtr<mozilla::dom::SRICheckDataVerifier> sriDataVerifier;
839 if (!aRequest->mIntegrity.IsEmpty()) {
840 nsAutoCString sourceUri;
841 if (mDocument->GetDocumentURI()) {
842 mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
844 sriDataVerifier = MakeUnique<SRICheckDataVerifier>(aRequest->mIntegrity,
845 sourceUri, mReporter);
848 RefPtr<ScriptLoadHandler> handler =
849 new ScriptLoadHandler(this, aRequest, std::move(sriDataVerifier));
851 nsresult rv = NS_NewIncrementalStreamLoader(aOutLoader, handler);
852 NS_ENSURE_SUCCESS(rv, rv);
853 return rv;
856 nsresult ScriptLoader::StartLoadInternal(
857 ScriptLoadRequest* aRequest, nsSecurityFlags securityFlags,
858 const Maybe<nsAutoString>& aCharsetForPreload) {
859 nsCOMPtr<nsIChannel> channel;
860 nsresult rv = CreateChannelForScriptLoading(
861 getter_AddRefs(channel), mDocument, aRequest, securityFlags);
863 NS_ENSURE_SUCCESS(rv, rv);
865 if (aRequest->mEarlyHintPreloaderId) {
866 nsCOMPtr<nsIHttpChannelInternal> channelInternal =
867 do_QueryInterface(channel);
868 NS_ENSURE_TRUE(channelInternal != nullptr, NS_ERROR_FAILURE);
870 rv = channelInternal->SetEarlyHintPreloaderId(
871 aRequest->mEarlyHintPreloaderId);
872 NS_ENSURE_SUCCESS(rv, rv);
875 PrepareLoadInfoForScriptLoading(channel, aRequest);
877 nsCOMPtr<nsIScriptGlobalObject> scriptGlobal = GetScriptGlobalObject();
878 if (!scriptGlobal) {
879 return NS_ERROR_FAILURE;
882 ScriptLoader::PrepareCacheInfoChannel(channel, aRequest);
884 LOG(("ScriptLoadRequest (%p): mode=%u tracking=%d", aRequest,
885 unsigned(aRequest->GetScriptLoadContext()->mScriptMode),
886 aRequest->GetScriptLoadContext()->IsTracking()));
888 PrepareRequestPriorityAndRequestDependencies(channel, aRequest);
890 rv =
891 PrepareHttpRequestAndInitiatorType(channel, aRequest, aCharsetForPreload);
892 NS_ENSURE_SUCCESS(rv, rv);
894 mozilla::net::PredictorLearn(
895 aRequest->mURI, mDocument->GetDocumentURI(),
896 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
897 mDocument->NodePrincipal()->OriginAttributesRef());
899 nsCOMPtr<nsIIncrementalStreamLoader> loader;
900 rv = PrepareIncrementalStreamLoader(getter_AddRefs(loader), aRequest);
901 NS_ENSURE_SUCCESS(rv, rv);
903 auto key = PreloadHashKey::CreateAsScript(
904 aRequest->mURI, aRequest->CORSMode(), aRequest->mKind);
905 aRequest->GetScriptLoadContext()->NotifyOpen(
906 key, channel, mDocument,
907 aRequest->GetScriptLoadContext()->IsLinkPreloadScript(),
908 aRequest->IsModuleRequest());
910 rv = channel->AsyncOpen(loader);
912 if (NS_FAILED(rv)) {
913 // Make sure to inform any <link preload> tags about failure to load the
914 // resource.
915 aRequest->GetScriptLoadContext()->NotifyStart(channel);
916 aRequest->GetScriptLoadContext()->NotifyStop(rv);
919 NS_ENSURE_SUCCESS(rv, rv);
921 return NS_OK;
924 bool ScriptLoader::PreloadURIComparator::Equals(const PreloadInfo& aPi,
925 nsIURI* const& aURI) const {
926 bool same;
927 return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) && same;
930 static bool CSPAllowsInlineScript(nsIScriptElement* aElement,
931 const nsAString& aNonce,
932 Document* aDocument) {
933 nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
934 if (!csp) {
935 // no CSP --> allow
936 return true;
939 bool parserCreated =
940 aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER;
941 nsCOMPtr<Element> element = do_QueryInterface(aElement);
943 bool allowInlineScript = false;
944 nsresult rv = csp->GetAllowsInline(
945 nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE,
946 false /* aHasUnsafeHash */, aNonce, parserCreated, element,
947 nullptr /* nsICSPEventListener */, u""_ns,
948 aElement->GetScriptLineNumber(),
949 aElement->GetScriptColumnNumber().oneOriginValue(), &allowInlineScript);
950 return NS_SUCCEEDED(rv) && allowInlineScript;
953 namespace {
954 RequestPriority FetchPriorityToRequestPriority(
955 const FetchPriority aFetchPriority) {
956 switch (aFetchPriority) {
957 case FetchPriority::High:
958 return RequestPriority::High;
959 case FetchPriority::Low:
960 return RequestPriority::Low;
961 case FetchPriority::Auto:
962 return RequestPriority::Auto;
965 MOZ_ASSERT_UNREACHABLE();
966 return RequestPriority::Auto;
968 } // namespace
970 already_AddRefed<ScriptLoadRequest> ScriptLoader::CreateLoadRequest(
971 ScriptKind aKind, nsIURI* aURI, nsIScriptElement* aElement,
972 nsIPrincipal* aTriggeringPrincipal, CORSMode aCORSMode,
973 const nsAString& aNonce, RequestPriority aRequestPriority,
974 const SRIMetadata& aIntegrity, ReferrerPolicy aReferrerPolicy,
975 ParserMetadata aParserMetadata) {
976 nsIURI* referrer = mDocument->GetDocumentURIAsReferrer();
977 nsCOMPtr<Element> domElement = do_QueryInterface(aElement);
978 RefPtr<ScriptFetchOptions> fetchOptions =
979 new ScriptFetchOptions(aCORSMode, aNonce, aRequestPriority,
980 aParserMetadata, aTriggeringPrincipal, domElement);
981 RefPtr<ScriptLoadContext> context = new ScriptLoadContext();
983 if (aKind == ScriptKind::eClassic || aKind == ScriptKind::eImportMap) {
984 RefPtr<ScriptLoadRequest> aRequest =
985 new ScriptLoadRequest(aKind, aURI, aReferrerPolicy, fetchOptions,
986 aIntegrity, referrer, context);
988 aRequest->NoCacheEntryFound();
989 return aRequest.forget();
992 MOZ_ASSERT(aKind == ScriptKind::eModule);
993 RefPtr<ModuleLoadRequest> aRequest = ModuleLoader::CreateTopLevel(
994 aURI, aReferrerPolicy, fetchOptions, aIntegrity, referrer, this, context);
995 return aRequest.forget();
998 bool ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement) {
999 // We need a document to evaluate scripts.
1000 NS_ENSURE_TRUE(mDocument, false);
1002 // Check to see if scripts has been turned off.
1003 if (!mEnabled || !mDocument->IsScriptEnabled()) {
1004 return false;
1007 NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script");
1009 nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
1011 ScriptKind scriptKind;
1012 if (aElement->GetScriptIsModule()) {
1013 scriptKind = ScriptKind::eModule;
1014 } else if (aElement->GetScriptIsImportMap()) {
1015 scriptKind = ScriptKind::eImportMap;
1016 } else {
1017 scriptKind = ScriptKind::eClassic;
1020 // Step 13. Check that the script is not an eventhandler
1021 if (IsScriptEventHandler(scriptKind, scriptContent)) {
1022 return false;
1025 // "In modern user agents that support module scripts, the script element with
1026 // the nomodule attribute will be ignored".
1027 // "The nomodule attribute must not be specified on module scripts (and will
1028 // be ignored if it is)."
1029 if (scriptKind == ScriptKind::eClassic && scriptContent->IsHTMLElement() &&
1030 scriptContent->AsElement()->HasAttr(nsGkAtoms::nomodule)) {
1031 return false;
1034 // Step 15. and later in the HTML5 spec
1035 if (aElement->GetScriptExternal()) {
1036 return ProcessExternalScript(aElement, scriptKind, scriptContent);
1039 return ProcessInlineScript(aElement, scriptKind);
1042 static ParserMetadata GetParserMetadata(nsIScriptElement* aElement) {
1043 return aElement->GetParserCreated() == mozilla::dom::NOT_FROM_PARSER
1044 ? ParserMetadata::NotParserInserted
1045 : ParserMetadata::ParserInserted;
1048 bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
1049 ScriptKind aScriptKind,
1050 nsIContent* aScriptContent) {
1051 LOG(("ScriptLoader (%p): Process external script for element %p", this,
1052 aElement));
1054 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
1055 // Step 30.1. If el's type is "importmap", then queue an element task on the
1056 // DOM manipulation task source given el to fire an event named error at el,
1057 // and return.
1058 if (aScriptKind == ScriptKind::eImportMap) {
1059 NS_DispatchToCurrentThread(
1060 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
1061 &nsIScriptElement::FireErrorEvent));
1062 nsContentUtils::ReportToConsole(
1063 nsIScriptError::warningFlag, "Script Loader"_ns, mDocument,
1064 nsContentUtils::eDOM_PROPERTIES, "ImportMapExternalNotSupported");
1065 return false;
1068 nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
1069 if (!scriptURI) {
1070 // Asynchronously report the failure to create a URI object
1071 NS_DispatchToCurrentThread(
1072 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
1073 &nsIScriptElement::FireErrorEvent));
1074 return false;
1077 nsString nonce = nsContentSecurityUtils::GetIsElementNonceableNonce(
1078 *aScriptContent->AsElement());
1079 SRIMetadata sriMetadata;
1081 nsAutoString integrity;
1082 aScriptContent->AsElement()->GetAttr(nsGkAtoms::integrity, integrity);
1083 GetSRIMetadata(integrity, &sriMetadata);
1086 RefPtr<ScriptLoadRequest> request =
1087 LookupPreloadRequest(aElement, aScriptKind, sriMetadata);
1089 if (request &&
1090 NS_FAILED(CheckContentPolicy(mDocument, aElement, nonce, request))) {
1091 LOG(("ScriptLoader (%p): content policy check failed for preload", this));
1093 // Probably plans have changed; even though the preload was allowed seems
1094 // like the actual load is not; let's cancel the preload request.
1095 request->Cancel();
1096 AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RejectedByPolicy);
1097 return false;
1100 if (request) {
1101 // Use the preload request.
1103 LOG(("ScriptLoadRequest (%p): Using preload request", request.get()));
1105 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-script-tree
1106 // Step 1. Disallow further import maps given settings object.
1107 if (request->IsModuleRequest()) {
1108 LOG(("ScriptLoadRequest (%p): Disallow further import maps.",
1109 request.get()));
1110 mModuleLoader->DisallowImportMaps();
1113 // It's possible these attributes changed since we started the preload so
1114 // update them here.
1115 request->GetScriptLoadContext()->SetScriptMode(
1116 aElement->GetScriptDeferred(), aElement->GetScriptAsync(), false);
1118 // The request will be added to another list or set as
1119 // mParserBlockingRequest below.
1120 if (request->GetScriptLoadContext()->mInCompilingList) {
1121 mOffThreadCompilingRequests.Remove(request);
1122 request->GetScriptLoadContext()->mInCompilingList = false;
1125 AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::Used);
1126 } else {
1127 // No usable preload found.
1129 nsCOMPtr<nsIPrincipal> principal =
1130 aElement->GetScriptURITriggeringPrincipal();
1131 if (!principal) {
1132 principal = aScriptContent->NodePrincipal();
1135 CORSMode ourCORSMode = aElement->GetCORSMode();
1136 const FetchPriority fetchPriority = aElement->GetFetchPriority();
1137 ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement);
1138 ParserMetadata parserMetadata = GetParserMetadata(aElement);
1140 request = CreateLoadRequest(aScriptKind, scriptURI, aElement, principal,
1141 ourCORSMode, nonce,
1142 FetchPriorityToRequestPriority(fetchPriority),
1143 sriMetadata, referrerPolicy, parserMetadata);
1144 request->GetScriptLoadContext()->mIsInline = false;
1145 request->GetScriptLoadContext()->SetScriptMode(
1146 aElement->GetScriptDeferred(), aElement->GetScriptAsync(), false);
1147 // keep request->GetScriptLoadContext()->mScriptFromHead to false so we
1148 // don't treat non preloaded scripts as blockers for full page load. See bug
1149 // 792438.
1151 LOG(("ScriptLoadRequest (%p): Created request for external script",
1152 request.get()));
1154 nsresult rv = StartLoad(request, Nothing());
1155 if (NS_FAILED(rv)) {
1156 ReportErrorToConsole(request, rv);
1158 // Asynchronously report the load failure
1159 nsCOMPtr<nsIRunnable> runnable =
1160 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
1161 &nsIScriptElement::FireErrorEvent);
1162 if (mDocument) {
1163 mDocument->Dispatch(runnable.forget());
1164 } else {
1165 NS_DispatchToCurrentThread(runnable.forget());
1167 return false;
1171 // We should still be in loading stage of script unless we're loading a
1172 // module or speculatively off-main-thread parsing a script.
1173 NS_ASSERTION(SpeculativeOMTParsingEnabled() ||
1174 !request->GetScriptLoadContext()->CompileStarted() ||
1175 request->IsModuleRequest(),
1176 "Request should not yet be in compiling stage.");
1178 if (request->GetScriptLoadContext()->IsAsyncScript()) {
1179 AddAsyncRequest(request);
1180 if (request->IsFinished()) {
1181 // The script is available already. Run it ASAP when the event
1182 // loop gets a chance to spin.
1184 // KVKV TODO: Instead of processing immediately, try off-thread-parsing
1185 // it and only schedule a pending ProcessRequest if that fails.
1186 ProcessPendingRequestsAsync();
1188 return false;
1190 if (!aElement->GetParserCreated()) {
1191 // Violate the HTML5 spec in order to make LABjs and the "order" plug-in
1192 // for RequireJS work with their Gecko-sniffed code path. See
1193 // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
1194 request->GetScriptLoadContext()->mIsNonAsyncScriptInserted = true;
1195 mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
1196 if (request->IsFinished()) {
1197 // The script is available already. Run it ASAP when the event
1198 // loop gets a chance to spin.
1199 ProcessPendingRequestsAsync();
1201 return false;
1203 // we now have a parser-inserted request that may or may not be still
1204 // loading
1205 if (request->GetScriptLoadContext()->IsDeferredScript()) {
1206 // We don't want to run this yet.
1207 // If we come here, the script is a parser-created script and it has
1208 // the defer attribute but not the async attribute OR it is a module
1209 // script without the async attribute. Since a
1210 // a parser-inserted script is being run, we came here by the parser
1211 // running the script, which means the parser is still alive and the
1212 // parse is ongoing.
1213 NS_ASSERTION(mDocument->GetCurrentContentSink() ||
1214 aElement->GetParserCreated() == FROM_PARSER_XSLT,
1215 "Non-XSLT Defer script on a document without an active "
1216 "parser; bug 592366.");
1217 AddDeferRequest(request);
1218 return false;
1221 if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
1222 // Need to maintain order for XSLT-inserted scripts
1223 NS_ASSERTION(!mParserBlockingRequest,
1224 "Parser-blocking scripts and XSLT scripts in the same doc!");
1225 request->GetScriptLoadContext()->mIsXSLT = true;
1226 mXSLTRequests.AppendElement(request);
1227 if (request->IsFinished()) {
1228 // The script is available already. Run it ASAP when the event
1229 // loop gets a chance to spin.
1230 ProcessPendingRequestsAsync();
1232 return true;
1235 if (request->IsFinished() && ReadyToExecuteParserBlockingScripts()) {
1236 // The request has already been loaded and there are no pending style
1237 // sheets. If the script comes from the network stream, cheat for
1238 // performance reasons and avoid a trip through the event loop.
1239 if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) {
1240 return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
1242 // Otherwise, we've got a document.written script, make a trip through
1243 // the event loop to hide the preload effects from the scripts on the
1244 // Web page.
1245 NS_ASSERTION(!mParserBlockingRequest,
1246 "There can be only one parser-blocking script at a time");
1247 NS_ASSERTION(mXSLTRequests.isEmpty(),
1248 "Parser-blocking scripts and XSLT scripts in the same doc!");
1249 mParserBlockingRequest = request;
1250 ProcessPendingRequestsAsync();
1251 return true;
1254 // The script hasn't loaded yet or there's a style sheet blocking it.
1255 // The script will be run when it loads or the style sheet loads.
1256 NS_ASSERTION(!mParserBlockingRequest,
1257 "There can be only one parser-blocking script at a time");
1258 NS_ASSERTION(mXSLTRequests.isEmpty(),
1259 "Parser-blocking scripts and XSLT scripts in the same doc!");
1260 mParserBlockingRequest = request;
1261 return true;
1264 bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement,
1265 ScriptKind aScriptKind) {
1266 // Is this document sandboxed without 'allow-scripts'?
1267 if (mDocument->HasScriptsBlockedBySandbox()) {
1268 return false;
1271 nsCOMPtr<Element> element = do_QueryInterface(aElement);
1272 nsString nonce = nsContentSecurityUtils::GetIsElementNonceableNonce(*element);
1274 // Does CSP allow this inline script to run?
1275 if (!CSPAllowsInlineScript(aElement, nonce, mDocument)) {
1276 return false;
1279 // Check if adding an import map script is allowed. If not, we bail out
1280 // early to prevent creating a load request.
1281 if (aScriptKind == ScriptKind::eImportMap) {
1282 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
1283 // Step 31.2 type is "importmap":
1284 // Step 1. If el's relevant global object's import maps allowed is false,
1285 // then queue an element task on the DOM manipulation task source given el
1286 // to fire an event named error at el, and return.
1287 if (!mModuleLoader->IsImportMapAllowed()) {
1288 NS_WARNING("ScriptLoader: import maps allowed is false.");
1289 const char* msg = mModuleLoader->HasImportMapRegistered()
1290 ? "ImportMapNotAllowedMultiple"
1291 : "ImportMapNotAllowedAfterModuleLoad";
1292 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
1293 "Script Loader"_ns, mDocument,
1294 nsContentUtils::eDOM_PROPERTIES, msg);
1295 NS_DispatchToCurrentThread(
1296 NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
1297 &nsIScriptElement::FireErrorEvent));
1298 return false;
1302 // Inline classic scripts ignore their CORS mode and are always CORS_NONE.
1303 CORSMode corsMode = CORS_NONE;
1304 if (aScriptKind == ScriptKind::eModule) {
1305 corsMode = aElement->GetCORSMode();
1307 // <https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element>
1308 // step 29 specifies to use the fetch priority. Presumably it has no effect
1309 // for inline scripts.
1310 const auto fetchPriority = aElement->GetFetchPriority();
1312 ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement);
1313 ParserMetadata parserMetadata = GetParserMetadata(aElement);
1315 // NOTE: The `nonce` as specified here is significant, because it's inherited
1316 // by other scripts (e.g. modules created via dynamic imports).
1317 RefPtr<ScriptLoadRequest> request =
1318 CreateLoadRequest(aScriptKind, mDocument->GetDocumentURI(), aElement,
1319 mDocument->NodePrincipal(), corsMode, nonce,
1320 FetchPriorityToRequestPriority(fetchPriority),
1321 SRIMetadata(), // SRI doesn't apply
1322 referrerPolicy, parserMetadata);
1323 request->GetScriptLoadContext()->mIsInline = true;
1324 request->GetScriptLoadContext()->mLineNo = aElement->GetScriptLineNumber();
1325 request->GetScriptLoadContext()->mColumnNo =
1326 aElement->GetScriptColumnNumber();
1327 request->mFetchSourceOnly = true;
1328 request->SetTextSource(request->mLoadContext.get());
1329 TRACE_FOR_TEST_BOOL(request->GetScriptLoadContext()->GetScriptElement(),
1330 "scriptloader_load_source");
1331 CollectScriptTelemetry(request);
1333 // Only the 'async' attribute is heeded on an inline module script and
1334 // inline classic scripts ignore both these attributes.
1335 MOZ_ASSERT(!aElement->GetScriptDeferred());
1336 MOZ_ASSERT_IF(!request->IsModuleRequest(), !aElement->GetScriptAsync());
1337 request->GetScriptLoadContext()->SetScriptMode(
1338 false, aElement->GetScriptAsync(), false);
1340 LOG(("ScriptLoadRequest (%p): Created request for inline script",
1341 request.get()));
1343 request->mBaseURL = mDocument->GetDocBaseURI();
1345 if (request->IsModuleRequest()) {
1346 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph
1347 // Step 1. Disallow further import maps given settings object.
1348 mModuleLoader->DisallowImportMaps();
1350 ModuleLoadRequest* modReq = request->AsModuleRequest();
1351 if (aElement->GetParserCreated() != NOT_FROM_PARSER) {
1352 if (aElement->GetScriptAsync()) {
1353 AddAsyncRequest(modReq);
1354 } else {
1355 AddDeferRequest(modReq);
1359 // This calls OnFetchComplete directly since there's no need to start
1360 // fetching an inline script.
1361 nsresult rv = modReq->OnFetchComplete(NS_OK);
1362 if (NS_FAILED(rv)) {
1363 ReportErrorToConsole(modReq, rv);
1364 HandleLoadError(modReq, rv);
1367 return false;
1370 if (request->IsImportMapRequest()) {
1371 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
1372 // Step 31.2 type is "importmap":
1373 // Impl note: Step 1 is done above before creating a ScriptLoadRequest.
1374 MOZ_ASSERT(mModuleLoader->IsImportMapAllowed());
1376 // Step 2. Set el's relevant global object's import maps allowed to false.
1377 mModuleLoader->DisallowImportMaps();
1379 // Step 3. Let result be the result of creating an import map parse result
1380 // given source text and base URL.
1381 UniquePtr<ImportMap> importMap = mModuleLoader->ParseImportMap(request);
1382 if (!importMap) {
1383 // If parsing import maps fails, the exception will be reported in
1384 // ModuleLoaderBase::ParseImportMap, and the registration of the import
1385 // map will bail out early.
1386 return false;
1389 // Remove any module preloads. Module specifier resolution is invalidated by
1390 // adding an import map, and incorrect dependencies may have been loaded.
1391 mPreloads.RemoveElementsBy([](const PreloadInfo& info) {
1392 if (info.mRequest->IsModuleRequest()) {
1393 info.mRequest->Cancel();
1394 return true;
1396 return false;
1399 // TODO: Bug 1781758: Move RegisterImportMap into EvaluateScriptElement.
1401 // https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-element
1402 // The spec defines 'register an import map' should be done in
1403 // 'execute the script element', because inside 'execute the script element'
1404 // it will perform a 'preparation-time document check'.
1405 // However, as import maps could be only inline scripts by now, the
1406 // 'preparation-time document check' will never fail for import maps.
1407 // So we simply call 'register an import map' here.
1408 mModuleLoader->RegisterImportMap(std::move(importMap));
1409 return false;
1412 request->mState = ScriptLoadRequest::State::Ready;
1413 if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
1414 (!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) {
1415 // Need to maintain order for XSLT-inserted scripts
1416 NS_ASSERTION(!mParserBlockingRequest,
1417 "Parser-blocking scripts and XSLT scripts in the same doc!");
1418 mXSLTRequests.AppendElement(request);
1419 return true;
1421 if (aElement->GetParserCreated() == NOT_FROM_PARSER) {
1422 NS_ASSERTION(
1423 !nsContentUtils::IsSafeToRunScript(),
1424 "A script-inserted script is inserted without an update batch?");
1425 RunScriptWhenSafe(request);
1426 return false;
1428 if (aElement->GetParserCreated() == FROM_PARSER_NETWORK &&
1429 !ReadyToExecuteParserBlockingScripts()) {
1430 NS_ASSERTION(!mParserBlockingRequest,
1431 "There can be only one parser-blocking script at a time");
1432 mParserBlockingRequest = request;
1433 NS_ASSERTION(mXSLTRequests.isEmpty(),
1434 "Parser-blocking scripts and XSLT scripts in the same doc!");
1435 return true;
1437 // We now have a document.written inline script or we have an inline script
1438 // from the network but there is no style sheet that is blocking scripts.
1439 // Don't check for style sheets blocking scripts in the document.write
1440 // case to avoid style sheet network activity affecting when
1441 // document.write returns. It's not really necessary to do this if
1442 // there's no document.write currently on the call stack. However,
1443 // this way matches IE more closely than checking if document.write
1444 // is on the call stack.
1445 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
1446 "Not safe to run a parser-inserted script?");
1447 return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
1450 ScriptLoadRequest* ScriptLoader::LookupPreloadRequest(
1451 nsIScriptElement* aElement, ScriptKind aScriptKind,
1452 const SRIMetadata& aSRIMetadata) {
1453 MOZ_ASSERT(aElement);
1455 nsTArray<PreloadInfo>::index_type i =
1456 mPreloads.IndexOf(aElement->GetScriptURI(), 0, PreloadURIComparator());
1457 if (i == nsTArray<PreloadInfo>::NoIndex) {
1458 return nullptr;
1460 RefPtr<ScriptLoadRequest> request = mPreloads[i].mRequest;
1461 if (aScriptKind != request->mKind) {
1462 return nullptr;
1465 // Found preloaded request. Note that a script-inserted script can steal a
1466 // preload!
1467 request->GetScriptLoadContext()->SetIsLoadRequest(aElement);
1469 if (request->GetScriptLoadContext()->mWasCompiledOMT &&
1470 !request->IsModuleRequest()) {
1471 request->SetReady();
1474 nsString preloadCharset(mPreloads[i].mCharset);
1475 mPreloads.RemoveElementAt(i);
1477 // Double-check that the charset the preload used is the same as the charset
1478 // we have now.
1479 nsAutoString elementCharset;
1480 aElement->GetScriptCharset(elementCharset);
1482 // Bug 1832361: charset and crossorigin attributes shouldn't affect matching
1483 // of module scripts and modulepreload
1484 if (!request->IsModuleRequest() &&
1485 (!elementCharset.Equals(preloadCharset) ||
1486 aElement->GetCORSMode() != request->CORSMode())) {
1487 // Drop the preload.
1488 request->Cancel();
1489 AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RequestMismatch);
1490 return nullptr;
1493 if (!aSRIMetadata.CanTrustBeDelegatedTo(request->mIntegrity)) {
1494 // Don't cancel link preload requests, we want to deliver onload according
1495 // the result of the load, cancellation would unexpectedly lead to error
1496 // notification.
1497 if (!request->GetScriptLoadContext()->IsLinkPreloadScript()) {
1498 request->Cancel();
1500 return nullptr;
1503 // Report any errors that we skipped while preloading.
1504 ReportPreloadErrorsToConsole(request);
1506 // This makes sure the pending preload (if exists) for this resource is
1507 // properly marked as used and thus not notified in the console as unused.
1508 request->GetScriptLoadContext()->NotifyUsage(mDocument);
1509 // A used preload must no longer be found in the Document's hash table. Any
1510 // <link preload> tag after the <script> tag will start a new request, that
1511 // can be satisfied from a different cache, but not from the preload cache.
1512 request->GetScriptLoadContext()->RemoveSelf(mDocument);
1514 return request;
1517 void ScriptLoader::GetSRIMetadata(const nsAString& aIntegrityAttr,
1518 SRIMetadata* aMetadataOut) {
1519 MOZ_ASSERT(aMetadataOut->IsEmpty());
1521 if (aIntegrityAttr.IsEmpty()) {
1522 return;
1525 MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
1526 ("ScriptLoader::GetSRIMetadata, integrity=%s",
1527 NS_ConvertUTF16toUTF8(aIntegrityAttr).get()));
1529 nsAutoCString sourceUri;
1530 if (mDocument->GetDocumentURI()) {
1531 mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
1533 SRICheck::IntegrityMetadata(aIntegrityAttr, sourceUri, mReporter,
1534 aMetadataOut);
1537 ReferrerPolicy ScriptLoader::GetReferrerPolicy(nsIScriptElement* aElement) {
1538 ReferrerPolicy scriptReferrerPolicy = aElement->GetReferrerPolicy();
1539 if (scriptReferrerPolicy != ReferrerPolicy::_empty) {
1540 return scriptReferrerPolicy;
1542 return mDocument->GetReferrerPolicy();
1545 void ScriptLoader::CancelAndClearScriptLoadRequests() {
1546 // Cancel all requests that have not been executed and remove them.
1548 if (mParserBlockingRequest) {
1549 mParserBlockingRequest->Cancel();
1550 mParserBlockingRequest = nullptr;
1553 mDeferRequests.CancelRequestsAndClear();
1554 mLoadingAsyncRequests.CancelRequestsAndClear();
1555 mLoadedAsyncRequests.CancelRequestsAndClear();
1556 mNonAsyncExternalScriptInsertedRequests.CancelRequestsAndClear();
1557 mXSLTRequests.CancelRequestsAndClear();
1558 mOffThreadCompilingRequests.CancelRequestsAndClear();
1560 if (mModuleLoader) {
1561 mModuleLoader->CancelAndClearDynamicImports();
1564 for (ModuleLoader* loader : mWebExtModuleLoaders) {
1565 loader->CancelAndClearDynamicImports();
1568 for (ModuleLoader* loader : mShadowRealmModuleLoaders) {
1569 loader->CancelAndClearDynamicImports();
1572 for (size_t i = 0; i < mPreloads.Length(); i++) {
1573 mPreloads[i].mRequest->Cancel();
1575 mPreloads.Clear();
1578 nsresult ScriptLoader::CompileOffThreadOrProcessRequest(
1579 ScriptLoadRequest* aRequest) {
1580 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
1581 "Processing requests when running scripts is unsafe.");
1583 if (!aRequest->GetScriptLoadContext()->mCompileOrDecodeTask &&
1584 !aRequest->GetScriptLoadContext()->CompileStarted()) {
1585 bool couldCompile = false;
1586 nsresult rv = AttemptOffThreadScriptCompile(aRequest, &couldCompile);
1587 if (NS_FAILED(rv)) {
1588 HandleLoadError(aRequest, rv);
1589 return rv;
1592 if (couldCompile) {
1593 return NS_OK;
1597 return ProcessRequest(aRequest);
1600 namespace {
1602 class OffThreadCompilationCompleteTask : public Task {
1603 public:
1604 OffThreadCompilationCompleteTask(ScriptLoadRequest* aRequest,
1605 ScriptLoader* aLoader)
1606 : Task(Kind::MainThreadOnly, EventQueuePriority::Normal),
1607 mRequest(aRequest),
1608 mLoader(aLoader) {
1609 MOZ_ASSERT(NS_IsMainThread());
1612 void RecordStartTime() { mStartTime = TimeStamp::Now(); }
1613 void RecordStopTime() { mStopTime = TimeStamp::Now(); }
1615 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
1616 bool GetName(nsACString& aName) override {
1617 aName.AssignLiteral("dom::OffThreadCompilationCompleteTask");
1618 return true;
1620 #endif
1622 TaskResult Run() override {
1623 MOZ_ASSERT(NS_IsMainThread());
1625 RefPtr<ScriptLoadContext> context = mRequest->GetScriptLoadContext();
1627 if (!context->mCompileOrDecodeTask) {
1628 // Request has been cancelled by MaybeCancelOffThreadScript.
1629 return TaskResult::Complete;
1632 RecordStopTime();
1634 if (profiler_is_active()) {
1635 ProfilerString8View scriptSourceString;
1636 if (mRequest->IsTextSource()) {
1637 scriptSourceString = "ScriptCompileOffThread";
1638 } else {
1639 MOZ_ASSERT(mRequest->IsBytecode());
1640 scriptSourceString = "BytecodeDecodeOffThread";
1643 nsAutoCString profilerLabelString;
1644 mRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString);
1645 PROFILER_MARKER_TEXT(scriptSourceString, JS,
1646 MarkerTiming::Interval(mStartTime, mStopTime),
1647 profilerLabelString);
1650 (void)mLoader->ProcessOffThreadRequest(mRequest);
1652 mRequest = nullptr;
1653 mLoader = nullptr;
1654 return TaskResult::Complete;
1657 private:
1658 // NOTE:
1659 // These fields are main-thread only, and this task shouldn't be freed off
1660 // main thread.
1662 // This is guaranteed by not having off-thread tasks which depends on this
1663 // task, because otherwise the off-thread task's mDependencies can be the
1664 // last reference, which results in freeing this task off main thread.
1666 // If such task is added, these fields must be moved to separate storage.
1667 RefPtr<ScriptLoadRequest> mRequest;
1668 RefPtr<ScriptLoader> mLoader;
1670 TimeStamp mStartTime;
1671 TimeStamp mStopTime;
1674 } /* anonymous namespace */
1676 // TODO: This uses the same heuristics and the same threshold as the
1677 // JS::CanCompileOffThread / JS::CanDecodeOffThread APIs, but the
1678 // heuristics needs to be updated to reflect the change regarding the
1679 // Stencil API, and also the thread management on the consumer side
1680 // (bug 1846160).
1681 static constexpr size_t OffThreadMinimumTextLength = 5 * 1000;
1682 static constexpr size_t OffThreadMinimumBytecodeLength = 5 * 1000;
1684 nsresult ScriptLoader::AttemptOffThreadScriptCompile(
1685 ScriptLoadRequest* aRequest, bool* aCouldCompileOut) {
1686 // If speculative parsing is enabled, the request may not be ready to run if
1687 // the element is not yet available.
1688 MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled() && !aRequest->IsModuleRequest(),
1689 aRequest->IsFinished());
1690 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT);
1691 MOZ_ASSERT(aCouldCompileOut && !*aCouldCompileOut);
1693 // Don't off-thread compile inline scripts.
1694 if (aRequest->GetScriptLoadContext()->mIsInline) {
1695 return NS_OK;
1698 nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalForRequest(aRequest);
1699 if (!globalObject) {
1700 return NS_ERROR_FAILURE;
1703 AutoJSAPI jsapi;
1704 if (!jsapi.Init(globalObject)) {
1705 return NS_ERROR_FAILURE;
1708 JSContext* cx = jsapi.cx();
1709 JS::CompileOptions options(cx);
1711 // Introduction script will actually be computed and set when the script is
1712 // collected from offthread
1713 JS::Rooted<JSScript*> dummyIntroductionScript(cx);
1714 nsresult rv = FillCompileOptionsForRequest(cx, aRequest, &options,
1715 &dummyIntroductionScript);
1716 if (NS_WARN_IF(NS_FAILED(rv))) {
1717 return rv;
1720 if (aRequest->IsTextSource()) {
1721 if (!StaticPrefs::javascript_options_parallel_parsing() ||
1722 aRequest->ScriptTextLength() < OffThreadMinimumTextLength) {
1723 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
1724 "scriptloader_main_thread_compile");
1725 return NS_OK;
1727 } else {
1728 MOZ_ASSERT(aRequest->IsBytecode());
1730 JS::TranscodeRange bytecode = aRequest->Bytecode();
1731 if (!StaticPrefs::javascript_options_parallel_parsing() ||
1732 bytecode.length() < OffThreadMinimumBytecodeLength) {
1733 return NS_OK;
1737 RefPtr<CompileOrDecodeTask> compileOrDecodeTask;
1738 rv = CreateOffThreadTask(cx, aRequest, options,
1739 getter_AddRefs(compileOrDecodeTask));
1740 NS_ENSURE_SUCCESS(rv, rv);
1742 RefPtr<OffThreadCompilationCompleteTask> completeTask =
1743 new OffThreadCompilationCompleteTask(aRequest, this);
1745 completeTask->RecordStartTime();
1747 aRequest->GetScriptLoadContext()->mCompileOrDecodeTask = compileOrDecodeTask;
1748 completeTask->AddDependency(compileOrDecodeTask);
1750 TaskController::Get()->AddTask(compileOrDecodeTask.forget());
1751 TaskController::Get()->AddTask(completeTask.forget());
1753 aRequest->GetScriptLoadContext()->BlockOnload(mDocument);
1755 // Once the compilation is finished, the completeTask will be run on
1756 // the main thread to call ScriptLoader::ProcessOffThreadRequest for the
1757 // request.
1758 aRequest->mState = ScriptLoadRequest::State::Compiling;
1760 // Requests that are not tracked elsewhere are added to a list while they are
1761 // being compiled off-thread, so we can cancel the compilation later if
1762 // necessary.
1764 // Non-top-level modules not tracked because these are cancelled from their
1765 // importing module.
1766 if (aRequest->IsTopLevel() && !aRequest->isInList()) {
1767 mOffThreadCompilingRequests.AppendElement(aRequest);
1768 aRequest->GetScriptLoadContext()->mInCompilingList = true;
1771 *aCouldCompileOut = true;
1773 return NS_OK;
1776 CompileOrDecodeTask::CompileOrDecodeTask()
1777 : Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal),
1778 mMutex("CompileOrDecodeTask"),
1779 mOptions(JS::OwningCompileOptions::ForFrontendContext()) {}
1781 CompileOrDecodeTask::~CompileOrDecodeTask() {
1782 if (mFrontendContext) {
1783 JS::DestroyFrontendContext(mFrontendContext);
1784 mFrontendContext = nullptr;
1788 nsresult CompileOrDecodeTask::InitFrontendContext() {
1789 mFrontendContext = JS::NewFrontendContext();
1790 if (!mFrontendContext) {
1791 mIsCancelled = true;
1792 return NS_ERROR_OUT_OF_MEMORY;
1794 return NS_OK;
1797 void CompileOrDecodeTask::DidRunTask(const MutexAutoLock& aProofOfLock,
1798 RefPtr<JS::Stencil>&& aStencil) {
1799 if (aStencil) {
1800 if (!JS::PrepareForInstantiate(mFrontendContext, *aStencil,
1801 mInstantiationStorage)) {
1802 aStencil = nullptr;
1806 mStencil = std::move(aStencil);
1809 already_AddRefed<JS::Stencil> CompileOrDecodeTask::StealResult(
1810 JSContext* aCx, JS::InstantiationStorage* aInstantiationStorage) {
1811 JS::FrontendContext* fc = mFrontendContext;
1812 mFrontendContext = nullptr;
1813 auto destroyFrontendContext =
1814 mozilla::MakeScopeExit([&]() { JS::DestroyFrontendContext(fc); });
1816 MOZ_ASSERT(fc);
1818 if (JS::HadFrontendErrors(fc)) {
1819 (void)JS::ConvertFrontendErrorsToRuntimeErrors(aCx, fc, mOptions);
1820 return nullptr;
1823 if (!mStencil && JS::IsTranscodeFailureResult(mResult)) {
1824 // Decode failure with bad content isn't reported as error.
1825 JS_ReportErrorASCII(aCx, "failed to decode cache");
1826 return nullptr;
1829 // Report warnings.
1830 if (!JS::ConvertFrontendErrorsToRuntimeErrors(aCx, fc, mOptions)) {
1831 return nullptr;
1834 MOZ_ASSERT(mStencil,
1835 "If this task is cancelled, StealResult shouldn't be called");
1837 // This task is started and finished successfully.
1838 *aInstantiationStorage = std::move(mInstantiationStorage);
1840 return mStencil.forget();
1843 void CompileOrDecodeTask::Cancel() {
1844 MOZ_ASSERT(NS_IsMainThread());
1846 MutexAutoLock lock(mMutex);
1848 mIsCancelled = true;
1851 enum class CompilationTarget { Script, Module };
1853 template <CompilationTarget target>
1854 class ScriptOrModuleCompileTask final : public CompileOrDecodeTask {
1855 public:
1856 explicit ScriptOrModuleCompileTask(
1857 ScriptLoader::MaybeSourceText&& aMaybeSource)
1858 : CompileOrDecodeTask(), mMaybeSource(std::move(aMaybeSource)) {}
1860 nsresult Init(JS::CompileOptions& aOptions) {
1861 nsresult rv = InitFrontendContext();
1862 NS_ENSURE_SUCCESS(rv, rv);
1864 if (!mOptions.copy(mFrontendContext, aOptions)) {
1865 mIsCancelled = true;
1866 return NS_ERROR_OUT_OF_MEMORY;
1869 return NS_OK;
1872 TaskResult Run() override {
1873 MutexAutoLock lock(mMutex);
1875 if (IsCancelled(lock)) {
1876 return TaskResult::Complete;
1879 RefPtr<JS::Stencil> stencil = Compile();
1881 DidRunTask(lock, std::move(stencil));
1882 return TaskResult::Complete;
1885 private:
1886 already_AddRefed<JS::Stencil> Compile() {
1887 size_t stackSize = TaskController::GetThreadStackSize();
1888 JS::SetNativeStackQuota(mFrontendContext,
1889 JS::ThreadStackQuotaForSize(stackSize));
1891 auto compile = [&](auto& source) {
1892 if constexpr (target == CompilationTarget::Script) {
1893 return JS::CompileGlobalScriptToStencil(mFrontendContext, mOptions,
1894 source);
1896 return JS::CompileModuleScriptToStencil(mFrontendContext, mOptions,
1897 source);
1899 return mMaybeSource.mapNonEmpty(compile);
1902 public:
1903 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
1904 bool GetName(nsACString& aName) override {
1905 if constexpr (target == CompilationTarget::Script) {
1906 aName.AssignLiteral("ScriptCompileTask");
1907 } else {
1908 aName.AssignLiteral("ModuleCompileTask");
1910 return true;
1912 #endif
1914 private:
1915 ScriptLoader::MaybeSourceText mMaybeSource;
1918 using ScriptCompileTask =
1919 class ScriptOrModuleCompileTask<CompilationTarget::Script>;
1920 using ModuleCompileTask =
1921 class ScriptOrModuleCompileTask<CompilationTarget::Module>;
1923 class ScriptDecodeTask final : public CompileOrDecodeTask {
1924 public:
1925 explicit ScriptDecodeTask(const JS::TranscodeRange& aRange)
1926 : mRange(aRange) {}
1928 nsresult Init(JS::DecodeOptions& aOptions) {
1929 nsresult rv = InitFrontendContext();
1930 NS_ENSURE_SUCCESS(rv, rv);
1932 if (!mDecodeOptions.copy(mFrontendContext, aOptions)) {
1933 mIsCancelled = true;
1934 return NS_ERROR_OUT_OF_MEMORY;
1937 return NS_OK;
1940 TaskResult Run() override {
1941 MutexAutoLock lock(mMutex);
1943 if (IsCancelled(lock)) {
1944 return TaskResult::Complete;
1947 RefPtr<JS::Stencil> stencil = Decode();
1949 JS::OwningCompileOptions compileOptions(
1950 (JS::OwningCompileOptions::ForFrontendContext()));
1951 mOptions.steal(std::move(mDecodeOptions));
1953 DidRunTask(lock, std::move(stencil));
1954 return TaskResult::Complete;
1957 private:
1958 already_AddRefed<JS::Stencil> Decode() {
1959 // NOTE: JS::DecodeStencil doesn't need the stack quota.
1961 RefPtr<JS::Stencil> stencil;
1962 mResult = JS::DecodeStencil(mFrontendContext, mDecodeOptions, mRange,
1963 getter_AddRefs(stencil));
1964 return stencil.forget();
1967 public:
1968 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
1969 bool GetName(nsACString& aName) override {
1970 aName.AssignLiteral("ScriptDecodeTask");
1971 return true;
1973 #endif
1975 private:
1976 JS::OwningDecodeOptions mDecodeOptions;
1978 JS::TranscodeRange mRange;
1981 nsresult ScriptLoader::CreateOffThreadTask(
1982 JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions& aOptions,
1983 CompileOrDecodeTask** aCompileOrDecodeTask) {
1984 if (aRequest->IsBytecode()) {
1985 JS::TranscodeRange bytecode = aRequest->Bytecode();
1986 JS::DecodeOptions decodeOptions(aOptions);
1987 RefPtr<ScriptDecodeTask> decodeTask = new ScriptDecodeTask(bytecode);
1988 nsresult rv = decodeTask->Init(decodeOptions);
1989 NS_ENSURE_SUCCESS(rv, rv);
1990 decodeTask.forget(aCompileOrDecodeTask);
1991 return NS_OK;
1994 MaybeSourceText maybeSource;
1995 nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource,
1996 aRequest->mLoadContext.get());
1997 NS_ENSURE_SUCCESS(rv, rv);
1999 if (ShouldApplyDelazifyStrategy(aRequest)) {
2000 ApplyDelazifyStrategy(&aOptions);
2001 mTotalFullParseSize +=
2002 aRequest->ScriptTextLength() > 0
2003 ? static_cast<uint32_t>(aRequest->ScriptTextLength())
2004 : 0;
2006 LOG(
2007 ("ScriptLoadRequest (%p): non-on-demand-only (omt) Parsing Enabled "
2008 "for url=%s mTotalFullParseSize=%u",
2009 aRequest, aRequest->mURI->GetSpecOrDefault().get(),
2010 mTotalFullParseSize));
2013 if (aRequest->IsModuleRequest()) {
2014 RefPtr<ModuleCompileTask> compileTask =
2015 new ModuleCompileTask(std::move(maybeSource));
2016 rv = compileTask->Init(aOptions);
2017 NS_ENSURE_SUCCESS(rv, rv);
2018 compileTask.forget(aCompileOrDecodeTask);
2019 return NS_OK;
2022 if (StaticPrefs::dom_expose_test_interfaces()) {
2023 switch (aOptions.eagerDelazificationStrategy()) {
2024 case JS::DelazificationOption::OnDemandOnly:
2025 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2026 "delazification_on_demand_only");
2027 break;
2028 case JS::DelazificationOption::CheckConcurrentWithOnDemand:
2029 case JS::DelazificationOption::ConcurrentDepthFirst:
2030 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2031 "delazification_concurrent_depth_first");
2032 break;
2033 case JS::DelazificationOption::ConcurrentLargeFirst:
2034 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2035 "delazification_concurrent_large_first");
2036 break;
2037 case JS::DelazificationOption::ParseEverythingEagerly:
2038 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2039 "delazification_parse_everything_eagerly");
2040 break;
2044 RefPtr<ScriptCompileTask> compileTask =
2045 new ScriptCompileTask(std::move(maybeSource));
2046 rv = compileTask->Init(aOptions);
2047 NS_ENSURE_SUCCESS(rv, rv);
2048 compileTask.forget(aCompileOrDecodeTask);
2049 return NS_OK;
2052 nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) {
2053 MOZ_ASSERT(aRequest->IsCompiling());
2054 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT);
2056 if (aRequest->IsCanceled()) {
2057 return NS_OK;
2060 aRequest->GetScriptLoadContext()->mWasCompiledOMT = true;
2062 if (aRequest->GetScriptLoadContext()->mInCompilingList) {
2063 mOffThreadCompilingRequests.Remove(aRequest);
2064 aRequest->GetScriptLoadContext()->mInCompilingList = false;
2067 if (aRequest->IsModuleRequest()) {
2068 MOZ_ASSERT(aRequest->GetScriptLoadContext()->mCompileOrDecodeTask);
2069 ModuleLoadRequest* request = aRequest->AsModuleRequest();
2070 return request->OnFetchComplete(NS_OK);
2073 // Element may not be ready yet if speculatively compiling, so process the
2074 // request in ProcessPendingRequests when it is available.
2075 MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled(),
2076 aRequest->GetScriptLoadContext()->GetScriptElement());
2077 if (!aRequest->GetScriptLoadContext()->GetScriptElement()) {
2078 // Unblock onload here in case this request never gets executed.
2079 aRequest->GetScriptLoadContext()->MaybeUnblockOnload();
2080 return NS_OK;
2083 aRequest->SetReady();
2085 if (aRequest == mParserBlockingRequest) {
2086 if (!ReadyToExecuteParserBlockingScripts()) {
2087 // If not ready to execute scripts, schedule an async call to
2088 // ProcessPendingRequests to handle it.
2089 ProcessPendingRequestsAsync();
2090 return NS_OK;
2093 // Same logic as in top of ProcessPendingRequests.
2094 mParserBlockingRequest = nullptr;
2095 UnblockParser(aRequest);
2096 ProcessRequest(aRequest);
2097 ContinueParserAsync(aRequest);
2098 return NS_OK;
2101 // Async scripts and blocking scripts can be executed right away.
2102 if ((aRequest->GetScriptLoadContext()->IsAsyncScript() ||
2103 aRequest->GetScriptLoadContext()->IsBlockingScript()) &&
2104 !aRequest->isInList()) {
2105 return ProcessRequest(aRequest);
2108 // Process other scripts in the proper order.
2109 ProcessPendingRequests();
2110 return NS_OK;
2113 nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) {
2114 LOG(("ScriptLoadRequest (%p): Process request", aRequest));
2116 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
2117 "Processing requests when running scripts is unsafe.");
2118 NS_ASSERTION(aRequest->IsFinished(),
2119 "Processing a request that is not ready to run.");
2121 NS_ENSURE_ARG(aRequest);
2123 auto unblockOnload = MakeScopeExit(
2124 [&] { aRequest->GetScriptLoadContext()->MaybeUnblockOnload(); });
2126 if (aRequest->IsModuleRequest()) {
2127 ModuleLoadRequest* request = aRequest->AsModuleRequest();
2128 if (request->IsDynamicImport()) {
2129 request->ProcessDynamicImport();
2130 return NS_OK;
2133 if (request->mModuleScript) {
2134 if (!request->InstantiateModuleGraph()) {
2135 request->mModuleScript = nullptr;
2139 if (!request->mModuleScript) {
2140 // There was an error fetching a module script. Nothing to do here.
2141 LOG(("ScriptLoadRequest (%p): Error loading request, firing error",
2142 aRequest));
2143 FireScriptAvailable(NS_ERROR_FAILURE, aRequest);
2144 return NS_OK;
2148 nsCOMPtr<nsINode> scriptElem =
2149 do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement());
2151 nsCOMPtr<Document> doc;
2152 if (!aRequest->GetScriptLoadContext()->mIsInline ||
2153 aRequest->IsModuleRequest()) {
2154 doc = scriptElem->OwnerDoc();
2157 nsCOMPtr<nsIScriptElement> oldParserInsertedScript;
2158 uint32_t parserCreated = aRequest->GetScriptLoadContext()->GetParserCreated();
2159 if (parserCreated) {
2160 oldParserInsertedScript = mCurrentParserInsertedScript;
2161 mCurrentParserInsertedScript =
2162 aRequest->GetScriptLoadContext()->GetScriptElement();
2165 aRequest->GetScriptLoadContext()->GetScriptElement()->BeginEvaluating();
2167 FireScriptAvailable(NS_OK, aRequest);
2169 // The window may have gone away by this point, in which case there's no point
2170 // in trying to run the script.
2173 // Try to perform a microtask checkpoint
2174 nsAutoMicroTask mt;
2177 nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow();
2178 bool runScript = !!pwin;
2179 if (runScript) {
2180 nsContentUtils::DispatchTrustedEvent(
2181 scriptElem->OwnerDoc(), scriptElem, u"beforescriptexecute"_ns,
2182 CanBubble::eYes, Cancelable::eYes, &runScript);
2185 // Inner window could have gone away after firing beforescriptexecute
2186 pwin = mDocument->GetInnerWindow();
2187 if (!pwin) {
2188 runScript = false;
2191 nsresult rv = NS_OK;
2192 if (runScript) {
2193 if (doc) {
2194 doc->IncrementIgnoreDestructiveWritesCounter();
2196 rv = EvaluateScriptElement(aRequest);
2197 if (doc) {
2198 doc->DecrementIgnoreDestructiveWritesCounter();
2201 nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(), scriptElem,
2202 u"afterscriptexecute"_ns,
2203 CanBubble::eYes, Cancelable::eNo);
2206 FireScriptEvaluated(rv, aRequest);
2208 aRequest->GetScriptLoadContext()->GetScriptElement()->EndEvaluating();
2210 if (parserCreated) {
2211 mCurrentParserInsertedScript = oldParserInsertedScript;
2214 if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) {
2215 // The request was parsed off-main-thread, but the result of the off
2216 // thread parse was not actually needed to process the request
2217 // (disappearing window, some other error, ...). Finish the
2218 // request to avoid leaks.
2219 MOZ_ASSERT(!aRequest->IsModuleRequest());
2220 aRequest->GetScriptLoadContext()->MaybeCancelOffThreadScript();
2223 // Free any source data, but keep the bytecode content as we might have to
2224 // save it later.
2225 aRequest->ClearScriptSource();
2226 if (aRequest->IsBytecode()) {
2227 // We received bytecode as input, thus we were decoding, and we will not be
2228 // encoding the bytecode once more. We can safely clear the content of this
2229 // buffer.
2230 aRequest->DropBytecode();
2233 return rv;
2236 void ScriptLoader::FireScriptAvailable(nsresult aResult,
2237 ScriptLoadRequest* aRequest) {
2238 for (int32_t i = 0; i < mObservers.Count(); i++) {
2239 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
2240 obs->ScriptAvailable(
2241 aResult, aRequest->GetScriptLoadContext()->GetScriptElement(),
2242 aRequest->GetScriptLoadContext()->mIsInline, aRequest->mURI,
2243 aRequest->GetScriptLoadContext()->mLineNo);
2246 bool isInlineClassicScript = aRequest->GetScriptLoadContext()->mIsInline &&
2247 !aRequest->IsModuleRequest();
2248 RefPtr<nsIScriptElement> scriptElement =
2249 aRequest->GetScriptLoadContext()->GetScriptElement();
2250 scriptElement->ScriptAvailable(aResult, scriptElement, isInlineClassicScript,
2251 aRequest->mURI,
2252 aRequest->GetScriptLoadContext()->mLineNo);
2255 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
2256 MOZ_CAN_RUN_SCRIPT_BOUNDARY void ScriptLoader::FireScriptEvaluated(
2257 nsresult aResult, ScriptLoadRequest* aRequest) {
2258 for (int32_t i = 0; i < mObservers.Count(); i++) {
2259 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
2260 RefPtr<nsIScriptElement> scriptElement =
2261 aRequest->GetScriptLoadContext()->GetScriptElement();
2262 obs->ScriptEvaluated(aResult, scriptElement,
2263 aRequest->GetScriptLoadContext()->mIsInline);
2266 RefPtr<nsIScriptElement> scriptElement =
2267 aRequest->GetScriptLoadContext()->GetScriptElement();
2268 scriptElement->ScriptEvaluated(aResult, scriptElement,
2269 aRequest->GetScriptLoadContext()->mIsInline);
2272 already_AddRefed<nsIGlobalObject> ScriptLoader::GetGlobalForRequest(
2273 ScriptLoadRequest* aRequest) {
2274 if (aRequest->IsModuleRequest()) {
2275 ModuleLoader* loader =
2276 ModuleLoader::From(aRequest->AsModuleRequest()->mLoader);
2277 nsCOMPtr<nsIGlobalObject> global = loader->GetGlobalObject();
2278 return global.forget();
2281 return GetScriptGlobalObject();
2284 already_AddRefed<nsIScriptGlobalObject> ScriptLoader::GetScriptGlobalObject() {
2285 if (!mDocument) {
2286 return nullptr;
2289 nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow();
2290 if (!pwin) {
2291 return nullptr;
2294 nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
2295 NS_ASSERTION(globalObject, "windows must be global objects");
2297 // and make sure we are setup for this type of script.
2298 nsresult rv = globalObject->EnsureScriptEnvironment();
2299 if (NS_FAILED(rv)) {
2300 return nullptr;
2303 return globalObject.forget();
2306 nsresult ScriptLoader::FillCompileOptionsForRequest(
2307 JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
2308 JS::MutableHandle<JSScript*> aIntroductionScript) {
2309 // It's very important to use aRequest->mURI, not the final URI of the channel
2310 // aRequest ended up getting script data from, as the script filename.
2311 nsresult rv = aRequest->mURI->GetSpec(aRequest->mURL);
2312 if (NS_WARN_IF(NS_FAILED(rv))) {
2313 return rv;
2316 if (mDocument) {
2317 mDocument->NoteScriptTrackingStatus(
2318 aRequest->mURL, aRequest->GetScriptLoadContext()->IsTracking());
2321 const char* introductionType;
2322 if (aRequest->IsModuleRequest() &&
2323 !aRequest->AsModuleRequest()->IsTopLevel()) {
2324 introductionType = "importedModule";
2325 } else if (!aRequest->GetScriptLoadContext()->mIsInline) {
2326 introductionType = "srcScript";
2327 } else if (aRequest->GetScriptLoadContext()->GetParserCreated() ==
2328 FROM_PARSER_NETWORK) {
2329 introductionType = "inlineScript";
2330 } else {
2331 introductionType = "injectedScript";
2333 aOptions->setIntroductionInfoToCaller(aCx, introductionType,
2334 aIntroductionScript);
2335 aOptions->setFileAndLine(aRequest->mURL.get(),
2336 aRequest->GetScriptLoadContext()->mLineNo);
2337 // The column is only relevant for inline scripts in order for SpiderMonkey to
2338 // properly compute offsets relatively to the script position within the HTML
2339 // file. injectedScript are not concerned and are always considered to start
2340 // at column 0.
2341 if (aRequest->GetScriptLoadContext()->mIsInline &&
2342 aRequest->GetScriptLoadContext()->GetParserCreated() ==
2343 FROM_PARSER_NETWORK) {
2344 aOptions->setColumn(aRequest->GetScriptLoadContext()->mColumnNo);
2346 aOptions->setIsRunOnce(true);
2347 aOptions->setNoScriptRval(true);
2348 if (aRequest->mSourceMapURL) {
2349 aOptions->setSourceMapURL(aRequest->mSourceMapURL->get());
2351 if (aRequest->mOriginPrincipal) {
2352 nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalForRequest(aRequest);
2353 nsIPrincipal* scriptPrin = globalObject->PrincipalOrNull();
2354 MOZ_ASSERT(scriptPrin);
2355 bool subsumes = scriptPrin->Subsumes(aRequest->mOriginPrincipal);
2356 aOptions->setMutedErrors(!subsumes);
2359 if (aRequest->IsModuleRequest()) {
2360 aOptions->setHideScriptFromDebugger(true);
2363 aOptions->setDeferDebugMetadata(true);
2365 aOptions->borrowBuffer = true;
2367 return NS_OK;
2370 /* static */
2371 bool ScriptLoader::ShouldCacheBytecode(ScriptLoadRequest* aRequest) {
2372 using mozilla::TimeDuration;
2373 using mozilla::TimeStamp;
2375 // We need the nsICacheInfoChannel to exist to be able to open the alternate
2376 // data output stream. This pointer would only be non-null if the bytecode was
2377 // activated at the time the channel got created in StartLoad.
2378 if (!aRequest->mCacheInfo) {
2379 LOG(("ScriptLoadRequest (%p): Cannot cache anything (cacheInfo = %p)",
2380 aRequest, aRequest->mCacheInfo.get()));
2381 return false;
2384 // Look at the preference to know which strategy (parameters) should be used
2385 // when the bytecode cache is enabled.
2386 int32_t strategy = StaticPrefs::dom_script_loader_bytecode_cache_strategy();
2388 // List of parameters used by the strategies.
2389 bool hasSourceLengthMin = false;
2390 bool hasFetchCountMin = false;
2391 size_t sourceLengthMin = 100;
2392 uint32_t fetchCountMin = 4;
2394 LOG(("ScriptLoadRequest (%p): Bytecode-cache: strategy = %d.", aRequest,
2395 strategy));
2396 switch (strategy) {
2397 case -2: {
2398 // Reader mode, keep requesting alternate data but no longer save it.
2399 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Encoding disabled.",
2400 aRequest));
2401 return false;
2403 case -1: {
2404 // Eager mode, skip heuristics!
2405 hasSourceLengthMin = false;
2406 hasFetchCountMin = false;
2407 break;
2409 default:
2410 case 0: {
2411 hasSourceLengthMin = true;
2412 hasFetchCountMin = true;
2413 sourceLengthMin = 1024;
2414 // If we were to optimize only for speed, without considering the impact
2415 // on memory, we should set this threshold to 2. (Bug 900784 comment 120)
2416 fetchCountMin = 4;
2417 break;
2421 // If the script is too small/large, do not attempt at creating a bytecode
2422 // cache for this script, as the overhead of parsing it might not be worth the
2423 // effort.
2424 if (hasSourceLengthMin) {
2425 size_t sourceLength;
2426 size_t minLength;
2427 MOZ_ASSERT(aRequest->IsTextSource());
2428 sourceLength = aRequest->ReceivedScriptTextLength();
2429 minLength = sourceLengthMin;
2430 if (sourceLength < minLength) {
2431 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Script is too small.",
2432 aRequest));
2433 return false;
2437 // Check that we loaded the cache entry a few times before attempting any
2438 // bytecode-cache optimization, such that we do not waste time on entry which
2439 // are going to be dropped soon.
2440 if (hasFetchCountMin) {
2441 uint32_t fetchCount = 0;
2442 if (NS_FAILED(aRequest->mCacheInfo->GetCacheTokenFetchCount(&fetchCount))) {
2443 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Cannot get fetchCount.",
2444 aRequest));
2445 return false;
2447 LOG(("ScriptLoadRequest (%p): Bytecode-cache: fetchCount = %d.", aRequest,
2448 fetchCount));
2449 if (fetchCount < fetchCountMin) {
2450 return false;
2454 LOG(("ScriptLoadRequest (%p): Bytecode-cache: Trigger encoding.", aRequest));
2455 return true;
2458 class MOZ_RAII AutoSetProcessingScriptTag {
2459 nsCOMPtr<nsIScriptContext> mContext;
2460 bool mOldTag;
2462 public:
2463 explicit AutoSetProcessingScriptTag(nsIScriptContext* aContext)
2464 : mContext(aContext), mOldTag(mContext->GetProcessingScriptTag()) {
2465 mContext->SetProcessingScriptTag(true);
2468 ~AutoSetProcessingScriptTag() { mContext->SetProcessingScriptTag(mOldTag); }
2471 static nsresult ExecuteCompiledScript(JSContext* aCx, JSExecutionContext& aExec,
2472 ClassicScript* aLoaderScript) {
2473 JS::Rooted<JSScript*> script(aCx, aExec.GetScript());
2474 if (!script) {
2475 // Compilation succeeds without producing a script if scripting is
2476 // disabled for the global.
2477 return NS_OK;
2480 if (JS::GetScriptPrivate(script).isUndefined()) {
2481 aLoaderScript->AssociateWithScript(script);
2484 return aExec.ExecScript();
2487 nsresult ScriptLoader::EvaluateScriptElement(ScriptLoadRequest* aRequest) {
2488 using namespace mozilla::Telemetry;
2489 MOZ_ASSERT(aRequest->IsFinished());
2491 // We need a document to evaluate scripts.
2492 if (!mDocument) {
2493 return NS_ERROR_FAILURE;
2496 nsCOMPtr<nsIContent> scriptContent(
2497 do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement()));
2498 MOZ_ASSERT(scriptContent);
2499 Document* ownerDoc = scriptContent->OwnerDoc();
2500 if (ownerDoc != mDocument) {
2501 // Willful violation of HTML5 as of 2010-12-01
2502 return NS_ERROR_FAILURE;
2505 nsCOMPtr<nsIGlobalObject> globalObject;
2506 nsCOMPtr<nsIScriptContext> context;
2507 if (!IsWebExtensionRequest(aRequest)) {
2508 // Otherwise we have to ensure that there is a nsIScriptContext.
2509 nsCOMPtr<nsIScriptGlobalObject> scriptGlobal = GetScriptGlobalObject();
2510 if (!scriptGlobal) {
2511 return NS_ERROR_FAILURE;
2514 MOZ_ASSERT_IF(
2515 aRequest->IsModuleRequest(),
2516 aRequest->AsModuleRequest()->GetGlobalObject() == scriptGlobal);
2518 // Make sure context is a strong reference since we access it after
2519 // we've executed a script, which may cause all other references to
2520 // the context to go away.
2521 context = scriptGlobal->GetScriptContext();
2522 if (!context) {
2523 return NS_ERROR_FAILURE;
2526 globalObject = scriptGlobal;
2529 // Update our current script.
2530 // This must be destroyed after destroying nsAutoMicroTask, see:
2531 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620505#c4
2532 nsIScriptElement* currentScript =
2533 aRequest->IsModuleRequest()
2534 ? nullptr
2535 : aRequest->GetScriptLoadContext()->GetScriptElement();
2536 AutoCurrentScriptUpdater scriptUpdater(this, currentScript);
2538 Maybe<AutoSetProcessingScriptTag> setProcessingScriptTag;
2539 if (context) {
2540 setProcessingScriptTag.emplace(context);
2543 // https://wicg.github.io/import-maps/#integration-script-type
2544 // Switch on the script's type for scriptElement:
2545 // "importmap"
2546 // Assert: Never reached.
2547 MOZ_ASSERT(!aRequest->IsImportMapRequest());
2549 if (aRequest->IsModuleRequest()) {
2550 return aRequest->AsModuleRequest()->EvaluateModule();
2553 return EvaluateScript(globalObject, aRequest);
2556 nsresult ScriptLoader::CompileOrDecodeClassicScript(
2557 JSContext* aCx, JSExecutionContext& aExec, ScriptLoadRequest* aRequest) {
2558 nsAutoCString profilerLabelString;
2559 aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString);
2561 nsresult rv;
2562 if (aRequest->IsBytecode()) {
2563 if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) {
2564 LOG(("ScriptLoadRequest (%p): Decode Bytecode & instantiate and Execute",
2565 aRequest));
2566 rv = aExec.JoinOffThread(aRequest->GetScriptLoadContext());
2567 } else {
2568 LOG(("ScriptLoadRequest (%p): Decode Bytecode and Execute", aRequest));
2569 AUTO_PROFILER_MARKER_TEXT("BytecodeDecodeMainThread", JS,
2570 MarkerInnerWindowIdFromJSContext(aCx),
2571 profilerLabelString);
2573 rv = aExec.Decode(aRequest->Bytecode());
2576 // We do not expect to be saving anything when we already have some
2577 // bytecode.
2578 MOZ_ASSERT(!aRequest->mCacheInfo);
2579 return rv;
2582 MOZ_ASSERT(aRequest->IsSource());
2583 bool encodeBytecode = ShouldCacheBytecode(aRequest);
2584 aExec.SetEncodeBytecode(encodeBytecode);
2586 if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) {
2587 // Off-main-thread parsing.
2588 LOG(
2589 ("ScriptLoadRequest (%p): instantiate off-thread result and "
2590 "Execute",
2591 aRequest));
2592 MOZ_ASSERT(aRequest->IsTextSource());
2593 rv = aExec.JoinOffThread(aRequest->GetScriptLoadContext());
2594 } else {
2595 // Main thread parsing (inline and small scripts)
2596 LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest));
2597 MOZ_ASSERT(aRequest->IsTextSource());
2598 MaybeSourceText maybeSource;
2599 rv = aRequest->GetScriptSource(aCx, &maybeSource,
2600 aRequest->mLoadContext.get());
2601 if (NS_SUCCEEDED(rv)) {
2602 AUTO_PROFILER_MARKER_TEXT("ScriptCompileMainThread", JS,
2603 MarkerInnerWindowIdFromJSContext(aCx),
2604 profilerLabelString);
2606 auto compile = [&](auto& source) { return aExec.Compile(source); };
2608 MOZ_ASSERT(!maybeSource.empty());
2609 TimeStamp startTime = TimeStamp::Now();
2610 rv = maybeSource.mapNonEmpty(compile);
2611 mMainThreadParseTime += TimeStamp::Now() - startTime;
2614 return rv;
2617 /* static */
2618 nsCString& ScriptLoader::BytecodeMimeTypeFor(ScriptLoadRequest* aRequest) {
2619 if (aRequest->IsModuleRequest()) {
2620 return nsContentUtils::JSModuleBytecodeMimeType();
2622 return nsContentUtils::JSScriptBytecodeMimeType();
2625 void ScriptLoader::MaybePrepareForBytecodeEncodingBeforeExecute(
2626 ScriptLoadRequest* aRequest, JS::Handle<JSScript*> aScript) {
2627 if (!ShouldCacheBytecode(aRequest)) {
2628 return;
2631 aRequest->MarkForBytecodeEncoding(aScript);
2634 nsresult ScriptLoader::MaybePrepareForBytecodeEncodingAfterExecute(
2635 ScriptLoadRequest* aRequest, nsresult aRv) {
2636 if (aRequest->IsMarkedForBytecodeEncoding()) {
2637 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2638 "scriptloader_encode");
2639 // Check that the TranscodeBuffer which is going to receive the encoded
2640 // bytecode only contains the SRI, and nothing more.
2642 // NOTE: This assertion will fail once we start encoding more data after the
2643 // first encode.
2644 MOZ_ASSERT(aRequest->GetSRILength() == aRequest->SRIAndBytecode().length());
2645 RegisterForBytecodeEncoding(aRequest);
2646 MOZ_ASSERT(IsAlreadyHandledForBytecodeEncodingPreparation(aRequest));
2648 return aRv;
2651 LOG(("ScriptLoadRequest (%p): Bytecode-cache: disabled (rv = %X)", aRequest,
2652 unsigned(aRv)));
2653 TRACE_FOR_TEST_NONE(aRequest->GetScriptLoadContext()->GetScriptElement(),
2654 "scriptloader_no_encode");
2655 aRequest->mCacheInfo = nullptr;
2656 MOZ_ASSERT(IsAlreadyHandledForBytecodeEncodingPreparation(aRequest));
2658 return aRv;
2661 bool ScriptLoader::IsAlreadyHandledForBytecodeEncodingPreparation(
2662 ScriptLoadRequest* aRequest) {
2663 return aRequest->isInList() || !aRequest->mCacheInfo;
2666 void ScriptLoader::MaybePrepareModuleForBytecodeEncodingBeforeExecute(
2667 JSContext* aCx, ModuleLoadRequest* aRequest) {
2669 ModuleScript* moduleScript = aRequest->mModuleScript;
2670 JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
2672 if (aRequest->IsMarkedForBytecodeEncoding()) {
2673 // This module is imported multiple times, and already marked.
2674 return;
2677 if (ShouldCacheBytecode(aRequest)) {
2678 aRequest->MarkModuleForBytecodeEncoding();
2682 for (ModuleLoadRequest* childRequest : aRequest->mImports) {
2683 MaybePrepareModuleForBytecodeEncodingBeforeExecute(aCx, childRequest);
2687 nsresult ScriptLoader::MaybePrepareModuleForBytecodeEncodingAfterExecute(
2688 ModuleLoadRequest* aRequest, nsresult aRv) {
2689 if (IsAlreadyHandledForBytecodeEncodingPreparation(aRequest)) {
2690 // This module is imported multiple times and already handled.
2691 return aRv;
2694 aRv = MaybePrepareForBytecodeEncodingAfterExecute(aRequest, aRv);
2696 for (ModuleLoadRequest* childRequest : aRequest->mImports) {
2697 aRv = MaybePrepareModuleForBytecodeEncodingAfterExecute(childRequest, aRv);
2700 return aRv;
2703 nsresult ScriptLoader::EvaluateScript(nsIGlobalObject* aGlobalObject,
2704 ScriptLoadRequest* aRequest) {
2705 nsAutoMicroTask mt;
2706 AutoEntryScript aes(aGlobalObject, "EvaluateScript", true);
2707 JSContext* cx = aes.cx();
2709 nsAutoCString profilerLabelString;
2710 aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString);
2712 // Create a ClassicScript object and associate it with the JSScript.
2713 MOZ_ASSERT(aRequest->mLoadedScript->IsClassicScript());
2714 MOZ_ASSERT(aRequest->mLoadedScript->GetFetchOptions() ==
2715 aRequest->mFetchOptions);
2716 MOZ_ASSERT(aRequest->mLoadedScript->GetURI() == aRequest->mURI);
2717 aRequest->mLoadedScript->SetBaseURL(aRequest->mBaseURL);
2718 RefPtr<ClassicScript> classicScript =
2719 aRequest->mLoadedScript->AsClassicScript();
2720 JS::Rooted<JS::Value> classicScriptValue(cx, JS::PrivateValue(classicScript));
2722 JS::CompileOptions options(cx);
2723 JS::Rooted<JSScript*> introductionScript(cx);
2724 nsresult rv =
2725 FillCompileOptionsForRequest(cx, aRequest, &options, &introductionScript);
2727 if (NS_FAILED(rv)) {
2728 return rv;
2731 // Apply the delazify strategy if the script is small.
2732 if (aRequest->IsTextSource() &&
2733 aRequest->ScriptTextLength() < OffThreadMinimumTextLength &&
2734 ShouldApplyDelazifyStrategy(aRequest)) {
2735 ApplyDelazifyStrategy(&options);
2736 mTotalFullParseSize +=
2737 aRequest->ScriptTextLength() > 0
2738 ? static_cast<uint32_t>(aRequest->ScriptTextLength())
2739 : 0;
2741 LOG(
2742 ("ScriptLoadRequest (%p): non-on-demand-only (non-omt) Parsing Enabled "
2743 "for url=%s mTotalFullParseSize=%u",
2744 aRequest, aRequest->mURI->GetSpecOrDefault().get(),
2745 mTotalFullParseSize));
2748 TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
2749 "scriptloader_execute");
2750 JS::Rooted<JSObject*> global(cx, aGlobalObject->GetGlobalJSObject());
2751 JSExecutionContext exec(cx, global, options, classicScriptValue,
2752 introductionScript);
2754 rv = CompileOrDecodeClassicScript(cx, exec, aRequest);
2756 if (NS_FAILED(rv)) {
2757 return rv;
2760 // TODO (yulia): rewrite this section. rv can be a failing pattern other than
2761 // NS_OK which will pass the NS_FAILED check above. If we call exec.GetScript
2762 // in that case, it will crash.
2763 if (rv == NS_OK) {
2764 JS::Rooted<JSScript*> script(cx, exec.GetScript());
2765 MaybePrepareForBytecodeEncodingBeforeExecute(aRequest, script);
2768 LOG(("ScriptLoadRequest (%p): Evaluate Script", aRequest));
2769 AUTO_PROFILER_MARKER_TEXT("ScriptExecution", JS,
2770 MarkerInnerWindowIdFromJSContext(cx),
2771 profilerLabelString);
2773 rv = ExecuteCompiledScript(cx, exec, classicScript);
2777 // This must be called also for compilation failure case, in order to
2778 // dispatch test-only event.
2779 rv = MaybePrepareForBytecodeEncodingAfterExecute(aRequest, rv);
2781 // Even if we are not saving the bytecode of the current script, we have
2782 // to trigger the encoding of the bytecode, as the current script can
2783 // call functions of a script for which we are recording the bytecode.
2784 LOG(("ScriptLoadRequest (%p): ScriptLoader = %p", aRequest, this));
2785 MaybeTriggerBytecodeEncoding();
2787 return rv;
2790 /* static */
2791 LoadedScript* ScriptLoader::GetActiveScript(JSContext* aCx) {
2792 JS::Value value = JS::GetScriptedCallerPrivate(aCx);
2793 if (value.isUndefined()) {
2794 return nullptr;
2797 return static_cast<LoadedScript*>(value.toPrivate());
2800 void ScriptLoader::RegisterForBytecodeEncoding(ScriptLoadRequest* aRequest) {
2801 MOZ_ASSERT(aRequest->mCacheInfo);
2802 MOZ_ASSERT(aRequest->IsMarkedForBytecodeEncoding());
2803 MOZ_DIAGNOSTIC_ASSERT(!aRequest->isInList());
2804 mBytecodeEncodingQueue.AppendElement(aRequest);
2807 void ScriptLoader::LoadEventFired() {
2808 mLoadEventFired = true;
2809 MaybeTriggerBytecodeEncoding();
2811 if (!mMainThreadParseTime.IsZero()) {
2812 glean::javascript_pageload::parse_time.AccumulateRawDuration(
2813 mMainThreadParseTime);
2817 void ScriptLoader::Destroy() {
2818 if (mShutdownObserver) {
2819 mShutdownObserver->Unregister();
2820 mShutdownObserver = nullptr;
2823 CancelAndClearScriptLoadRequests();
2824 GiveUpBytecodeEncoding();
2827 void ScriptLoader::MaybeTriggerBytecodeEncoding() {
2828 // If we already gave up, ensure that we are not going to enqueue any script,
2829 // and that we finalize them properly.
2830 if (mGiveUpEncoding) {
2831 LOG(("ScriptLoader (%p): Keep giving-up bytecode encoding.", this));
2832 GiveUpBytecodeEncoding();
2833 return;
2836 // We wait for the load event to be fired before saving the bytecode of
2837 // any script to the cache. It is quite common to have load event
2838 // listeners trigger more JavaScript execution, that we want to save as
2839 // part of this start-up bytecode cache.
2840 if (!mLoadEventFired) {
2841 LOG(("ScriptLoader (%p): Wait for the load-end event to fire.", this));
2842 return;
2845 // No need to fire any event if there is no bytecode to be saved.
2846 if (mBytecodeEncodingQueue.isEmpty()) {
2847 LOG(("ScriptLoader (%p): No script in queue to be encoded.", this));
2848 return;
2851 // Wait until all scripts are loaded before saving the bytecode, such that
2852 // we capture most of the intialization of the page.
2853 if (HasPendingRequests()) {
2854 LOG(("ScriptLoader (%p): Wait for other pending request to finish.", this));
2855 return;
2858 // Create a new runnable dedicated to encoding the content of the bytecode of
2859 // all enqueued scripts when the document is idle. In case of failure, we
2860 // give-up on encoding the bytecode.
2861 nsCOMPtr<nsIRunnable> encoder = NewRunnableMethod(
2862 "ScriptLoader::EncodeBytecode", this, &ScriptLoader::EncodeBytecode);
2863 if (NS_FAILED(NS_DispatchToCurrentThreadQueue(encoder.forget(),
2864 EventQueuePriority::Idle))) {
2865 GiveUpBytecodeEncoding();
2866 return;
2869 LOG(("ScriptLoader (%p): Schedule bytecode encoding.", this));
2872 void ScriptLoader::EncodeBytecode() {
2873 LOG(("ScriptLoader (%p): Start bytecode encoding.", this));
2875 // If any script got added in the previous loop cycle, wait until all
2876 // remaining script executions are completed, such that we capture most of
2877 // the initialization.
2878 if (HasPendingRequests()) {
2879 return;
2882 // Should not be encoding modules at all.
2883 nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
2884 if (!globalObject) {
2885 GiveUpBytecodeEncoding();
2886 return;
2889 nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
2890 if (!context) {
2891 GiveUpBytecodeEncoding();
2892 return;
2895 AutoEntryScript aes(globalObject, "encode bytecode", true);
2896 RefPtr<ScriptLoadRequest> request;
2897 while (!mBytecodeEncodingQueue.isEmpty()) {
2898 request = mBytecodeEncodingQueue.StealFirst();
2899 MOZ_ASSERT(!IsWebExtensionRequest(request),
2900 "Bytecode for web extension content scrips is not cached");
2901 EncodeRequestBytecode(aes.cx(), request);
2902 request->DropBytecode();
2903 request->DropBytecodeCacheReferences();
2907 void ScriptLoader::EncodeRequestBytecode(JSContext* aCx,
2908 ScriptLoadRequest* aRequest) {
2909 using namespace mozilla::Telemetry;
2910 nsresult rv = NS_OK;
2911 MOZ_ASSERT(aRequest->mCacheInfo);
2912 auto bytecodeFailed = mozilla::MakeScopeExit([&]() {
2913 TRACE_FOR_TEST_NONE(aRequest->GetScriptLoadContext()->GetScriptElement(),
2914 "scriptloader_bytecode_failed");
2917 bool result;
2918 if (aRequest->IsModuleRequest()) {
2919 ModuleScript* moduleScript = aRequest->AsModuleRequest()->mModuleScript;
2920 JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
2921 result =
2922 JS::FinishIncrementalEncoding(aCx, module, aRequest->SRIAndBytecode());
2923 } else {
2924 JS::Rooted<JSScript*> script(aCx, aRequest->mScriptForBytecodeEncoding);
2925 result =
2926 JS::FinishIncrementalEncoding(aCx, script, aRequest->SRIAndBytecode());
2928 if (!result) {
2929 // Encoding can be aborted for non-supported syntax (e.g. asm.js), or
2930 // any other internal error.
2931 // We don't care the error and just give up encoding.
2932 JS_ClearPendingException(aCx);
2934 LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", aRequest));
2935 return;
2938 Vector<uint8_t> compressedBytecode;
2939 // TODO probably need to move this to a helper thread
2940 if (!ScriptBytecodeCompress(aRequest->SRIAndBytecode(),
2941 aRequest->GetSRILength(), compressedBytecode)) {
2942 return;
2945 if (compressedBytecode.length() >= UINT32_MAX) {
2946 LOG(
2947 ("ScriptLoadRequest (%p): Bytecode cache is too large to be decoded "
2948 "correctly.",
2949 aRequest));
2950 return;
2953 // Open the output stream to the cache entry alternate data storage. This
2954 // might fail if the stream is already open by another request, in which
2955 // case, we just ignore the current one.
2956 nsCOMPtr<nsIAsyncOutputStream> output;
2957 rv = aRequest->mCacheInfo->OpenAlternativeOutputStream(
2958 BytecodeMimeTypeFor(aRequest),
2959 static_cast<int64_t>(compressedBytecode.length()),
2960 getter_AddRefs(output));
2961 if (NS_FAILED(rv)) {
2962 LOG(
2963 ("ScriptLoadRequest (%p): Cannot open bytecode cache (rv = %X, output "
2964 "= %p)",
2965 aRequest, unsigned(rv), output.get()));
2966 return;
2968 MOZ_ASSERT(output);
2970 auto closeOutStream = mozilla::MakeScopeExit([&]() {
2971 rv = output->CloseWithStatus(rv);
2972 LOG(("ScriptLoadRequest (%p): Closing (rv = %X)", aRequest, unsigned(rv)));
2975 uint32_t n;
2976 rv = output->Write(reinterpret_cast<char*>(compressedBytecode.begin()),
2977 compressedBytecode.length(), &n);
2978 LOG(
2979 ("ScriptLoadRequest (%p): Write bytecode cache (rv = %X, length = %u, "
2980 "written = %u)",
2981 aRequest, unsigned(rv), unsigned(compressedBytecode.length()), n));
2982 if (NS_FAILED(rv)) {
2983 return;
2986 MOZ_RELEASE_ASSERT(compressedBytecode.length() == n);
2988 bytecodeFailed.release();
2989 TRACE_FOR_TEST_NONE(aRequest->GetScriptLoadContext()->GetScriptElement(),
2990 "scriptloader_bytecode_saved");
2993 void ScriptLoader::GiveUpBytecodeEncoding() {
2994 // If the document went away prematurely, we still want to set this, in order
2995 // to avoid queuing more scripts.
2996 mGiveUpEncoding = true;
2998 // Ideally we prefer to properly end the incremental encoder, such that we
2999 // would not keep a large buffer around. If we cannot, we fallback on the
3000 // removal of all request from the current list and these large buffers would
3001 // be removed at the same time as the source object.
3002 nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
3003 AutoAllowLegacyScriptExecution exemption;
3004 Maybe<AutoEntryScript> aes;
3006 if (globalObject) {
3007 nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
3008 if (context) {
3009 aes.emplace(globalObject, "give-up bytecode encoding", true);
3013 while (!mBytecodeEncodingQueue.isEmpty()) {
3014 RefPtr<ScriptLoadRequest> request = mBytecodeEncodingQueue.StealFirst();
3015 LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", request.get()));
3016 TRACE_FOR_TEST_NONE(request->GetScriptLoadContext()->GetScriptElement(),
3017 "scriptloader_bytecode_failed");
3018 MOZ_ASSERT(!IsWebExtensionRequest(request));
3020 if (aes.isSome()) {
3021 if (request->IsModuleRequest()) {
3022 ModuleScript* moduleScript = request->AsModuleRequest()->mModuleScript;
3023 JS::Rooted<JSObject*> module(aes->cx(), moduleScript->ModuleRecord());
3024 JS::AbortIncrementalEncoding(module);
3025 } else {
3026 JS::Rooted<JSScript*> script(aes->cx(),
3027 request->mScriptForBytecodeEncoding);
3028 JS::AbortIncrementalEncoding(script);
3032 request->DropBytecode();
3033 request->DropBytecodeCacheReferences();
3037 bool ScriptLoader::HasPendingRequests() const {
3038 return mParserBlockingRequest || !mXSLTRequests.isEmpty() ||
3039 !mLoadedAsyncRequests.isEmpty() ||
3040 !mNonAsyncExternalScriptInsertedRequests.isEmpty() ||
3041 !mDeferRequests.isEmpty() || HasPendingDynamicImports() ||
3042 !mPendingChildLoaders.IsEmpty();
3043 // mOffThreadCompilingRequests are already being processed.
3046 bool ScriptLoader::HasPendingDynamicImports() const {
3047 if (mModuleLoader && mModuleLoader->HasPendingDynamicImports()) {
3048 return true;
3051 for (ModuleLoader* loader : mWebExtModuleLoaders) {
3052 if (loader->HasPendingDynamicImports()) {
3053 return true;
3057 for (ModuleLoader* loader : mShadowRealmModuleLoaders) {
3058 if (loader->HasPendingDynamicImports()) {
3059 return true;
3063 return false;
3066 void ScriptLoader::ProcessPendingRequestsAsync() {
3067 if (HasPendingRequests()) {
3068 nsCOMPtr<nsIRunnable> task =
3069 NewRunnableMethod("dom::ScriptLoader::ProcessPendingRequests", this,
3070 &ScriptLoader::ProcessPendingRequests);
3071 if (mDocument) {
3072 mDocument->Dispatch(task.forget());
3073 } else {
3074 NS_DispatchToCurrentThread(task.forget());
3079 void ScriptLoader::ProcessPendingRequests() {
3080 RefPtr<ScriptLoadRequest> request;
3082 if (mParserBlockingRequest && mParserBlockingRequest->IsFinished() &&
3083 ReadyToExecuteParserBlockingScripts()) {
3084 request.swap(mParserBlockingRequest);
3085 UnblockParser(request);
3086 ProcessRequest(request);
3087 ContinueParserAsync(request);
3090 while (ReadyToExecuteParserBlockingScripts() && !mXSLTRequests.isEmpty() &&
3091 mXSLTRequests.getFirst()->IsFinished()) {
3092 request = mXSLTRequests.StealFirst();
3093 ProcessRequest(request);
3096 while (ReadyToExecuteScripts() && !mLoadedAsyncRequests.isEmpty()) {
3097 request = mLoadedAsyncRequests.StealFirst();
3098 if (request->IsModuleRequest()) {
3099 ProcessRequest(request);
3100 } else {
3101 CompileOffThreadOrProcessRequest(request);
3105 while (ReadyToExecuteScripts() &&
3106 !mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
3107 mNonAsyncExternalScriptInsertedRequests.getFirst()->IsFinished()) {
3108 // Violate the HTML5 spec and execute these in the insertion order in
3109 // order to make LABjs and the "order" plug-in for RequireJS work with
3110 // their Gecko-sniffed code path. See
3111 // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
3112 request = mNonAsyncExternalScriptInsertedRequests.StealFirst();
3113 ProcessRequest(request);
3116 if (mDeferCheckpointReached && mXSLTRequests.isEmpty()) {
3117 while (ReadyToExecuteScripts() && !mDeferRequests.isEmpty() &&
3118 mDeferRequests.getFirst()->IsFinished()) {
3119 request = mDeferRequests.StealFirst();
3120 ProcessRequest(request);
3124 while (!mPendingChildLoaders.IsEmpty() &&
3125 ReadyToExecuteParserBlockingScripts()) {
3126 RefPtr<ScriptLoader> child = mPendingChildLoaders[0];
3127 mPendingChildLoaders.RemoveElementAt(0);
3128 child->RemoveParserBlockingScriptExecutionBlocker();
3131 if (mDeferCheckpointReached && mDocument && !mParserBlockingRequest &&
3132 mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
3133 mXSLTRequests.isEmpty() && mDeferRequests.isEmpty() &&
3134 MaybeRemovedDeferRequests()) {
3135 return ProcessPendingRequests();
3138 if (mDeferCheckpointReached && mDocument && !mParserBlockingRequest &&
3139 mLoadingAsyncRequests.isEmpty() && mLoadedAsyncRequests.isEmpty() &&
3140 mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
3141 mXSLTRequests.isEmpty() && mDeferRequests.isEmpty()) {
3142 // No more pending scripts; time to unblock onload.
3143 // OK to unblock onload synchronously here, since callers must be
3144 // prepared for the world changing anyway.
3145 mDeferCheckpointReached = false;
3146 mDocument->UnblockOnload(true);
3150 bool ScriptLoader::ReadyToExecuteParserBlockingScripts() {
3151 // Make sure the SelfReadyToExecuteParserBlockingScripts check is first, so
3152 // that we don't block twice on an ancestor.
3153 if (!SelfReadyToExecuteParserBlockingScripts()) {
3154 return false;
3157 if (mDocument && mDocument->GetWindowContext()) {
3158 for (WindowContext* wc =
3159 mDocument->GetWindowContext()->GetParentWindowContext();
3160 wc; wc = wc->GetParentWindowContext()) {
3161 if (Document* doc = wc->GetDocument()) {
3162 ScriptLoader* ancestor = doc->ScriptLoader();
3163 if (!ancestor->SelfReadyToExecuteParserBlockingScripts() &&
3164 ancestor->AddPendingChildLoader(this)) {
3165 AddParserBlockingScriptExecutionBlocker();
3166 return false;
3172 return true;
3175 template <typename Unit>
3176 static nsresult ConvertToUnicode(nsIChannel* aChannel, const uint8_t* aData,
3177 uint32_t aLength,
3178 const nsAString& aHintCharset,
3179 Document* aDocument, Unit*& aBufOut,
3180 size_t& aLengthOut) {
3181 if (!aLength) {
3182 aBufOut = nullptr;
3183 aLengthOut = 0;
3184 return NS_OK;
3187 auto data = Span(aData, aLength);
3189 // The encoding info precedence is as follows from high to low:
3190 // The BOM
3191 // HTTP Content-Type (if name recognized)
3192 // charset attribute (if name recognized)
3193 // The encoding of the document
3195 UniquePtr<Decoder> unicodeDecoder;
3197 const Encoding* encoding;
3198 std::tie(encoding, std::ignore) = Encoding::ForBOM(data);
3199 if (encoding) {
3200 unicodeDecoder = encoding->NewDecoderWithBOMRemoval();
3203 if (!unicodeDecoder && aChannel) {
3204 nsAutoCString label;
3205 if (NS_SUCCEEDED(aChannel->GetContentCharset(label)) &&
3206 (encoding = Encoding::ForLabel(label))) {
3207 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
3211 if (!unicodeDecoder && (encoding = Encoding::ForLabel(aHintCharset))) {
3212 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
3215 if (!unicodeDecoder && aDocument) {
3216 unicodeDecoder =
3217 aDocument->GetDocumentCharacterSet()->NewDecoderWithoutBOMHandling();
3220 if (!unicodeDecoder) {
3221 // Curiously, there are various callers that don't pass aDocument. The
3222 // fallback in the old code was ISO-8859-1, which behaved like
3223 // windows-1252.
3224 unicodeDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
3227 auto signalOOM = mozilla::MakeScopeExit([&aBufOut, &aLengthOut]() {
3228 aBufOut = nullptr;
3229 aLengthOut = 0;
3232 CheckedInt<size_t> bufferLength =
3233 ScriptDecoding<Unit>::MaxBufferLength(unicodeDecoder, aLength);
3234 if (!bufferLength.isValid()) {
3235 return NS_ERROR_OUT_OF_MEMORY;
3238 CheckedInt<size_t> bufferByteSize = bufferLength * sizeof(Unit);
3239 if (!bufferByteSize.isValid()) {
3240 return NS_ERROR_OUT_OF_MEMORY;
3243 aBufOut = static_cast<Unit*>(js_malloc(bufferByteSize.value()));
3244 if (!aBufOut) {
3245 return NS_ERROR_OUT_OF_MEMORY;
3248 signalOOM.release();
3249 aLengthOut = ScriptDecoding<Unit>::DecodeInto(
3250 unicodeDecoder, data, Span(aBufOut, bufferLength.value()),
3251 /* aEndOfSource = */ true);
3252 return NS_OK;
3255 /* static */
3256 nsresult ScriptLoader::ConvertToUTF16(
3257 nsIChannel* aChannel, const uint8_t* aData, uint32_t aLength,
3258 const nsAString& aHintCharset, Document* aDocument,
3259 UniquePtr<char16_t[], JS::FreePolicy>& aBufOut, size_t& aLengthOut) {
3260 char16_t* bufOut;
3261 nsresult rv = ConvertToUnicode(aChannel, aData, aLength, aHintCharset,
3262 aDocument, bufOut, aLengthOut);
3263 if (NS_SUCCEEDED(rv)) {
3264 aBufOut.reset(bufOut);
3266 return rv;
3269 /* static */
3270 nsresult ScriptLoader::ConvertToUTF8(
3271 nsIChannel* aChannel, const uint8_t* aData, uint32_t aLength,
3272 const nsAString& aHintCharset, Document* aDocument,
3273 UniquePtr<Utf8Unit[], JS::FreePolicy>& aBufOut, size_t& aLengthOut) {
3274 Utf8Unit* bufOut;
3275 nsresult rv = ConvertToUnicode(aChannel, aData, aLength, aHintCharset,
3276 aDocument, bufOut, aLengthOut);
3277 if (NS_SUCCEEDED(rv)) {
3278 aBufOut.reset(bufOut);
3280 return rv;
3283 nsresult ScriptLoader::OnStreamComplete(
3284 nsIIncrementalStreamLoader* aLoader, ScriptLoadRequest* aRequest,
3285 nsresult aChannelStatus, nsresult aSRIStatus,
3286 SRICheckDataVerifier* aSRIDataVerifier) {
3287 NS_ASSERTION(aRequest, "null request in stream complete handler");
3288 NS_ENSURE_TRUE(aRequest, NS_ERROR_FAILURE);
3290 nsresult rv = VerifySRI(aRequest, aLoader, aSRIStatus, aSRIDataVerifier);
3292 if (NS_SUCCEEDED(rv)) {
3293 // If we are loading from source, save the computed SRI hash or a dummy SRI
3294 // hash in case we are going to save the bytecode of this script in the
3295 // cache.
3296 if (aRequest->IsSource()) {
3297 rv = SaveSRIHash(aRequest, aSRIDataVerifier);
3300 if (NS_SUCCEEDED(rv)) {
3301 rv = PrepareLoadedRequest(aRequest, aLoader, aChannelStatus);
3304 if (NS_FAILED(rv)) {
3305 ReportErrorToConsole(aRequest, rv);
3309 if (NS_FAILED(rv)) {
3310 // When loading bytecode, we verify the SRI hash. If it does not match the
3311 // one from the document we restart the load, forcing us to load the source
3312 // instead. If this happens do not remove the current request from script
3313 // loader's data structures or fire any events.
3314 if (aChannelStatus != NS_BINDING_RETARGETED) {
3315 HandleLoadError(aRequest, rv);
3319 // Process our request and/or any pending ones
3320 ProcessPendingRequests();
3322 return rv;
3325 nsresult ScriptLoader::VerifySRI(ScriptLoadRequest* aRequest,
3326 nsIIncrementalStreamLoader* aLoader,
3327 nsresult aSRIStatus,
3328 SRICheckDataVerifier* aSRIDataVerifier) const {
3329 nsCOMPtr<nsIRequest> channelRequest;
3330 aLoader->GetRequest(getter_AddRefs(channelRequest));
3331 nsCOMPtr<nsIChannel> channel;
3332 channel = do_QueryInterface(channelRequest);
3334 nsresult rv = NS_OK;
3335 if (!aRequest->mIntegrity.IsEmpty() && NS_SUCCEEDED((rv = aSRIStatus))) {
3336 MOZ_ASSERT(aSRIDataVerifier);
3337 MOZ_ASSERT(mReporter);
3339 nsAutoCString sourceUri;
3340 if (mDocument && mDocument->GetDocumentURI()) {
3341 mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
3343 rv = aSRIDataVerifier->Verify(aRequest->mIntegrity, channel, sourceUri,
3344 mReporter);
3345 if (channelRequest) {
3346 mReporter->FlushReportsToConsole(
3347 nsContentUtils::GetInnerWindowID(channelRequest));
3348 } else {
3349 mReporter->FlushConsoleReports(mDocument);
3351 if (NS_FAILED(rv)) {
3352 rv = NS_ERROR_SRI_CORRUPT;
3356 return rv;
3359 nsresult ScriptLoader::SaveSRIHash(
3360 ScriptLoadRequest* aRequest, SRICheckDataVerifier* aSRIDataVerifier) const {
3361 MOZ_ASSERT(aRequest->IsSource());
3362 JS::TranscodeBuffer& bytecode = aRequest->SRIAndBytecode();
3363 MOZ_ASSERT(bytecode.empty());
3365 uint32_t len = 0;
3367 // If the integrity metadata does not correspond to a valid hash function,
3368 // IsComplete would be false.
3369 if (!aRequest->mIntegrity.IsEmpty() && aSRIDataVerifier->IsComplete()) {
3370 MOZ_ASSERT(bytecode.length() == 0);
3372 // Encode the SRI computed hash.
3373 len = aSRIDataVerifier->DataSummaryLength();
3375 if (!bytecode.resize(len)) {
3376 return NS_ERROR_OUT_OF_MEMORY;
3379 DebugOnly<nsresult> res =
3380 aSRIDataVerifier->ExportDataSummary(len, bytecode.begin());
3381 MOZ_ASSERT(NS_SUCCEEDED(res));
3382 } else {
3383 MOZ_ASSERT(bytecode.length() == 0);
3385 // Encode a dummy SRI hash.
3386 len = SRICheckDataVerifier::EmptyDataSummaryLength();
3388 if (!bytecode.resize(len)) {
3389 return NS_ERROR_OUT_OF_MEMORY;
3392 DebugOnly<nsresult> res =
3393 SRICheckDataVerifier::ExportEmptyDataSummary(len, bytecode.begin());
3394 MOZ_ASSERT(NS_SUCCEEDED(res));
3397 // Verify that the exported and predicted length correspond.
3398 DebugOnly<uint32_t> srilen{};
3399 MOZ_ASSERT(NS_SUCCEEDED(
3400 SRICheckDataVerifier::DataSummaryLength(len, bytecode.begin(), &srilen)));
3401 MOZ_ASSERT(srilen == len);
3403 MOZ_ASSERT(bytecode.length() == len);
3404 aRequest->SetSRILength(len);
3406 if (aRequest->GetSRILength() != len) {
3407 // The bytecode is aligned in the bytecode buffer, and space might be
3408 // reserved for padding after the SRI hash.
3409 if (!bytecode.resize(aRequest->GetSRILength())) {
3410 return NS_ERROR_OUT_OF_MEMORY;
3414 return NS_OK;
3417 void ScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest,
3418 nsresult aResult) const {
3419 MOZ_ASSERT(aRequest);
3421 if (aRequest->GetScriptLoadContext()->IsPreload()) {
3422 // Skip reporting errors in preload requests. If the request is actually
3423 // used then we will report the error in ReportPreloadErrorsToConsole below.
3424 aRequest->GetScriptLoadContext()->mUnreportedPreloadError = aResult;
3425 return;
3428 bool isScript = !aRequest->IsModuleRequest();
3429 const char* message;
3430 if (aResult == NS_ERROR_MALFORMED_URI) {
3431 message = isScript ? "ScriptSourceMalformed" : "ModuleSourceMalformed";
3432 } else if (aResult == NS_ERROR_DOM_BAD_URI) {
3433 message = isScript ? "ScriptSourceNotAllowed" : "ModuleSourceNotAllowed";
3434 } else if (aResult == NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI) {
3435 MOZ_ASSERT(!isScript);
3436 message = "WebExtContentScriptModuleSourceNotAllowed";
3437 } else if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
3438 aResult)) {
3439 // Blocking classifier error codes already show their own console messages.
3440 return;
3441 } else {
3442 message = isScript ? "ScriptSourceLoadFailed" : "ModuleSourceLoadFailed";
3445 AutoTArray<nsString, 1> params;
3446 CopyUTF8toUTF16(aRequest->mURI->GetSpecOrDefault(), *params.AppendElement());
3448 nsIScriptElement* element =
3449 aRequest->GetScriptLoadContext()->GetScriptElement();
3450 uint32_t lineNo = element ? element->GetScriptLineNumber() : 0;
3451 JS::ColumnNumberOneOrigin columnNo;
3452 if (element) {
3453 columnNo = element->GetScriptColumnNumber();
3456 nsContentUtils::ReportToConsole(
3457 nsIScriptError::warningFlag, "Script Loader"_ns, mDocument,
3458 nsContentUtils::eDOM_PROPERTIES, message, params, nullptr, u""_ns, lineNo,
3459 columnNo.oneOriginValue());
3462 void ScriptLoader::ReportWarningToConsole(
3463 ScriptLoadRequest* aRequest, const char* aMessageName,
3464 const nsTArray<nsString>& aParams) const {
3465 nsIScriptElement* element =
3466 aRequest->GetScriptLoadContext()->GetScriptElement();
3467 uint32_t lineNo = element ? element->GetScriptLineNumber() : 0;
3468 JS::ColumnNumberOneOrigin columnNo;
3469 if (element) {
3470 columnNo = element->GetScriptColumnNumber();
3472 nsContentUtils::ReportToConsole(
3473 nsIScriptError::warningFlag, "Script Loader"_ns, mDocument,
3474 nsContentUtils::eDOM_PROPERTIES, aMessageName, aParams, nullptr, u""_ns,
3475 lineNo, columnNo.oneOriginValue());
3478 void ScriptLoader::ReportPreloadErrorsToConsole(ScriptLoadRequest* aRequest) {
3479 if (NS_FAILED(aRequest->GetScriptLoadContext()->mUnreportedPreloadError)) {
3480 ReportErrorToConsole(
3481 aRequest, aRequest->GetScriptLoadContext()->mUnreportedPreloadError);
3482 aRequest->GetScriptLoadContext()->mUnreportedPreloadError = NS_OK;
3485 if (aRequest->IsModuleRequest()) {
3486 for (const auto& childRequest : aRequest->AsModuleRequest()->mImports) {
3487 ReportPreloadErrorsToConsole(childRequest);
3492 void ScriptLoader::HandleLoadError(ScriptLoadRequest* aRequest,
3493 nsresult aResult) {
3495 * Handle script not loading error because source was an tracking URL (or
3496 * fingerprinting, cryptomining, etc).
3497 * We make a note of this script node by including it in a dedicated
3498 * array of blocked tracking nodes under its parent document.
3500 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
3501 aResult)) {
3502 nsCOMPtr<nsIContent> cont =
3503 do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement());
3504 mDocument->AddBlockedNodeByClassifier(cont);
3507 if (aRequest->IsModuleRequest()) {
3508 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mIsInline);
3509 aRequest->AsModuleRequest()->OnFetchComplete(aResult);
3512 if (aRequest->GetScriptLoadContext()->mInDeferList) {
3513 MOZ_ASSERT_IF(aRequest->IsModuleRequest(),
3514 aRequest->AsModuleRequest()->IsTopLevel());
3515 if (aRequest->isInList()) {
3516 RefPtr<ScriptLoadRequest> req = mDeferRequests.Steal(aRequest);
3517 FireScriptAvailable(aResult, req);
3519 } else if (aRequest->GetScriptLoadContext()->mInAsyncList) {
3520 MOZ_ASSERT_IF(aRequest->IsModuleRequest(),
3521 aRequest->AsModuleRequest()->IsTopLevel());
3522 if (aRequest->isInList()) {
3523 RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
3524 FireScriptAvailable(aResult, req);
3526 } else if (aRequest->GetScriptLoadContext()->mIsNonAsyncScriptInserted) {
3527 if (aRequest->isInList()) {
3528 RefPtr<ScriptLoadRequest> req =
3529 mNonAsyncExternalScriptInsertedRequests.Steal(aRequest);
3530 FireScriptAvailable(aResult, req);
3532 } else if (aRequest->GetScriptLoadContext()->mIsXSLT) {
3533 if (aRequest->isInList()) {
3534 RefPtr<ScriptLoadRequest> req = mXSLTRequests.Steal(aRequest);
3535 FireScriptAvailable(aResult, req);
3537 } else if (aRequest->GetScriptLoadContext()->IsPreload()) {
3538 if (aRequest->IsModuleRequest()) {
3539 aRequest->Cancel();
3541 if (aRequest->IsTopLevel()) {
3542 // Request may already have been removed by
3543 // CancelAndClearScriptLoadRequests.
3544 mPreloads.RemoveElement(aRequest, PreloadRequestComparator());
3546 MOZ_ASSERT(!aRequest->isInList());
3547 AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::LoadError);
3548 } else if (aRequest->IsModuleRequest()) {
3549 ModuleLoadRequest* modReq = aRequest->AsModuleRequest();
3550 if (modReq->IsDynamicImport()) {
3551 MOZ_ASSERT(modReq->IsTopLevel());
3552 if (aRequest->isInList()) {
3553 modReq->CancelDynamicImport(aResult);
3555 } else {
3556 MOZ_ASSERT(!modReq->isInList());
3557 modReq->Cancel();
3559 } else if (mParserBlockingRequest == aRequest) {
3560 MOZ_ASSERT(!aRequest->isInList());
3561 mParserBlockingRequest = nullptr;
3562 UnblockParser(aRequest);
3564 // Ensure that we treat aRequest->GetScriptLoadContext()->GetScriptElement()
3565 // as our current parser-inserted script while firing onerror on it.
3566 MOZ_ASSERT(aRequest->GetScriptLoadContext()
3567 ->GetScriptElement()
3568 ->GetParserCreated());
3569 nsCOMPtr<nsIScriptElement> oldParserInsertedScript =
3570 mCurrentParserInsertedScript;
3571 mCurrentParserInsertedScript =
3572 aRequest->GetScriptLoadContext()->GetScriptElement();
3573 FireScriptAvailable(aResult, aRequest);
3574 ContinueParserAsync(aRequest);
3575 mCurrentParserInsertedScript = oldParserInsertedScript;
3576 } else {
3577 // This happens for blocking requests cancelled by ParsingComplete().
3578 // Ignore cancellation status for link-preload requests, as cancellation can
3579 // be omitted for them when SRI is stronger on consumer tags.
3580 MOZ_ASSERT(aRequest->IsCanceled() ||
3581 aRequest->GetScriptLoadContext()->IsLinkPreloadScript());
3582 MOZ_ASSERT(!aRequest->isInList());
3586 void ScriptLoader::UnblockParser(ScriptLoadRequest* aParserBlockingRequest) {
3587 aParserBlockingRequest->GetScriptLoadContext()
3588 ->GetScriptElement()
3589 ->UnblockParser();
3592 void ScriptLoader::ContinueParserAsync(
3593 ScriptLoadRequest* aParserBlockingRequest) {
3594 aParserBlockingRequest->GetScriptLoadContext()
3595 ->GetScriptElement()
3596 ->ContinueParserAsync();
3599 uint32_t ScriptLoader::NumberOfProcessors() {
3600 if (mNumberOfProcessors > 0) {
3601 return mNumberOfProcessors;
3604 int32_t numProcs = PR_GetNumberOfProcessors();
3605 if (numProcs > 0) {
3606 mNumberOfProcessors = numProcs;
3608 return mNumberOfProcessors;
3611 int32_t ScriptLoader::PhysicalSizeOfMemoryInGB() {
3612 // 0 is a valid result from PR_GetPhysicalMemorySize() which
3613 // means a failure occured.
3614 if (mPhysicalSizeOfMemory >= 0) {
3615 return mPhysicalSizeOfMemory;
3618 // Save the size in GB.
3619 mPhysicalSizeOfMemory =
3620 static_cast<int32_t>(PR_GetPhysicalMemorySize() >> 30);
3621 return mPhysicalSizeOfMemory;
3624 bool ScriptLoader::ShouldApplyDelazifyStrategy(ScriptLoadRequest* aRequest) {
3625 // Full parse everything if negative.
3626 if (StaticPrefs::dom_script_loader_delazification_max_size() < 0) {
3627 return true;
3630 // Be conservative on machines with 2GB or less of memory.
3631 if (PhysicalSizeOfMemoryInGB() <=
3632 StaticPrefs::dom_script_loader_delazification_min_mem()) {
3633 return false;
3636 uint32_t max_size = static_cast<uint32_t>(
3637 StaticPrefs::dom_script_loader_delazification_max_size());
3638 uint32_t script_size =
3639 aRequest->ScriptTextLength() > 0
3640 ? static_cast<uint32_t>(aRequest->ScriptTextLength())
3641 : 0;
3643 if (mTotalFullParseSize + script_size < max_size) {
3644 return true;
3647 if (LOG_ENABLED()) {
3648 nsCString url = aRequest->mURI->GetSpecOrDefault();
3649 LOG(
3650 ("ScriptLoadRequest (%p): non-on-demand-only Parsing Disabled for (%s) "
3651 "with size=%u because mTotalFullParseSize=%u would exceed max_size=%u",
3652 aRequest, url.get(), script_size, mTotalFullParseSize, max_size));
3655 return false;
3658 void ScriptLoader::ApplyDelazifyStrategy(JS::CompileOptions* aOptions) {
3659 JS::DelazificationOption strategy =
3660 JS::DelazificationOption::ParseEverythingEagerly;
3661 uint32_t strategyIndex =
3662 StaticPrefs::dom_script_loader_delazification_strategy();
3664 // Assert that all enumerated values of DelazificationOption are dense between
3665 // OnDemandOnly and ParseEverythingEagerly.
3666 #ifdef DEBUG
3667 uint32_t count = 0;
3668 uint32_t mask = 0;
3669 # define _COUNT_ENTRIES(Name) count++;
3670 # define _MASK_ENTRIES(Name) \
3671 mask |= 1 << uint32_t(JS::DelazificationOption::Name);
3673 FOREACH_DELAZIFICATION_STRATEGY(_COUNT_ENTRIES);
3674 MOZ_ASSERT(count == uint32_t(strategy) + 1);
3675 FOREACH_DELAZIFICATION_STRATEGY(_MASK_ENTRIES);
3676 MOZ_ASSERT(((mask + 1) & mask) == 0);
3677 # undef _COUNT_ENTRIES
3678 # undef _MASK_ENTRIES
3679 #endif
3681 // Any strategy index larger than ParseEverythingEagerly would default to
3682 // ParseEverythingEagerly.
3683 if (strategyIndex <= uint32_t(strategy)) {
3684 strategy = JS::DelazificationOption(uint8_t(strategyIndex));
3687 aOptions->setEagerDelazificationStrategy(strategy);
3690 bool ScriptLoader::ShouldCompileOffThread(ScriptLoadRequest* aRequest) {
3691 if (NumberOfProcessors() <= 1) {
3692 return false;
3694 if (aRequest == mParserBlockingRequest) {
3695 return true;
3697 if (SpeculativeOMTParsingEnabled()) {
3698 // Processing non async inserted scripts too early can potentially delay the
3699 // load event from firing so focus on other scripts instead.
3700 if (aRequest->GetScriptLoadContext()->mIsNonAsyncScriptInserted &&
3701 !StaticPrefs::
3702 dom_script_loader_external_scripts_speculate_non_parser_inserted_enabled()) {
3703 return false;
3706 // Async and link preload scripts do not need to be parsed right away.
3707 if (aRequest->GetScriptLoadContext()->IsAsyncScript() &&
3708 !StaticPrefs::
3709 dom_script_loader_external_scripts_speculate_async_enabled()) {
3710 return false;
3713 if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript() &&
3714 !StaticPrefs::
3715 dom_script_loader_external_scripts_speculate_link_preload_enabled()) {
3716 return false;
3719 return true;
3721 return false;
3724 nsresult ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest,
3725 nsIIncrementalStreamLoader* aLoader,
3726 nsresult aStatus) {
3727 if (NS_FAILED(aStatus)) {
3728 return aStatus;
3731 if (aRequest->IsCanceled()) {
3732 return NS_BINDING_ABORTED;
3734 MOZ_ASSERT(aRequest->IsFetching());
3735 CollectScriptTelemetry(aRequest);
3737 // If we don't have a document, then we need to abort further
3738 // evaluation.
3739 if (!mDocument) {
3740 return NS_ERROR_NOT_AVAILABLE;
3743 // If the load returned an error page, then we need to abort
3744 nsCOMPtr<nsIRequest> req;
3745 nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
3746 NS_ASSERTION(req, "StreamLoader's request went away prematurely");
3747 NS_ENSURE_SUCCESS(rv, rv);
3749 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
3750 if (httpChannel) {
3751 bool requestSucceeded;
3752 rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
3753 if (NS_SUCCEEDED(rv) && !requestSucceeded) {
3754 return NS_ERROR_NOT_AVAILABLE;
3757 if (aRequest->IsModuleRequest()) {
3758 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
3759 // Update script's referrer-policy if there's a Referrer-Policy header in
3760 // the HTTP response.
3761 ReferrerPolicy policy =
3762 nsContentUtils::GetReferrerPolicyFromChannel(httpChannel);
3763 if (policy != ReferrerPolicy::_empty) {
3764 aRequest->UpdateReferrerPolicy(policy);
3768 nsAutoCString sourceMapURL;
3769 if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
3770 aRequest->mSourceMapURL = Some(NS_ConvertUTF8toUTF16(sourceMapURL));
3773 nsCOMPtr<nsIClassifiedChannel> classifiedChannel = do_QueryInterface(req);
3774 MOZ_ASSERT(classifiedChannel);
3775 if (classifiedChannel &&
3776 classifiedChannel->IsThirdPartyTrackingResource()) {
3777 aRequest->GetScriptLoadContext()->SetIsTracking();
3781 nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
3782 // If this load was subject to a CORS check, don't flag it with a separate
3783 // origin principal, so that it will treat our document's principal as the
3784 // origin principal. Module loads always use CORS.
3785 if (!aRequest->IsModuleRequest() && aRequest->CORSMode() == CORS_NONE) {
3786 rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
3787 channel, getter_AddRefs(aRequest->mOriginPrincipal));
3788 NS_ENSURE_SUCCESS(rv, rv);
3791 // This assertion could fire errorously if we ran out of memory when
3792 // inserting the request in the array. However it's an unlikely case
3793 // so if you see this assertion it is likely something else that is
3794 // wrong, especially if you see it more than once.
3795 NS_ASSERTION(mDeferRequests.Contains(aRequest) ||
3796 mLoadingAsyncRequests.Contains(aRequest) ||
3797 mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) ||
3798 mXSLTRequests.Contains(aRequest) ||
3799 (aRequest->IsModuleRequest() &&
3800 (aRequest->AsModuleRequest()->IsRegisteredDynamicImport() ||
3801 !aRequest->AsModuleRequest()->IsTopLevel())) ||
3802 mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
3803 mParserBlockingRequest == aRequest,
3804 "aRequest should be pending!");
3806 nsCOMPtr<nsIURI> uri;
3807 rv = channel->GetOriginalURI(getter_AddRefs(uri));
3808 NS_ENSURE_SUCCESS(rv, rv);
3810 aRequest->SetBaseURLFromChannelAndOriginalURI(channel, uri);
3812 if (aRequest->IsModuleRequest()) {
3813 ModuleLoadRequest* request = aRequest->AsModuleRequest();
3815 // When loading a module, only responses with a JavaScript MIME type are
3816 // acceptable.
3817 nsAutoCString mimeType;
3818 channel->GetContentType(mimeType);
3819 NS_ConvertUTF8toUTF16 typeString(mimeType);
3820 if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
3821 return NS_ERROR_FAILURE;
3824 // Attempt to compile off main thread.
3825 bool couldCompile = false;
3826 rv = AttemptOffThreadScriptCompile(request, &couldCompile);
3827 NS_ENSURE_SUCCESS(rv, rv);
3828 if (couldCompile) {
3829 return NS_OK;
3832 // Otherwise compile it right away and start fetching descendents.
3833 return request->OnFetchComplete(NS_OK);
3836 // The script is now loaded and ready to run.
3837 aRequest->SetReady();
3839 // If speculative parsing is enabled attempt to compile all
3840 // external scripts off-main-thread. Otherwise, only omt compile scripts
3841 // blocking the parser.
3842 if (ShouldCompileOffThread(aRequest)) {
3843 MOZ_ASSERT(!aRequest->IsModuleRequest());
3844 bool couldCompile = false;
3845 nsresult rv = AttemptOffThreadScriptCompile(aRequest, &couldCompile);
3846 NS_ENSURE_SUCCESS(rv, rv);
3847 if (couldCompile) {
3848 MOZ_ASSERT(aRequest->mState == ScriptLoadRequest::State::Compiling,
3849 "Request should be off-thread compiling now.");
3850 return NS_OK;
3853 // If off-thread compile was rejected, continue with regular processing.
3856 MaybeMoveToLoadedList(aRequest);
3858 return NS_OK;
3861 void ScriptLoader::DeferCheckpointReached() {
3862 if (mDeferEnabled) {
3863 // Have to check because we apparently get ParsingComplete
3864 // without BeginDeferringScripts in some cases
3865 mDeferCheckpointReached = true;
3868 mDeferEnabled = false;
3869 ProcessPendingRequests();
3872 void ScriptLoader::ParsingComplete(bool aTerminated) {
3873 if (aTerminated) {
3874 CancelAndClearScriptLoadRequests();
3876 // Have to call this even if aTerminated so we'll correctly unblock onload.
3877 DeferCheckpointReached();
3881 void ScriptLoader::PreloadURI(
3882 nsIURI* aURI, const nsAString& aCharset, const nsAString& aType,
3883 const nsAString& aCrossOrigin, const nsAString& aNonce,
3884 const nsAString& aFetchPriority, const nsAString& aIntegrity,
3885 bool aScriptFromHead, bool aAsync, bool aDefer, bool aLinkPreload,
3886 const ReferrerPolicy aReferrerPolicy, uint64_t aEarlyHintPreloaderId) {
3887 NS_ENSURE_TRUE_VOID(mDocument);
3888 // Check to see if scripts has been turned off.
3889 if (!mEnabled || !mDocument->IsScriptEnabled()) {
3890 return;
3893 ScriptKind scriptKind = ScriptKind::eClassic;
3895 static const char kASCIIWhitespace[] = "\t\n\f\r ";
3897 nsAutoString type(aType);
3898 type.Trim(kASCIIWhitespace);
3899 if (type.LowerCaseEqualsASCII("module")) {
3900 scriptKind = ScriptKind::eModule;
3903 if (scriptKind == ScriptKind::eClassic && !aType.IsEmpty() &&
3904 !nsContentUtils::IsJavascriptMIMEType(aType)) {
3905 // Unknown type. Don't load it.
3906 return;
3909 SRIMetadata sriMetadata;
3910 GetSRIMetadata(aIntegrity, &sriMetadata);
3912 const auto requestPriority = FetchPriorityToRequestPriority(
3913 nsGenericHTMLElement::ToFetchPriority(aFetchPriority));
3915 // For link type "modulepreload":
3916 // https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload
3917 // Step 11. Let options be a script fetch options whose cryptographic nonce is
3918 // cryptographic nonce, integrity metadata is integrity metadata, parser
3919 // metadata is "not-parser-inserted", credentials mode is credentials mode,
3920 // referrer policy is referrer policy, and fetch priority is fetch priority.
3922 // We treat speculative <script> loads as parser-inserted, because they
3923 // come from a parser. This will also match how they should be treated
3924 // as a normal load.
3925 RefPtr<ScriptLoadRequest> request =
3926 CreateLoadRequest(scriptKind, aURI, nullptr, mDocument->NodePrincipal(),
3927 Element::StringToCORSMode(aCrossOrigin), aNonce,
3928 requestPriority, sriMetadata, aReferrerPolicy,
3929 aLinkPreload ? ParserMetadata::NotParserInserted
3930 : ParserMetadata::ParserInserted);
3931 request->GetScriptLoadContext()->mIsInline = false;
3932 request->GetScriptLoadContext()->mScriptFromHead = aScriptFromHead;
3933 request->GetScriptLoadContext()->SetScriptMode(aDefer, aAsync, aLinkPreload);
3934 request->GetScriptLoadContext()->SetIsPreloadRequest();
3935 request->mEarlyHintPreloaderId = aEarlyHintPreloaderId;
3937 if (LOG_ENABLED()) {
3938 nsAutoCString url;
3939 aURI->GetAsciiSpec(url);
3940 LOG(("ScriptLoadRequest (%p): Created preload request for %s",
3941 request.get(), url.get()));
3944 nsAutoString charset(aCharset);
3945 nsresult rv = StartLoad(request, Some(charset));
3946 if (NS_FAILED(rv)) {
3947 return;
3950 PreloadInfo* pi = mPreloads.AppendElement();
3951 pi->mRequest = request;
3952 pi->mCharset = aCharset;
3955 void ScriptLoader::AddDeferRequest(ScriptLoadRequest* aRequest) {
3956 MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsDeferredScript());
3957 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInDeferList &&
3958 !aRequest->GetScriptLoadContext()->mInAsyncList);
3959 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInCompilingList);
3961 aRequest->GetScriptLoadContext()->mInDeferList = true;
3962 mDeferRequests.AppendElement(aRequest);
3963 if (mDeferEnabled && aRequest == mDeferRequests.getFirst() && mDocument &&
3964 !mBlockingDOMContentLoaded) {
3965 MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING);
3966 mBlockingDOMContentLoaded = true;
3967 mDocument->BlockDOMContentLoaded();
3971 void ScriptLoader::AddAsyncRequest(ScriptLoadRequest* aRequest) {
3972 MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsAsyncScript());
3973 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInDeferList &&
3974 !aRequest->GetScriptLoadContext()->mInAsyncList);
3975 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mInCompilingList);
3977 aRequest->GetScriptLoadContext()->mInAsyncList = true;
3978 if (aRequest->IsFinished()) {
3979 mLoadedAsyncRequests.AppendElement(aRequest);
3980 } else {
3981 mLoadingAsyncRequests.AppendElement(aRequest);
3985 void ScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) {
3986 MOZ_ASSERT(aRequest->IsFinished());
3987 MOZ_ASSERT(aRequest->IsTopLevel());
3989 // If it's async, move it to the loaded list.
3990 // aRequest->GetScriptLoadContext()->mInAsyncList really _should_ be in a
3991 // list, but the consequences if it's not are bad enough we want to avoid
3992 // trying to move it if it's not.
3993 if (aRequest->GetScriptLoadContext()->mInAsyncList) {
3994 MOZ_ASSERT(aRequest->isInList());
3995 if (aRequest->isInList()) {
3996 RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
3997 mLoadedAsyncRequests.AppendElement(req);
3999 } else if (aRequest->IsModuleRequest() &&
4000 aRequest->AsModuleRequest()->IsDynamicImport()) {
4001 // Process dynamic imports with async scripts.
4002 MOZ_ASSERT(!aRequest->isInList());
4003 mLoadedAsyncRequests.AppendElement(aRequest);
4007 bool ScriptLoader::MaybeRemovedDeferRequests() {
4008 if (mDeferRequests.isEmpty() && mDocument && mBlockingDOMContentLoaded) {
4009 mBlockingDOMContentLoaded = false;
4010 mDocument->UnblockDOMContentLoaded();
4011 return true;
4013 return false;
4016 DocGroup* ScriptLoader::GetDocGroup() const { return mDocument->GetDocGroup(); }
4018 void ScriptLoader::BeginDeferringScripts() {
4019 mDeferEnabled = true;
4020 if (mDeferCheckpointReached) {
4021 // We already completed a parse and were just waiting for some async
4022 // scripts to load (and were already blocking the load event waiting for
4023 // that to happen), when document.open() happened and now we're doing a
4024 // new parse. We shouldn't block the load event again, but _should_ reset
4025 // mDeferCheckpointReached to false. It'll get set to true again when the
4026 // DeferCheckpointReached call that corresponds to this
4027 // BeginDeferringScripts call happens (on document.close()), since we just
4028 // set mDeferEnabled to true.
4029 mDeferCheckpointReached = false;
4030 } else {
4031 if (mDocument) {
4032 mDocument->BlockOnload();
4037 nsAutoScriptLoaderDisabler::nsAutoScriptLoaderDisabler(Document* aDoc) {
4038 mLoader = aDoc->ScriptLoader();
4039 mWasEnabled = mLoader->GetEnabled();
4040 if (mWasEnabled) {
4041 mLoader->SetEnabled(false);
4045 nsAutoScriptLoaderDisabler::~nsAutoScriptLoaderDisabler() {
4046 if (mWasEnabled) {
4047 mLoader->SetEnabled(true);
4051 #undef TRACE_FOR_TEST
4052 #undef TRACE_FOR_TEST_BOOL
4053 #undef TRACE_FOR_TEST_NONE
4055 #undef LOG
4057 } // namespace mozilla::dom