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