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"
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/PropertyAndElement.h" // JS_DefineProperty
18 #include "js/SourceText.h"
19 #include "js/loader/LoadedScript.h"
20 #include "js/loader/ScriptLoadRequest.h"
21 #include "js/loader/ModuleLoaderBase.h"
22 #include "js/loader/ModuleLoadRequest.h"
23 #include "mozilla/dom/RequestBinding.h"
24 #include "mozilla/Assertions.h"
26 #include "xpcpublic.h"
27 #include "GeckoProfiler.h"
28 #include "nsContentSecurityManager.h"
29 #include "nsIContent.h"
30 #include "nsJSUtils.h"
31 #include "mozilla/CycleCollectedJSContext.h"
32 #include "mozilla/dom/AutoEntryScript.h"
33 #include "mozilla/dom/Document.h"
34 #include "mozilla/dom/Element.h"
35 #include "nsIPrincipal.h"
36 #include "mozilla/LoadInfo.h"
37 #include "mozilla/Maybe.h"
40 using namespace JS::loader
;
42 namespace mozilla::dom
{
46 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
48 #define LOG_ENABLED() \
49 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
51 //////////////////////////////////////////////////////////////
53 //////////////////////////////////////////////////////////////
55 ModuleLoader::ModuleLoader(ScriptLoader
* aLoader
,
56 nsIGlobalObject
* aGlobalObject
, Kind aKind
)
57 : ModuleLoaderBase(aLoader
, aGlobalObject
), mKind(aKind
) {}
59 ScriptLoader
* ModuleLoader::GetScriptLoader() {
60 return static_cast<ScriptLoader
*>(mLoader
.get());
63 bool ModuleLoader::CanStartLoad(ModuleLoadRequest
* aRequest
, nsresult
* aRvOut
) {
64 if (!GetScriptLoader()->GetDocument()) {
65 *aRvOut
= NS_ERROR_NULL_POINTER
;
69 // If this document is sandboxed without 'allow-scripts', abort.
70 if (GetScriptLoader()->GetDocument()->HasScriptsBlockedBySandbox()) {
75 // To prevent dynamic code execution, content scripts can only
76 // load moz-extension URLs.
77 nsCOMPtr
<nsIPrincipal
> principal
= aRequest
->TriggeringPrincipal();
78 if (BasePrincipal::Cast(principal
)->ContentScriptAddonPolicy() &&
79 !aRequest
->mURI
->SchemeIs("moz-extension")) {
80 *aRvOut
= NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI
;
86 aRequest
->mURI
->GetAsciiSpec(url
);
87 LOG(("ScriptLoadRequest (%p): Start Module Load (url = %s)", aRequest
,
94 nsresult
ModuleLoader::StartFetch(ModuleLoadRequest
* aRequest
) {
95 // According to the spec, module scripts have different behaviour to classic
96 // scripts and always use CORS. Only exception: Non linkable about: pages
97 // which load local module scripts.
98 bool isAboutPageLoadingChromeURI
= ScriptLoader::IsAboutPageLoadingChromeURI(
99 aRequest
, GetScriptLoader()->GetDocument());
101 nsContentSecurityManager::CORSSecurityMapping corsMapping
=
102 isAboutPageLoadingChromeURI
103 ? nsContentSecurityManager::CORSSecurityMapping::DISABLE_CORS_CHECKS
104 : nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS
;
106 nsSecurityFlags securityFlags
=
107 nsContentSecurityManager::ComputeSecurityFlags(aRequest
->CORSMode(),
110 securityFlags
|= nsILoadInfo::SEC_ALLOW_CHROME
;
112 // Delegate Shared Behavior to base ScriptLoader
114 // aCharsetForPreload is passed as Nothing() because this is not a preload
115 // and `StartLoadInternal` is able to find the charset by using `aRequest`
117 nsresult rv
= GetScriptLoader()->StartLoadInternal(
118 aRequest
, securityFlags
, Nothing() /* aCharsetForPreload */);
119 NS_ENSURE_SUCCESS(rv
, rv
);
121 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-import()-module-script-graph
122 // Step 1. Disallow further import maps given settings object.
123 if (!aRequest
->GetScriptLoadContext()->IsPreload()) {
124 LOG(("ScriptLoadRequest (%p): Disallow further import maps.", aRequest
));
125 DisallowImportMaps();
128 LOG(("ScriptLoadRequest (%p): Start fetching module", aRequest
));
133 void ModuleLoader::AsyncExecuteInlineModule(ModuleLoadRequest
* aRequest
) {
134 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
135 mozilla::NewRunnableMethod
<RefPtr
<ModuleLoadRequest
>>(
136 "ModuleLoader::ExecuteInlineModule", this,
137 &ModuleLoader::ExecuteInlineModule
, aRequest
)));
140 void ModuleLoader::ExecuteInlineModule(ModuleLoadRequest
* aRequest
) {
141 MOZ_ASSERT(aRequest
->IsFinished());
142 MOZ_ASSERT(aRequest
->IsTopLevel());
143 MOZ_ASSERT(aRequest
->GetScriptLoadContext()->mIsInline
);
145 if (aRequest
->GetScriptLoadContext()->GetParserCreated() == NOT_FROM_PARSER
) {
146 GetScriptLoader()->RunScriptWhenSafe(aRequest
);
148 GetScriptLoader()->MaybeMoveToLoadedList(aRequest
);
149 GetScriptLoader()->ProcessPendingRequests();
152 aRequest
->GetScriptLoadContext()->MaybeUnblockOnload();
155 void ModuleLoader::OnModuleLoadComplete(ModuleLoadRequest
* aRequest
) {
156 MOZ_ASSERT(aRequest
->IsFinished());
158 if (aRequest
->IsTopLevel()) {
159 if (aRequest
->GetScriptLoadContext()->mIsInline
&&
160 aRequest
->GetScriptLoadContext()->GetParserCreated() ==
162 if (aRequest
->mImports
.Length() == 0) {
163 GetScriptLoader()->RunScriptWhenSafe(aRequest
);
165 AsyncExecuteInlineModule(aRequest
);
168 } else if (aRequest
->GetScriptLoadContext()->mIsInline
&&
169 aRequest
->GetScriptLoadContext()->GetParserCreated() !=
171 !nsContentUtils::IsSafeToRunScript()) {
172 // Avoid giving inline async module scripts that don't have
173 // external dependencies a guaranteed execution time relative
174 // to the HTML parse. That is, deliberately avoid guaranteeing
175 // that the script would always observe a DOM shape where the
176 // parser has not added further elements to the DOM.
177 // (If `nsContentUtils::IsSafeToRunScript()` returns `true`,
178 // we come here synchronously from the parser. If it returns
179 // `false` we come here from an external dependency completing
180 // its fetch, in which case we already are at an unspecific
181 // point relative to the parse.)
182 AsyncExecuteInlineModule(aRequest
);
185 GetScriptLoader()->MaybeMoveToLoadedList(aRequest
);
186 GetScriptLoader()->ProcessPendingRequestsAsync();
190 aRequest
->GetScriptLoadContext()->MaybeUnblockOnload();
193 nsresult
ModuleLoader::CompileFetchedModule(
194 JSContext
* aCx
, JS::Handle
<JSObject
*> aGlobal
, JS::CompileOptions
& aOptions
,
195 ModuleLoadRequest
* aRequest
, JS::MutableHandle
<JSObject
*> aModuleOut
) {
196 if (aRequest
->GetScriptLoadContext()->mWasCompiledOMT
) {
197 JS::InstantiationStorage storage
;
198 RefPtr
<JS::Stencil
> stencil
=
199 aRequest
->GetScriptLoadContext()->StealOffThreadResult(aCx
, &storage
);
201 return NS_ERROR_FAILURE
;
204 JS::InstantiateOptions
instantiateOptions(aOptions
);
205 aModuleOut
.set(JS::InstantiateModuleStencil(aCx
, instantiateOptions
,
208 return NS_ERROR_FAILURE
;
211 if (aRequest
->IsTextSource() &&
212 ScriptLoader::ShouldCacheBytecode(aRequest
)) {
213 if (!JS::StartIncrementalEncoding(aCx
, std::move(stencil
))) {
214 return NS_ERROR_FAILURE
;
221 if (!nsJSUtils::IsScriptable(aGlobal
)) {
222 return NS_ERROR_FAILURE
;
225 RefPtr
<JS::Stencil
> stencil
;
226 if (aRequest
->IsTextSource()) {
227 MaybeSourceText maybeSource
;
228 nsresult rv
= aRequest
->GetScriptSource(aCx
, &maybeSource
,
229 aRequest
->mLoadContext
.get());
230 NS_ENSURE_SUCCESS(rv
, rv
);
232 auto compile
= [&](auto& source
) {
233 return JS::CompileModuleScriptToStencil(aCx
, aOptions
, source
);
235 stencil
= maybeSource
.mapNonEmpty(compile
);
237 MOZ_ASSERT(aRequest
->IsBytecode());
238 JS::DecodeOptions
decodeOptions(aOptions
);
239 decodeOptions
.borrowBuffer
= true;
241 JS::TranscodeRange range
= aRequest
->Bytecode();
242 JS::TranscodeResult tr
=
243 JS::DecodeStencil(aCx
, decodeOptions
, range
, getter_AddRefs(stencil
));
244 if (tr
!= JS::TranscodeResult::Ok
) {
245 return NS_ERROR_DOM_JS_DECODING_ERROR
;
250 return NS_ERROR_FAILURE
;
253 JS::InstantiateOptions
instantiateOptions(aOptions
);
255 JS::InstantiateModuleStencil(aCx
, instantiateOptions
, stencil
));
257 return NS_ERROR_FAILURE
;
260 if (aRequest
->IsTextSource() && ScriptLoader::ShouldCacheBytecode(aRequest
)) {
261 if (!JS::StartIncrementalEncoding(aCx
, std::move(stencil
))) {
262 return NS_ERROR_FAILURE
;
270 already_AddRefed
<ModuleLoadRequest
> ModuleLoader::CreateTopLevel(
271 nsIURI
* aURI
, ReferrerPolicy aReferrerPolicy
,
272 ScriptFetchOptions
* aFetchOptions
, const SRIMetadata
& aIntegrity
,
273 nsIURI
* aReferrer
, ScriptLoader
* aLoader
, ScriptLoadContext
* aContext
) {
274 RefPtr
<ModuleLoadRequest
> request
= new ModuleLoadRequest(
275 aURI
, aReferrerPolicy
, aFetchOptions
, aIntegrity
, aReferrer
, aContext
,
277 /* is top level */ false, /* is dynamic import */
278 aLoader
->GetModuleLoader(),
279 ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI
), nullptr);
281 request
->NoCacheEntryFound();
282 return request
.forget();
285 already_AddRefed
<ModuleLoadRequest
> ModuleLoader::CreateStaticImport(
286 nsIURI
* aURI
, ModuleLoadRequest
* aParent
) {
287 RefPtr
<ScriptLoadContext
> newContext
= new ScriptLoadContext();
288 newContext
->mIsInline
= false;
289 // Propagated Parent values. TODO: allow child modules to use root module's
291 newContext
->mScriptMode
= aParent
->GetScriptLoadContext()->mScriptMode
;
293 RefPtr
<ModuleLoadRequest
> request
= new ModuleLoadRequest(
294 aURI
, aParent
->ReferrerPolicy(), aParent
->mFetchOptions
, SRIMetadata(),
295 aParent
->mURI
, newContext
, false, /* is top level */
296 false, /* is dynamic import */
297 aParent
->mLoader
, aParent
->mVisitedSet
, aParent
->GetRootModule());
299 request
->NoCacheEntryFound();
300 return request
.forget();
303 already_AddRefed
<ModuleLoadRequest
> ModuleLoader::CreateDynamicImport(
304 JSContext
* aCx
, nsIURI
* aURI
, LoadedScript
* aMaybeActiveScript
,
305 JS::Handle
<JSString
*> aSpecifier
, JS::Handle
<JSObject
*> aPromise
) {
306 MOZ_ASSERT(aSpecifier
);
307 MOZ_ASSERT(aPromise
);
309 RefPtr
<ScriptFetchOptions
> options
= nullptr;
310 nsIURI
* baseURL
= nullptr;
311 RefPtr
<ScriptLoadContext
> context
= new ScriptLoadContext();
312 ReferrerPolicy referrerPolicy
;
314 if (aMaybeActiveScript
) {
315 // https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule
316 // Step 6.3. Set fetchOptions to the new descendant script fetch options for
317 // referencingScript's fetch options.
318 options
= aMaybeActiveScript
->GetFetchOptions();
319 referrerPolicy
= aMaybeActiveScript
->ReferrerPolicy();
320 baseURL
= aMaybeActiveScript
->BaseURL();
322 // We don't have a referencing script so fall back on using
323 // options from the document. This can happen when the user
324 // triggers an inline event handler, as there is no active script
326 Document
* document
= GetScriptLoader()->GetDocument();
328 nsCOMPtr
<nsIPrincipal
> principal
= GetGlobalObject()->PrincipalOrNull();
329 MOZ_ASSERT_IF(GetKind() == WebExtension
,
330 BasePrincipal::Cast(principal
)->ContentScriptAddonPolicy());
331 MOZ_ASSERT_IF(GetKind() == Normal
, principal
== document
->NodePrincipal());
333 // https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule
334 // Step 4. Let fetchOptions be the default classic script fetch options.
336 // https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options
337 // The default classic script fetch options are a script fetch options whose
338 // cryptographic nonce is the empty string, integrity metadata is the empty
339 // string, parser metadata is "not-parser-inserted", credentials mode is
340 // "same-origin", referrer policy is the empty string, and fetch priority is
342 options
= new ScriptFetchOptions(
343 mozilla::CORS_NONE
, /* aNonce = */ u
""_ns
, RequestPriority::Auto
,
344 ParserMetadata::NotParserInserted
, principal
, nullptr);
345 referrerPolicy
= document
->GetReferrerPolicy();
346 baseURL
= document
->GetDocBaseURI();
349 context
->mIsInline
= false;
350 context
->mScriptMode
= ScriptLoadContext::ScriptMode::eAsync
;
352 RefPtr
<ModuleLoadRequest
> request
= new ModuleLoadRequest(
353 aURI
, referrerPolicy
, options
, SRIMetadata(), baseURL
, context
, true,
354 /* is top level */ true, /* is dynamic import */
355 this, ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI
), nullptr);
357 request
->SetDynamicImport(aMaybeActiveScript
, aSpecifier
, aPromise
);
358 request
->NoCacheEntryFound();
360 return request
.forget();
363 ModuleLoader::~ModuleLoader() {
364 LOG(("ModuleLoader::~ModuleLoader %p", this));
371 } // namespace mozilla::dom