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