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/OffThreadScriptCompilation.h"
17 #include "js/PropertyAndElement.h" // JS_DefineProperty
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"
37 using namespace JS::loader
;
39 namespace mozilla::dom
{
43 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
45 #define LOG_ENABLED() \
46 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
48 //////////////////////////////////////////////////////////////
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
;
66 // If this document is sandboxed without 'allow-scripts', abort.
67 if (GetScriptLoader()->GetDocument()->HasScriptsBlockedBySandbox()) {
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
;
83 aRequest
->mURI
->GetAsciiSpec(url
);
84 LOG(("ScriptLoadRequest (%p): Start Module Load (url = %s)", aRequest
,
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(),
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`
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
));
130 void ModuleLoader::OnModuleLoadComplete(ModuleLoadRequest
* aRequest
) {
131 MOZ_ASSERT(aRequest
->IsFinished());
133 if (aRequest
->IsTopLevel()) {
134 if (aRequest
->GetScriptLoadContext()->mIsInline
&&
135 aRequest
->GetScriptLoadContext()->GetParserCreated() ==
137 GetScriptLoader()->RunScriptWhenSafe(aRequest
);
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;
158 return NS_ERROR_FAILURE
;
161 JS::InstantiateOptions
instantiateOptions(aOptions
);
162 aModuleOut
.set(JS::InstantiateModuleStencil(aCx
, instantiateOptions
,
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
;
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
);
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
;
211 return NS_ERROR_FAILURE
;
214 JS::InstantiateOptions
instantiateOptions(aOptions
);
216 JS::InstantiateModuleStencil(aCx
, instantiateOptions
, stencil
));
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
;
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
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();
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
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
299 options
= new ScriptFetchOptions(
300 mozilla::CORS_NONE
, document
->GetReferrerPolicy(),
301 /* aNonce = */ u
""_ns
, ParserMetadata::NotParserInserted
, principal
,
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));
331 } // namespace mozilla::dom