From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- dom/script/AutoEntryScript.cpp | 82 + dom/script/AutoEntryScript.h | 78 + dom/script/ModuleLoader.cpp | 371 +++ dom/script/ModuleLoader.h | 91 + dom/script/ScriptCompression.cpp | 176 ++ dom/script/ScriptCompression.h | 43 + dom/script/ScriptDecoding.h | 88 + dom/script/ScriptElement.cpp | 222 ++ dom/script/ScriptElement.h | 54 + dom/script/ScriptLoadContext.cpp | 206 ++ dom/script/ScriptLoadContext.h | 253 ++ dom/script/ScriptLoadHandler.cpp | 473 ++++ dom/script/ScriptLoadHandler.h | 136 ++ dom/script/ScriptLoader.cpp | 4110 ++++++++++++++++++++++++++++++++ dom/script/ScriptLoader.h | 788 ++++++ dom/script/ScriptSettings.cpp | 709 ++++++ dom/script/ScriptSettings.h | 429 ++++ dom/script/ScriptTrace.h | 57 + dom/script/ShadowRealmGlobalScope.cpp | 126 + dom/script/ShadowRealmGlobalScope.h | 94 + dom/script/moz.build | 54 + dom/script/nsIScriptElement.cpp | 102 + dom/script/nsIScriptElement.h | 375 +++ dom/script/nsIScriptLoaderObserver.idl | 48 + 24 files changed, 9165 insertions(+) create mode 100644 dom/script/AutoEntryScript.cpp create mode 100644 dom/script/AutoEntryScript.h create mode 100644 dom/script/ModuleLoader.cpp create mode 100644 dom/script/ModuleLoader.h create mode 100644 dom/script/ScriptCompression.cpp create mode 100644 dom/script/ScriptCompression.h create mode 100644 dom/script/ScriptDecoding.h create mode 100644 dom/script/ScriptElement.cpp create mode 100644 dom/script/ScriptElement.h create mode 100644 dom/script/ScriptLoadContext.cpp create mode 100644 dom/script/ScriptLoadContext.h create mode 100644 dom/script/ScriptLoadHandler.cpp create mode 100644 dom/script/ScriptLoadHandler.h create mode 100644 dom/script/ScriptLoader.cpp create mode 100644 dom/script/ScriptLoader.h create mode 100644 dom/script/ScriptSettings.cpp create mode 100644 dom/script/ScriptSettings.h create mode 100644 dom/script/ScriptTrace.h create mode 100644 dom/script/ShadowRealmGlobalScope.cpp create mode 100644 dom/script/ShadowRealmGlobalScope.h create mode 100644 dom/script/moz.build create mode 100644 dom/script/nsIScriptElement.cpp create mode 100644 dom/script/nsIScriptElement.h create mode 100644 dom/script/nsIScriptLoaderObserver.idl (limited to 'dom/script') diff --git a/dom/script/AutoEntryScript.cpp b/dom/script/AutoEntryScript.cpp new file mode 100644 index 0000000000..ed58e3a1fd --- /dev/null +++ b/dom/script/AutoEntryScript.cpp @@ -0,0 +1,82 @@ +/* -*- 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 "mozilla/dom/AutoEntryScript.h" + +#include +#include +#include "js/ProfilingCategory.h" +#include "js/ProfilingStack.h" +#include "jsapi.h" +#include "mozilla/Assertions.h" +#include "mozilla/Maybe.h" +#include "mozilla/Span.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsGlobalWindowInner.h" +#include "nsIDocShell.h" +#include "nsJSUtils.h" +#include "nsPIDOMWindow.h" +#include "nsPIDOMWindowInlines.h" +#include "nsString.h" +#include "xpcpublic.h" + +namespace mozilla::dom { + +namespace { +// Assert if it's not safe to run script. The helper class +// AutoAllowLegacyScriptExecution allows to allow-list +// legacy cases where it's actually not safe to run script. +#ifdef DEBUG +void AssertIfNotSafeToRunScript() { + // if it's safe to run script, then there is nothing to do here. + if (nsContentUtils::IsSafeToRunScript()) { + return; + } + + // auto allowing legacy script execution is fine for now. + if (AutoAllowLegacyScriptExecution::IsAllowed()) { + return; + } + + MOZ_ASSERT(false, "is it safe to run script?"); +} +#endif + +} // namespace + +AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject, + const char* aReason, bool aIsMainThread) + : AutoJSAPI(aGlobalObject, aIsMainThread, eEntryScript), + mWebIDLCallerPrincipal(nullptr) + // This relies on us having a cx() because the AutoJSAPI constructor + // already ran. + , + mCallerOverride(cx()), + mAutoProfilerLabel( + "", aReason, JS::ProfilingCategoryPair::JS, + uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS)), + mJSThreadExecution(aGlobalObject, aIsMainThread) { + MOZ_ASSERT(aGlobalObject); + + if (aIsMainThread) { +#ifdef DEBUG + AssertIfNotSafeToRunScript(); +#endif + mScriptActivity.emplace(true); + } +} + +AutoEntryScript::AutoEntryScript(JSObject* aObject, const char* aReason, + bool aIsMainThread) + : AutoEntryScript(xpc::NativeGlobal(aObject), aReason, aIsMainThread) { + // xpc::NativeGlobal uses JS::GetNonCCWObjectGlobal, which asserts that + // aObject is not a CCW. +} + +AutoEntryScript::~AutoEntryScript() = default; + +} // namespace mozilla::dom diff --git a/dom/script/AutoEntryScript.h b/dom/script/AutoEntryScript.h new file mode 100644 index 0000000000..905baf0857 --- /dev/null +++ b/dom/script/AutoEntryScript.h @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +#ifndef DOM_SCRIPT_AUTOENTRYSCRIPT_H_ +#define DOM_SCRIPT_AUTOENTRYSCRIPT_H_ + +#include "MainThreadUtils.h" +#include "js/Debug.h" +#include "js/TypeDecls.h" +#include "jsapi.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/dom/ScriptSettings.h" + +class nsIGlobalObject; +class nsIPrincipal; + +namespace xpc { +class AutoScriptActivity; +} + +namespace mozilla::dom { + +/* + * A class that represents a new script entry point. + * + * |aReason| should be a statically-allocated C string naming the reason we're + * invoking JavaScript code: "setTimeout", "event", and so on. The devtools use + * these strings to label JS execution in timeline and profiling displays. + * + */ +class MOZ_STACK_CLASS AutoEntryScript : public AutoJSAPI { + public: + // Constructing the AutoEntryScript will ensure that it enters the + // Realm of aGlobalObject's JSObject and exposes that JSObject to active JS. + AutoEntryScript(nsIGlobalObject* aGlobalObject, const char* aReason, + bool aIsMainThread = NS_IsMainThread()); + + // aObject can be any object from the relevant global. It must not be a + // cross-compartment wrapper because CCWs are not associated with a single + // global. + // + // Constructing the AutoEntryScript will ensure that it enters the + // Realm of aObject JSObject and exposes aObject's global to active JS. + AutoEntryScript(JSObject* aObject, const char* aReason, + bool aIsMainThread = NS_IsMainThread()); + + ~AutoEntryScript(); + + void SetWebIDLCallerPrincipal(nsIPrincipal* aPrincipal) { + mWebIDLCallerPrincipal = aPrincipal; + } + + private: + // It's safe to make this a weak pointer, since it's the subject principal + // when we go on the stack, so can't go away until after we're gone. In + // particular, this is only used from the CallSetup constructor, and only in + // the aIsJSImplementedWebIDL case. And in that case, the subject principal + // is the principal of the callee function that is part of the CallArgs just a + // bit up the stack, and which will outlive us. So we know the principal + // can't go away until then either. + nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal; + friend nsIPrincipal* GetWebIDLCallerPrincipal(); + + Maybe mScriptActivity; + JS::AutoHideScriptedCaller mCallerOverride; + AutoProfilerLabel mAutoProfilerLabel; + AutoRequestJSThreadExecution mJSThreadExecution; +}; + +} // namespace mozilla::dom + +#endif // DOM_SCRIPT_AUTOENTRYSCRIPT_H_ diff --git a/dom/script/ModuleLoader.cpp b/dom/script/ModuleLoader.cpp new file mode 100644 index 0000000000..5d6b90cbf0 --- /dev/null +++ b/dom/script/ModuleLoader.cpp @@ -0,0 +1,371 @@ +/* -*- 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 "ModuleLoader.h" + +#include "jsapi.h" +#include "js/CompileOptions.h" // JS::CompileOptions, JS::InstantiateOptions +#include "js/ContextOptions.h" // JS::ContextOptionsRef +#include "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil +#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/PropertyAndElement.h" // JS_DefineProperty +#include "js/Realm.h" +#include "js/SourceText.h" +#include "js/loader/LoadedScript.h" +#include "js/loader/ScriptLoadRequest.h" +#include "js/loader/ModuleLoaderBase.h" +#include "js/loader/ModuleLoadRequest.h" +#include "mozilla/dom/RequestBinding.h" +#include "mozilla/Assertions.h" +#include "nsError.h" +#include "xpcpublic.h" +#include "GeckoProfiler.h" +#include "nsContentSecurityManager.h" +#include "nsIContent.h" +#include "nsJSUtils.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "nsIPrincipal.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Maybe.h" + +using JS::SourceText; +using namespace JS::loader; + +namespace mozilla::dom { + +#undef LOG +#define LOG(args) \ + MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args) + +#define LOG_ENABLED() \ + MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug) + +////////////////////////////////////////////////////////////// +// DOM module loader +////////////////////////////////////////////////////////////// + +ModuleLoader::ModuleLoader(ScriptLoader* aLoader, + nsIGlobalObject* aGlobalObject, Kind aKind) + : ModuleLoaderBase(aLoader, aGlobalObject), mKind(aKind) {} + +ScriptLoader* ModuleLoader::GetScriptLoader() { + return static_cast(mLoader.get()); +} + +bool ModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) { + if (!GetScriptLoader()->GetDocument()) { + *aRvOut = NS_ERROR_NULL_POINTER; + return false; + } + + // If this document is sandboxed without 'allow-scripts', abort. + if (GetScriptLoader()->GetDocument()->HasScriptsBlockedBySandbox()) { + *aRvOut = NS_OK; + return false; + } + + // To prevent dynamic code execution, content scripts can only + // load moz-extension URLs. + nsCOMPtr principal = aRequest->TriggeringPrincipal(); + if (BasePrincipal::Cast(principal)->ContentScriptAddonPolicy() && + !aRequest->mURI->SchemeIs("moz-extension")) { + *aRvOut = NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI; + return false; + } + + if (LOG_ENABLED()) { + nsAutoCString url; + aRequest->mURI->GetAsciiSpec(url); + LOG(("ScriptLoadRequest (%p): Start Module Load (url = %s)", aRequest, + url.get())); + } + + return true; +} + +nsresult ModuleLoader::StartFetch(ModuleLoadRequest* aRequest) { + // 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. + bool isAboutPageLoadingChromeURI = ScriptLoader::IsAboutPageLoadingChromeURI( + aRequest, GetScriptLoader()->GetDocument()); + + nsContentSecurityManager::CORSSecurityMapping corsMapping = + isAboutPageLoadingChromeURI + ? nsContentSecurityManager::CORSSecurityMapping::DISABLE_CORS_CHECKS + : nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS; + + nsSecurityFlags securityFlags = + nsContentSecurityManager::ComputeSecurityFlags(aRequest->CORSMode(), + corsMapping); + + securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME; + + // Delegate Shared Behavior to base ScriptLoader + // + // aCharsetForPreload is passed as Nothing() because this is not a preload + // and `StartLoadInternal` is able to find the charset by using `aRequest` + // for this case. + nsresult rv = GetScriptLoader()->StartLoadInternal( + aRequest, securityFlags, Nothing() /* aCharsetForPreload */); + NS_ENSURE_SUCCESS(rv, rv); + + // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-import()-module-script-graph + // Step 1. Disallow further import maps given settings object. + if (!aRequest->GetScriptLoadContext()->IsPreload()) { + LOG(("ScriptLoadRequest (%p): Disallow further import maps.", aRequest)); + DisallowImportMaps(); + } + + LOG(("ScriptLoadRequest (%p): Start fetching module", aRequest)); + + return NS_OK; +} + +void ModuleLoader::AsyncExecuteInlineModule(ModuleLoadRequest* aRequest) { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( + mozilla::NewRunnableMethod>( + "ModuleLoader::ExecuteInlineModule", this, + &ModuleLoader::ExecuteInlineModule, aRequest))); +} + +void ModuleLoader::ExecuteInlineModule(ModuleLoadRequest* aRequest) { + MOZ_ASSERT(aRequest->IsFinished()); + MOZ_ASSERT(aRequest->IsTopLevel()); + MOZ_ASSERT(aRequest->GetScriptLoadContext()->mIsInline); + + if (aRequest->GetScriptLoadContext()->GetParserCreated() == NOT_FROM_PARSER) { + GetScriptLoader()->RunScriptWhenSafe(aRequest); + } else { + GetScriptLoader()->MaybeMoveToLoadedList(aRequest); + GetScriptLoader()->ProcessPendingRequests(); + } + + aRequest->GetScriptLoadContext()->MaybeUnblockOnload(); +} + +void ModuleLoader::OnModuleLoadComplete(ModuleLoadRequest* aRequest) { + MOZ_ASSERT(aRequest->IsFinished()); + + if (aRequest->IsTopLevel()) { + if (aRequest->GetScriptLoadContext()->mIsInline && + aRequest->GetScriptLoadContext()->GetParserCreated() == + NOT_FROM_PARSER) { + if (aRequest->mImports.Length() == 0) { + GetScriptLoader()->RunScriptWhenSafe(aRequest); + } else { + AsyncExecuteInlineModule(aRequest); + return; + } + } else if (aRequest->GetScriptLoadContext()->mIsInline && + aRequest->GetScriptLoadContext()->GetParserCreated() != + NOT_FROM_PARSER && + !nsContentUtils::IsSafeToRunScript()) { + // Avoid giving inline async module scripts that don't have + // external dependencies a guaranteed execution time relative + // to the HTML parse. That is, deliberately avoid guaranteeing + // that the script would always observe a DOM shape where the + // parser has not added further elements to the DOM. + // (If `nsContentUtils::IsSafeToRunScript()` returns `true`, + // we come here synchronously from the parser. If it returns + // `false` we come here from an external dependency completing + // its fetch, in which case we already are at an unspecific + // point relative to the parse.) + AsyncExecuteInlineModule(aRequest); + return; + } else { + GetScriptLoader()->MaybeMoveToLoadedList(aRequest); + GetScriptLoader()->ProcessPendingRequestsAsync(); + } + } + + aRequest->GetScriptLoadContext()->MaybeUnblockOnload(); +} + +nsresult ModuleLoader::CompileFetchedModule( + JSContext* aCx, JS::Handle aGlobal, JS::CompileOptions& aOptions, + ModuleLoadRequest* aRequest, JS::MutableHandle aModuleOut) { + if (aRequest->GetScriptLoadContext()->mWasCompiledOMT) { + JS::InstantiationStorage storage; + RefPtr stencil = + aRequest->GetScriptLoadContext()->StealOffThreadResult(aCx, &storage); + if (!stencil) { + return NS_ERROR_FAILURE; + } + + JS::InstantiateOptions instantiateOptions(aOptions); + aModuleOut.set(JS::InstantiateModuleStencil(aCx, instantiateOptions, + stencil, &storage)); + if (!aModuleOut) { + return NS_ERROR_FAILURE; + } + + if (aRequest->IsTextSource() && + ScriptLoader::ShouldCacheBytecode(aRequest)) { + if (!JS::StartIncrementalEncoding(aCx, std::move(stencil))) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; + } + + if (!nsJSUtils::IsScriptable(aGlobal)) { + return NS_ERROR_FAILURE; + } + + RefPtr stencil; + if (aRequest->IsTextSource()) { + MaybeSourceText maybeSource; + nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource, + aRequest->mLoadContext.get()); + NS_ENSURE_SUCCESS(rv, rv); + + auto compile = [&](auto& source) { + return JS::CompileModuleScriptToStencil(aCx, aOptions, source); + }; + stencil = maybeSource.mapNonEmpty(compile); + } else { + MOZ_ASSERT(aRequest->IsBytecode()); + JS::DecodeOptions decodeOptions(aOptions); + decodeOptions.borrowBuffer = true; + + JS::TranscodeRange range = aRequest->Bytecode(); + JS::TranscodeResult tr = + JS::DecodeStencil(aCx, decodeOptions, range, getter_AddRefs(stencil)); + if (tr != JS::TranscodeResult::Ok) { + return NS_ERROR_DOM_JS_DECODING_ERROR; + } + } + + if (!stencil) { + return NS_ERROR_FAILURE; + } + + JS::InstantiateOptions instantiateOptions(aOptions); + aModuleOut.set( + JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil)); + if (!aModuleOut) { + return NS_ERROR_FAILURE; + } + + if (aRequest->IsTextSource() && ScriptLoader::ShouldCacheBytecode(aRequest)) { + if (!JS::StartIncrementalEncoding(aCx, std::move(stencil))) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +/* static */ +already_AddRefed ModuleLoader::CreateTopLevel( + nsIURI* aURI, ReferrerPolicy aReferrerPolicy, + ScriptFetchOptions* aFetchOptions, const SRIMetadata& aIntegrity, + nsIURI* aReferrer, ScriptLoader* aLoader, ScriptLoadContext* aContext) { + RefPtr request = new ModuleLoadRequest( + aURI, aReferrerPolicy, aFetchOptions, aIntegrity, aReferrer, aContext, + true, + /* is top level */ false, /* is dynamic import */ + aLoader->GetModuleLoader(), + ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI), nullptr); + + request->NoCacheEntryFound(); + return request.forget(); +} + +already_AddRefed ModuleLoader::CreateStaticImport( + nsIURI* aURI, ModuleLoadRequest* aParent) { + RefPtr newContext = new ScriptLoadContext(); + newContext->mIsInline = false; + // Propagated Parent values. TODO: allow child modules to use root module's + // script mode. + newContext->mScriptMode = aParent->GetScriptLoadContext()->mScriptMode; + + RefPtr request = new ModuleLoadRequest( + aURI, aParent->ReferrerPolicy(), aParent->mFetchOptions, SRIMetadata(), + aParent->mURI, newContext, false, /* is top level */ + false, /* is dynamic import */ + aParent->mLoader, aParent->mVisitedSet, aParent->GetRootModule()); + + request->NoCacheEntryFound(); + return request.forget(); +} + +already_AddRefed ModuleLoader::CreateDynamicImport( + JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript, + JS::Handle aSpecifier, JS::Handle aPromise) { + MOZ_ASSERT(aSpecifier); + MOZ_ASSERT(aPromise); + + RefPtr options = nullptr; + nsIURI* baseURL = nullptr; + RefPtr context = new ScriptLoadContext(); + ReferrerPolicy referrerPolicy; + + if (aMaybeActiveScript) { + // https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule + // Step 6.3. Set fetchOptions to the new descendant script fetch options for + // referencingScript's fetch options. + options = aMaybeActiveScript->GetFetchOptions(); + referrerPolicy = aMaybeActiveScript->ReferrerPolicy(); + baseURL = aMaybeActiveScript->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 = GetScriptLoader()->GetDocument(); + + nsCOMPtr principal = GetGlobalObject()->PrincipalOrNull(); + MOZ_ASSERT_IF(GetKind() == WebExtension, + BasePrincipal::Cast(principal)->ContentScriptAddonPolicy()); + MOZ_ASSERT_IF(GetKind() == Normal, principal == document->NodePrincipal()); + + // https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule + // Step 4. Let fetchOptions be the default classic script fetch options. + // + // https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options + // The default classic script fetch options are a script fetch options whose + // cryptographic nonce is the empty string, integrity metadata is the empty + // string, parser metadata is "not-parser-inserted", credentials mode is + // "same-origin", referrer policy is the empty string, and fetch priority is + // "auto". + options = new ScriptFetchOptions( + mozilla::CORS_NONE, /* aNonce = */ u""_ns, RequestPriority::Auto, + ParserMetadata::NotParserInserted, principal, nullptr); + referrerPolicy = document->GetReferrerPolicy(); + baseURL = document->GetDocBaseURI(); + } + + context->mIsInline = false; + context->mScriptMode = ScriptLoadContext::ScriptMode::eAsync; + + RefPtr request = new ModuleLoadRequest( + aURI, referrerPolicy, options, SRIMetadata(), baseURL, context, true, + /* is top level */ true, /* is dynamic import */ + this, ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI), nullptr); + + request->SetDynamicImport(aMaybeActiveScript, aSpecifier, aPromise); + request->NoCacheEntryFound(); + + return request.forget(); +} + +ModuleLoader::~ModuleLoader() { + LOG(("ModuleLoader::~ModuleLoader %p", this)); + mLoader = nullptr; +} + +#undef LOG +#undef LOG_ENABLED + +} // namespace mozilla::dom diff --git a/dom/script/ModuleLoader.h b/dom/script/ModuleLoader.h new file mode 100644 index 0000000000..4f3f270f0b --- /dev/null +++ b/dom/script/ModuleLoader.h @@ -0,0 +1,91 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_ModuleLoader_h +#define mozilla_dom_ModuleLoader_h + +#include "mozilla/dom/ScriptLoadContext.h" +#include "js/loader/ModuleLoaderBase.h" +#include "js/loader/ScriptLoadRequest.h" +#include "ScriptLoader.h" + +class nsIURI; + +namespace JS { + +class CompileOptions; + +namespace loader { + +class ModuleLoadRequest; + +} // namespace loader +} // namespace JS + +namespace mozilla::dom { + +class ScriptLoader; +class SRIMetadata; + +////////////////////////////////////////////////////////////// +// DOM Module loader implementation +////////////////////////////////////////////////////////////// + +class ModuleLoader final : public JS::loader::ModuleLoaderBase { + private: + virtual ~ModuleLoader(); + + public: + enum Kind { Normal, WebExtension }; + + ModuleLoader(ScriptLoader* aLoader, nsIGlobalObject* aGlobalObject, + Kind aKind); + + Kind GetKind() const { return mKind; } + + ScriptLoader* GetScriptLoader(); + + bool CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) override; + + nsresult StartFetch(ModuleLoadRequest* aRequest) override; + + void OnModuleLoadComplete(ModuleLoadRequest* aRequest) override; + + nsresult CompileFetchedModule( + JSContext* aCx, JS::Handle aGlobal, + JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest, + JS::MutableHandle aModuleScript) override; + + // Create a top-level module load request. + static already_AddRefed CreateTopLevel( + nsIURI* aURI, ReferrerPolicy aReferrerPolicy, + ScriptFetchOptions* aFetchOptions, const SRIMetadata& aIntegrity, + nsIURI* aReferrer, ScriptLoader* aLoader, ScriptLoadContext* aContext); + + // Create a module load request for a static module import. + already_AddRefed CreateStaticImport( + nsIURI* aURI, ModuleLoadRequest* aParent) override; + + // Create a module load request for a dynamic module import. + already_AddRefed CreateDynamicImport( + JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript, + JS::Handle aSpecifier, + JS::Handle aPromise) override; + + static ModuleLoader* From(ModuleLoaderBase* aLoader) { + return static_cast(aLoader); + } + + void AsyncExecuteInlineModule(ModuleLoadRequest* aRequest); + void ExecuteInlineModule(ModuleLoadRequest* aRequest); + + private: + const Kind mKind; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_ModuleLoader_h diff --git a/dom/script/ScriptCompression.cpp b/dom/script/ScriptCompression.cpp new file mode 100644 index 0000000000..837fefe096 --- /dev/null +++ b/dom/script/ScriptCompression.cpp @@ -0,0 +1,176 @@ +/* -*- 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 "zlib.h" +#include "ScriptLoadRequest.h" +#include "ScriptLoader.h" +#include "mozilla/PerfStats.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Vector.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_browser.h" + +using namespace mozilla; + +namespace JS::loader { + +#undef LOG +#define LOG(args) \ + MOZ_LOG(mozilla::dom::ScriptLoader::gScriptLoaderLog, \ + mozilla::LogLevel::Debug, args) + +/* + * ScriptBytecodeDataLayout + * + * ScriptBytecodeDataLayout provides accessors to maintain the correct data + * layout of the ScriptLoadRequest's script bytecode buffer. + */ +class ScriptBytecodeDataLayout { + public: + explicit ScriptBytecodeDataLayout(mozilla::Vector& aBytecode, + size_t aBytecodeOffset) + : mBytecode(aBytecode), mBytecodeOffset(aBytecodeOffset) {} + + uint8_t* prelude() const { return mBytecode.begin(); } + size_t preludeLength() const { return mBytecodeOffset; } + + uint8_t* bytecode() const { return prelude() + mBytecodeOffset; } + size_t bytecodeLength() const { return mBytecode.length() - preludeLength(); } + + mozilla::Vector& mBytecode; + size_t mBytecodeOffset; +}; + +/* + * ScriptBytecodeCompressedDataLayout + * + * ScriptBytecodeCompressedDataLayout provides accessors to maintain the correct + * data layout of a compressed script bytecode buffer. + */ +class ScriptBytecodeCompressedDataLayout { + public: + using UncompressedLengthType = uint32_t; + + explicit ScriptBytecodeCompressedDataLayout( + mozilla::Vector& aBytecode, size_t aBytecodeOffset) + : mBytecode(aBytecode), mBytecodeOffset(aBytecodeOffset) {} + + uint8_t* prelude() const { return mBytecode.begin(); } + size_t preludeLength() const { return mBytecodeOffset; } + + uint8_t* uncompressedLength() const { return prelude() + mBytecodeOffset; } + size_t uncompressedLengthLength() const { + return sizeof(UncompressedLengthType); + } + + uint8_t* bytecode() const { + return uncompressedLength() + uncompressedLengthLength(); + } + size_t bytecodeLength() const { + return mBytecode.length() - uncompressedLengthLength() - preludeLength(); + } + + mozilla::Vector& mBytecode; + size_t mBytecodeOffset; +}; + +bool ScriptBytecodeCompress(Vector& aBytecodeBuf, + size_t aBytecodeOffset, + Vector& aCompressedBytecodeBufOut) { + // TODO probably need to move this to a helper thread + + AUTO_PROFILER_MARKER_TEXT("ScriptBytecodeCompress", JS, {}, ""_ns); + PerfStats::AutoMetricRecording + autoRecording; + + ScriptBytecodeDataLayout uncompressedLayout(aBytecodeBuf, aBytecodeOffset); + ScriptBytecodeCompressedDataLayout compressedLayout( + aCompressedBytecodeBufOut, uncompressedLayout.preludeLength()); + ScriptBytecodeCompressedDataLayout::UncompressedLengthType + uncompressedLength = uncompressedLayout.bytecodeLength(); + z_stream zstream{.next_in = uncompressedLayout.bytecode(), + .avail_in = uncompressedLength}; + + // Note: deflateInit needs to be called before deflateBound. + const uint32_t compressionLevel = + StaticPrefs::browser_cache_jsbc_compression_level(); + if (deflateInit(&zstream, compressionLevel) != Z_OK) { + LOG( + ("ScriptLoadRequest: Unable to initialize bytecode cache " + "compression.")); + return false; + } + auto autoDestroy = MakeScopeExit([&]() { deflateEnd(&zstream); }); + + auto compressedLength = deflateBound(&zstream, uncompressedLength); + if (!aCompressedBytecodeBufOut.resizeUninitialized( + compressedLength + compressedLayout.preludeLength() + + compressedLayout.uncompressedLengthLength())) { + return false; + } + memcpy(compressedLayout.prelude(), uncompressedLayout.prelude(), + uncompressedLayout.preludeLength()); + memcpy(compressedLayout.uncompressedLength(), &uncompressedLength, + sizeof(uncompressedLength)); + zstream.next_out = compressedLayout.bytecode(); + zstream.avail_out = compressedLength; + + int ret = deflate(&zstream, Z_FINISH); + if (ret == Z_MEM_ERROR) { + return false; + } + MOZ_RELEASE_ASSERT(ret == Z_STREAM_END); + + aCompressedBytecodeBufOut.shrinkTo(zstream.next_out - + aCompressedBytecodeBufOut.begin()); + return true; +} + +bool ScriptBytecodeDecompress(Vector& aCompressedBytecodeBuf, + size_t aBytecodeOffset, + Vector& aBytecodeBufOut) { + AUTO_PROFILER_MARKER_TEXT("ScriptBytecodeDecompress", JS, {}, ""_ns); + PerfStats::AutoMetricRecording + autoRecording; + + ScriptBytecodeDataLayout uncompressedLayout(aBytecodeBufOut, aBytecodeOffset); + ScriptBytecodeCompressedDataLayout compressedLayout( + aCompressedBytecodeBuf, uncompressedLayout.preludeLength()); + ScriptBytecodeCompressedDataLayout::UncompressedLengthType uncompressedLength; + memcpy(&uncompressedLength, compressedLayout.uncompressedLength(), + compressedLayout.uncompressedLengthLength()); + if (!aBytecodeBufOut.resizeUninitialized(uncompressedLayout.preludeLength() + + uncompressedLength)) { + return false; + } + memcpy(uncompressedLayout.prelude(), compressedLayout.prelude(), + compressedLayout.preludeLength()); + + z_stream zstream{nullptr}; + zstream.next_in = compressedLayout.bytecode(); + zstream.avail_in = static_cast(compressedLayout.bytecodeLength()); + zstream.next_out = uncompressedLayout.bytecode(); + zstream.avail_out = uncompressedLength; + if (inflateInit(&zstream) != Z_OK) { + LOG(("ScriptLoadRequest: inflateInit FAILED (%s)", zstream.msg)); + return false; + } + auto autoDestroy = MakeScopeExit([&]() { inflateEnd(&zstream); }); + + int ret = inflate(&zstream, Z_NO_FLUSH); + bool ok = (ret == Z_OK || ret == Z_STREAM_END) && zstream.avail_in == 0; + if (!ok) { + LOG(("ScriptLoadReques: inflate FAILED (%s)", zstream.msg)); + return false; + } + + return true; +} + +#undef LOG + +} // namespace JS::loader diff --git a/dom/script/ScriptCompression.h b/dom/script/ScriptCompression.h new file mode 100644 index 0000000000..c824aa9c15 --- /dev/null +++ b/dom/script/ScriptCompression.h @@ -0,0 +1,43 @@ +/* -*- 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/. */ + +#ifndef js_loader_ScriptCompression_h +#define js_loader_ScriptCompression_h + +#include "ErrorList.h" +#include "mozilla/Vector.h" + +namespace JS::loader { + +class ScriptLoadRequest; + +/** + * Compress the bytecode stored in a buffer. All data before the bytecode is + * copied into the output buffer without modification. + * + * @param aBytecodeBuf buffer containing the uncompressed bytecode + * @param aBytecodeOffset offset of the bytecode in the buffer + * @param aCompressedBytecodeBufOut buffer to store the compressed bytecode in + */ +bool ScriptBytecodeCompress( + mozilla::Vector& aBytecodeBuf, size_t aBytecodeOffset, + mozilla::Vector& aCompressedBytecodeBufOut); + +/** + * Uncompress the bytecode stored in a buffer. All data before the bytecode is + * copied into the output buffer without modification. + * + * @param aCompressedBytecodeBuf buffer containing the compressed bytecode + * @param aBytecodeOffset offset of the bytecode in the buffer + * @param aBytecodeBufOut buffer to store the uncompressed bytecode in + */ +bool ScriptBytecodeDecompress(mozilla::Vector& aCompressedBytecodeBuf, + size_t aBytecodeOffset, + mozilla::Vector& aBytecodeBufOut); + +} // namespace JS::loader + +#endif // js_loader_ScriptCompression_h diff --git a/dom/script/ScriptDecoding.h b/dom/script/ScriptDecoding.h new file mode 100644 index 0000000000..ea4c4aeef3 --- /dev/null +++ b/dom/script/ScriptDecoding.h @@ -0,0 +1,88 @@ +/* -*- 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/. */ + +/* Script decoding templates for processing byte data as UTF-8 or UTF-16. */ + +#ifndef mozilla_dom_ScriptDecoding_h +#define mozilla_dom_ScriptDecoding_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/CheckedInt.h" // mozilla::CheckedInt +#include "mozilla/Encoding.h" // mozilla::Decoder +#include "mozilla/Span.h" // mozilla::Span +#include "mozilla/UniquePtr.h" // mozilla::UniquePtr + +#include // size_t +#include // uint8_t, uint32_t +#include // std::is_same + +namespace mozilla::dom { + +template +struct ScriptDecoding { + static_assert(std::is_same::value || + std::is_same::value, + "must be either UTF-8 or UTF-16"); +}; + +template <> +struct ScriptDecoding { + static CheckedInt MaxBufferLength(const UniquePtr& aDecoder, + uint32_t aByteLength) { + return aDecoder->MaxUTF16BufferLength(aByteLength); + } + + static size_t DecodeInto(const UniquePtr& aDecoder, + const Span& aSrc, + Span aDest, bool aEndOfSource) { + uint32_t result; + size_t read; + size_t written; + std::tie(result, read, written, std::ignore) = + aDecoder->DecodeToUTF16(aSrc, aDest, aEndOfSource); + MOZ_ASSERT(result == kInputEmpty); + MOZ_ASSERT(read == aSrc.Length()); + MOZ_ASSERT(written <= aDest.Length()); + + return written; + } +}; + +template <> +struct ScriptDecoding { + static CheckedInt MaxBufferLength(const UniquePtr& aDecoder, + uint32_t aByteLength) { + return aDecoder->MaxUTF8BufferLength(aByteLength); + } + + static size_t DecodeInto(const UniquePtr& aDecoder, + const Span& aSrc, + Span aDest, bool aEndOfSource) { + uint32_t result; + size_t read; + size_t written; + // Until C++ char8_t happens, our decoder APIs deal in |uint8_t| while + // |Utf8Unit| internally deals with |char|, so there's inevitable impedance + // mismatch between |aDest| as |Utf8Unit| and |AsWritableBytes(aDest)| as + // |Span|. :-( The written memory will be interpreted through + // |char Utf8Unit::mValue| which is *permissible* because any object's + // memory can be interpreted as |char|. Unfortunately, until + // twos-complement is mandated, we have to play fast and loose and *hope* + // interpreting memory storing |uint8_t| as |char| will pick up the desired + // wrapped-around value. ¯\_(ツ)_/¯ + std::tie(result, read, written, std::ignore) = + aDecoder->DecodeToUTF8(aSrc, AsWritableBytes(aDest), aEndOfSource); + MOZ_ASSERT(result == kInputEmpty); + MOZ_ASSERT(read == aSrc.Length()); + MOZ_ASSERT(written <= aDest.Length()); + + return written; + } +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_ScriptDecoding_h diff --git a/dom/script/ScriptElement.cpp b/dom/script/ScriptElement.cpp new file mode 100644 index 0000000000..1887aa5937 --- /dev/null +++ b/dom/script/ScriptElement.cpp @@ -0,0 +1,222 @@ +/* -*- 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 "ScriptElement.h" +#include "ScriptLoader.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "nsContentUtils.h" +#include "nsThreadUtils.h" +#include "nsPresContext.h" +#include "nsIParser.h" +#include "nsGkAtoms.h" +#include "nsContentSink.h" + +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMETHODIMP +ScriptElement::ScriptAvailable(nsresult aResult, nsIScriptElement* aElement, + bool aIsInlineClassicScript, nsIURI* aURI, + uint32_t aLineNo) { + if (!aIsInlineClassicScript && NS_FAILED(aResult)) { + nsCOMPtr parser = do_QueryReferent(mCreatorParser); + if (parser) { + nsCOMPtr sink = parser->GetContentSink(); + if (sink) { + nsCOMPtr parserDoc = do_QueryInterface(sink->GetTarget()); + if (GetAsContent()->OwnerDoc() != parserDoc) { + // Suppress errors when we've moved between docs. + // /html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-module.html + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=1849107 + return NS_OK; + } + } + } + + if (parser) { + parser->IncrementScriptNestingLevel(); + } + nsresult rv = FireErrorEvent(); + if (parser) { + parser->DecrementScriptNestingLevel(); + } + return rv; + } + return NS_OK; +} + +/* virtual */ +nsresult ScriptElement::FireErrorEvent() { + nsIContent* cont = GetAsContent(); + + return nsContentUtils::DispatchTrustedEvent( + cont->OwnerDoc(), cont, u"error"_ns, CanBubble::eNo, Cancelable::eNo); +} + +NS_IMETHODIMP +ScriptElement::ScriptEvaluated(nsresult aResult, nsIScriptElement* aElement, + bool aIsInline) { + nsresult rv = NS_OK; + if (!aIsInline) { + nsCOMPtr cont = GetAsContent(); + + RefPtr presContext = + nsContentUtils::GetContextForContent(cont); + + nsEventStatus status = nsEventStatus_eIgnore; + EventMessage message = NS_SUCCEEDED(aResult) ? eLoad : eLoadError; + WidgetEvent event(true, message); + // Load event doesn't bubble. + event.mFlags.mBubbles = (message != eLoad); + + EventDispatcher::Dispatch(cont, presContext, &event, nullptr, &status); + } + + return rv; +} + +void ScriptElement::CharacterDataChanged(nsIContent* aContent, + const CharacterDataChangeInfo&) { + MaybeProcessScript(); +} + +void ScriptElement::AttributeChanged(Element* aElement, int32_t aNameSpaceID, + nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue) { + // https://html.spec.whatwg.org/#script-processing-model + // When a script element el that is not parser-inserted experiences one of the + // events listed in the following list, the user agent must immediately + // prepare the script element el: + // - The script element is connected and has a src attribute set where + // previously the element had no such attribute. + if (aElement->IsSVGElement() && ((aNameSpaceID != kNameSpaceID_XLink && + aNameSpaceID != kNameSpaceID_None) || + aAttribute != nsGkAtoms::href)) { + return; + } + if (aElement->IsHTMLElement() && + (aNameSpaceID != kNameSpaceID_None || aAttribute != nsGkAtoms::src)) { + return; + } + if (mParserCreated == NOT_FROM_PARSER && + aModType == MutationEvent_Binding::ADDITION) { + auto* cont = GetAsContent(); + if (cont->IsInComposedDoc()) { + MaybeProcessScript(); + } + } +} + +void ScriptElement::ContentAppended(nsIContent* aFirstNewContent) { + MaybeProcessScript(); +} + +void ScriptElement::ContentInserted(nsIContent* aChild) { + MaybeProcessScript(); +} + +bool ScriptElement::MaybeProcessScript() { + nsIContent* cont = GetAsContent(); + + NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.contains(this), + "You forgot to add self as observer"); + + // https://html.spec.whatwg.org/#parsing-main-incdata + // An end tag whose tag name is "script" + // - If the active speculative HTML parser is null and the JavaScript + // execution context stack is empty, then perform a microtask checkpoint. + nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( + "ScriptElement::MaybeProcessScript", []() { nsAutoMicroTask mt; })); + + if (mAlreadyStarted || !mDoneAddingChildren || !cont->GetComposedDoc() || + mMalformed) { + return false; + } + + if (!HasScriptContent()) { + // In the case of an empty, non-external classic script, there is nothing + // to process. However, we must perform a microtask checkpoint afterwards, + // as per https://html.spec.whatwg.org/#clean-up-after-running-script + if (mKind == JS::loader::ScriptKind::eClassic && !mExternal) { + nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( + "ScriptElement::MaybeProcessScript", []() { nsAutoMicroTask mt; })); + } + return false; + } + + // Check the type attribute to determine language and version. If type exists, + // it trumps the deprecated 'language='. + nsAutoString type; + bool hasType = GetScriptType(type); + if (!type.IsEmpty()) { + NS_ENSURE_TRUE(nsContentUtils::IsJavascriptMIMEType(type) || + type.LowerCaseEqualsASCII("module") || + type.LowerCaseEqualsASCII("importmap"), + false); + } else if (!hasType) { + // "language" is a deprecated attribute of HTML, so we check it only for + // HTML script elements. + if (cont->IsHTMLElement()) { + nsAutoString language; + cont->AsElement()->GetAttr(nsGkAtoms::language, language); + if (!language.IsEmpty() && + !nsContentUtils::IsJavaScriptLanguage(language)) { + return false; + } + } + } + + Document* ownerDoc = cont->OwnerDoc(); + FreezeExecutionAttrs(ownerDoc); + + mAlreadyStarted = true; + + nsCOMPtr parser = ((nsIScriptElement*)this)->GetCreatorParser(); + if (parser) { + nsCOMPtr sink = parser->GetContentSink(); + if (sink) { + nsCOMPtr parserDoc = do_QueryInterface(sink->GetTarget()); + if (ownerDoc != parserDoc) { + // Refactor this: https://bugzilla.mozilla.org/show_bug.cgi?id=1849107 + return false; + } + } + } + + RefPtr loader = ownerDoc->ScriptLoader(); + return loader->ProcessScriptElement(this); +} + +bool ScriptElement::GetScriptType(nsAString& aType) { + Element* element = GetAsContent()->AsElement(); + + nsAutoString type; + if (!element->GetAttr(nsGkAtoms::type, type)) { + return false; + } + + // ASCII whitespace https://infra.spec.whatwg.org/#ascii-whitespace: + // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE. + static const char kASCIIWhitespace[] = "\t\n\f\r "; + + const bool wasEmptyBeforeTrim = type.IsEmpty(); + type.Trim(kASCIIWhitespace); + + // If the value before trim was not empty and the value is now empty, do not + // trim as we want to retain pure whitespace (by restoring original value) + // because we need to treat "" and " " (etc) differently. + if (!wasEmptyBeforeTrim && type.IsEmpty()) { + return element->GetAttr(nsGkAtoms::type, aType); + } + + aType.Assign(type); + return true; +} diff --git a/dom/script/ScriptElement.h b/dom/script/ScriptElement.h new file mode 100644 index 0000000000..568ed9f786 --- /dev/null +++ b/dom/script/ScriptElement.h @@ -0,0 +1,54 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_ScriptElement_h +#define mozilla_dom_ScriptElement_h + +#include "mozilla/Attributes.h" +#include "nsIScriptLoaderObserver.h" +#include "nsIScriptElement.h" +#include "nsStubMutationObserver.h" + +namespace mozilla::dom { + +/** + * Baseclass useful for script elements (such as and + * ). Currently the class assumes that only the 'src' + * attribute and the children of the class affect what script to execute. + */ + +class ScriptElement : public nsIScriptElement, public nsStubMutationObserver { + public: + // nsIScriptLoaderObserver + NS_DECL_NSISCRIPTLOADEROBSERVER + + // nsIMutationObserver + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + + explicit ScriptElement(FromParser aFromParser) + : nsIScriptElement(aFromParser) {} + + virtual nsresult FireErrorEvent() override; + + virtual bool GetScriptType(nsAString& aType) override; + + protected: + // Internal methods + + /** + * Check if this element contains any script, linked or inline + */ + virtual bool HasScriptContent() = 0; + + virtual bool MaybeProcessScript() override; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_ScriptElement_h diff --git a/dom/script/ScriptLoadContext.cpp b/dom/script/ScriptLoadContext.cpp new file mode 100644 index 0000000000..75d54a309d --- /dev/null +++ b/dom/script/ScriptLoadContext.cpp @@ -0,0 +1,206 @@ +/* -*- 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 "GeckoProfiler.h" + +#include "mozilla/dom/Document.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/Unused.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include "js/SourceText.h" +#include "js/loader/LoadContextBase.h" +#include "js/loader/ModuleLoadRequest.h" + +#include "ScriptLoadContext.h" +#include "ModuleLoadRequest.h" +#include "nsContentUtils.h" +#include "nsICacheInfoChannel.h" +#include "nsIClassOfService.h" +#include "nsISupportsPriority.h" + +namespace mozilla::dom { + +////////////////////////////////////////////////////////////// +// ScriptLoadContext +////////////////////////////////////////////////////////////// + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoadContext) +NS_INTERFACE_MAP_END_INHERITING(JS::loader::LoadContextBase) + +NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptLoadContext) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ScriptLoadContext, + JS::loader::LoadContextBase) + MOZ_ASSERT(!tmp->mCompileOrDecodeTask); + tmp->MaybeUnblockOnload(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ScriptLoadContext, + JS::loader::LoadContextBase) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDocument) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_ADDREF_INHERITED(ScriptLoadContext, JS::loader::LoadContextBase) +NS_IMPL_RELEASE_INHERITED(ScriptLoadContext, JS::loader::LoadContextBase) + +ScriptLoadContext::ScriptLoadContext() + : JS::loader::LoadContextBase(JS::loader::ContextKind::Window), + mScriptMode(ScriptMode::eBlocking), + mScriptFromHead(false), + mIsInline(true), + mInDeferList(false), + mInAsyncList(false), + mIsNonAsyncScriptInserted(false), + mIsXSLT(false), + mInCompilingList(false), + mIsTracking(false), + mWasCompiledOMT(false), + mLineNo(1), + mColumnNo(0), + mIsPreload(false), + mUnreportedPreloadError(NS_OK) {} + +ScriptLoadContext::~ScriptLoadContext() { + MOZ_ASSERT(NS_IsMainThread()); + + // Off-thread parsing must have completed or cancelled by this point. + MOZ_DIAGNOSTIC_ASSERT(!mCompileOrDecodeTask); + + mRequest = nullptr; + + MaybeUnblockOnload(); +} + +void ScriptLoadContext::BlockOnload(Document* aDocument) { + MOZ_ASSERT(!mLoadBlockedDocument); + aDocument->BlockOnload(); + mLoadBlockedDocument = aDocument; +} + +void ScriptLoadContext::MaybeUnblockOnload() { + if (mLoadBlockedDocument) { + mLoadBlockedDocument->UnblockOnload(false); + mLoadBlockedDocument = nullptr; + } +} + +void ScriptLoadContext::MaybeCancelOffThreadScript() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mCompileOrDecodeTask) { + return; + } + + // Cancel the task if it hasn't been started yet or wait for it to finish. + mCompileOrDecodeTask->Cancel(); + mCompileOrDecodeTask = nullptr; + + MaybeUnblockOnload(); +} + +void ScriptLoadContext::SetScriptMode(bool aDeferAttr, bool aAsyncAttr, + bool aLinkPreload) { + if (aLinkPreload) { + mScriptMode = ScriptMode::eLinkPreload; + } else if (aAsyncAttr) { + mScriptMode = ScriptMode::eAsync; + } else if (aDeferAttr || mRequest->IsModuleRequest()) { + mScriptMode = ScriptMode::eDeferred; + } else { + mScriptMode = ScriptMode::eBlocking; + } +} + +// static +void ScriptLoadContext::PrioritizeAsPreload(nsIChannel* aChannel) { + if (nsCOMPtr cos = do_QueryInterface(aChannel)) { + cos->AddClassFlags(nsIClassOfService::Unblocked); + } + if (nsCOMPtr sp = do_QueryInterface(aChannel)) { + sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST); + } +} + +bool ScriptLoadContext::IsPreload() const { + if (mRequest->IsModuleRequest() && !mRequest->IsTopLevel()) { + JS::loader::ModuleLoadRequest* root = + mRequest->AsModuleRequest()->GetRootModule(); + return root->GetScriptLoadContext()->IsPreload(); + } + + MOZ_ASSERT_IF(mIsPreload, !GetScriptElement()); + return mIsPreload; +} + +bool ScriptLoadContext::CompileStarted() const { + return mRequest->IsCompiling() || (mRequest->IsFinished() && mWasCompiledOMT); +} + +nsIScriptElement* ScriptLoadContext::GetScriptElement() const { + nsCOMPtr scriptElement = + do_QueryInterface(mRequest->mFetchOptions->mElement); + return scriptElement; +} + +void ScriptLoadContext::SetIsLoadRequest(nsIScriptElement* aElement) { + MOZ_ASSERT(aElement); + MOZ_ASSERT(!GetScriptElement()); + MOZ_ASSERT(IsPreload()); + // We are not tracking our own element, and are relying on the one in + // FetchOptions. + mRequest->mFetchOptions->mElement = do_QueryInterface(aElement); + mIsPreload = false; +} + +void ScriptLoadContext::GetProfilerLabel(nsACString& aOutString) { + if (!profiler_is_active()) { + aOutString.Append("