diff options
Diffstat (limited to '')
-rw-r--r-- | dom/script/ModuleLoader.cpp | 318 |
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 |