forked from mirrors/gecko-dev
		
	 7045acf305
			
		
	
	
		7045acf305
		
	
	
	
	
		
			
			Now we can move the JS engine integration hooks into the generic module loader. There is still some code there to deal with web extension globals. This will be removed in a later patch. Differential Revision: https://phabricator.services.mozilla.com/D141735
		
			
				
	
	
		
			359 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			359 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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 "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) : ModuleLoaderBase(aLoader) {
 | |
| }
 | |
| 
 | |
| ScriptLoader* ModuleLoader::GetScriptLoader() {
 | |
|   return static_cast<ScriptLoader*>(mLoader.get());
 | |
| }
 | |
| 
 | |
| nsresult ModuleLoader::StartModuleLoad(ScriptLoadRequest* aRequest) {
 | |
|   return StartModuleLoadImpl(aRequest, RestartRequest::No);
 | |
| }
 | |
| 
 | |
| nsresult ModuleLoader::RestartModuleLoad(ScriptLoadRequest* aRequest) {
 | |
|   return StartModuleLoadImpl(aRequest, RestartRequest::Yes);
 | |
| }
 | |
| 
 | |
| nsresult ModuleLoader::StartModuleLoadImpl(ScriptLoadRequest* aRequest,
 | |
|                                            RestartRequest aRestart) {
 | |
|   MOZ_ASSERT(aRequest->IsFetching());
 | |
|   NS_ENSURE_TRUE(GetScriptLoader()->GetDocument(), NS_ERROR_NULL_POINTER);
 | |
|   aRequest->SetUnknownDataType();
 | |
| 
 | |
|   // If this document is sandboxed without 'allow-scripts', abort.
 | |
|   if (GetScriptLoader()->GetDocument()->HasScriptsBlockedBySandbox()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (LOG_ENABLED()) {
 | |
|     nsAutoCString url;
 | |
|     aRequest->mURI->GetAsciiSpec(url);
 | |
|     LOG(("ScriptLoadRequest (%p): Start Module Load (url = %s)", aRequest,
 | |
|          url.get()));
 | |
|   }
 | |
| 
 | |
|   // 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")) {
 | |
|     return NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI;
 | |
|   }
 | |
| 
 | |
|   // 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 we're restarting the request, the module should already be in the
 | |
|   // "fetching" map.
 | |
|   MOZ_ASSERT_IF(
 | |
|       aRestart == RestartRequest::Yes,
 | |
|       IsModuleFetching(request->mURI,
 | |
|                        aRequest->GetLoadContext()->GetWebExtGlobal()));
 | |
| 
 | |
|   if (aRestart == RestartRequest::No &&
 | |
|       ModuleMapContainsURL(request->mURI,
 | |
|                            aRequest->GetLoadContext()->GetWebExtGlobal())) {
 | |
|     LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest));
 | |
|     WaitForModuleFetch(request->mURI,
 | |
|                        aRequest->GetLoadContext()->GetWebExtGlobal())
 | |
|         ->Then(GetMainThreadSerialEventTarget(), __func__, request,
 | |
|                &ModuleLoadRequest::ModuleLoaded,
 | |
|                &ModuleLoadRequest::LoadFailed);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsSecurityFlags securityFlags;
 | |
| 
 | |
|   // 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 (GetScriptLoader()->IsAboutPageLoadingChromeURI(
 | |
|           aRequest, GetScriptLoader()->GetDocument())) {
 | |
|     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;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
 | |
| 
 | |
|   // Delegate Shared Behavior to base ScriptLoader
 | |
|   nsresult rv = GetScriptLoader()->StartLoadInternal(aRequest, securityFlags);
 | |
| 
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // We successfully started fetching a module so put its URL in the module
 | |
|   // map and mark it as fetching.
 | |
|   if (aRestart == RestartRequest::No) {
 | |
|     SetModuleFetchStarted(aRequest->AsModuleRequest());
 | |
|   }
 | |
|   LOG(("ScriptLoadRequest (%p): Start fetching module", aRequest));
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void ModuleLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest) {
 | |
|   MOZ_ASSERT(aRequest->IsReadyToRun());
 | |
| 
 | |
|   if (aRequest->IsTopLevel()) {
 | |
|     if (aRequest->IsDynamicImport() ||
 | |
|         (aRequest->GetLoadContext()->mIsInline &&
 | |
|          aRequest->GetLoadContext()->GetParserCreated() == NOT_FROM_PARSER)) {
 | |
|       GetScriptLoader()->RunScriptWhenSafe(aRequest);
 | |
|     } else {
 | |
|       GetScriptLoader()->MaybeMoveToLoadedList(aRequest);
 | |
|       GetScriptLoader()->ProcessPendingRequests();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   aRequest->GetLoadContext()->MaybeUnblockOnload();
 | |
| }
 | |
| 
 | |
| nsresult ModuleLoader::CompileOrFinishModuleScript(
 | |
|     JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions,
 | |
|     ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModule) {
 | |
|   if (aRequest->GetLoadContext()->mWasCompiledOMT) {
 | |
|     JS::Rooted<JS::InstantiationStorage> storage(aCx);
 | |
| 
 | |
|     RefPtr<JS::Stencil> stencil;
 | |
|     if (aRequest->IsTextSource()) {
 | |
|       stencil = JS::FinishCompileModuleToStencilOffThread(
 | |
|           aCx, aRequest->GetLoadContext()->mOffThreadToken, storage.address());
 | |
|     } else {
 | |
|       MOZ_ASSERT(aRequest->IsBytecode());
 | |
|       stencil = JS::FinishDecodeStencilOffThread(
 | |
|           aCx, aRequest->GetLoadContext()->mOffThreadToken, storage.address());
 | |
|     }
 | |
| 
 | |
|     aRequest->GetLoadContext()->mOffThreadToken = nullptr;
 | |
| 
 | |
|     if (!stencil) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     JS::InstantiateOptions instantiateOptions(aOptions);
 | |
|     aModule.set(JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil,
 | |
|                                              storage.address()));
 | |
|     if (!aModule) {
 | |
|       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_OK;
 | |
|   }
 | |
| 
 | |
|   RefPtr<JS::Stencil> stencil;
 | |
|   if (aRequest->IsTextSource()) {
 | |
|     MaybeSourceText maybeSource;
 | |
|     nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     stencil = maybeSource.constructed<SourceText<char16_t>>()
 | |
|                   ? JS::CompileModuleScriptToStencil(
 | |
|                         aCx, aOptions, maybeSource.ref<SourceText<char16_t>>())
 | |
|                   : JS::CompileModuleScriptToStencil(
 | |
|                         aCx, aOptions, maybeSource.ref<SourceText<Utf8Unit>>());
 | |
|   } 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);
 | |
|   aModule.set(JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil));
 | |
|   if (!aModule) {
 | |
|     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(aParent->GetLoadContext()->mElement);
 | |
|   newContext->mIsInline = false;
 | |
|   // Propagated Parent values. TODO: allow child modules to use root module's
 | |
|   // script mode.
 | |
|   newContext->mScriptMode = aParent->GetLoadContext()->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;
 | |
|   nsIURI* baseURL = nullptr;
 | |
|   RefPtr<ScriptLoadContext> context;
 | |
| 
 | |
|   if (aMaybeActiveScript) {
 | |
|     options = aMaybeActiveScript->GetFetchOptions();
 | |
|     baseURL = aMaybeActiveScript->BaseURL();
 | |
|     nsCOMPtr<Element> element = aMaybeActiveScript->GetScriptElement();
 | |
|     context = new ScriptLoadContext(element);
 | |
|   } 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();
 | |
| 
 | |
|     // Use the document's principal for all loads, except WebExtension
 | |
|     // content-scripts.
 | |
|     // Only remember the global for content-scripts as well.
 | |
|     nsCOMPtr<nsIPrincipal> principal = nsContentUtils::SubjectPrincipal(aCx);
 | |
|     nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
 | |
|     if (!BasePrincipal::Cast(principal)->ContentScriptAddonPolicy()) {
 | |
|       principal = document->NodePrincipal();
 | |
|       MOZ_ASSERT(global);
 | |
|       global = nullptr;  // Null global is the usual case for most loads.
 | |
|     } else {
 | |
|       MOZ_ASSERT(
 | |
|           xpc::IsWebExtensionContentScriptSandbox(global->GetGlobalJSObject()));
 | |
|     }
 | |
| 
 | |
|     options = new ScriptFetchOptions(
 | |
|         mozilla::CORS_NONE, document->GetReferrerPolicy(), principal, global);
 | |
|     baseURL = document->GetDocBaseURI();
 | |
|     context = new ScriptLoadContext(nullptr);
 | |
|   }
 | |
| 
 | |
|   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
 |