Bug 1879816: Make nsCocoaWindow::Destroy close the window and let destruction happen...
[gecko.git] / dom / script / ModuleLoader.cpp
blob5d6b90cbf0fb74022d1508693ca8bb7bfa35767b
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/PropertyAndElement.h" // JS_DefineProperty
17 #include "js/Realm.h"
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"
25 #include "nsError.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"
39 using JS::SourceText;
40 using namespace JS::loader;
42 namespace mozilla::dom {
44 #undef LOG
45 #define LOG(args) \
46 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
48 #define LOG_ENABLED() \
49 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
51 //////////////////////////////////////////////////////////////
52 // DOM module loader
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;
66 return false;
69 // If this document is sandboxed without 'allow-scripts', abort.
70 if (GetScriptLoader()->GetDocument()->HasScriptsBlockedBySandbox()) {
71 *aRvOut = NS_OK;
72 return false;
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;
81 return false;
84 if (LOG_ENABLED()) {
85 nsAutoCString url;
86 aRequest->mURI->GetAsciiSpec(url);
87 LOG(("ScriptLoadRequest (%p): Start Module Load (url = %s)", aRequest,
88 url.get()));
91 return true;
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(),
108 corsMapping);
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`
116 // for this case.
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));
130 return NS_OK;
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);
147 } else {
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() ==
161 NOT_FROM_PARSER) {
162 if (aRequest->mImports.Length() == 0) {
163 GetScriptLoader()->RunScriptWhenSafe(aRequest);
164 } else {
165 AsyncExecuteInlineModule(aRequest);
166 return;
168 } else if (aRequest->GetScriptLoadContext()->mIsInline &&
169 aRequest->GetScriptLoadContext()->GetParserCreated() !=
170 NOT_FROM_PARSER &&
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);
183 return;
184 } else {
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);
200 if (!stencil) {
201 return NS_ERROR_FAILURE;
204 JS::InstantiateOptions instantiateOptions(aOptions);
205 aModuleOut.set(JS::InstantiateModuleStencil(aCx, instantiateOptions,
206 stencil, &storage));
207 if (!aModuleOut) {
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;
218 return NS_OK;
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);
236 } else {
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;
249 if (!stencil) {
250 return NS_ERROR_FAILURE;
253 JS::InstantiateOptions instantiateOptions(aOptions);
254 aModuleOut.set(
255 JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil));
256 if (!aModuleOut) {
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;
266 return NS_OK;
269 /* static */
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,
276 true,
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
290 // script mode.
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();
321 } else {
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
325 // there.
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
341 // "auto".
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));
365 mLoader = nullptr;
368 #undef LOG
369 #undef LOG_ENABLED
371 } // namespace mozilla::dom