Bug 1843838 - Rename ScriptLoadRequest::IsReadyToRun to IsFinished since this returns...
[gecko.git] / dom / script / ModuleLoader.cpp
blob3e2e53e024708947e959dc02f542ac3ac480504f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ScriptLoader.h"
8 #include "ModuleLoader.h"
10 #include "jsapi.h"
11 #include "js/CompileOptions.h" // JS::CompileOptions, JS::InstantiateOptions
12 #include "js/ContextOptions.h" // JS::ContextOptionsRef
13 #include "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil
14 #include "js/MemoryFunctions.h"
15 #include "js/Modules.h" // JS::FinishDynamicModuleImport, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{DynamicImport,Metadata}Hook
16 #include "js/OffThreadScriptCompilation.h"
17 #include "js/PropertyAndElement.h" // JS_DefineProperty
18 #include "js/Realm.h"
19 #include "js/SourceText.h"
20 #include "js/loader/LoadedScript.h"
21 #include "js/loader/ScriptLoadRequest.h"
22 #include "js/loader/ModuleLoaderBase.h"
23 #include "js/loader/ModuleLoadRequest.h"
24 #include "xpcpublic.h"
25 #include "GeckoProfiler.h"
26 #include "nsContentSecurityManager.h"
27 #include "nsIContent.h"
28 #include "nsJSUtils.h"
29 #include "mozilla/dom/AutoEntryScript.h"
30 #include "mozilla/dom/Document.h"
31 #include "mozilla/dom/Element.h"
32 #include "nsIPrincipal.h"
33 #include "mozilla/LoadInfo.h"
34 #include "mozilla/Maybe.h"
36 using JS::SourceText;
37 using namespace JS::loader;
39 namespace mozilla::dom {
41 #undef LOG
42 #define LOG(args) \
43 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
45 #define LOG_ENABLED() \
46 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
48 //////////////////////////////////////////////////////////////
49 // DOM module loader
50 //////////////////////////////////////////////////////////////
52 ModuleLoader::ModuleLoader(ScriptLoader* aLoader,
53 nsIGlobalObject* aGlobalObject, Kind aKind)
54 : ModuleLoaderBase(aLoader, aGlobalObject), mKind(aKind) {}
56 ScriptLoader* ModuleLoader::GetScriptLoader() {
57 return static_cast<ScriptLoader*>(mLoader.get());
60 bool ModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) {
61 if (!GetScriptLoader()->GetDocument()) {
62 *aRvOut = NS_ERROR_NULL_POINTER;
63 return false;
66 // If this document is sandboxed without 'allow-scripts', abort.
67 if (GetScriptLoader()->GetDocument()->HasScriptsBlockedBySandbox()) {
68 *aRvOut = NS_OK;
69 return false;
72 // To prevent dynamic code execution, content scripts can only
73 // load moz-extension URLs.
74 nsCOMPtr<nsIPrincipal> principal = aRequest->TriggeringPrincipal();
75 if (BasePrincipal::Cast(principal)->ContentScriptAddonPolicy() &&
76 !aRequest->mURI->SchemeIs("moz-extension")) {
77 *aRvOut = NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI;
78 return false;
81 if (LOG_ENABLED()) {
82 nsAutoCString url;
83 aRequest->mURI->GetAsciiSpec(url);
84 LOG(("ScriptLoadRequest (%p): Start Module Load (url = %s)", aRequest,
85 url.get()));
88 return true;
91 nsresult ModuleLoader::StartFetch(ModuleLoadRequest* aRequest) {
92 // According to the spec, module scripts have different behaviour to classic
93 // scripts and always use CORS. Only exception: Non linkable about: pages
94 // which load local module scripts.
95 bool isAboutPageLoadingChromeURI = ScriptLoader::IsAboutPageLoadingChromeURI(
96 aRequest, GetScriptLoader()->GetDocument());
98 nsContentSecurityManager::CORSSecurityMapping corsMapping =
99 isAboutPageLoadingChromeURI
100 ? nsContentSecurityManager::CORSSecurityMapping::DISABLE_CORS_CHECKS
101 : nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS;
103 nsSecurityFlags securityFlags =
104 nsContentSecurityManager::ComputeSecurityFlags(aRequest->CORSMode(),
105 corsMapping);
107 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
109 // Delegate Shared Behavior to base ScriptLoader
111 // aCharsetForPreload is passed as Nothing() because this is not a preload
112 // and `StartLoadInternal` is able to find the charset by using `aRequest`
113 // for this case.
114 nsresult rv = GetScriptLoader()->StartLoadInternal(
115 aRequest, securityFlags, Nothing() /* aCharsetForPreload */);
116 NS_ENSURE_SUCCESS(rv, rv);
118 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-import()-module-script-graph
119 // Step 1. Disallow further import maps given settings object.
120 if (!aRequest->GetScriptLoadContext()->IsPreload()) {
121 LOG(("ScriptLoadRequest (%p): Disallow further import maps.", aRequest));
122 DisallowImportMaps();
125 LOG(("ScriptLoadRequest (%p): Start fetching module", aRequest));
127 return NS_OK;
130 void ModuleLoader::OnModuleLoadComplete(ModuleLoadRequest* aRequest) {
131 MOZ_ASSERT(aRequest->IsFinished());
133 if (aRequest->IsTopLevel()) {
134 if (aRequest->GetScriptLoadContext()->mIsInline &&
135 aRequest->GetScriptLoadContext()->GetParserCreated() ==
136 NOT_FROM_PARSER) {
137 GetScriptLoader()->RunScriptWhenSafe(aRequest);
138 } else {
139 GetScriptLoader()->MaybeMoveToLoadedList(aRequest);
140 GetScriptLoader()->ProcessPendingRequests();
144 aRequest->GetScriptLoadContext()->MaybeUnblockOnload();
147 nsresult ModuleLoader::CompileFetchedModule(
148 JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions,
149 ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleOut) {
150 if (aRequest->GetScriptLoadContext()->mWasCompiledOMT) {
151 JS::InstantiationStorage storage;
152 RefPtr<JS::Stencil> stencil = JS::FinishOffThreadStencil(
153 aCx, aRequest->GetScriptLoadContext()->mOffThreadToken, &storage);
155 aRequest->GetScriptLoadContext()->mOffThreadToken = nullptr;
157 if (!stencil) {
158 return NS_ERROR_FAILURE;
161 JS::InstantiateOptions instantiateOptions(aOptions);
162 aModuleOut.set(JS::InstantiateModuleStencil(aCx, instantiateOptions,
163 stencil, &storage));
164 if (!aModuleOut) {
165 return NS_ERROR_FAILURE;
168 if (aRequest->IsTextSource() &&
169 ScriptLoader::ShouldCacheBytecode(aRequest)) {
170 if (!JS::StartIncrementalEncoding(aCx, std::move(stencil))) {
171 return NS_ERROR_FAILURE;
175 return NS_OK;
178 if (!nsJSUtils::IsScriptable(aGlobal)) {
179 return NS_ERROR_FAILURE;
182 RefPtr<JS::Stencil> stencil;
183 if (aRequest->IsTextSource()) {
184 MaybeSourceText maybeSource;
185 nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource);
186 NS_ENSURE_SUCCESS(rv, rv);
188 auto compile = [&](auto& source) {
189 return JS::CompileModuleScriptToStencil(aCx, aOptions, source);
191 stencil = maybeSource.mapNonEmpty(compile);
192 } else {
193 MOZ_ASSERT(aRequest->IsBytecode());
194 JS::DecodeOptions decodeOptions(aOptions);
195 decodeOptions.borrowBuffer = true;
197 auto& bytecode = aRequest->mScriptBytecode;
198 auto& offset = aRequest->mBytecodeOffset;
200 JS::TranscodeRange range(bytecode.begin() + offset,
201 bytecode.length() - offset);
203 JS::TranscodeResult tr =
204 JS::DecodeStencil(aCx, decodeOptions, range, getter_AddRefs(stencil));
205 if (tr != JS::TranscodeResult::Ok) {
206 return NS_ERROR_DOM_JS_DECODING_ERROR;
210 if (!stencil) {
211 return NS_ERROR_FAILURE;
214 JS::InstantiateOptions instantiateOptions(aOptions);
215 aModuleOut.set(
216 JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil));
217 if (!aModuleOut) {
218 return NS_ERROR_FAILURE;
221 if (aRequest->IsTextSource() && ScriptLoader::ShouldCacheBytecode(aRequest)) {
222 if (!JS::StartIncrementalEncoding(aCx, std::move(stencil))) {
223 return NS_ERROR_FAILURE;
227 return NS_OK;
230 /* static */
231 already_AddRefed<ModuleLoadRequest> ModuleLoader::CreateTopLevel(
232 nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
233 const SRIMetadata& aIntegrity, nsIURI* aReferrer, ScriptLoader* aLoader,
234 ScriptLoadContext* aContext) {
235 RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
236 aURI, aFetchOptions, aIntegrity, aReferrer, aContext, true,
237 /* is top level */ false, /* is dynamic import */
238 aLoader->GetModuleLoader(),
239 ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI), nullptr);
241 return request.forget();
244 already_AddRefed<ModuleLoadRequest> ModuleLoader::CreateStaticImport(
245 nsIURI* aURI, ModuleLoadRequest* aParent) {
246 RefPtr<ScriptLoadContext> newContext = new ScriptLoadContext();
247 newContext->mIsInline = false;
248 // Propagated Parent values. TODO: allow child modules to use root module's
249 // script mode.
250 newContext->mScriptMode = aParent->GetScriptLoadContext()->mScriptMode;
252 RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
253 aURI, aParent->mFetchOptions, SRIMetadata(), aParent->mURI, newContext,
254 false, /* is top level */
255 false, /* is dynamic import */
256 aParent->mLoader, aParent->mVisitedSet, aParent->GetRootModule());
258 return request.forget();
261 already_AddRefed<ModuleLoadRequest> ModuleLoader::CreateDynamicImport(
262 JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
263 JS::Handle<JS::Value> aReferencingPrivate, JS::Handle<JSString*> aSpecifier,
264 JS::Handle<JSObject*> aPromise) {
265 MOZ_ASSERT(aSpecifier);
266 MOZ_ASSERT(aPromise);
268 RefPtr<ScriptFetchOptions> options = nullptr;
269 nsIURI* baseURL = nullptr;
270 RefPtr<ScriptLoadContext> context = new ScriptLoadContext();
272 if (aMaybeActiveScript) {
273 // https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule
274 // Step 6.3. Set fetchOptions to the new descendant script fetch options for
275 // referencingScript's fetch options.
276 options = aMaybeActiveScript->GetFetchOptions();
277 baseURL = aMaybeActiveScript->BaseURL();
278 } else {
279 // We don't have a referencing script so fall back on using
280 // options from the document. This can happen when the user
281 // triggers an inline event handler, as there is no active script
282 // there.
283 Document* document = GetScriptLoader()->GetDocument();
285 nsCOMPtr<nsIPrincipal> principal = GetGlobalObject()->PrincipalOrNull();
286 MOZ_ASSERT_IF(GetKind() == WebExtension,
287 BasePrincipal::Cast(principal)->ContentScriptAddonPolicy());
288 MOZ_ASSERT_IF(GetKind() == Normal, principal == document->NodePrincipal());
290 // https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule
291 // Step 4. Let fetchOptions be the default classic script fetch options.
293 // https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options
294 // The default classic script fetch options are a script fetch options whose
295 // cryptographic nonce is the empty string, integrity metadata is the empty
296 // string, parser metadata is "not-parser-inserted", credentials mode is
297 // "same-origin", referrer policy is the empty string, and fetch priority is
298 // "auto".
299 options = new ScriptFetchOptions(
300 mozilla::CORS_NONE, document->GetReferrerPolicy(),
301 /* aNonce = */ u""_ns, ParserMetadata::NotParserInserted, principal,
302 nullptr);
303 baseURL = document->GetDocBaseURI();
306 context->mIsInline = false;
307 context->mScriptMode = ScriptLoadContext::ScriptMode::eAsync;
309 RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
310 aURI, options, SRIMetadata(), baseURL, context, true,
311 /* is top level */ true, /* is dynamic import */
312 this, ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI), nullptr);
314 request->mDynamicReferencingPrivate = aReferencingPrivate;
315 request->mDynamicSpecifier = aSpecifier;
316 request->mDynamicPromise = aPromise;
318 HoldJSObjects(request.get());
320 return request.forget();
323 ModuleLoader::~ModuleLoader() {
324 LOG(("ModuleLoader::~ModuleLoader %p", this));
325 mLoader = nullptr;
328 #undef LOG
329 #undef LOG_ENABLED
331 } // namespace mozilla::dom