summaryrefslogtreecommitdiffstats
path: root/dom/script/ModuleLoader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/script/ModuleLoader.cpp')
-rw-r--r--dom/script/ModuleLoader.cpp318
1 files changed, 318 insertions, 0 deletions
diff --git a/dom/script/ModuleLoader.cpp b/dom/script/ModuleLoader.cpp
new file mode 100644
index 0000000000..6cb9bf0245
--- /dev/null
+++ b/dom/script/ModuleLoader.cpp
@@ -0,0 +1,318 @@
+/* -*- 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/OffThreadScriptCompilation.h"
+#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 "xpcpublic.h"
+#include "GeckoProfiler.h"
+#include "nsContentSecurityManager.h"
+#include "nsIContent.h"
+#include "nsJSUtils.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIPrincipal.h"
+#include "mozilla/LoadInfo.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<ScriptLoader*>(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<nsIPrincipal> 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, 0, 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::OnModuleLoadComplete(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->IsReadyToRun());
+
+ if (aRequest->IsTopLevel()) {
+ if (aRequest->GetScriptLoadContext()->mIsInline &&
+ aRequest->GetScriptLoadContext()->GetParserCreated() ==
+ NOT_FROM_PARSER) {
+ GetScriptLoader()->RunScriptWhenSafe(aRequest);
+ } else {
+ GetScriptLoader()->MaybeMoveToLoadedList(aRequest);
+ GetScriptLoader()->ProcessPendingRequests();
+ }
+ }
+
+ aRequest->GetScriptLoadContext()->MaybeUnblockOnload();
+}
+
+nsresult ModuleLoader::CompileFetchedModule(
+ JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions,
+ ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleOut) {
+ if (aRequest->GetScriptLoadContext()->mWasCompiledOMT) {
+ JS::Rooted<JS::InstantiationStorage> storage(aCx);
+ RefPtr<JS::Stencil> stencil = JS::FinishOffThreadStencil(
+ aCx, aRequest->GetScriptLoadContext()->mOffThreadToken,
+ storage.address());
+
+ aRequest->GetScriptLoadContext()->mOffThreadToken = nullptr;
+
+ if (!stencil) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::InstantiateOptions instantiateOptions(aOptions);
+ aModuleOut.set(JS::InstantiateModuleStencil(aCx, instantiateOptions,
+ stencil, storage.address()));
+ 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<JS::Stencil> stencil;
+ if (aRequest->IsTextSource()) {
+ MaybeSourceText maybeSource;
+ nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource);
+ 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;
+
+ auto& bytecode = aRequest->mScriptBytecode;
+ auto& offset = aRequest->mBytecodeOffset;
+
+ JS::TranscodeRange range(bytecode.begin() + offset,
+ bytecode.length() - offset);
+
+ 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<ModuleLoadRequest> ModuleLoader::CreateTopLevel(
+ nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
+ const SRIMetadata& aIntegrity, nsIURI* aReferrer, ScriptLoader* aLoader,
+ ScriptLoadContext* aContext) {
+ RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
+ aURI, aFetchOptions, aIntegrity, aReferrer, aContext, true,
+ /* is top level */ false, /* is dynamic import */
+ aLoader->GetModuleLoader(),
+ ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI), nullptr);
+
+ return request.forget();
+}
+
+already_AddRefed<ModuleLoadRequest> ModuleLoader::CreateStaticImport(
+ nsIURI* aURI, ModuleLoadRequest* aParent) {
+ RefPtr<ScriptLoadContext> 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<ModuleLoadRequest> request = new ModuleLoadRequest(
+ aURI, aParent->mFetchOptions, SRIMetadata(), aParent->mURI, newContext,
+ false, /* is top level */
+ false, /* is dynamic import */
+ aParent->mLoader, aParent->mVisitedSet, aParent->GetRootModule());
+
+ return request.forget();
+}
+
+already_AddRefed<ModuleLoadRequest> ModuleLoader::CreateDynamicImport(
+ JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
+ JS::Handle<JS::Value> aReferencingPrivate, JS::Handle<JSString*> aSpecifier,
+ JS::Handle<JSObject*> aPromise) {
+ MOZ_ASSERT(aSpecifier);
+ MOZ_ASSERT(aPromise);
+
+ RefPtr<ScriptFetchOptions> options = nullptr;
+ nsIURI* baseURL = nullptr;
+ RefPtr<ScriptLoadContext> context = new ScriptLoadContext();
+
+ if (aMaybeActiveScript) {
+ options = aMaybeActiveScript->GetFetchOptions();
+ 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<nsIPrincipal> principal = GetGlobalObject()->PrincipalOrNull();
+ MOZ_ASSERT_IF(GetKind() == WebExtension,
+ BasePrincipal::Cast(principal)->ContentScriptAddonPolicy());
+ MOZ_ASSERT_IF(GetKind() == Normal, principal == document->NodePrincipal());
+
+ options = new ScriptFetchOptions(
+ mozilla::CORS_NONE, document->GetReferrerPolicy(), principal, nullptr);
+ baseURL = document->GetDocBaseURI();
+ }
+
+ context->mIsInline = false;
+ context->mScriptMode = ScriptLoadContext::ScriptMode::eAsync;
+
+ RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
+ aURI, options, SRIMetadata(), baseURL, context, true,
+ /* is top level */ true, /* is dynamic import */
+ this, ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI), nullptr);
+
+ request->mDynamicReferencingPrivate = aReferencingPrivate;
+ request->mDynamicSpecifier = aSpecifier;
+ request->mDynamicPromise = aPromise;
+
+ HoldJSObjects(request.get());
+
+ return request.forget();
+}
+
+ModuleLoader::~ModuleLoader() {
+ LOG(("ModuleLoader::~ModuleLoader %p", this));
+ mLoader = nullptr;
+}
+
+#undef LOG
+#undef LOG_ENABLED
+
+} // namespace mozilla::dom