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