Source code

Revision control

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 "ScriptLoadRequest.h"
#include "ScriptTrace.h"
#include "LoadedScript.h"
#include "ModuleLoadRequest.h"
#include "prsystem.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "js/Array.h" // JS::GetArrayLength
#include "js/CompilationAndEvaluation.h"
#include "js/MemoryFunctions.h"
#include "js/Modules.h" // JS::FinishDynamicModuleImport, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{DynamicImport,Metadata}Hook
#include "js/OffThreadScriptCompilation.h"
#include "js/Realm.h"
#include "js/SourceText.h"
#include "js/Utility.h"
#include "xpcpublic.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIContent.h"
#include "nsJSUtils.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/Element.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/StaticPrefs_dom.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/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"
#ifdef MOZ_GECKO_PROFILER
# include "ProfilerMarkerPayload.h"
#endif
using JS::SourceText;
using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT;
namespace mozilla {
namespace dom {
JSObject* GetElementCallback(JSContext* aCx, JS::HandleValue aValue) {
JS::RootedValue privateValue(aCx, aValue);
MOZ_ASSERT(!privateValue.isObjectOrNull() && !privateValue.isUndefined());
LoadedScript* script = static_cast<LoadedScript*>(privateValue.toPrivate());
if (!script->GetFetchOptions()) {
return nullptr;
}
nsCOMPtr<Element> domElement = script->GetFetchOptions()->mElement;
if (!domElement) {
return nullptr;
}
JSObject* globalObject =
domElement->OwnerDoc()->GetScopeObject()->GetGlobalJSObject();
JSAutoRealm ar(aCx, globalObject);
JS::Rooted<JS::Value> elementValue(aCx);
nsresult rv = nsContentUtils::WrapNative(aCx, domElement, &elementValue,
/* aAllowWrapping = */ true);
if (NS_FAILED(rv)) {
return nullptr;
}
return elementValue.toObjectOrNull();
}
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;
//////////////////////////////////////////////////////////////
// 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,
mDeferRequests, mXSLTRequests, mDynamicImportRequests,
mParserBlockingRequest, mBytecodeEncodingQueue,
mPreloads, mPendingChildLoaders, mFetchedModules)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader)
ScriptLoader::ScriptLoader(Document* aDocument)
: mDocument(aDocument),
mParserBlockingBlockerCount(0),
mBlockerCount(0),
mNumberOfProcessors(0),
mEnabled(true),
mDeferEnabled(false),
mSpeculativeOMTParsingEnabled(false),
mDeferCheckpointReached(false),
mBlockingDOMContentLoaded(false),
mLoadEventFired(false),
mGiveUpEncoding(false),
mReporter(new ConsoleReportCollector()) {
LOG(("ScriptLoader::ScriptLoader %p", this));
EnsureModuleHooksInitialized();
mSpeculativeOMTParsingEnabled = StaticPrefs::
dom_script_loader_external_scripts_speculative_omt_parse_enabled();
}
ScriptLoader::~ScriptLoader() {
LOG(("ScriptLoader::~ScriptLoader %p", this));
mObservers.Clear();
if (mParserBlockingRequest) {
mParserBlockingRequest->FireScriptAvailable(NS_ERROR_ABORT);
}
for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
req = req->getNext()) {
req->FireScriptAvailable(NS_ERROR_ABORT);
}
for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req;
req = req->getNext()) {
req->FireScriptAvailable(NS_ERROR_ABORT);
}
for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
req = req->getNext()) {
req->FireScriptAvailable(NS_ERROR_ABORT);
}
for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
req = req->getNext()) {
req->FireScriptAvailable(NS_ERROR_ABORT);
}
for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
req = req->getNext()) {
FinishDynamicImport(req->AsModuleRequest(), NS_ERROR_ABORT);
}
for (ScriptLoadRequest* req =
mNonAsyncExternalScriptInsertedRequests.getFirst();
req; req = req->getNext()) {
req->FireScriptAvailable(NS_ERROR_ABORT);
}
// 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);
}
}
// 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;
// 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->IsLoadingSource()) {
if (aRequest->mIsInline) {
AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Inline);
} else if (aRequest->IsTextSource()) {
AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::SourceFallback);
}
// TODO: Add telemetry for BinAST encoded source.
} else {
MOZ_ASSERT(aRequest->IsLoading());
if (aRequest->IsTextSource()) {
AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Source);
} else if (aRequest->IsBytecode()) {
AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::AltData);
}
// TODO: Add telemetry for BinAST encoded source.
}
}
// 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->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) {
// if we are not dealing with a contentPrincipal it can not be a
// Principal with a scheme of about: and there is nothing left to do
if (!aRequest->TriggeringPrincipal()->GetIsContentPrincipal()) {
return false;
}
if (!aRequest->TriggeringPrincipal()->SchemeIs("about")) {
return false;
}
// if the triggering uri is not of scheme about:, there is nothing to do
// if the about: page is linkable from content, there is nothing to do
uint32_t aboutModuleFlags = 0;
nsresult rv =
aRequest->TriggeringPrincipal()->GetAboutModuleFlags(&aboutModuleFlags);
NS_ENSURE_SUCCESS(rv, false);
if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) {
return false;
}
// if the uri to be loaded is not of scheme chrome:, there is nothing to do.
if (!aRequest->mURI->SchemeIs("chrome")) {
return false;
}
// seems like an about page wants to load a chrome URI.
return true;
}
bool ScriptLoader::ModuleMapContainsURL(nsIURI* aURL) const {
// Returns whether we have fetched, or are currently fetching, a module script
// for a URL.
return mFetchingModules.Contains(aURL) || mFetchedModules.Contains(aURL);
}
bool ScriptLoader::IsFetchingModule(ModuleLoadRequest* aRequest) const {
bool fetching = mFetchingModules.Contains(aRequest->mURI);
MOZ_ASSERT_IF(fetching, !mFetchedModules.Contains(aRequest->mURI));
return fetching;
}
void ScriptLoader::SetModuleFetchStarted(ModuleLoadRequest* aRequest) {
// Update the module map to indicate that a module is currently being fetched.
MOZ_ASSERT(aRequest->IsLoading());
MOZ_ASSERT(!ModuleMapContainsURL(aRequest->mURI));
mFetchingModules.Put(aRequest->mURI,
RefPtr<GenericNonExclusivePromise::Private>{});
}
void ScriptLoader::SetModuleFetchFinishedAndResumeWaitingRequests(
ModuleLoadRequest* aRequest, nsresult aResult) {
// Update module map with the result of fetching a single module script.
//
// If any requests for the same URL are waiting on this one to complete, they
// will have ModuleLoaded or LoadFailed on them when the promise is
// resolved/rejected. This is set up in StartLoad.
LOG(
("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == "
"%u)",
aRequest, aRequest->mModuleScript.get(), unsigned(aResult)));
RefPtr<GenericNonExclusivePromise::Private> promise;
MOZ_ALWAYS_TRUE(
mFetchingModules.Remove(aRequest->mURI, getter_AddRefs(promise)));
RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);
mFetchedModules.Put(aRequest->mURI, RefPtr{moduleScript});
if (promise) {
if (moduleScript) {
LOG(("ScriptLoadRequest (%p): resolving %p", aRequest, promise.get()));
promise->Resolve(true, __func__);
} else {
LOG(("ScriptLoadRequest (%p): rejecting %p", aRequest, promise.get()));
promise->Reject(aResult, __func__);
}
}
}
RefPtr<GenericNonExclusivePromise> ScriptLoader::WaitForModuleFetch(
nsIURI* aURL) {
MOZ_ASSERT(ModuleMapContainsURL(aURL));
if (auto entry = mFetchingModules.Lookup(aURL)) {
if (!entry.Data()) {
entry.Data() = new GenericNonExclusivePromise::Private(__func__);
}
return entry.Data();
}
RefPtr<ModuleScript> ms;
MOZ_ALWAYS_TRUE(mFetchedModules.Get(aURL, getter_AddRefs(ms)));
if (!ms) {
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
}
ModuleScript* ScriptLoader::GetFetchedModule(nsIURI* aURL) const {
if (LOG_ENABLED()) {
nsAutoCString url;
aURL->GetAsciiSpec(url);
LOG(("GetFetchedModule %s", url.get()));
}
bool found;
ModuleScript* ms = mFetchedModules.GetWeak(aURL, &found);
MOZ_ASSERT(found);
return ms;
}
nsresult ScriptLoader::ProcessFetchedModuleSource(ModuleLoadRequest* aRequest) {
MOZ_ASSERT(!aRequest->mModuleScript);
nsresult rv = CreateModuleScript(aRequest);
MOZ_ASSERT(NS_FAILED(rv) == !aRequest->mModuleScript);
aRequest->ClearScriptSource();
if (NS_FAILED(rv)) {
aRequest->LoadFailed();
return rv;
}
if (!aRequest->mIsInline) {
SetModuleFetchFinishedAndResumeWaitingRequests(aRequest, rv);
}
if (!aRequest->mModuleScript->HasParseError()) {
StartFetchingModuleDependencies(aRequest);
}
return NS_OK;
}
static nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
nsCOMArray<nsIURI>* aUrlsOut);
nsresult ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest) {
MOZ_ASSERT(!aRequest->mModuleScript);
MOZ_ASSERT(aRequest->mBaseURL);
LOG(("ScriptLoadRequest (%p): Create module script", aRequest));
nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
if (!globalObject) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
if (!context) {
return NS_ERROR_FAILURE;
}
nsAutoMicroTask mt;
AutoEntryScript aes(globalObject, "CompileModule", true);
bool oldProcessingScriptTag = context->GetProcessingScriptTag();
context->SetProcessingScriptTag(true);
nsresult rv;
{
JSContext* cx = aes.cx();
JS::Rooted<JSObject*> module(cx);
if (aRequest->mWasCompiledOMT) {
module = JS::FinishOffThreadModule(cx, aRequest->mOffThreadToken);
aRequest->mOffThreadToken = nullptr;
rv = module ? NS_OK : NS_ERROR_FAILURE;
} else {
JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
JS::CompileOptions options(cx);
rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
if (NS_SUCCEEDED(rv)) {
MaybeSourceText maybeSource;
rv = GetScriptSource(cx, aRequest, &maybeSource);
if (NS_SUCCEEDED(rv)) {
rv = maybeSource.constructed<SourceText<char16_t>>()
? nsJSUtils::CompileModule(
cx, maybeSource.ref<SourceText<char16_t>>(), global,
options, &module)
: nsJSUtils::CompileModule(
cx, maybeSource.ref<SourceText<Utf8Unit>>(), global,
options, &module);
}
}
}
MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
RefPtr<ModuleScript> moduleScript =
new ModuleScript(aRequest->mFetchOptions, aRequest->mBaseURL);
aRequest->mModuleScript = moduleScript;
if (!module) {
LOG(("ScriptLoadRequest (%p): compilation failed (%d)", aRequest,
unsigned(rv)));
MOZ_ASSERT(aes.HasException());
JS::Rooted<JS::Value> error(cx);
if (!aes.StealException(&error)) {
aRequest->mModuleScript = nullptr;
return NS_ERROR_FAILURE;
}
moduleScript->SetParseError(error);
aRequest->ModuleErrored();
return NS_OK;
}
moduleScript->SetModuleRecord(module);
// Validate requested modules and treat failure to resolve module specifiers
// the same as a parse error.
rv = ResolveRequestedModules(aRequest, nullptr);
if (NS_FAILED(rv)) {
aRequest->ModuleErrored();
return NS_OK;
}
}
context->SetProcessingScriptTag(oldProcessingScriptTag);
LOG(("ScriptLoadRequest (%p): module script == %p", aRequest,
aRequest->mModuleScript.get()));
return rv;
}
static nsresult HandleResolveFailure(JSContext* aCx, LoadedScript* aScript,
const nsAString& aSpecifier,
uint32_t aLineNumber,
uint32_t aColumnNumber,
JS::MutableHandle<JS::Value> errorOut) {
JS::Rooted<JSString*> filename(aCx);
if (aScript) {
nsAutoCString url;
aScript->BaseURL()->GetAsciiSpec(url);
filename = JS_NewStringCopyZ(aCx, url.get());
} else {
filename = JS_NewStringCopyZ(aCx, "(unknown)");
}
if (!filename) {
return NS_ERROR_OUT_OF_MEMORY;
}
AutoTArray<nsString, 1> errorParams;
errorParams.AppendElement(aSpecifier);
nsAutoString errorText;
nsresult rv = nsContentUtils::FormatLocalizedString(
nsContentUtils::eDOM_PROPERTIES, "ModuleResolveFailure", errorParams,
errorText);
NS_ENSURE_SUCCESS(rv, rv);
JS::Rooted<JSString*> string(aCx, JS_NewUCStringCopyZ(aCx, errorText.get()));
if (!string) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber,
aColumnNumber, nullptr, string, errorOut)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
static already_AddRefed<nsIURI> ResolveModuleSpecifier(
ScriptLoader* aLoader, LoadedScript* aScript, const nsAString& aSpecifier) {
// The following module specifiers are allowed by the spec:
// - a valid absolute URL
// - a valid relative URL that starts with "/", "./" or "../"
//
// Bareword module specifiers are currently disallowed as these may be given
// special meanings in the future.
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
if (NS_SUCCEEDED(rv)) {
return uri.forget();
}
if (rv != NS_ERROR_MALFORMED_URI) {
return nullptr;
}
if (!StringBeginsWith(aSpecifier, u"/"_ns) &&
!StringBeginsWith(aSpecifier, u"./"_ns) &&
!StringBeginsWith(aSpecifier, u"../"_ns)) {
return nullptr;
}
// Get the document's base URL if we don't have a referencing script here.
nsCOMPtr<nsIURI> baseURL;
if (aScript) {
baseURL = aScript->BaseURL();
} else {
baseURL = aLoader->GetDocument()->GetDocBaseURI();
}
rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, baseURL);
if (NS_SUCCEEDED(rv)) {
return uri.forget();
}
return nullptr;
}
static nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
nsCOMArray<nsIURI>* aUrlsOut) {
ModuleScript* ms = aRequest->mModuleScript;
AutoJSAPI jsapi;
if (!jsapi.Init(ms->ModuleRecord())) {
return NS_ERROR_FAILURE;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> moduleRecord(cx, ms->ModuleRecord());
JS::Rooted<JSObject*> requestedModules(cx);
requestedModules = JS::GetRequestedModules(cx, moduleRecord);
MOZ_ASSERT(requestedModules);
uint32_t length;
if (!JS::GetArrayLength(cx, requestedModules, &length)) {
return NS_ERROR_FAILURE;
}
JS::Rooted<JS::Value> element(cx);
for (uint32_t i = 0; i < length; i++) {
if (!JS_GetElement(cx, requestedModules, i, &element)) {
return NS_ERROR_FAILURE;
}
JS::Rooted<JSString*> str(cx, JS::GetRequestedModuleSpecifier(cx, element));
MOZ_ASSERT(str);
nsAutoJSString specifier;
if (!specifier.init(cx, str)) {
return NS_ERROR_FAILURE;
}
// Let url be the result of resolving a module specifier given module script
// and requested.
nsCOMPtr<nsIURI> uri =
ResolveModuleSpecifier(aRequest->mLoader, ms, specifier);
if (!uri) {
uint32_t lineNumber = 0;
uint32_t columnNumber = 0;
JS::GetRequestedModuleSourcePos(cx, element, &lineNumber, &columnNumber);
JS::Rooted<JS::Value> error(cx);
nsresult rv = HandleResolveFailure(cx, ms, specifier, lineNumber,
columnNumber, &error);
NS_ENSURE_SUCCESS(rv, rv);
ms->SetParseError(error);
return NS_ERROR_FAILURE;
}
if (aUrlsOut) {
aUrlsOut->AppendElement(uri.forget());
}
}
return NS_OK;
}
void ScriptLoader::StartFetchingModuleDependencies(
ModuleLoadRequest* aRequest) {
LOG(("ScriptLoadRequest (%p): Start fetching module dependencies", aRequest));
MOZ_ASSERT(aRequest->mModuleScript);
MOZ_ASSERT(!aRequest->mModuleScript->HasParseError());
MOZ_ASSERT(!aRequest->IsReadyToRun());
auto visitedSet = aRequest->mVisitedSet;
MOZ_ASSERT(visitedSet->Contains(aRequest->mURI));
aRequest->mProgress = ModuleLoadRequest::Progress::eFetchingImports;
nsCOMArray<nsIURI> urls;
nsresult rv = ResolveRequestedModules(aRequest, &urls);
if (NS_FAILED(rv)) {
aRequest->ModuleErrored();
return;
}
// Remove already visited URLs from the list. Put unvisited URLs into the
// visited set.
int32_t i = 0;
while (i < urls.Count()) {
nsIURI* url = urls[i];
if (visitedSet->Contains(url)) {
urls.RemoveObjectAt(i);
} else {
visitedSet->PutEntry(url);
i++;
}
}
if (urls.Count() == 0) {
// There are no descendants to load so this request is ready.
aRequest->DependenciesLoaded();
return;
}
// For each url in urls, fetch a module script tree given url, module script's
// CORS setting, and module script's settings object.
nsTArray<RefPtr<GenericPromise>> importsReady;
for (auto url : urls) {
RefPtr<GenericPromise> childReady =
StartFetchingModuleAndDependencies(aRequest, url);
importsReady.AppendElement(childReady);
}
// Wait for all imports to become ready.
RefPtr<GenericPromise::AllPromiseType> allReady =
GenericPromise::All(GetMainThreadSerialEventTarget(), importsReady);
allReady->Then(GetMainThreadSerialEventTarget(), __func__, aRequest,
&ModuleLoadRequest::DependenciesLoaded,
&ModuleLoadRequest::ModuleErrored);
}
RefPtr<GenericPromise> ScriptLoader::StartFetchingModuleAndDependencies(
ModuleLoadRequest* aParent, nsIURI* aURI) {
MOZ_ASSERT(aURI);
RefPtr<ModuleLoadRequest> childRequest =
ModuleLoadRequest::CreateStaticImport(aURI, aParent);
aParent->mImports.AppendElement(childRequest);
if (LOG_ENABLED()) {
nsAutoCString url1;
aParent->mURI->GetAsciiSpec(url1);
nsAutoCString url2;
aURI->GetAsciiSpec(url2);
LOG(("ScriptLoadRequest (%p): Start fetching dependency %p", aParent,
childRequest.get()));
LOG(("StartFetchingModuleAndDependencies \"%s\" -> \"%s\"", url1.get(),
url2.get()));
}
RefPtr<GenericPromise> ready = childRequest->mReady.Ensure(__func__);
nsresult rv = StartLoad(childRequest);
if (NS_FAILED(rv)) {
MOZ_ASSERT(!childRequest->mModuleScript);
LOG(("ScriptLoadRequest (%p): rejecting %p", aParent,
&childRequest->mReady));
childRequest->mReady.Reject(rv, __func__);
return ready;
}
return ready;
}
static ScriptLoader* GetCurrentScriptLoader(JSContext* aCx) {
auto reportError = mozilla::MakeScopeExit([aCx]() {
JS_ReportErrorASCII(aCx, "No ScriptLoader found for the current context");
});
JSObject* object = JS::CurrentGlobalOrNull(aCx);
if (!object) {
return nullptr;
}
nsIGlobalObject* global = xpc::NativeGlobal(object);
if (!global) {
return nullptr;
}
nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(win);
if (!innerWindow) {
return nullptr;
}
Document* document = innerWindow->GetDocument();
if (!document) {
return nullptr;
}
ScriptLoader* loader = document->ScriptLoader();
if (!loader) {
return nullptr;
}
reportError.release();
return loader;
}
static LoadedScript* GetLoadedScriptOrNull(
JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate) {
if (aReferencingPrivate.isUndefined()) {
return nullptr;
}
auto script = static_cast<LoadedScript*>(aReferencingPrivate.toPrivate());
if (script->IsEventScript()) {
return nullptr;
}
MOZ_ASSERT_IF(
script->IsModuleScript(),
JS::GetModulePrivate(script->AsModuleScript()->ModuleRecord()) ==
aReferencingPrivate);
return script;
}
// 8.1.3.8.1 HostResolveImportedModule(referencingModule, specifier)
JSObject* HostResolveImportedModule(JSContext* aCx,
JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSString*> aSpecifier) {
JS::Rooted<JSObject*> module(aCx);
ScriptLoader::ResolveImportedModule(aCx, aReferencingPrivate, aSpecifier,
&module);
return module;
}
/* static */
void ScriptLoader::ResolveImportedModule(
JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSString*> aSpecifier, JS::MutableHandle<JSObject*> aModuleOut) {
MOZ_ASSERT(!aModuleOut);
RefPtr<LoadedScript> script(GetLoadedScriptOrNull(aCx, aReferencingPrivate));
// Let url be the result of resolving a module specifier given referencing
// module script and specifier.
nsAutoJSString string;
if (!string.init(aCx, aSpecifier)) {
return;
}
RefPtr<ScriptLoader> loader = GetCurrentScriptLoader(aCx);
if (!loader) {
return;
}
nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(loader, script, string);
// This cannot fail because resolving a module specifier must have been
// previously successful with these same two arguments.
MOZ_ASSERT(uri, "Failed to resolve previously-resolved module specifier");
// Let resolved module script be moduleMap[url]. (This entry must exist for us
// to have gotten to this point.)
ModuleScript* ms = loader->GetFetchedModule(uri);
MOZ_ASSERT(ms, "Resolved module not found in module map");
MOZ_ASSERT(!ms->HasParseError());
MOZ_ASSERT(ms->ModuleRecord());
aModuleOut.set(ms->ModuleRecord());
}
bool HostPopulateImportMeta(JSContext* aCx,
JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSObject*> aMetaObject) {
RefPtr<ModuleScript> script =
static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
MOZ_ASSERT(script->IsModuleScript());
MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) ==
aReferencingPrivate);
nsAutoCString url;
MOZ_DIAGNOSTIC_ASSERT(script->BaseURL());
MOZ_ALWAYS_SUCCEEDS(script->BaseURL()->GetAsciiSpec(url));
JS::Rooted<JSString*> urlString(aCx, JS_NewStringCopyZ(aCx, url.get()));
if (!urlString) {
JS_ReportOutOfMemory(aCx);
return false;
}
return JS_DefineProperty(aCx, aMetaObject, "url", urlString,
JSPROP_ENUMERATE);
}
bool HostImportModuleDynamically(JSContext* aCx,
JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSString*> aSpecifier,
JS::Handle<JSObject*> aPromise) {
RefPtr<LoadedScript> script(GetLoadedScriptOrNull(aCx, aReferencingPrivate));
// Attempt to resolve the module specifier.
nsAutoJSString specifier;
if (!specifier.init(aCx, aSpecifier)) {
return false;
}
RefPtr<ScriptLoader> loader = GetCurrentScriptLoader(aCx);
if (!loader) {
return false;
}
nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(loader, script, specifier);
if (!uri) {
JS::Rooted<JS::Value> error(aCx);
nsresult rv = HandleResolveFailure(aCx, script, specifier, 0, 0, &error);
if (NS_FAILED(rv)) {
JS_ReportOutOfMemory(aCx);
return false;
}
JS_SetPendingException(aCx, error);
return false;
}
// Create a new top-level load request.
ScriptFetchOptions* options;
nsIURI* baseURL;
if (script) {
options = script->GetFetchOptions();
baseURL = script->BaseURL();
} else {
// We don't have a referencing script so fall back on using
// options from the document. This can happen when the user
// triggers an inline event handler, as there is no active script
// there.
Document* document = loader->GetDocument();
options = new ScriptFetchOptions(mozilla::CORS_NONE,
document->GetReferrerPolicy(), nullptr,
document->NodePrincipal());
baseURL = document->GetDocBaseURI();
}
RefPtr<ModuleLoadRequest> request = ModuleLoadRequest::CreateDynamicImport(
uri, options, baseURL, loader, aReferencingPrivate, aSpecifier, aPromise);
loader->StartDynamicImport(request);
return true;
}
void ScriptLoader::StartDynamicImport(ModuleLoadRequest* aRequest) {
LOG(("ScriptLoadRequest (%p): Start dynamic import", aRequest));
mDynamicImportRequests.AppendElement(aRequest);
nsresult rv = StartLoad(aRequest);
if (NS_FAILED(rv)) {
FinishDynamicImport(aRequest, rv);
}
}
void ScriptLoader::FinishDynamicImport(ModuleLoadRequest* aRequest,
nsresult aResult) {
AutoJSAPI jsapi;
MOZ_ALWAYS_TRUE(jsapi.Init(aRequest->mDynamicPromise));
FinishDynamicImport(jsapi.cx(), aRequest, aResult);
}
void ScriptLoader::FinishDynamicImport(JSContext* aCx,
ModuleLoadRequest* aRequest,
nsresult aResult) {
LOG(("ScriptLoadRequest (%p): Finish dynamic import %x %d", aRequest,
unsigned(aResult), JS_IsExceptionPending(aCx)));
// Complete the dynamic import, report failures indicated by aResult or as a
// pending exception on the context.
JS::DynamicImportStatus status =
(NS_FAILED(aResult) || JS_IsExceptionPending(aCx))
? JS::DynamicImportStatus::Failed
: JS::DynamicImportStatus::Ok;
if (NS_FAILED(aResult) &&
aResult != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
JSMSG_DYNAMIC_IMPORT_FAILED);
}
JS::Rooted<JS::Value> referencingScript(aCx,
aRequest->mDynamicReferencingPrivate);
JS::Rooted<JSString*> specifier(aCx, aRequest->mDynamicSpecifier);
JS::Rooted<JSObject*> promise(aCx, aRequest->mDynamicPromise);
JS::FinishDynamicModuleImport(aCx, status, referencingScript, specifier,
promise);
// FinishDynamicModuleImport clears any pending exception.
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
aRequest->ClearDynamicImport();
}
static void DynamicImportPrefChangedCallback(const char* aPrefName,
void* aClosure) {
bool enabled = Preferences::GetBool(aPrefName);
JS::ModuleDynamicImportHook hook =
enabled ? HostImportModuleDynamically : nullptr;
AutoJSAPI jsapi;
jsapi.Init();
JSRuntime* rt = JS_GetRuntime(jsapi.cx());
JS::SetModuleDynamicImportHook(rt, hook);
}
void ScriptLoader::EnsureModuleHooksInitialized() {
AutoJSAPI jsapi;
jsapi.Init();
JSRuntime* rt = JS_GetRuntime(jsapi.cx());
if (JS::GetModuleResolveHook(rt)) {
return;
}
JS::SetModuleResolveHook(rt, HostResolveImportedModule);
JS::SetModuleMetadataHook(rt, HostPopulateImportMeta);
JS::SetScriptPrivateReferenceHooks(rt, HostAddRefTopLevelScript,
HostReleaseTopLevelScript);
Preferences::RegisterCallbackAndCall(DynamicImportPrefChangedCallback,
"javascript.options.dynamicImport",
(void*)nullptr);
}
void ScriptLoader::CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest) {
LOG(("ScriptLoadRequest (%p): Check dependencies loaded", aRequest));
RefPtr<ModuleScript> moduleScript = aRequest->mModuleScript;
if (!moduleScript || moduleScript->HasParseError()) {
return;
}
for (auto childRequest : aRequest->mImports) {
ModuleScript* childScript = childRequest->mModuleScript;
if (!childScript) {
aRequest->mModuleScript = nullptr;
LOG(("ScriptLoadRequest (%p): %p failed (load error)", aRequest,
childRequest.get()));
return;
}
}
LOG(("ScriptLoadRequest (%p): all ok", aRequest));
}
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()) {
mLoader->ProcessDynamicImport(mRequest->AsModuleRequest());
return NS_OK;
}
return mLoader->ProcessRequest(mRequest);
}
};
void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) {
auto runnable = new ScriptRequestProcessor(this, aRequest);
nsContentUtils::AddScriptRunner(runnable);
}
void ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest) {
MOZ_ASSERT(aRequest->IsReadyToRun());
if (aRequest->IsTopLevel()) {
if (aRequest->IsDynamicImport()) {
MOZ_ASSERT(aRequest->isInList());
RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
RunScriptWhenSafe(req);
} else if (aRequest->mIsInline &&
aRequest->GetParserCreated() == NOT_FROM_PARSER) {
MOZ_ASSERT(!aRequest->isInList());
RunScriptWhenSafe(aRequest);
} else {
MaybeMoveToLoadedList(aRequest);
ProcessPendingRequests();
}
}
aRequest->MaybeUnblockOnload();
}
JS::Value ScriptLoader::FindFirstParseError(ModuleLoadRequest* aRequest) {
MOZ_ASSERT(aRequest);
ModuleScript* moduleScript = aRequest->mModuleScript;
MOZ_ASSERT(moduleScript);
if (moduleScript->HasParseError()) {
return moduleScript->ParseError();
}
for (ModuleLoadRequest* childRequest : aRequest->mImports) {
JS::Value error = FindFirstParseError(childRequest);
if (!error.isUndefined()) {
return error;
}
}
return JS::UndefinedValue();
}
bool ScriptLoader::InstantiateModuleTree(ModuleLoadRequest* aRequest) {
// Instantiate a top-level module and record any error.
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aRequest->IsTopLevel());
LOG(("ScriptLoadRequest (%p): Instantiate module tree", aRequest));
ModuleScript* moduleScript = aRequest->mModuleScript;
MOZ_ASSERT(moduleScript);
JS::Value parseError = FindFirstParseError(aRequest);
if (!parseError.isUndefined()) {
moduleScript->SetErrorToRethrow(parseError);
LOG(("ScriptLoadRequest (%p): found parse error", aRequest));
return true;
}
MOZ_ASSERT(moduleScript->ModuleRecord());
nsAutoMicroTask mt;
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(moduleScript->ModuleRecord()))) {
return false;
}
JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
bool ok = NS_SUCCEEDED(nsJSUtils::ModuleInstantiate(jsapi.cx(), module));
if (!ok) {
LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest));
MOZ_ASSERT(jsapi.HasException());
JS::RootedValue exception(jsapi.cx());
if (!jsapi.StealException(&exception)) {
return false;
}
MOZ_ASSERT(!exception.isUndefined());
moduleScript->SetErrorToRethrow(exception);
}
return true;
}
nsresult ScriptLoader::InitDebuggerDataForModuleTree(
JSContext* aCx, ModuleLoadRequest* aRequest) {
// JS scripts can be associated with a DOM element for use by the debugger,
// but preloading can cause scripts to be compiled before DOM script element
// nodes have been created. This method ensures that this association takes
// place before the first time a module script is run.
MOZ_ASSERT(aRequest);
ModuleScript* moduleScript = aRequest->mModuleScript;
if (moduleScript->DebuggerDataInitialized()) {
return NS_OK;
}
for (ModuleLoadRequest* childRequest : aRequest->mImports) {
nsresult rv = InitDebuggerDataForModuleTree(aCx, childRequest);
NS_ENSURE_SUCCESS(rv, rv);
}
JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
MOZ_ASSERT(module);
// The script is now ready to be exposed to the debugger.
JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(module));
JS::ExposeScriptToDebugger(aCx, script);
moduleScript->SetDebuggerDataInitialized();
return NS_OK;
}
nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) {
MOZ_ASSERT(aRequest->IsBytecode());
aRequest->mScriptBytecode.clearAndFree();
TRACE_FOR_TEST(aRequest->GetScriptElement(), "scriptloader_fallback");
// Notify preload restart so that we can register this preload request again.
aRequest->NotifyRestart(mDocument);
// Start a new channel from which we explicitly request to stream the source
// instead of the bytecode.
aRequest->mProgress = ScriptLoadRequest::Progress::eLoading_Source;
nsresult 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) {
MOZ_ASSERT(aRequest->IsLoading());
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 Load (url = %s)", aRequest, url.get()));
}
if (aRequest->IsModuleRequest()) {
// Check whether the module has been fetched or is currently being fetched,
// and if so wait for it rather than starting a new fetch.
ModuleLoadRequest* request = aRequest->AsModuleRequest();
if (ModuleMapContainsURL(request->mURI)) {
LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest));
WaitForModuleFetch(request->mURI)
->Then(GetMainThreadSerialEventTarget(), __func__, request,
&ModuleLoadRequest::ModuleLoaded,
&ModuleLoadRequest::LoadFailed);
return NS_OK;
}
}
nsContentPolicyType contentPolicyType =
ScriptLoadRequestToContentPolicyType(aRequest);
nsCOMPtr<nsINode> context;
if (aRequest->GetScriptElement()) {
context = do_QueryInterface(aRequest->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));
nsSecurityFlags securityFlags;
if (aRequest->IsModuleRequest()) {
// According to the spec, module scripts have different behaviour to classic
// scripts and always use CORS. Only exception: Non linkable about: pages
// which load local module scripts.
if (IsAboutPageLoadingChromeURI(aRequest)) {
securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
} else {
securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
if (aRequest->CORSMode() == CORS_NONE ||
aRequest->CORSMode() == CORS_ANONYMOUS) {
securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
} else {
MOZ_ASSERT(aRequest->CORSMode() == CORS_USE_CREDENTIALS);
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
}
}
} else {
securityFlags =
aRequest->CORSMode() == CORS_NONE
? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
: nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
if (aRequest->CORSMode() == CORS_ANONYMOUS) {
securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
} else if (aRequest->CORSMode() == CORS_USE_CREDENTIALS) {
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
}
}
securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
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> globalObject = GetScriptGlobalObject();
if (!globalObject) {
return NS_ERROR_FAILURE;
}
// To avoid decoding issues, the build-id is part of the JSBytecodeMimeType
// constant.
aRequest->mCacheInfo = nullptr;
nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(channel));
if (cic && StaticPrefs::dom_script_loader_bytecode_cache_enabled() &&
// Globals with instrumentation have modified script bytecode and can't
// use cached bytecode.
!js::GlobalHasInstrumentation(globalObject->GetGlobalJSObject()) &&
// Bug 1436400: no bytecode cache support for modules yet.
!aRequest->IsModuleRequest()) {
if (!aRequest->IsLoadingSource()) {
// 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(nsContentUtils::JSBytecodeMimeType(),
""_ns, true);
} 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, true);
}
}
LOG(("ScriptLoadRequest (%p): mode=%u tracking=%d", aRequest,
unsigned(aRequest->mScriptMode), aRequest->IsTracking()));
if (aRequest->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.
ScriptLoadRequest::PrioritizeAsPreload(channel);
ScriptLoadRequest::AddLoadBackgroundFlag(channel);
} else if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
if (aRequest->mScriptFromHead && aRequest->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->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->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("*/*");
if (nsJSUtils::BinASTEncodingEnabled() &&
aRequest->ShouldAcceptBinASTEncoding()) {
acceptTypes = APPLICATION_JAVASCRIPT_BINAST ", */*";
}
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->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->NotifyOpen(key, channel, mDocument,
aRequest->IsLinkPreloadScript());
rv = channel->AsyncOpen(loader);
if (NS_FAILED(rv)) {
// Make sure to inform any <link preload> tags about failure to load the
// resource.
aRequest->NotifyStart(channel);
aRequest->NotifyStop(rv);
}
NS_ENSURE_SUCCESS(rv, rv);
if (aRequest->IsModuleRequest()) {
// We successfully started fetching a module so put its URL in the module
// map and mark it as fetching.
SetModuleFetchStarted(aRequest->AsModuleRequest());
LOG(("ScriptLoadRequest (%p): Start fetching module", aRequest));
}
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(nsIContentPolicy::TYPE_SCRIPT, nonce, parserCreated,
scriptContent, nullptr /* nsICSPEventListener */,
u""_ns, aElement->GetScriptLineNumber(),
aElement->GetScriptColumnNumber(),
&allowInlineScript);
return NS_SUCCEEDED(rv) && allowInlineScript;
}
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);
ScriptFetchOptions* fetchOptions = new ScriptFetchOptions(
aCORSMode, aReferrerPolicy, domElement, aTriggeringPrincipal);
if (aKind == ScriptKind::eClassic) {
return new ScriptLoadRequest(aKind, aURI, fetchOptions, aIntegrity,
referrer);
}
MOZ_ASSERT(aKind == ScriptKind::eModule);
return ModuleLoadRequest::CreateTopLevel(aURI, fetchOptions, aIntegrity,
referrer, this);
}
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 = aElement->GetScriptIsModule() ? ScriptKind::eModule
: 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,
nsAutoString aTypeAttr,
nsIContent* aScriptContent) {
LOG(("ScriptLoader (%p): Process external script for element %p", this,
aElement));
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()));
// It's possible these attributes changed since we started the preload so
// update them here.
request->SetScriptMode(aElement->GetScriptDeferred(),
aElement->GetScriptAsync(), 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->mIsInline = false;
request->SetScriptMode(aElement->GetScriptDeferred(),
aElement->GetScriptAsync(), false);
// keep request->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->InCompilingStage() ||
request->IsModuleRequest(),
"Request should not yet be in compiling stage.");
if (request->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->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->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->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,