Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ScriptLoader.h"
#include "ScriptLoadHandler.h"
#include "ScriptTrace.h"
#include "ModuleLoader.h"
#include "zlib.h"
#include "prsystem.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "js/Array.h" // JS::GetArrayLength
#include "js/CompilationAndEvaluation.h"
#include "js/ContextOptions.h" // JS::ContextOptionsRef
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/loader/ScriptLoadRequest.h"
#include "ScriptCompression.h"
#include "js/loader/LoadedScript.h"
#include "js/loader/ModuleLoadRequest.h"
#include "js/MemoryFunctions.h"
#include "js/Modules.h"
#include "js/OffThreadScriptCompilation.h"
#include "js/PropertyAndElement.h" // JS_DefineProperty
#include "js/Realm.h"
#include "js/SourceText.h"
#include "js/Transcoding.h"
#include "js/Utility.h"
#include "xpcpublic.h"
#include "GeckoProfiler.h"
#include "nsContentSecurityManager.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIContent.h"
#include "nsJSUtils.h"
#include "mozilla/dom/AutoEntryScript.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/JSExecutionContext.h"
#include "mozilla/dom/ScriptDecoding.h" // mozilla::dom::ScriptDecoding
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SRILogHelper.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_javascript.h"
#include "mozilla/StaticPrefs_network.h"
#include "nsAboutProtocolUtils.h"
#include "nsGkAtoms.h"
#include "nsNetUtil.h"
#include "nsGlobalWindowInner.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptContext.h"
#include "nsIPrincipal.h"
#include "nsJSPrincipals.h"
#include "nsContentPolicyUtils.h"
#include "nsIClassifiedChannel.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIClassOfService.h"
#include "nsICacheInfoChannel.h"
#include "nsITimedChannel.h"
#include "nsIScriptElement.h"
#include "nsISupportsPriority.h"
#include "nsIDocShell.h"
#include "nsContentUtils.h"
#include "nsUnicharUtils.h"
#include "nsError.h"
#include "nsThreadUtils.h"
#include "nsDocShellCID.h"
#include "nsIContentSecurityPolicy.h"
#include "mozilla/Logging.h"
#include "nsCRT.h"
#include "nsContentCreatorFunctions.h"
#include "nsProxyRelease.h"
#include "nsSandboxFlags.h"
#include "nsContentTypeParser.h"
#include "nsINetworkPredictor.h"
#include "nsMimeTypes.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/LoadInfo.h"
#include "ReferrerInfo.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/Attributes.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "nsIScriptError.h"
#include "nsIAsyncOutputStream.h"
using JS::SourceText;
using namespace JS::loader;
using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT;
namespace mozilla::dom {
LazyLogModule ScriptLoader::gCspPRLog("CSP");
LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader");
#undef LOG
#define LOG(args) \
MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
#define LOG_ENABLED() \
MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
// Alternate Data MIME type used by the ScriptLoader to register that we want to
// store bytecode without reading it.
static constexpr auto kNullMimeType = "javascript/null"_ns;
/////////////////////////////////////////////////////////////
// AsyncCompileShutdownObserver
/////////////////////////////////////////////////////////////
NS_IMPL_ISUPPORTS(AsyncCompileShutdownObserver, nsIObserver)
void AsyncCompileShutdownObserver::OnShutdown() {
if (mScriptLoader) {
mScriptLoader->Destroy();
MOZ_ASSERT(!mScriptLoader);
}
}
void AsyncCompileShutdownObserver::Unregister() {
if (mScriptLoader) {
mScriptLoader = nullptr;
nsContentUtils::UnregisterShutdownObserver(this);
}
}
NS_IMETHODIMP
AsyncCompileShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
OnShutdown();
return NS_OK;
}
//////////////////////////////////////////////////////////////
// ScriptLoader::PreloadInfo
//////////////////////////////////////////////////////////////
inline void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField) {
ImplCycleCollectionUnlink(aField.mRequest);
}
inline void ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback,
ScriptLoader::PreloadInfo& aField, const char* aName, uint32_t aFlags = 0) {
ImplCycleCollectionTraverse(aCallback, aField.mRequest, aName, aFlags);
}
//////////////////////////////////////////////////////////////
// ScriptLoader
//////////////////////////////////////////////////////////////
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoader)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION(ScriptLoader, mNonAsyncExternalScriptInsertedRequests,
mLoadingAsyncRequests, mLoadedAsyncRequests,
mOffThreadCompilingRequests, mDeferRequests,
mXSLTRequests, mParserBlockingRequest,
mBytecodeEncodingQueue, mPreloads,
mPendingChildLoaders, mModuleLoader,
mWebExtModuleLoaders)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader)
ScriptLoader::ScriptLoader(Document* aDocument)
: mDocument(aDocument),
mParserBlockingBlockerCount(0),
mBlockerCount(0),
mNumberOfProcessors(0),
mTotalFullParseSize(0),
mPhysicalSizeOfMemory(-1),
mEnabled(true),
mDeferEnabled(false),
mSpeculativeOMTParsingEnabled(false),
mDeferCheckpointReached(false),
mBlockingDOMContentLoaded(false),
mLoadEventFired(false),
mGiveUpEncoding(false),
mReporter(new ConsoleReportCollector()) {
LOG(("ScriptLoader::ScriptLoader %p", this));
mSpeculativeOMTParsingEnabled = StaticPrefs::
dom_script_loader_external_scripts_speculative_omt_parse_enabled();
mShutdownObserver = new AsyncCompileShutdownObserver(this);
nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
}
ScriptLoader::~ScriptLoader() {
LOG(("ScriptLoader::~ScriptLoader %p", this));
mObservers.Clear();
if (mParserBlockingRequest) {
FireScriptAvailable(NS_ERROR_ABORT, mParserBlockingRequest);
}
for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
req = req->getNext()) {
FireScriptAvailable(NS_ERROR_ABORT, req);
}
for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req;
req = req->getNext()) {
FireScriptAvailable(NS_ERROR_ABORT, req);
}
for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
req = req->getNext()) {
FireScriptAvailable(NS_ERROR_ABORT, req);
}
for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
req = req->getNext()) {
FireScriptAvailable(NS_ERROR_ABORT, req);
}
for (ScriptLoadRequest* req =
mNonAsyncExternalScriptInsertedRequests.getFirst();
req; req = req->getNext()) {
FireScriptAvailable(NS_ERROR_ABORT, req);
}
// Unblock the kids, in case any of them moved to a different document
// subtree in the meantime and therefore aren't actually going away.
for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) {
mPendingChildLoaders[j]->RemoveParserBlockingScriptExecutionBlocker();
}
for (size_t i = 0; i < mPreloads.Length(); i++) {
AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::NotUsed);
}
if (mShutdownObserver) {
mShutdownObserver->Unregister();
mShutdownObserver = nullptr;
}
mModuleLoader = nullptr;
}
void ScriptLoader::SetGlobalObject(nsIGlobalObject* aGlobalObject) {
if (!aGlobalObject) {
// The document is being detached.
return;
}
MOZ_ASSERT(!HasPendingRequests());
if (mModuleLoader) {
MOZ_ASSERT(mModuleLoader->GetGlobalObject() == aGlobalObject);
return;
}
// The module loader is associated with a global object, so don't create it
// until we have a global set.
mModuleLoader = new ModuleLoader(this, aGlobalObject, ModuleLoader::Normal);
}
void ScriptLoader::RegisterContentScriptModuleLoader(ModuleLoader* aLoader) {
MOZ_ASSERT(aLoader);
MOZ_ASSERT(aLoader->GetScriptLoader() == this);
mWebExtModuleLoaders.AppendElement(aLoader);
}
// Collect telemtry data about the cache information, and the kind of source
// which are being loaded, and where it is being loaded from.
static void CollectScriptTelemetry(ScriptLoadRequest* aRequest) {
using namespace mozilla::Telemetry;
MOZ_ASSERT(aRequest->IsFetching());
// Skip this function if we are not running telemetry.
if (!CanRecordExtended()) {
return;
}
// Report the script kind.
if (aRequest->IsModuleRequest()) {
AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ModuleScript);
} else {
AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ClassicScript);
}
// Report the type of source. This is used to monitor the status of the
// JavaScript Start-up Bytecode Cache, with the expectation of an almost zero
// source-fallback and alternate-data being roughtly equal to source loads.
if (aRequest->mFetchSourceOnly) {
if (aRequest->GetScriptLoadContext()->mIsInline) {
AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Inline);
} else if (aRequest->IsTextSource()) {
AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::SourceFallback);
}
} else {
if (aRequest->IsTextSource()) {
AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Source);
} else if (aRequest->IsBytecode()) {
AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::AltData);
}
}
}
// Helper method for checking if the script element is an event-handler
// This means that it has both a for-attribute and a event-attribute.
// Also, if the for-attribute has a value that matches "\s*window\s*",
// and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
// eventhandler. (both matches are case insensitive).
// This is how IE seems to filter out a window's onload handler from a
// <script for=... event=...> element.
static bool IsScriptEventHandler(ScriptKind kind, nsIContent* aScriptElement) {
if (kind != ScriptKind::eClassic) {
return false;
}
if (!aScriptElement->IsHTMLElement()) {
return false;
}
nsAutoString forAttr, eventAttr;
if (!aScriptElement->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_for,
forAttr) ||
!aScriptElement->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::event,
eventAttr)) {
return false;
}
const nsAString& for_str =
nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(forAttr);
if (!for_str.LowerCaseEqualsLiteral("window")) {
return true;
}
// We found for="window", now check for event="onload".
const nsAString& event_str =
nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(eventAttr, false);
if (!StringBeginsWith(event_str, u"onload"_ns,
nsCaseInsensitiveStringComparator)) {
// It ain't "onload.*".
return true;
}
nsAutoString::const_iterator start, end;
event_str.BeginReading(start);
event_str.EndReading(end);
start.advance(6); // advance past "onload"
if (start != end && *start != '(' && *start != ' ') {
// We got onload followed by something other than space or
// '('. Not good enough.
return true;
}
return false;
}
nsContentPolicyType ScriptLoadRequestToContentPolicyType(
ScriptLoadRequest* aRequest) {
if (aRequest->GetScriptLoadContext()->IsPreload()) {
return aRequest->IsModuleRequest()
? nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD;
}
return aRequest->IsModuleRequest() ? nsIContentPolicy::TYPE_INTERNAL_MODULE
: nsIContentPolicy::TYPE_INTERNAL_SCRIPT;
}
nsresult ScriptLoader::CheckContentPolicy(Document* aDocument,
nsISupports* aContext,
const nsAString& aType,
ScriptLoadRequest* aRequest) {
nsContentPolicyType contentPolicyType =
ScriptLoadRequestToContentPolicyType(aRequest);
nsCOMPtr<nsINode> requestingNode = do_QueryInterface(aContext);
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
aDocument->NodePrincipal(), // loading principal
aDocument->NodePrincipal(), // triggering principal
requestingNode, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
contentPolicyType);
// snapshot the nonce at load start time for performing CSP checks
if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT ||
contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_MODULE) {
nsCOMPtr<nsINode> node = do_QueryInterface(aContext);
if (node) {
nsString* cspNonce =
static_cast<nsString*>(node->GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
secCheckLoadInfo->SetCspNonce(*cspNonce);
}
}
}
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
nsresult rv = NS_CheckContentLoadPolicy(
aRequest->mURI, secCheckLoadInfo, NS_LossyConvertUTF16toASCII(aType),
&shouldLoad, nsContentUtils::GetContentPolicy());
if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
return NS_ERROR_CONTENT_BLOCKED;
}
return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
}
return NS_OK;
}
/* static */
bool ScriptLoader::IsAboutPageLoadingChromeURI(ScriptLoadRequest* aRequest,
Document* aDocument) {
// if the uri to be loaded is not of scheme chrome:, there is nothing to do.
if (!aRequest->mURI->SchemeIs("chrome")) {
return false;
}
// we can either get here with a regular contentPrincipal or with a
// NullPrincipal in case we are showing an error page in a sandboxed iframe.
// In either case if the about: page is linkable from content, there is
// nothing to do.
uint32_t aboutModuleFlags = 0;
nsresult rv = NS_OK;
nsCOMPtr<nsIPrincipal> triggeringPrincipal = aRequest->TriggeringPrincipal();
if (triggeringPrincipal->GetIsContentPrincipal()) {
if (!triggeringPrincipal->SchemeIs("about")) {
return false;
}
rv = triggeringPrincipal->GetAboutModuleFlags(&aboutModuleFlags);
NS_ENSURE_SUCCESS(rv, false);
} else if (triggeringPrincipal->GetIsNullPrincipal()) {
nsCOMPtr<nsIURI> docURI = aDocument->GetDocumentURI();
if (!docURI->SchemeIs("about")) {
return false;
}
nsCOMPtr<nsIAboutModule> aboutModule;
rv = NS_GetAboutModule(docURI, getter_AddRefs(aboutModule));
if (NS_FAILED(rv) || !aboutModule) {
return false;
}
rv = aboutModule->GetURIFlags(docURI, &aboutModuleFlags);
NS_ENSURE_SUCCESS(rv, false);
} else {
return false;
}
if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) {
return false;
}
// seems like an about page wants to load a chrome URI.
return true;
}
nsIURI* ScriptLoader::GetBaseURI() const {
MOZ_ASSERT(mDocument);
return mDocument->GetDocBaseURI();
}
class ScriptRequestProcessor : public Runnable {
private:
RefPtr<ScriptLoader> mLoader;
RefPtr<ScriptLoadRequest> mRequest;
public:
ScriptRequestProcessor(ScriptLoader* aLoader, ScriptLoadRequest* aRequest)
: Runnable("dom::ScriptRequestProcessor"),
mLoader(aLoader),
mRequest(aRequest) {}
NS_IMETHOD Run() override {
if (mRequest->IsModuleRequest() &&
mRequest->AsModuleRequest()->IsDynamicImport()) {
mRequest->AsModuleRequest()->ProcessDynamicImport();
return NS_OK;
}
return mLoader->ProcessRequest(mRequest);
}
};
void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) {
auto* runnable = new ScriptRequestProcessor(this, aRequest);
nsContentUtils::AddScriptRunner(runnable);
}
nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) {
MOZ_ASSERT(aRequest->IsBytecode());
aRequest->mScriptBytecode.clearAndFree();
TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
"scriptloader_fallback");
// Notify preload restart so that we can register this preload request again.
aRequest->GetScriptLoadContext()->NotifyRestart(mDocument);
// Start a new channel from which we explicitly request to stream the source
// instead of the bytecode.
aRequest->mFetchSourceOnly = true;
nsresult rv;
if (aRequest->IsModuleRequest()) {
rv = aRequest->AsModuleRequest()->RestartModuleLoad();
} else {
rv = StartLoad(aRequest);
}
if (NS_FAILED(rv)) {
return rv;
}
// Close the current channel and this ScriptLoadHandler as we created a new
// one for the same request.
return NS_BINDING_RETARGETED;
}
nsresult ScriptLoader::StartLoad(ScriptLoadRequest* aRequest) {
if (aRequest->IsModuleRequest()) {
return aRequest->AsModuleRequest()->StartModuleLoad();
}
return StartClassicLoad(aRequest);
}
nsresult ScriptLoader::StartClassicLoad(ScriptLoadRequest* aRequest) {
MOZ_ASSERT(aRequest->IsFetching());
NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
aRequest->SetUnknownDataType();
// If this document is sandboxed without 'allow-scripts', abort.
if (mDocument->HasScriptsBlockedBySandbox()) {
return NS_OK;
}
if (LOG_ENABLED()) {
nsAutoCString url;
aRequest->mURI->GetAsciiSpec(url);
LOG(("ScriptLoadRequest (%p): Start Classic Load (url = %s)", aRequest,
url.get()));
}
nsSecurityFlags securityFlags =
nsContentSecurityManager::ComputeSecurityFlags(
aRequest->CORSMode(), nsContentSecurityManager::CORSSecurityMapping::
CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS);
securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
nsresult rv = StartLoadInternal(aRequest, securityFlags);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
static bool IsWebExtensionRequest(ScriptLoadRequest* aRequest) {
if (!aRequest->IsModuleRequest()) {
return false;
}
ModuleLoader* loader =
ModuleLoader::From(aRequest->AsModuleRequest()->mLoader);
return loader->GetKind() == ModuleLoader::WebExtension;
}
nsresult ScriptLoader::StartLoadInternal(ScriptLoadRequest* aRequest,
nsSecurityFlags securityFlags) {
nsContentPolicyType contentPolicyType =
ScriptLoadRequestToContentPolicyType(aRequest);
nsCOMPtr<nsINode> context;
if (aRequest->GetScriptLoadContext()->GetScriptElement()) {
context =
do_QueryInterface(aRequest->GetScriptLoadContext()->GetScriptElement());
} else {
context = mDocument;
}
nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER);
nsIDocShell* docshell = window->GetDocShell();
nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
nsCOMPtr<nsIChannel> channel;
nsresult rv = NS_NewChannelWithTriggeringPrincipal(
getter_AddRefs(channel), aRequest->mURI, context,
aRequest->TriggeringPrincipal(), securityFlags, contentPolicyType,
nullptr, // aPerformanceStorage
loadGroup, prompter);
NS_ENSURE_SUCCESS(rv, rv);
// snapshot the nonce at load start time for performing CSP checks
if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT ||
contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_MODULE) {
if (context) {
nsString* cspNonce =
static_cast<nsString*>(context->GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetCspNonce(*cspNonce);
}
}
}
nsCOMPtr<nsIScriptGlobalObject> scriptGlobal = GetScriptGlobalObject();
if (!scriptGlobal) {
return NS_ERROR_FAILURE;
}
// To avoid decoding issues, the build-id is part of the bytecode MIME type
// constant.
aRequest->mCacheInfo = nullptr;
nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(channel));
if (cic && StaticPrefs::dom_script_loader_bytecode_cache_enabled()) {
MOZ_ASSERT(!IsWebExtensionRequest(aRequest),
"Can not bytecode cache WebExt code");
if (!aRequest->mFetchSourceOnly) {
// Inform the HTTP cache that we prefer to have information coming from
// the bytecode cache instead of the sources, if such entry is already
// registered.
LOG(("ScriptLoadRequest (%p): Maybe request bytecode", aRequest));
cic->PreferAlternativeDataType(
BytecodeMimeTypeFor(aRequest), ""_ns,
nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC);
} else {
// If we are explicitly loading from the sources, such as after a
// restarted request, we might still want to save the bytecode after.
//
// The following tell the cache to look for an alternative data type which
// does not exist, such that we can later save the bytecode with a
// different alternative data type.
LOG(("ScriptLoadRequest (%p): Request saving bytecode later", aRequest));
cic->PreferAlternativeDataType(
kNullMimeType, ""_ns,
nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC);
}
}
LOG(("ScriptLoadRequest (%p): mode=%u tracking=%d", aRequest,
unsigned(aRequest->GetScriptLoadContext()->mScriptMode),
aRequest->GetScriptLoadContext()->IsTracking()));
if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) {
// This is <link rel="preload" as="script"> initiated speculative load,
// put it to the group that is not blocked by leaders and doesn't block
// follower at the same time. Giving it a much higher priority will make
// this request be processed ahead of other Unblocked requests, but with
// the same weight as Leaders. This will make us behave similar way for
// both http2 and http1.
ScriptLoadContext::PrioritizeAsPreload(channel);
ScriptLoadContext::AddLoadBackgroundFlag(channel);
} else if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
if (aRequest->GetScriptLoadContext()->mScriptFromHead &&
aRequest->GetScriptLoadContext()->IsBlockingScript()) {
// synchronous head scripts block loading of most other non js/css
// content such as images, Leader implicitely disallows tailing
cos->AddClassFlags(nsIClassOfService::Leader);
} else if (aRequest->GetScriptLoadContext()->IsDeferredScript() &&
!StaticPrefs::network_http_tailing_enabled()) {
// Bug 1395525 and the !StaticPrefs::network_http_tailing_enabled() bit:
// We want to make sure that turing tailing off by the pref makes the
// browser behave exactly the same way as before landing the tailing
// patch.
// head/body deferred scripts are blocked by leaders but are not
// allowed tailing because they block DOMContentLoaded
cos->AddClassFlags(nsIClassOfService::TailForbidden);
} else {
// other scripts (=body sync or head/body async) are neither blocked
// nor prioritized
cos->AddClassFlags(nsIClassOfService::Unblocked);
if (aRequest->GetScriptLoadContext()->IsAsyncScript()) {
// async scripts are allowed tailing, since those and only those
// don't block DOMContentLoaded; this flag doesn't enforce tailing,
// just overweights the Unblocked flag when the channel is found
// to be a thrird-party tracker and thus set the Tail flag to engage
// tailing.
cos->AddClassFlags(nsIClassOfService::TailAllowed);
}
}
}
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
if (httpChannel) {
// HTTP content negotation has little value in this context.
nsAutoCString acceptTypes("*/*");
rv = httpChannel->SetRequestHeader("Accept"_ns, acceptTypes, false);
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsCOMPtr<nsIReferrerInfo> referrerInfo =
new ReferrerInfo(aRequest->mReferrer, aRequest->ReferrerPolicy());
rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsCOMPtr<nsIHttpChannelInternal> internalChannel(
do_QueryInterface(httpChannel));
if (internalChannel) {
rv = internalChannel->SetIntegrityMetadata(
aRequest->mIntegrity.GetIntegrityString());
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
mozilla::net::PredictorLearn(
aRequest->mURI, mDocument->GetDocumentURI(),
nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
mDocument->NodePrincipal()->OriginAttributesRef());
// Set the initiator type
nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
if (timedChannel) {
if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) {
timedChannel->SetInitiatorType(u"link"_ns);
} else {
timedChannel->SetInitiatorType(u"script"_ns);
}
}
UniquePtr<mozilla::dom::SRICheckDataVerifier> sriDataVerifier;
if (!aRequest->mIntegrity.IsEmpty()) {
nsAutoCString sourceUri;
if (mDocument->GetDocumentURI()) {
mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
}
sriDataVerifier = MakeUnique<SRICheckDataVerifier>(aRequest->mIntegrity,
sourceUri, mReporter);
}
RefPtr<ScriptLoadHandler> handler =
new ScriptLoadHandler(this, aRequest, std::move(sriDataVerifier));
nsCOMPtr<nsIIncrementalStreamLoader> loader;
rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), handler);
NS_ENSURE_SUCCESS(rv, rv);
auto key = PreloadHashKey::CreateAsScript(
aRequest->mURI, aRequest->CORSMode(), aRequest->mKind);
aRequest->GetScriptLoadContext()->NotifyOpen(
key, channel, mDocument,
aRequest->GetScriptLoadContext()->IsLinkPreloadScript());
rv = channel->AsyncOpen(loader);
if (NS_FAILED(rv)) {
// Make sure to inform any <link preload> tags about failure to load the
// resource.
aRequest->GetScriptLoadContext()->NotifyStart(channel);
aRequest->GetScriptLoadContext()->NotifyStop(rv);
}
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
bool ScriptLoader::PreloadURIComparator::Equals(const PreloadInfo& aPi,
nsIURI* const& aURI) const {
bool same;
return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) && same;
}
static bool CSPAllowsInlineScript(nsIScriptElement* aElement,
Document* aDocument) {
nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
nsresult rv = NS_OK;
if (!csp) {
// no CSP --> allow
return true;
}
// query the nonce
nsCOMPtr<Element> scriptContent = do_QueryInterface(aElement);
nsAutoString nonce;
if (scriptContent) {
nsString* cspNonce =
static_cast<nsString*>(scriptContent->GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
nonce = *cspNonce;
}
}
bool parserCreated =
aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER;
bool allowInlineScript = false;
rv = csp->GetAllowsInline(
nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE, nonce, parserCreated,
scriptContent, nullptr /* nsICSPEventListener */, u""_ns,
aElement->GetScriptLineNumber(), aElement->GetScriptColumnNumber(),
&allowInlineScript);
return NS_SUCCEEDED(rv) && allowInlineScript;
}
already_AddRefed<ScriptLoadRequest> ScriptLoader::CreateLoadRequest(
ScriptKind aKind, nsIURI* aURI, nsIScriptElement* aElement,
nsIPrincipal* aTriggeringPrincipal, CORSMode aCORSMode,
const SRIMetadata& aIntegrity, ReferrerPolicy aReferrerPolicy) {
nsIURI* referrer = mDocument->GetDocumentURIAsReferrer();
nsCOMPtr<Element> domElement = do_QueryInterface(aElement);
RefPtr<ScriptFetchOptions> fetchOptions = new ScriptFetchOptions(
aCORSMode, aReferrerPolicy, aTriggeringPrincipal, domElement);
RefPtr<ScriptLoadContext> context = new ScriptLoadContext();
if (aKind == ScriptKind::eClassic || aKind == ScriptKind::eImportMap) {
RefPtr<ScriptLoadRequest> aRequest = new ScriptLoadRequest(
aKind, aURI, fetchOptions, aIntegrity, referrer, context);
return aRequest.forget();
}
MOZ_ASSERT(aKind == ScriptKind::eModule);
RefPtr<ModuleLoadRequest> aRequest = ModuleLoader::CreateTopLevel(
aURI, fetchOptions, aIntegrity, referrer, this, context);
return aRequest.forget();
}
bool ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement) {
// We need a document to evaluate scripts.
NS_ENSURE_TRUE(mDocument, false);
// Check to see if scripts has been turned off.
if (!mEnabled || !mDocument->IsScriptEnabled()) {
return false;
}
NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script");
nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
nsAutoString type;
bool hasType = aElement->GetScriptType(type);
ScriptKind scriptKind;
if (aElement->GetScriptIsModule()) {
scriptKind = ScriptKind::eModule;
} else if (aElement->GetScriptIsImportMap()) {
scriptKind = ScriptKind::eImportMap;
} else {
scriptKind = ScriptKind::eClassic;
}
// Step 13. Check that the script is not an eventhandler
if (IsScriptEventHandler(scriptKind, scriptContent)) {
return false;
}
// For classic scripts, check the type attribute to determine language and
// version. If type exists, it trumps the deprecated 'language='
if (scriptKind == ScriptKind::eClassic) {
if (!type.IsEmpty()) {
NS_ENSURE_TRUE(nsContentUtils::IsJavascriptMIMEType(type), false);
} else if (!hasType) {
// no 'type=' element
// "language" is a deprecated attribute of HTML, so we check it only for
// HTML script elements.
if (scriptContent->IsHTMLElement()) {
nsAutoString language;
scriptContent->AsElement()->GetAttr(kNameSpaceID_None,
nsGkAtoms::language, language);
if (!language.IsEmpty()) {
if (!nsContentUtils::IsJavaScriptLanguage(language)) {
return false;
}
}
}
}
}
// "In modern user agents that support module scripts, the script element with
// the nomodule attribute will be ignored".
// "The nomodule attribute must not be specified on module scripts (and will
// be ignored if it is)."
if (mDocument->ModuleScriptsEnabled() && scriptKind == ScriptKind::eClassic &&
scriptContent->IsHTMLElement() &&
scriptContent->AsElement()->HasAttr(kNameSpaceID_None,
nsGkAtoms::nomodule)) {
return false;
}
// Step 15. and later in the HTML5 spec
if (aElement->GetScriptExternal()) {
return ProcessExternalScript(aElement, scriptKind, type, scriptContent);
}
return ProcessInlineScript(aElement, scriptKind);
}
bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
ScriptKind aScriptKind,
const nsAutoString& aTypeAttr,
nsIContent* aScriptContent) {
LOG(("ScriptLoader (%p): Process external script for element %p", this,
aElement));
// Bug 1765745: Support external import maps.
if (aScriptKind == ScriptKind::eImportMap) {
NS_DispatchToCurrentThread(
NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
&nsIScriptElement::FireErrorEvent));
return false;
}
nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
if (!scriptURI) {
// Asynchronously report the failure to create a URI object
NS_DispatchToCurrentThread(
NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
&nsIScriptElement::FireErrorEvent));
return false;
}
SRIMetadata sriMetadata;
{
nsAutoString integrity;
aScriptContent->AsElement()->GetAttr(kNameSpaceID_None,
nsGkAtoms::integrity, integrity);
GetSRIMetadata(integrity, &sriMetadata);
}
RefPtr<ScriptLoadRequest> request =
LookupPreloadRequest(aElement, aScriptKind, sriMetadata);
if (request &&
NS_FAILED(CheckContentPolicy(mDocument, aElement, aTypeAttr, request))) {
LOG(("ScriptLoader (%p): content policy check failed for preload", this));
// Probably plans have changed; even though the preload was allowed seems
// like the actual load is not; let's cancel the preload request.
request->Cancel();
AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RejectedByPolicy);
return false;
}
if (request) {
// Use the preload request.
LOG(("ScriptLoadRequest (%p): Using preload request", request.get()));
// If this preload request is for a module load, set acquiring import maps
// to false.
if (request->IsModuleRequest()) {
LOG(("ScriptLoadRequest (%p): Set acquiring import maps to false",
request.get()));
mModuleLoader->SetAcquiringImportMaps(false);
}
// It's possible these attributes changed since we started the preload so
// update them here.
request->GetScriptLoadContext()->SetScriptMode(
aElement->GetScriptDeferred(), aElement->GetScriptAsync(), false);
// The request will be added to another list or set as
// mParserBlockingRequest below.
if (request->GetScriptLoadContext()->mInCompilingList) {
mOffThreadCompilingRequests.Remove(request);
request->GetScriptLoadContext()->mInCompilingList = false;
}
AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::Used);
} else {
// No usable preload found.
nsCOMPtr<nsIPrincipal> principal =
aElement->GetScriptURITriggeringPrincipal();
if (!principal) {
principal = aScriptContent->NodePrincipal();
}
CORSMode ourCORSMode = aElement->GetCORSMode();
ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement);
request = CreateLoadRequest(aScriptKind, scriptURI, aElement, principal,
ourCORSMode, sriMetadata, referrerPolicy);
request->GetScriptLoadContext()->mIsInline = false;
request->GetScriptLoadContext()->SetScriptMode(
aElement->GetScriptDeferred(), aElement->GetScriptAsync(), false);
// keep request->GetScriptLoadContext()->mScriptFromHead to false so we
// don't treat non preloaded scripts as blockers for full page load. See bug
// 792438.
LOG(("ScriptLoadRequest (%p): Created request for external script",
request.get()));
nsresult rv = StartLoad(request);
if (NS_FAILED(rv)) {
ReportErrorToConsole(request, rv);
// Asynchronously report the load failure
nsCOMPtr<nsIRunnable> runnable =
NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
&nsIScriptElement::FireErrorEvent);
if (mDocument) {
mDocument->Dispatch(TaskCategory::Other, runnable.forget());
} else {
NS_DispatchToCurrentThread(runnable);
}
return false;
}
}
// We should still be in loading stage of script unless we're loading a
// module or speculatively off-main-thread parsing a script.
NS_ASSERTION(SpeculativeOMTParsingEnabled() ||
!request->GetScriptLoadContext()->CompileStarted() ||
request->IsModuleRequest(),
"Request should not yet be in compiling stage.");
if (request->GetScriptLoadContext()->IsAsyncScript()) {
AddAsyncRequest(request);
if (request->IsReadyToRun()) {
// The script is available already. Run it ASAP when the event
// loop gets a chance to spin.
// KVKV TODO: Instead of processing immediately, try off-thread-parsing
// it and only schedule a pending ProcessRequest if that fails.
ProcessPendingRequestsAsync();
}
return false;
}
if (!aElement->GetParserCreated()) {
// Violate the HTML5 spec in order to make LABjs and the "order" plug-in
// for RequireJS work with their Gecko-sniffed code path. See
request->GetScriptLoadContext()->mIsNonAsyncScriptInserted = true;
mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
if (request->IsReadyToRun()) {
// The script is available already. Run it ASAP when the event
// loop gets a chance to spin.
ProcessPendingRequestsAsync();
}
return false;
}
// we now have a parser-inserted request that may or may not be still
// loading
if (request->GetScriptLoadContext()->IsDeferredScript()) {
// We don't want to run this yet.
// If we come here, the script is a parser-created script and it has
// the defer attribute but not the async attribute. Since a
// a parser-inserted script is being run, we came here by the parser
// running the script, which means the parser is still alive and the
// parse is ongoing.
NS_ASSERTION(mDocument->GetCurrentContentSink() ||
aElement->GetParserCreated() == FROM_PARSER_XSLT,
"Non-XSLT Defer script on a document without an active "
"parser; bug 592366.");
AddDeferRequest(request);
return false;
}
if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
// Need to maintain order for XSLT-inserted scripts
NS_ASSERTION(!mParserBlockingRequest,
"Parser-blocking scripts and XSLT scripts in the same doc!");
request->GetScriptLoadContext()->mIsXSLT = true;
mXSLTRequests.AppendElement(request);
if (request->IsReadyToRun()) {
// The script is available already. Run it ASAP when the event
// loop gets a chance to spin.
ProcessPendingRequestsAsync();
}
return true;
}
if (request->IsReadyToRun() && ReadyToExecuteParserBlockingScripts()) {
// The request has already been loaded and there are no pending style
// sheets. If the script comes from the network stream, cheat for
// performance reasons and avoid a trip through the event loop.
if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) {
return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
}
// Otherwise, we've got a document.written script, make a trip through
// the event loop to hide the preload effects from the scripts on the
// Web page.
NS_ASSERTION(!mParserBlockingRequest,
"There can be only one parser-blocking script at a time");
NS_ASSERTION(mXSLTRequests.isEmpty(),
"Parser-blocking scripts and XSLT scripts in the same doc!");
mParserBlockingRequest = request;
ProcessPendingRequestsAsync();
return true;
}
// The script hasn't loaded yet or there's a style sheet blocking it.
// The script will be run when it loads or the style sheet loads.
NS_ASSERTION(!mParserBlockingRequest,
"There can be only one parser-blocking script at a time");
NS_ASSERTION(mXSLTRequests.isEmpty(),
"Parser-blocking scripts and XSLT scripts in the same doc!");
mParserBlockingRequest = request;
return true;
}
bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement,
ScriptKind aScriptKind) {
// Is this document sandboxed without 'allow-scripts'?
if (mDocument->HasScriptsBlockedBySandbox()) {
return false;
}
// Does CSP allow this inline script to run?
if (!CSPAllowsInlineScript(aElement, mDocument)) {
return false;
}
// Inline classic scripts ignore their CORS mode and are always CORS_NONE.
CORSMode corsMode = CORS_NONE;
if (aScriptKind == ScriptKind::eModule) {
corsMode = aElement->GetCORSMode();
}
ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement);
RefPtr<ScriptLoadRequest> request =
CreateLoadRequest(aScriptKind, mDocument->GetDocumentURI(), aElement,
mDocument->NodePrincipal(), corsMode,
SRIMetadata(), // SRI doesn't apply
referrerPolicy);
request->GetScriptLoadContext()->mIsInline = true;
request->GetScriptLoadContext()->mLineNo = aElement->GetScriptLineNumber();
request->mFetchSourceOnly = true;
request->SetTextSource();
TRACE_FOR_TEST_BOOL(request->GetScriptLoadContext()->GetScriptElement(),
"scriptloader_load_source");
CollectScriptTelemetry(request);
// Only the 'async' attribute is heeded on an inline module script and
// inline classic scripts ignore both these attributes.
MOZ_ASSERT(!aElement->GetScriptDeferred());
MOZ_ASSERT_IF(!request->IsModuleRequest(), !aElement->GetScriptAsync());
request->GetScriptLoadContext()->SetScriptMode(
false, aElement->GetScriptAsync(), false);
LOG(("ScriptLoadRequest (%p): Created request for inline script",
request.get()));
request->mBaseURL = mDocument->GetDocBaseURI();
if (request->IsModuleRequest()) {
// Set acquiring import maps to false for inline modules.
mModuleLoader->SetAcquiringImportMaps(false);
ModuleLoadRequest* modReq = request->AsModuleRequest();
if (aElement->GetParserCreated() != NOT_FROM_PARSER) {
if (aElement->GetScriptAsync()) {
AddAsyncRequest(modReq);
} else {
AddDeferRequest(modReq);
}
}
{
// We must perform a microtask checkpoint when inserting script elements
// For the non-inline module cases this happens in ProcessRequest.
mozilla::nsAutoMicroTask mt;
}
// This calls OnFetchComplete directly since there's no need to start
// fetching an inline script.
nsresult rv = modReq->OnFetchComplete(NS_OK);
if (NS_FAILED(rv)) {
ReportErrorToConsole(modReq, rv);
HandleLoadError(modReq, rv);
}
return false;
}
if (request->IsImportMapRequest()) {
// If the script's type is "importmap":
//
// Step 1: If the element's node document's acquiring import maps is false,
// then queue a task to fire an event named error at the element, and
// return.
if (!mModuleLoader->GetAcquiringImportMaps()) {
NS_WARNING("ScriptLoader: acquiring import maps is false.");
NS_DispatchToCurrentThread(
NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
&nsIScriptElement::FireErrorEvent));
return false;
}
// Step 2: Set the element's node document's acquiring import maps to false.
mModuleLoader->SetAcquiringImportMaps(false);
UniquePtr<ImportMap> importMap = mModuleLoader->ParseImportMap(request);
//
// Step 1. If element’s the script’s result is null, then fire an event
// named error at element, and return.
if (!importMap) {
NS_DispatchToCurrentThread(
NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
&nsIScriptElement::FireErrorEvent));
return false;
}
// Step 3. Assert: element’s the script’s type is "importmap".
MOZ_ASSERT(aElement->GetScriptIsImportMap());
// Step 4 to step 9 is done in RegisterImportMap.
mModuleLoader->RegisterImportMap(std::move(importMap));
return false;
}
request->mState = ScriptLoadRequest::State::Ready;
if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
(!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) {
// Need to maintain order for XSLT-inserted scripts
NS_ASSERTION(!mParserBlockingRequest,
"Parser-blocking scripts and XSLT scripts in the same doc!");
mXSLTRequests.AppendElement(request);
return true;
}
if (aElement->GetParserCreated() == NOT_FROM_PARSER) {
NS_ASSERTION(
!nsContentUtils::IsSafeToRunScript(),
"A script-inserted script is inserted without an update batch?");
RunScriptWhenSafe(request);
return false;
}
if (aElement->GetParserCreated() == FROM_PARSER_NETWORK &&
!ReadyToExecuteParserBlockingScripts()) {
NS_ASSERTION(!mParserBlockingRequest,
"There can be only one parser-blocking script at a time");
mParserBlockingRequest = request;
NS_ASSERTION(mXSLTRequests.isEmpty(),
"Parser-blocking scripts and XSLT scripts in the same doc!");
return true;
}
// We now have a document.written inline script or we have an inline script
// from the network but there is no style sheet that is blocking scripts.
// Don't check for style sheets blocking scripts in the document.write
// case to avoid style sheet network activity affecting when
// document.write returns. It's not really necessary to do this if
// there's no document.write currently on the call stack. However,
// this way matches IE more closely than checking if document.write
// is on the call stack.
NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
"Not safe to run a parser-inserted script?");
return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
}
ScriptLoadRequest* ScriptLoader::LookupPreloadRequest(
nsIScriptElement* aElement, ScriptKind aScriptKind,
const SRIMetadata& aSRIMetadata) {
MOZ_ASSERT(aElement);
nsTArray<PreloadInfo>::index_type i =
mPreloads.IndexOf(aElement->GetScriptURI(), 0, PreloadURIComparator());
if (i == nsTArray<PreloadInfo>::NoIndex) {
return nullptr;
}
// Found preloaded request. Note that a script-inserted script can steal a
// preload!
RefPtr<ScriptLoadRequest> request = mPreloads[i].mRequest;
request->GetScriptLoadContext()->SetIsLoadRequest(aElement);
if (request->GetScriptLoadContext()->mWasCompiledOMT &&
!request->IsModuleRequest()) {
request->SetReady();
}
nsString preloadCharset(mPreloads[i].mCharset);
mPreloads.RemoveElementAt(i);
// Double-check that the charset the preload used is the same as the charset
// we have now.
nsAutoString elementCharset;
aElement->GetScriptCharset(elementCharset);
if (!elementCharset.Equals(preloadCharset) ||
aElement->GetCORSMode() != request->CORSMode() ||
aScriptKind != request->mKind) {
// Drop the preload.
request->Cancel();
AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RequestMismatch);
return nullptr;
}
if (!aSRIMetadata.CanTrustBeDelegatedTo(request->mIntegrity)) {
// Don't cancel link preload requests, we want to deliver onload according
// the result of the load, cancellation would unexpectedly lead to error
// notification.
if (!request->GetScriptLoadContext()->IsLinkPreloadScript()) {
request->Cancel();
}
return nullptr;
}
// Report any errors that we skipped while preloading.
ReportPreloadErrorsToConsole(request);
// This makes sure the pending preload (if exists) for this resource is
// properly marked as used and thus not notified in the console as unused.
request->GetScriptLoadContext()->NotifyUsage();
// A used preload must no longer be found in the Document's hash table. Any
// <link preload> tag after the <script> tag will start a new request, that
// can be satisfied from a different cache, but not from the preload cache.
request->GetScriptLoadContext()->RemoveSelf(mDocument);
return request;
}
void ScriptLoader::GetSRIMetadata(const nsAString& aIntegrityAttr,
SRIMetadata* aMetadataOut) {
MOZ_ASSERT(aMetadataOut->IsEmpty());
if (aIntegrityAttr.IsEmpty()) {
return;
}
MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
("ScriptLoader::GetSRIMetadata, integrity=%s",
NS_ConvertUTF16toUTF8(aIntegrityAttr).get()));
nsAutoCString sourceUri;
if (mDocument->GetDocumentURI()) {
mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
}
SRICheck::IntegrityMetadata(aIntegrityAttr, sourceUri, mReporter,
aMetadataOut);
}
ReferrerPolicy ScriptLoader::GetReferrerPolicy(nsIScriptElement* aElement) {
ReferrerPolicy scriptReferrerPolicy = aElement->GetReferrerPolicy();
if (scriptReferrerPolicy != ReferrerPolicy::_empty) {
return scriptReferrerPolicy;
}
return mDocument->GetReferrerPolicy();
}
namespace {
class NotifyOffThreadScriptLoadCompletedRunnable : public Runnable {
RefPtr<ScriptLoadRequest> mRequest;
RefPtr<ScriptLoader> mLoader;
nsCOMPtr<nsISerialEventTarget> mEventTarget;
JS::OffThreadToken* mToken;
public:
ScriptLoadRequest* GetScriptLoadRequest() { return mRequest; }
NotifyOffThreadScriptLoadCompletedRunnable(ScriptLoadRequest* aRequest,
ScriptLoader* aLoader)
: Runnable("dom::NotifyOffThreadScriptLoadCompletedRunnable"),
mRequest(aRequest),
mLoader(aLoader),
mToken(nullptr) {
MOZ_ASSERT(NS_IsMainThread());
if (DocGroup* docGroup = aLoader->GetDocGroup()) {
mEventTarget = docGroup->EventTargetFor(TaskCategory::Other);
}
}
virtual ~NotifyOffThreadScriptLoadCompletedRunnable();
void SetToken(JS::OffThreadToken* aToken) {
MOZ_ASSERT(aToken && !mToken);
mToken = aToken;
}
static void Dispatch(
already_AddRefed<NotifyOffThreadScriptLoadCompletedRunnable>&& aSelf) {
RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> self = aSelf;
nsCOMPtr<nsISerialEventTarget> eventTarget = self->mEventTarget;
eventTarget->Dispatch(self.forget());
}
NS_DECL_NSIRUNNABLE
};
} /* anonymous namespace */
void ScriptLoader::CancelScriptLoadRequests() {
// Cancel all requests that have not been executed.
if (mParserBlockingRequest) {
mParserBlockingRequest->Cancel();
}
for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
req = req->getNext()) {
req->Cancel();
}
for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req;
req = req->getNext()) {
req->Cancel();
}
for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
req = req->getNext()) {
req->Cancel();
}
for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
req = req->getNext()) {
req->Cancel();
}
for (ScriptLoadRequest* req =
mNonAsyncExternalScriptInsertedRequests.getFirst();
req; req = req->getNext()) {
req->Cancel();
}
for (size_t i = 0; i < mPreloads.Length(); i++) {
mPreloads[i].mRequest->Cancel();
}
mOffThreadCompilingRequests.CancelRequestsAndClear();
}
nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) {
MOZ_ASSERT(aRequest->mState == ScriptLoadRequest::State::Compiling);
MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT);
if (aRequest->IsCanceled()) {
return NS_OK;
}
aRequest->GetScriptLoadContext()->mWasCompiledOMT = true;
if (aRequest->GetScriptLoadContext()->mInCompilingList) {
mOffThreadCompilingRequests.Remove(aRequest);
aRequest->GetScriptLoadContext()->mInCompilingList = false;
}
if (aRequest->IsModuleRequest()) {
MOZ_ASSERT(aRequest->GetScriptLoadContext()->mOffThreadToken);
ModuleLoadRequest* request = aRequest->AsModuleRequest();
return request->OnFetchComplete(NS_OK);
}
// Element may not be ready yet if speculatively compiling, so process the
// request in ProcessPendingRequests when it is available.
MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled(),
aRequest->GetScriptLoadContext()->GetScriptElement());
if (!aRequest->GetScriptLoadContext()->GetScriptElement()) {
// Unblock onload here in case this request never gets executed.
aRequest->GetScriptLoadContext()->MaybeUnblockOnload();
return NS_OK;
}
aRequest->SetReady();
if (aRequest == mParserBlockingRequest) {
if (!ReadyToExecuteParserBlockingScripts()) {
// If not ready to execute scripts, schedule an async call to
// ProcessPendingRequests to handle it.
ProcessPendingRequestsAsync();
return NS_OK;
}
// Same logic as in top of ProcessPendingRequests.
mParserBlockingRequest = nullptr;
UnblockParser(aRequest);
ProcessRequest(aRequest);
ContinueParserAsync(aRequest);
return NS_OK;
}
// Async scripts and blocking scripts can be executed right away.
if ((aRequest->GetScriptLoadContext()->IsAsyncScript() ||
aRequest->GetScriptLoadContext()->IsBlockingScript()) &&
!aRequest->isInList()) {
return ProcessRequest(aRequest);
}
// Process other scripts in the proper order.
ProcessPendingRequests();
return NS_OK;
}
NotifyOffThreadScriptLoadCompletedRunnable::
~NotifyOffThreadScriptLoadCompletedRunnable() {
if (MOZ_UNLIKELY(mRequest || mLoader) && !NS_IsMainThread()) {
NS_ReleaseOnMainThread(
"NotifyOffThreadScriptLoadCompletedRunnable::mRequest",
mRequest.forget());
NS_ReleaseOnMainThread(
"NotifyOffThreadScriptLoadCompletedRunnable::mLoader",
mLoader.forget());
}
}
NS_IMETHODIMP
NotifyOffThreadScriptLoadCompletedRunnable::Run() {
MOZ_ASSERT(NS_IsMainThread());
// We want these to be dropped on the main thread, once we return from this
// function.
RefPtr<ScriptLoadRequest> request = std::move(mRequest);
// Runnable pointer should have been cleared in the offthread callback.
MOZ_ASSERT(!request->GetScriptLoadContext()->mRunnable);
if (profiler_is_active()) {
ProfilerString8View scriptSourceString;
if (request->IsTextSource()) {
scriptSourceString = "ScriptCompileOffThread";
} else {
MOZ_ASSERT(request->IsBytecode());
scriptSourceString = "BytecodeDecodeOffThread";
}
nsAutoCString profilerLabelString;
request->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString);
PROFILER_MARKER_TEXT(
scriptSourceString, JS,
MarkerTiming::Interval(
request->GetScriptLoadContext()->mOffThreadParseStartTime,
request->GetScriptLoadContext()->mOffThreadParseStopTime),
profilerLabelString);
}
RefPtr<ScriptLoader> loader = std::move(mLoader);
// Request was already cancelled at some earlier point.
if (!request->GetScriptLoadContext()->mOffThreadToken) {
return NS_OK;
}
return loader->ProcessOffThreadRequest(request);
}
static void OffThreadScriptLoaderCallback(JS::OffThreadToken* aToken,
void* aCallbackData) {
RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> aRunnable = dont_AddRef(
static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData));
MOZ_ASSERT(
aRunnable.get() ==
aRunnable->GetScriptLoadRequest()->GetScriptLoadContext()->mRunnable);
aRunnable->GetScriptLoadRequest()
->GetScriptLoadContext()
->mOffThreadParseStopTime = TimeStamp::Now();
LogRunnable::Run run(aRunnable);
aRunnable->SetToken(aToken);
// If mRunnable was cleared then request was canceled so do nothing.
if (!aRunnable->GetScriptLoadRequest()
->GetScriptLoadContext()
->mRunnable.exchange(nullptr)) {
return;
}
NotifyOffThreadScriptLoadCompletedRunnable::Dispatch(aRunnable.forget());
}
nsresult ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest,
bool* aCouldCompileOut) {
// If speculative parsing is enabled, the request may not be ready to run if
// the element is not yet available.
MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled() && !aRequest->IsModuleRequest(),
aRequest->IsReadyToRun());
MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT);
MOZ_ASSERT(aCouldCompileOut && !*aCouldCompileOut);
// Don't off-thread compile inline scripts.
if (aRequest->GetScriptLoadContext()->mIsInline) {
return NS_OK;
}
nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalForRequest(aRequest);
if (!globalObject) {
return NS_ERROR_FAILURE;
}
AutoJSAPI jsapi;
if (!jsapi.Init(globalObject)) {
return NS_ERROR_FAILURE;
}
JSContext* cx = jsapi.cx();
JS::CompileOptions options(cx);
// Introduction script will actually be computed and set when the script is
// collected from offthread
JS::Rooted<JSScript*> dummyIntroductionScript(cx);
nsresult rv = FillCompileOptionsForRequest(cx, aRequest, &options,
&dummyIntroductionScript);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (aRequest->IsTextSource()) {
if (!JS::CanCompileOffThread(cx, options, aRequest->ScriptTextLength())) {
TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
"scriptloader_main_thread_compile");
return NS_OK;
}
} else {
MOZ_ASSERT(aRequest->IsBytecode());
size_t length =
aRequest->mScriptBytecode.length() - aRequest->mBytecodeOffset;
JS::DecodeOptions decodeOptions(options);
if (!JS::CanDecodeOffThread(cx, decodeOptions, length)) {
return NS_OK;
}
}
RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> runnable =
new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this);
// Emulate dispatch. CompileOffThreadModule will call
// OffThreadScriptLoaderCallback were we will emulate run.
LogRunnable::LogDispatch(runnable);
aRequest->GetScriptLoadContext()->mOffThreadParseStartTime = TimeStamp::Now();
// Save the runnable so it can be properly cleared during cancellation.
aRequest->GetScriptLoadContext()->mRunnable = runnable.get();
auto signalOOM = mozilla::MakeScopeExit(
[&aRequest]() { aRequest->GetScriptLoadContext()->mRunnable = nullptr; });
if (aRequest->IsBytecode()) {
JS::DecodeOptions decodeOptions(options);
aRequest->GetScriptLoadContext()->mOffThreadToken =
JS::DecodeStencilOffThread(cx, decodeOptions, aRequest->mScriptBytecode,
aRequest->mBytecodeOffset,
OffThreadScriptLoaderCallback,
static_cast<void*>(runnable));
if (!aRequest->GetScriptLoadContext()->mOffThreadToken) {
return NS_ERROR_OUT_OF_MEMORY;
}
} else if (aRequest->IsModuleRequest()) {
MOZ_ASSERT(aRequest->IsTextSource());
MaybeSourceText maybeSource;
nsresult rv = aRequest->GetScriptSource(cx, &maybeSource);
NS_ENSURE_SUCCESS(rv, rv);
auto compile = [&](auto& source) {
return JS::CompileModuleToStencilOffThread(
cx, options, source, OffThreadScriptLoaderCallback, runnable.get());
};
MOZ_ASSERT(!maybeSource.empty());
JS::