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/. */
6
7 #include "GeckoProfiler.h"
8 #include "LoadedScript.h"
9 #include "ModuleLoadRequest.h"
10 #include "ScriptLoadRequest.h"
11 #include "mozilla/dom/ScriptTrace.h"
12
13 #include "js/Array.h" // JS::GetArrayLength
14 #include "js/CompilationAndEvaluation.h"
15 #include "js/ContextOptions.h" // JS::ContextOptionsRef
16 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
17 #include "js/Modules.h" // JS::FinishDynamicModuleImport, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{DynamicImport,Metadata}Hook
18 #include "js/OffThreadScriptCompilation.h"
19 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetElement
20 #include "js/SourceText.h"
21 #include "mozilla/dom/AutoEntryScript.h"
22 #include "mozilla/CycleCollectedJSContext.h" // nsAutoMicroTask
23 #include "nsContentUtils.h"
24 #include "nsICacheInfoChannel.h" //nsICacheInfoChannel
25 #include "nsNetUtil.h" // NS_NewURI
26
27 using JS::SourceText;
28
29 namespace JS::loader {
30
31 mozilla::LazyLogModule ModuleLoaderBase::gCspPRLog("CSP");
32 mozilla::LazyLogModule ModuleLoaderBase::gModuleLoaderBaseLog(
33 "ModuleLoaderBase");
34
35 #undef LOG
36 #define LOG(args) \
37 MOZ_LOG(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug, \
38 args)
39
40 #define LOG_ENABLED() \
41 MOZ_LOG_TEST(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug)
42
43 //////////////////////////////////////////////////////////////
44 // ModuleLoaderBase::mFetchingModules / ModuleLoaderBase::mFetchingModules
45 //////////////////////////////////////////////////////////////
46
ImplCycleCollectionUnlink(nsRefPtrHashtable<ModuleMapKey,mozilla::GenericNonExclusivePromise::Private> & aField)47 inline void ImplCycleCollectionUnlink(
48 nsRefPtrHashtable<ModuleMapKey,
49 mozilla::GenericNonExclusivePromise::Private>& aField) {
50 for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) {
51 ImplCycleCollectionUnlink(iter.Key());
52
53 RefPtr<mozilla::GenericNonExclusivePromise::Private> promise =
54 iter.UserData();
55 if (promise) {
56 promise->Reject(NS_ERROR_ABORT, __func__);
57 }
58 }
59
60 aField.Clear();
61 }
62
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback & aCallback,nsRefPtrHashtable<ModuleMapKey,mozilla::GenericNonExclusivePromise::Private> & aField,const char * aName,uint32_t aFlags=0)63 inline void ImplCycleCollectionTraverse(
64 nsCycleCollectionTraversalCallback& aCallback,
65 nsRefPtrHashtable<ModuleMapKey,
66 mozilla::GenericNonExclusivePromise::Private>& aField,
67 const char* aName, uint32_t aFlags = 0) {
68 for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) {
69 ImplCycleCollectionTraverse(aCallback, iter.Key(), "mFetchingModules key",
70 aFlags);
71 }
72 }
73
ImplCycleCollectionUnlink(nsRefPtrHashtable<ModuleMapKey,ModuleScript> & aField)74 inline void ImplCycleCollectionUnlink(
75 nsRefPtrHashtable<ModuleMapKey, ModuleScript>& aField) {
76 for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) {
77 ImplCycleCollectionUnlink(iter.Key());
78 }
79
80 aField.Clear();
81 }
82
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback & aCallback,nsRefPtrHashtable<ModuleMapKey,ModuleScript> & aField,const char * aName,uint32_t aFlags=0)83 inline void ImplCycleCollectionTraverse(
84 nsCycleCollectionTraversalCallback& aCallback,
85 nsRefPtrHashtable<ModuleMapKey, ModuleScript>& aField, const char* aName,
86 uint32_t aFlags = 0) {
87 for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) {
88 ImplCycleCollectionTraverse(aCallback, iter.Key(), "mFetchedModules key",
89 aFlags);
90 CycleCollectionNoteChild(aCallback, iter.UserData(), "mFetchedModules data",
91 aFlags);
92 }
93 }
94
95 //////////////////////////////////////////////////////////////
96 // ModuleLoaderBase
97 //////////////////////////////////////////////////////////////
98
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase)99 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase)
100 NS_INTERFACE_MAP_END
101
102 NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase, mFetchingModules, mFetchedModules,
103 mDynamicImportRequests, mLoader)
104
105 NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase)
106 NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase)
107
108 bool ModuleLoaderBase::ModuleMapContainsURL(nsIURI* aURL,
109 nsIGlobalObject* aGlobal) const {
110 // Returns whether we have fetched, or are currently fetching, a module script
111 // for a URL.
112 ModuleMapKey key(aURL, aGlobal);
113 return mFetchingModules.Contains(key) || mFetchedModules.Contains(key);
114 }
115
SetModuleFetchStarted(ModuleLoadRequest * aRequest)116 void ModuleLoaderBase::SetModuleFetchStarted(ModuleLoadRequest* aRequest) {
117 // Update the module map to indicate that a module is currently being fetched.
118
119 MOZ_ASSERT(aRequest->IsLoading());
120 MOZ_ASSERT(!ModuleMapContainsURL(aRequest->mURI,
121 aRequest->mLoadContext->GetWebExtGlobal()));
122 ModuleMapKey key(aRequest->mURI, aRequest->mLoadContext->GetWebExtGlobal());
123 mFetchingModules.InsertOrUpdate(
124 key, RefPtr<mozilla::GenericNonExclusivePromise::Private>{});
125 }
126
SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest * aRequest,nsresult aResult)127 void ModuleLoaderBase::SetModuleFetchFinishedAndResumeWaitingRequests(
128 ModuleLoadRequest* aRequest, nsresult aResult) {
129 // Update module map with the result of fetching a single module script.
130 //
131 // If any requests for the same URL are waiting on this one to complete, they
132 // will have ModuleLoaded or LoadFailed on them when the promise is
133 // resolved/rejected. This is set up in StartLoad.
134
135 LOG(
136 ("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == "
137 "%u)",
138 aRequest, aRequest->mModuleScript.get(), unsigned(aResult)));
139
140 ModuleMapKey key(aRequest->mURI, aRequest->mLoadContext->GetWebExtGlobal());
141 RefPtr<mozilla::GenericNonExclusivePromise::Private> promise;
142 if (!mFetchingModules.Remove(key, getter_AddRefs(promise))) {
143 LOG(
144 ("ScriptLoadRequest (%p): Key not found in mFetchingModules, "
145 "assuming we have an inline module or have finished fetching already",
146 aRequest));
147 return;
148 }
149
150 RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
151 MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);
152
153 mFetchedModules.InsertOrUpdate(key, RefPtr{moduleScript});
154
155 if (promise) {
156 if (moduleScript) {
157 LOG(("ScriptLoadRequest (%p): resolving %p", aRequest, promise.get()));
158 promise->Resolve(true, __func__);
159 } else {
160 LOG(("ScriptLoadRequest (%p): rejecting %p", aRequest, promise.get()));
161 promise->Reject(aResult, __func__);
162 }
163 }
164 }
165
166 RefPtr<mozilla::GenericNonExclusivePromise>
WaitForModuleFetch(nsIURI * aURL,nsIGlobalObject * aGlobal)167 ModuleLoaderBase::WaitForModuleFetch(nsIURI* aURL, nsIGlobalObject* aGlobal) {
168 MOZ_ASSERT(ModuleMapContainsURL(aURL, aGlobal));
169
170 ModuleMapKey key(aURL, aGlobal);
171 if (auto entry = mFetchingModules.Lookup(key)) {
172 if (!entry.Data()) {
173 entry.Data() = new mozilla::GenericNonExclusivePromise::Private(__func__);
174 }
175 return entry.Data();
176 }
177
178 RefPtr<ModuleScript> ms;
179 MOZ_ALWAYS_TRUE(mFetchedModules.Get(key, getter_AddRefs(ms)));
180 if (!ms) {
181 return mozilla::GenericNonExclusivePromise::CreateAndReject(
182 NS_ERROR_FAILURE, __func__);
183 }
184
185 return mozilla::GenericNonExclusivePromise::CreateAndResolve(true, __func__);
186 }
187
GetFetchedModule(nsIURI * aURL,nsIGlobalObject * aGlobal) const188 ModuleScript* ModuleLoaderBase::GetFetchedModule(
189 nsIURI* aURL, nsIGlobalObject* aGlobal) const {
190 if (LOG_ENABLED()) {
191 nsAutoCString url;
192 aURL->GetAsciiSpec(url);
193 LOG(("GetFetchedModule %s %p", url.get(), aGlobal));
194 }
195
196 bool found;
197 ModuleMapKey key(aURL, aGlobal);
198 ModuleScript* ms = mFetchedModules.GetWeak(key, &found);
199 MOZ_ASSERT(found);
200 return ms;
201 }
202
ProcessFetchedModuleSource(ModuleLoadRequest * aRequest)203 nsresult ModuleLoaderBase::ProcessFetchedModuleSource(
204 ModuleLoadRequest* aRequest) {
205 MOZ_ASSERT(!aRequest->mModuleScript);
206
207 nsresult rv = CreateModuleScript(aRequest);
208 MOZ_ASSERT(NS_FAILED(rv) == !aRequest->mModuleScript);
209
210 aRequest->ClearScriptSource();
211
212 if (NS_FAILED(rv)) {
213 aRequest->LoadFailed();
214 return rv;
215 }
216
217 SetModuleFetchFinishedAndResumeWaitingRequests(aRequest, rv);
218
219 if (!aRequest->mModuleScript->HasParseError()) {
220 StartFetchingModuleDependencies(aRequest);
221 }
222
223 return NS_OK;
224 }
225
CreateModuleScript(ModuleLoadRequest * aRequest)226 nsresult ModuleLoaderBase::CreateModuleScript(ModuleLoadRequest* aRequest) {
227 MOZ_ASSERT(!aRequest->mModuleScript);
228 MOZ_ASSERT(aRequest->mBaseURL);
229
230 LOG(("ScriptLoadRequest (%p): Create module script", aRequest));
231
232 nsCOMPtr<nsIGlobalObject> globalObject =
233 mLoader->GetGlobalForRequest(aRequest);
234 if (!globalObject) {
235 return NS_ERROR_FAILURE;
236 }
237
238 mozilla::nsAutoMicroTask mt;
239
240 mozilla::AutoAllowLegacyScriptExecution exemption;
241
242 mozilla::dom::AutoEntryScript aes(globalObject, "CompileModule", true);
243
244 nsresult rv;
245 {
246 JSContext* cx = aes.cx();
247 JS::Rooted<JSObject*> module(cx);
248 JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
249
250 JS::CompileOptions options(cx);
251 JS::RootedScript introductionScript(cx);
252 rv = mLoader->FillCompileOptionsForRequest(cx, aRequest, global, &options,
253 &introductionScript);
254
255 if (NS_SUCCEEDED(rv)) {
256 rv = CompileOrFinishModuleScript(cx, global, options, aRequest, &module);
257 }
258
259 MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
260
261 if (module) {
262 JS::RootedValue privateValue(cx);
263 JS::RootedScript moduleScript(cx, JS::GetModuleScript(module));
264 JS::InstantiateOptions instantiateOptions(options);
265 if (!JS::UpdateDebugMetadata(cx, moduleScript, instantiateOptions,
266 privateValue, nullptr, introductionScript,
267 nullptr)) {
268 return NS_ERROR_OUT_OF_MEMORY;
269 }
270 }
271
272 RefPtr<ModuleScript> moduleScript =
273 new ModuleScript(aRequest->mFetchOptions, aRequest->mBaseURL,
274 aRequest->mLoadContext->mElement);
275 aRequest->mModuleScript = moduleScript;
276
277 if (!module) {
278 LOG(("ScriptLoadRequest (%p): compilation failed (%d)", aRequest,
279 unsigned(rv)));
280
281 MOZ_ASSERT(aes.HasException());
282 JS::Rooted<JS::Value> error(cx);
283 if (!aes.StealException(&error)) {
284 aRequest->mModuleScript = nullptr;
285 return NS_ERROR_FAILURE;
286 }
287
288 moduleScript->SetParseError(error);
289 aRequest->ModuleErrored();
290 return NS_OK;
291 }
292
293 moduleScript->SetModuleRecord(module);
294
295 // Validate requested modules and treat failure to resolve module specifiers
296 // the same as a parse error.
297 rv = ResolveRequestedModules(aRequest, nullptr);
298 if (NS_FAILED(rv)) {
299 aRequest->ModuleErrored();
300 return NS_OK;
301 }
302 }
303
304 LOG(("ScriptLoadRequest (%p): module script == %p", aRequest,
305 aRequest->mModuleScript.get()));
306
307 return rv;
308 }
309
HandleResolveFailure(JSContext * aCx,LoadedScript * aScript,const nsAString & aSpecifier,uint32_t aLineNumber,uint32_t aColumnNumber,JS::MutableHandle<JS::Value> errorOut)310 nsresult ModuleLoaderBase::HandleResolveFailure(
311 JSContext* aCx, LoadedScript* aScript, const nsAString& aSpecifier,
312 uint32_t aLineNumber, uint32_t aColumnNumber,
313 JS::MutableHandle<JS::Value> errorOut) {
314 JS::Rooted<JSString*> filename(aCx);
315 if (aScript) {
316 nsAutoCString url;
317 aScript->BaseURL()->GetAsciiSpec(url);
318 filename = JS_NewStringCopyZ(aCx, url.get());
319 } else {
320 filename = JS_NewStringCopyZ(aCx, "(unknown)");
321 }
322
323 if (!filename) {
324 return NS_ERROR_OUT_OF_MEMORY;
325 }
326
327 AutoTArray<nsString, 1> errorParams;
328 errorParams.AppendElement(aSpecifier);
329
330 nsAutoString errorText;
331 nsresult rv = nsContentUtils::FormatLocalizedString(
332 nsContentUtils::eDOM_PROPERTIES, "ModuleResolveFailure", errorParams,
333 errorText);
334 NS_ENSURE_SUCCESS(rv, rv);
335
336 JS::Rooted<JSString*> string(aCx, JS_NewUCStringCopyZ(aCx, errorText.get()));
337 if (!string) {
338 return NS_ERROR_OUT_OF_MEMORY;
339 }
340
341 if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber,
342 aColumnNumber, nullptr, string, JS::NothingHandleValue,
343 errorOut)) {
344 return NS_ERROR_OUT_OF_MEMORY;
345 }
346
347 return NS_OK;
348 }
349
ResolveModuleSpecifier(ScriptLoaderInterface * aLoader,LoadedScript * aScript,const nsAString & aSpecifier)350 already_AddRefed<nsIURI> ModuleLoaderBase::ResolveModuleSpecifier(
351 ScriptLoaderInterface* aLoader, LoadedScript* aScript,
352 const nsAString& aSpecifier) {
353 // The following module specifiers are allowed by the spec:
354 // - a valid absolute URL
355 // - a valid relative URL that starts with "/", "./" or "../"
356 //
357 // Bareword module specifiers are currently disallowed as these may be given
358 // special meanings in the future.
359
360 nsCOMPtr<nsIURI> uri;
361 nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
362 if (NS_SUCCEEDED(rv)) {
363 return uri.forget();
364 }
365
366 if (rv != NS_ERROR_MALFORMED_URI) {
367 return nullptr;
368 }
369
370 if (!StringBeginsWith(aSpecifier, u"/"_ns) &&
371 !StringBeginsWith(aSpecifier, u"./"_ns) &&
372 !StringBeginsWith(aSpecifier, u"../"_ns)) {
373 return nullptr;
374 }
375
376 // Get the document's base URL if we don't have a referencing script here.
377 nsCOMPtr<nsIURI> baseURL;
378 if (aScript) {
379 baseURL = aScript->BaseURL();
380 } else {
381 baseURL = aLoader->GetBaseURI();
382 }
383
384 rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, baseURL);
385 if (NS_SUCCEEDED(rv)) {
386 return uri.forget();
387 }
388
389 return nullptr;
390 }
391
ResolveRequestedModules(ModuleLoadRequest * aRequest,nsCOMArray<nsIURI> * aUrlsOut)392 nsresult ModuleLoaderBase::ResolveRequestedModules(
393 ModuleLoadRequest* aRequest, nsCOMArray<nsIURI>* aUrlsOut) {
394 ModuleScript* ms = aRequest->mModuleScript;
395
396 mozilla::dom::AutoJSAPI jsapi;
397 if (!jsapi.Init(ms->ModuleRecord())) {
398 return NS_ERROR_FAILURE;
399 }
400
401 JSContext* cx = jsapi.cx();
402 JS::Rooted<JSObject*> moduleRecord(cx, ms->ModuleRecord());
403 JS::Rooted<JSObject*> requestedModules(cx);
404 requestedModules = JS::GetRequestedModules(cx, moduleRecord);
405 MOZ_ASSERT(requestedModules);
406
407 uint32_t length;
408 if (!JS::GetArrayLength(cx, requestedModules, &length)) {
409 return NS_ERROR_FAILURE;
410 }
411
412 JS::Rooted<JS::Value> element(cx);
413 for (uint32_t i = 0; i < length; i++) {
414 if (!JS_GetElement(cx, requestedModules, i, &element)) {
415 return NS_ERROR_FAILURE;
416 }
417
418 JS::Rooted<JSString*> str(cx, JS::GetRequestedModuleSpecifier(cx, element));
419 MOZ_ASSERT(str);
420
421 nsAutoJSString specifier;
422 if (!specifier.init(cx, str)) {
423 return NS_ERROR_FAILURE;
424 }
425
426 // Let url be the result of resolving a module specifier given module script
427 // and requested.
428 ModuleLoaderBase* requestModuleLoader = aRequest->mLoader;
429 nsCOMPtr<nsIURI> uri =
430 ResolveModuleSpecifier(requestModuleLoader->mLoader, ms, specifier);
431 if (!uri) {
432 uint32_t lineNumber = 0;
433 uint32_t columnNumber = 0;
434 JS::GetRequestedModuleSourcePos(cx, element, &lineNumber, &columnNumber);
435
436 JS::Rooted<JS::Value> error(cx);
437 nsresult rv = HandleResolveFailure(cx, ms, specifier, lineNumber,
438 columnNumber, &error);
439 NS_ENSURE_SUCCESS(rv, rv);
440
441 ms->SetParseError(error);
442 return NS_ERROR_FAILURE;
443 }
444
445 if (aUrlsOut) {
446 aUrlsOut->AppendElement(uri.forget());
447 }
448 }
449
450 return NS_OK;
451 }
452
StartFetchingModuleDependencies(ModuleLoadRequest * aRequest)453 void ModuleLoaderBase::StartFetchingModuleDependencies(
454 ModuleLoadRequest* aRequest) {
455 LOG(("ScriptLoadRequest (%p): Start fetching module dependencies", aRequest));
456
457 if (aRequest->IsCanceled()) {
458 return;
459 }
460
461 MOZ_ASSERT(aRequest->mModuleScript);
462 MOZ_ASSERT(!aRequest->mModuleScript->HasParseError());
463 MOZ_ASSERT(!aRequest->IsReadyToRun());
464
465 auto visitedSet = aRequest->mVisitedSet;
466 MOZ_ASSERT(visitedSet->Contains(aRequest->mURI));
467
468 aRequest->mProgress = ModuleLoadRequest::Progress::eFetchingImports;
469
470 nsCOMArray<nsIURI> urls;
471 nsresult rv = ResolveRequestedModules(aRequest, &urls);
472 if (NS_FAILED(rv)) {
473 aRequest->mModuleScript = nullptr;
474 aRequest->ModuleErrored();
475 return;
476 }
477
478 // Remove already visited URLs from the list. Put unvisited URLs into the
479 // visited set.
480 int32_t i = 0;
481 while (i < urls.Count()) {
482 nsIURI* url = urls[i];
483 if (visitedSet->Contains(url)) {
484 urls.RemoveObjectAt(i);
485 } else {
486 visitedSet->PutEntry(url);
487 i++;
488 }
489 }
490
491 if (urls.Count() == 0) {
492 // There are no descendants to load so this request is ready.
493 aRequest->DependenciesLoaded();
494 return;
495 }
496
497 // For each url in urls, fetch a module script tree given url, module script's
498 // CORS setting, and module script's settings object.
499 nsTArray<RefPtr<mozilla::GenericPromise>> importsReady;
500 for (auto* url : urls) {
501 RefPtr<mozilla::GenericPromise> childReady =
502 StartFetchingModuleAndDependencies(aRequest, url);
503 importsReady.AppendElement(childReady);
504 }
505
506 // Wait for all imports to become ready.
507 RefPtr<mozilla::GenericPromise::AllPromiseType> allReady =
508 mozilla::GenericPromise::All(mozilla::GetMainThreadSerialEventTarget(),
509 importsReady);
510 allReady->Then(mozilla::GetMainThreadSerialEventTarget(), __func__, aRequest,
511 &ModuleLoadRequest::DependenciesLoaded,
512 &ModuleLoadRequest::ModuleErrored);
513 }
514
515 RefPtr<mozilla::GenericPromise>
StartFetchingModuleAndDependencies(ModuleLoadRequest * aParent,nsIURI * aURI)516 ModuleLoaderBase::StartFetchingModuleAndDependencies(ModuleLoadRequest* aParent,
517 nsIURI* aURI) {
518 MOZ_ASSERT(aURI);
519
520 RefPtr<ModuleLoadRequest> childRequest = CreateStaticImport(aURI, aParent);
521
522 aParent->mImports.AppendElement(childRequest);
523
524 if (LOG_ENABLED()) {
525 nsAutoCString url1;
526 aParent->mURI->GetAsciiSpec(url1);
527
528 nsAutoCString url2;
529 aURI->GetAsciiSpec(url2);
530
531 LOG(("ScriptLoadRequest (%p): Start fetching dependency %p", aParent,
532 childRequest.get()));
533 LOG(("StartFetchingModuleAndDependencies \"%s\" -> \"%s\"", url1.get(),
534 url2.get()));
535 }
536
537 RefPtr<mozilla::GenericPromise> ready = childRequest->mReady.Ensure(__func__);
538
539 nsresult rv = StartModuleLoad(childRequest);
540 if (NS_FAILED(rv)) {
541 MOZ_ASSERT(!childRequest->mModuleScript);
542 LOG(("ScriptLoadRequest (%p): rejecting %p", aParent,
543 &childRequest->mReady));
544
545 mLoader->ReportErrorToConsole(childRequest, rv);
546 childRequest->mReady.Reject(rv, __func__);
547 return ready;
548 }
549
550 return ready;
551 }
552
StartDynamicImport(ModuleLoadRequest * aRequest)553 void ModuleLoaderBase::StartDynamicImport(ModuleLoadRequest* aRequest) {
554 LOG(("ScriptLoadRequest (%p): Start dynamic import", aRequest));
555
556 mDynamicImportRequests.AppendElement(aRequest);
557
558 nsresult rv = StartModuleLoad(aRequest);
559 if (NS_FAILED(rv)) {
560 mLoader->ReportErrorToConsole(aRequest, rv);
561 FinishDynamicImportAndReject(aRequest, rv);
562 }
563 }
564
FinishDynamicImportAndReject(ModuleLoadRequest * aRequest,nsresult aResult)565 void ModuleLoaderBase::FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
566 nsresult aResult) {
567 mozilla::dom::AutoJSAPI jsapi;
568 MOZ_ASSERT(NS_FAILED(aResult));
569 MOZ_ALWAYS_TRUE(jsapi.Init(aRequest->mDynamicPromise));
570 FinishDynamicImport(jsapi.cx(), aRequest, aResult, nullptr);
571 }
572
FinishDynamicImport(JSContext * aCx,ModuleLoadRequest * aRequest,nsresult aResult,JS::Handle<JSObject * > aEvaluationPromise)573 void ModuleLoaderBase::FinishDynamicImport(
574 JSContext* aCx, ModuleLoadRequest* aRequest, nsresult aResult,
575 JS::Handle<JSObject*> aEvaluationPromise) {
576 // If aResult is a failed result, we don't have an EvaluationPromise. If it
577 // succeeded, evaluationPromise may still be null, but in this case it will
578 // be handled by rejecting the dynamic module import promise in the JSAPI.
579 MOZ_ASSERT_IF(NS_FAILED(aResult), !aEvaluationPromise);
580 LOG(("ScriptLoadRequest (%p): Finish dynamic import %x %d", aRequest,
581 unsigned(aResult), JS_IsExceptionPending(aCx)));
582
583 // Complete the dynamic import, report failures indicated by aResult or as a
584 // pending exception on the context.
585
586 if (NS_FAILED(aResult) &&
587 aResult != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
588 MOZ_ASSERT(!JS_IsExceptionPending(aCx));
589 JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
590 JSMSG_DYNAMIC_IMPORT_FAILED);
591 }
592
593 JS::Rooted<JS::Value> referencingScript(aCx,
594 aRequest->mDynamicReferencingPrivate);
595 JS::Rooted<JSString*> specifier(aCx, aRequest->mDynamicSpecifier);
596 JS::Rooted<JSObject*> promise(aCx, aRequest->mDynamicPromise);
597
598 JS::Rooted<JSObject*> moduleRequest(aCx,
599 JS::CreateModuleRequest(aCx, specifier));
600
601 JS::FinishDynamicModuleImport(aCx, aEvaluationPromise, referencingScript,
602 moduleRequest, promise);
603
604 // FinishDynamicModuleImport clears any pending exception.
605 MOZ_ASSERT(!JS_IsExceptionPending(aCx));
606
607 aRequest->ClearDynamicImport();
608 }
609
ModuleLoaderBase(ScriptLoaderInterface * aLoader)610 ModuleLoaderBase::ModuleLoaderBase(ScriptLoaderInterface* aLoader)
611 : mLoader(aLoader) {}
612
~ModuleLoaderBase()613 ModuleLoaderBase::~ModuleLoaderBase() {
614 mDynamicImportRequests.CancelRequestsAndClear();
615
616 LOG(("ModuleLoaderBase::~ModuleLoaderBase %p", this));
617 }
618
FindFirstParseError(ModuleLoadRequest * aRequest)619 JS::Value ModuleLoaderBase::FindFirstParseError(ModuleLoadRequest* aRequest) {
620 MOZ_ASSERT(aRequest);
621
622 ModuleScript* moduleScript = aRequest->mModuleScript;
623 MOZ_ASSERT(moduleScript);
624
625 if (moduleScript->HasParseError()) {
626 return moduleScript->ParseError();
627 }
628
629 for (ModuleLoadRequest* childRequest : aRequest->mImports) {
630 JS::Value error = FindFirstParseError(childRequest);
631 if (!error.isUndefined()) {
632 return error;
633 }
634 }
635
636 return JS::UndefinedValue();
637 }
638
InstantiateModuleTree(ModuleLoadRequest * aRequest)639 bool ModuleLoaderBase::InstantiateModuleTree(ModuleLoadRequest* aRequest) {
640 // Instantiate a top-level module and record any error.
641
642 MOZ_ASSERT(aRequest);
643 MOZ_ASSERT(aRequest->IsTopLevel());
644
645 LOG(("ScriptLoadRequest (%p): Instantiate module tree", aRequest));
646
647 ModuleScript* moduleScript = aRequest->mModuleScript;
648 MOZ_ASSERT(moduleScript);
649
650 JS::Value parseError = FindFirstParseError(aRequest);
651 if (!parseError.isUndefined()) {
652 moduleScript->SetErrorToRethrow(parseError);
653 LOG(("ScriptLoadRequest (%p): found parse error", aRequest));
654 return true;
655 }
656
657 MOZ_ASSERT(moduleScript->ModuleRecord());
658
659 mozilla::nsAutoMicroTask mt;
660 mozilla::dom::AutoJSAPI jsapi;
661 if (NS_WARN_IF(!jsapi.Init(moduleScript->ModuleRecord()))) {
662 return false;
663 }
664
665 JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
666 bool ok = NS_SUCCEEDED(nsJSUtils::ModuleInstantiate(jsapi.cx(), module));
667
668 if (!ok) {
669 LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest));
670 MOZ_ASSERT(jsapi.HasException());
671 JS::RootedValue exception(jsapi.cx());
672 if (!jsapi.StealException(&exception)) {
673 return false;
674 }
675 MOZ_ASSERT(!exception.isUndefined());
676 moduleScript->SetErrorToRethrow(exception);
677 }
678
679 return true;
680 }
681
InitDebuggerDataForModuleTree(JSContext * aCx,ModuleLoadRequest * aRequest)682 nsresult ModuleLoaderBase::InitDebuggerDataForModuleTree(
683 JSContext* aCx, ModuleLoadRequest* aRequest) {
684 // JS scripts can be associated with a DOM element for use by the debugger,
685 // but preloading can cause scripts to be compiled before DOM script element
686 // nodes have been created. This method ensures that this association takes
687 // place before the first time a module script is run.
688
689 MOZ_ASSERT(aRequest);
690
691 ModuleScript* moduleScript = aRequest->mModuleScript;
692 if (moduleScript->DebuggerDataInitialized()) {
693 return NS_OK;
694 }
695
696 for (ModuleLoadRequest* childRequest : aRequest->mImports) {
697 nsresult rv = InitDebuggerDataForModuleTree(aCx, childRequest);
698 NS_ENSURE_SUCCESS(rv, rv);
699 }
700
701 JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
702 MOZ_ASSERT(module);
703
704 // The script is now ready to be exposed to the debugger.
705 JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(module));
706 JS::ExposeScriptToDebugger(aCx, script);
707
708 moduleScript->SetDebuggerDataInitialized();
709 return NS_OK;
710 }
711
ProcessDynamicImport(ModuleLoadRequest * aRequest)712 void ModuleLoaderBase::ProcessDynamicImport(ModuleLoadRequest* aRequest) {
713 if (aRequest->mModuleScript) {
714 if (!InstantiateModuleTree(aRequest)) {
715 aRequest->mModuleScript = nullptr;
716 }
717 }
718
719 nsresult rv = NS_ERROR_FAILURE;
720 if (aRequest->mModuleScript) {
721 rv = EvaluateModule(aRequest);
722 }
723
724 if (NS_FAILED(rv)) {
725 FinishDynamicImportAndReject(aRequest, rv);
726 }
727 }
728
EvaluateModule(nsIGlobalObject * aGlobalObject,ScriptLoadRequest * aRequest)729 nsresult ModuleLoaderBase::EvaluateModule(nsIGlobalObject* aGlobalObject,
730 ScriptLoadRequest* aRequest) {
731 mozilla::nsAutoMicroTask mt;
732 mozilla::dom::AutoEntryScript aes(aGlobalObject, "EvaluateModule", true);
733 JSContext* cx = aes.cx();
734
735 nsAutoCString profilerLabelString;
736 if (aRequest->HasLoadContext()) {
737 aRequest->GetLoadContext()->GetProfilerLabel(profilerLabelString);
738 }
739
740 LOG(("ScriptLoadRequest (%p): Evaluate Module", aRequest));
741 AUTO_PROFILER_MARKER_TEXT("ModuleEvaluation", JS,
742 MarkerInnerWindowIdFromJSContext(cx),
743 profilerLabelString);
744
745 // When a module is already loaded, it is not feched a second time and the
746 // mDataType of the request might remain set to DataType::Unknown.
747 MOZ_ASSERT(aRequest->IsTextSource() || aRequest->IsUnknownDataType());
748
749 ModuleLoadRequest* request = aRequest->AsModuleRequest();
750 MOZ_ASSERT(request->mModuleScript);
751 MOZ_ASSERT_IF(request->HasLoadContext(),
752 !request->GetLoadContext()->mOffThreadToken);
753
754 ModuleScript* moduleScript = request->mModuleScript;
755 if (moduleScript->HasErrorToRethrow()) {
756 LOG(("ScriptLoadRequest (%p): module has error to rethrow", aRequest));
757 JS::Rooted<JS::Value> error(cx, moduleScript->ErrorToRethrow());
758 JS_SetPendingException(cx, error);
759 // For a dynamic import, the promise is rejected. Otherwise an error
760 // is either reported by AutoEntryScript.
761 if (request->IsDynamicImport()) {
762 FinishDynamicImport(cx, request, NS_OK, nullptr);
763 }
764 return NS_OK;
765 }
766
767 JS::Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());
768 MOZ_ASSERT(module);
769
770 nsresult rv = InitDebuggerDataForModuleTree(cx, request);
771 NS_ENSURE_SUCCESS(rv, rv);
772
773 if (request->HasLoadContext()) {
774 TRACE_FOR_TEST(aRequest->GetLoadContext()->GetScriptElement(),
775 "scriptloader_evaluate_module");
776 }
777
778 JS::Rooted<JS::Value> rval(cx);
779
780 rv = nsJSUtils::ModuleEvaluate(cx, module, &rval);
781
782 if (NS_SUCCEEDED(rv)) {
783 // If we have an infinite loop in a module, which is stopped by the
784 // user, the module evaluation will fail, but we will not have an
785 // AutoEntryScript exception.
786 MOZ_ASSERT(!aes.HasException());
787 }
788
789 if (NS_FAILED(rv)) {
790 LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest));
791 // For a dynamic import, the promise is rejected. Otherwise an error is
792 // either reported by AutoEntryScript.
793 rv = NS_OK;
794 }
795
796 JS::Rooted<JSObject*> aEvaluationPromise(cx);
797 if (rval.isObject()) {
798 // If the user cancels the evaluation on an infinite loop, we need
799 // to skip this step. In that case, ModuleEvaluate will not return a
800 // promise, rval will be undefined. We should treat it as a failed
801 // evaluation, and reject appropriately.
802 aEvaluationPromise.set(&rval.toObject());
803 }
804 if (request->IsDynamicImport()) {
805 FinishDynamicImport(cx, request, rv, aEvaluationPromise);
806 } else {
807 // If this is not a dynamic import, and if the promise is rejected,
808 // the value is unwrapped from the promise value.
809 if (!JS::ThrowOnModuleEvaluationFailure(cx, aEvaluationPromise)) {
810 LOG(("ScriptLoadRequest (%p): evaluation failed on throw", aRequest));
811 // For a dynamic import, the promise is rejected. Otherwise an
812 // error is either reported by AutoEntryScript.
813 rv = NS_OK;
814 }
815 }
816
817 if (aRequest->HasLoadContext()) {
818 TRACE_FOR_TEST_NONE(aRequest->GetLoadContext()->GetScriptElement(),
819 "scriptloader_no_encode");
820 }
821 aRequest->mCacheInfo = nullptr;
822 return rv;
823 }
824
EvaluateModule(ScriptLoadRequest * aRequest)825 nsresult ModuleLoaderBase::EvaluateModule(ScriptLoadRequest* aRequest) {
826 nsCOMPtr<nsIGlobalObject> globalObject =
827 mLoader->GetGlobalForRequest(aRequest);
828 if (!globalObject) {
829 return NS_ERROR_FAILURE;
830 }
831
832 return EvaluateModule(globalObject, aRequest);
833 }
834
CancelAndClearDynamicImports()835 void ModuleLoaderBase::CancelAndClearDynamicImports() {
836 for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
837 req = req->getNext()) {
838 req->Cancel();
839 // FinishDynamicImport must happen exactly once for each dynamic import
840 // request. If the load is aborted we do it when we remove the request
841 // from mDynamicImportRequests.
842 FinishDynamicImportAndReject(req->AsModuleRequest(), NS_ERROR_ABORT);
843 }
844 mDynamicImportRequests.CancelRequestsAndClear();
845 }
846
847 #undef LOG
848 #undef LOG_ENABLED
849
850 } // namespace JS::loader
851