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 "mozilla/Attributes.h"
8 #include "mozilla/Utf8.h"  // mozilla::Utf8Unit
9 
10 #include <cstdarg>
11 
12 #include "mozilla/Logging.h"
13 #ifdef ANDROID
14 #  include <android/log.h>
15 #endif
16 #ifdef XP_WIN
17 #  include <windows.h>
18 #endif
19 
20 #include "jsapi.h"
21 #include "js/Array.h"  // JS::GetArrayLength, JS::IsArrayObject
22 #include "js/CharacterEncoding.h"
23 #include "js/CompilationAndEvaluation.h"
24 #include "js/CompileOptions.h"  // JS::CompileOptions
25 #include "js/experimental/JSStencil.h"
26 #include "js/friend/JSMEnvironment.h"  // JS::ExecuteInJSMEnvironment, JS::GetJSMEnvironmentOfScriptedCaller, JS::NewJSMEnvironment
27 #include "js/Object.h"                 // JS::GetCompartment
28 #include "js/Printf.h"
29 #include "js/PropertyAndElement.h"  // JS_DefineFunctions, JS_DefineProperty, JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_HasOwnProperty, JS_HasOwnPropertyById, JS_SetProperty, JS_SetPropertyById
30 #include "js/PropertySpec.h"
31 #include "js/SourceText.h"  // JS::SourceText
32 #include "nsCOMPtr.h"
33 #include "nsDirectoryServiceDefs.h"
34 #include "nsDirectoryServiceUtils.h"
35 #include "nsIComponentManager.h"
36 #include "mozilla/Module.h"
37 #include "nsIFile.h"
38 #include "mozJSComponentLoader.h"
39 #include "mozJSLoaderUtils.h"
40 #include "nsIFileURL.h"
41 #include "nsIJARURI.h"
42 #include "nsIChannel.h"
43 #include "nsNetUtil.h"
44 #include "nsJSPrincipals.h"
45 #include "nsJSUtils.h"
46 #include "xpcprivate.h"
47 #include "xpcpublic.h"
48 #include "nsContentUtils.h"
49 #include "nsReadableUtils.h"
50 #include "nsXULAppAPI.h"
51 #include "WrapperFactory.h"
52 
53 #include "AutoMemMap.h"
54 #include "ScriptPreloader-inl.h"
55 
56 #include "mozilla/scache/StartupCache.h"
57 #include "mozilla/scache/StartupCacheUtils.h"
58 #include "mozilla/ClearOnShutdown.h"
59 #include "mozilla/MacroForEach.h"
60 #include "mozilla/Preferences.h"
61 #include "mozilla/ProfilerLabels.h"
62 #include "mozilla/ProfilerMarkers.h"
63 #include "mozilla/ResultExtensions.h"
64 #include "mozilla/ScriptPreloader.h"
65 #include "mozilla/ScopeExit.h"
66 #include "mozilla/dom/AutoEntryScript.h"
67 #include "mozilla/dom/ScriptSettings.h"
68 #include "mozilla/ResultExtensions.h"
69 #include "mozilla/UniquePtrExtensions.h"
70 #include "mozilla/Unused.h"
71 
72 using namespace mozilla;
73 using namespace mozilla::scache;
74 using namespace mozilla::loader;
75 using namespace xpc;
76 using namespace JS;
77 
78 #define JS_CACHE_PREFIX(aType) "jsloader/" aType
79 
80 /**
81  * Buffer sizes for serialization and deserialization of scripts.
82  * FIXME: bug #411579 (tune this macro!) Last updated: Jan 2008
83  */
84 #define XPC_SERIALIZATION_BUFFER_SIZE (64 * 1024)
85 #define XPC_DESERIALIZATION_BUFFER_SIZE (12 * 8192)
86 
87 // MOZ_LOG=JSComponentLoader:5
88 static LazyLogModule gJSCLLog("JSComponentLoader");
89 
90 #define LOG(args) MOZ_LOG(gJSCLLog, mozilla::LogLevel::Debug, args)
91 
92 // Components.utils.import error messages
93 #define ERROR_SCOPE_OBJ "%s - Second argument must be an object."
94 #define ERROR_NO_TARGET_OBJECT "%s - Couldn't find target object for import."
95 #define ERROR_NOT_PRESENT "%s - EXPORTED_SYMBOLS is not present."
96 #define ERROR_NOT_AN_ARRAY "%s - EXPORTED_SYMBOLS is not an array."
97 #define ERROR_GETTING_ARRAY_LENGTH \
98   "%s - Error getting array length of EXPORTED_SYMBOLS."
99 #define ERROR_ARRAY_ELEMENT "%s - EXPORTED_SYMBOLS[%d] is not a string."
100 #define ERROR_GETTING_SYMBOL "%s - Could not get symbol '%s'."
101 #define ERROR_SETTING_SYMBOL "%s - Could not set symbol '%s' on target object."
102 #define ERROR_UNINITIALIZED_SYMBOL \
103   "%s - Symbol '%s' accessed before initialization. Cyclic import?"
104 
Dump(JSContext * cx,unsigned argc,Value * vp)105 static bool Dump(JSContext* cx, unsigned argc, Value* vp) {
106   if (!nsJSUtils::DumpEnabled()) {
107     return true;
108   }
109 
110   CallArgs args = CallArgsFromVp(argc, vp);
111 
112   if (args.length() == 0) {
113     return true;
114   }
115 
116   RootedString str(cx, JS::ToString(cx, args[0]));
117   if (!str) {
118     return false;
119   }
120 
121   JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
122   if (!utf8str) {
123     return false;
124   }
125 
126   MOZ_LOG(nsContentUtils::DOMDumpLog(), mozilla::LogLevel::Debug,
127           ("[Backstage.Dump] %s", utf8str.get()));
128 #ifdef ANDROID
129   __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.get());
130 #endif
131 #ifdef XP_WIN
132   if (IsDebuggerPresent()) {
133     nsAutoJSString wstr;
134     if (!wstr.init(cx, str)) {
135       return false;
136     }
137     OutputDebugStringW(wstr.get());
138   }
139 #endif
140   fputs(utf8str.get(), stdout);
141   fflush(stdout);
142   return true;
143 }
144 
Debug(JSContext * cx,unsigned argc,Value * vp)145 static bool Debug(JSContext* cx, unsigned argc, Value* vp) {
146 #ifdef DEBUG
147   return Dump(cx, argc, vp);
148 #else
149   return true;
150 #endif
151 }
152 
153 static const JSFunctionSpec gGlobalFun[] = {
154     JS_FN("dump", Dump, 1, 0), JS_FN("debug", Debug, 1, 0),
155     JS_FN("atob", Atob, 1, 0), JS_FN("btoa", Btoa, 1, 0), JS_FS_END};
156 
157 class MOZ_STACK_CLASS JSCLContextHelper {
158  public:
159   explicit JSCLContextHelper(JSContext* aCx);
160   ~JSCLContextHelper();
161 
162   void reportErrorAfterPop(UniqueChars&& buf);
163 
164  private:
165   JSContext* mContext;
166   UniqueChars mBuf;
167 
168   // prevent copying and assignment
169   JSCLContextHelper(const JSCLContextHelper&) = delete;
170   const JSCLContextHelper& operator=(const JSCLContextHelper&) = delete;
171 };
172 
173 static nsresult MOZ_FORMAT_PRINTF(2, 3)
ReportOnCallerUTF8(JSContext * callerContext,const char * format,...)174     ReportOnCallerUTF8(JSContext* callerContext, const char* format, ...) {
175   if (!callerContext) {
176     return NS_ERROR_FAILURE;
177   }
178 
179   va_list ap;
180   va_start(ap, format);
181 
182   UniqueChars buf = JS_vsmprintf(format, ap);
183   if (!buf) {
184     va_end(ap);
185     return NS_ERROR_OUT_OF_MEMORY;
186   }
187 
188   JS_ReportErrorUTF8(callerContext, "%s", buf.get());
189 
190   va_end(ap);
191   return NS_OK;
192 }
193 
NS_IMPL_ISUPPORTS(mozJSComponentLoader,nsIMemoryReporter)194 NS_IMPL_ISUPPORTS(mozJSComponentLoader, nsIMemoryReporter)
195 
196 mozJSComponentLoader::mozJSComponentLoader()
197     : mModules(16),
198       mImports(16),
199       mInProgressImports(16),
200       mLocations(16),
201       mInitialized(false),
202       mLoaderGlobal(dom::RootingCx()) {
203   MOZ_ASSERT(!sSelf, "mozJSComponentLoader should be a singleton");
204 }
205 
206 #define ENSURE_DEP(name)          \
207   {                               \
208     nsresult rv = Ensure##name(); \
209     NS_ENSURE_SUCCESS(rv, rv);    \
210   }
211 #define ENSURE_DEPS(...) MOZ_FOR_EACH(ENSURE_DEP, (), (__VA_ARGS__));
212 #define BEGIN_ENSURE(self, ...) \
213   {                             \
214     if (m##self) return NS_OK;  \
215     ENSURE_DEPS(__VA_ARGS__);   \
216   }
217 
218 class MOZ_STACK_CLASS ComponentLoaderInfo {
219  public:
ComponentLoaderInfo(const nsACString & aLocation)220   explicit ComponentLoaderInfo(const nsACString& aLocation)
221       : mLocation(aLocation) {}
222 
IOService()223   nsIIOService* IOService() {
224     MOZ_ASSERT(mIOService);
225     return mIOService;
226   }
EnsureIOService()227   nsresult EnsureIOService() {
228     if (mIOService) {
229       return NS_OK;
230     }
231     nsresult rv;
232     mIOService = do_GetIOService(&rv);
233     return rv;
234   }
235 
URI()236   nsIURI* URI() {
237     MOZ_ASSERT(mURI);
238     return mURI;
239   }
EnsureURI()240   nsresult EnsureURI() {
241     BEGIN_ENSURE(URI, IOService);
242     return mIOService->NewURI(mLocation, nullptr, nullptr,
243                               getter_AddRefs(mURI));
244   }
245 
ScriptChannel()246   nsIChannel* ScriptChannel() {
247     MOZ_ASSERT(mScriptChannel);
248     return mScriptChannel;
249   }
EnsureScriptChannel()250   nsresult EnsureScriptChannel() {
251     BEGIN_ENSURE(ScriptChannel, IOService, URI);
252     return NS_NewChannel(
253         getter_AddRefs(mScriptChannel), mURI,
254         nsContentUtils::GetSystemPrincipal(),
255         nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
256         nsIContentPolicy::TYPE_SCRIPT,
257         nullptr,  // nsICookieJarSettings
258         nullptr,  // aPerformanceStorage
259         nullptr,  // aLoadGroup
260         nullptr,  // aCallbacks
261         nsIRequest::LOAD_NORMAL, mIOService);
262   }
263 
ResolvedURI()264   nsIURI* ResolvedURI() {
265     MOZ_ASSERT(mResolvedURI);
266     return mResolvedURI;
267   }
EnsureResolvedURI()268   nsresult EnsureResolvedURI() {
269     BEGIN_ENSURE(ResolvedURI, URI);
270     return ResolveURI(mURI, getter_AddRefs(mResolvedURI));
271   }
272 
Key()273   const nsACString& Key() { return mLocation; }
274 
GetLocation(nsCString & aLocation)275   [[nodiscard]] nsresult GetLocation(nsCString& aLocation) {
276     nsresult rv = EnsureURI();
277     NS_ENSURE_SUCCESS(rv, rv);
278     return mURI->GetSpec(aLocation);
279   }
280 
281  private:
282   const nsACString& mLocation;
283   nsCOMPtr<nsIIOService> mIOService;
284   nsCOMPtr<nsIURI> mURI;
285   nsCOMPtr<nsIChannel> mScriptChannel;
286   nsCOMPtr<nsIURI> mResolvedURI;
287 };
288 
289 template <typename... Args>
ReportOnCallerUTF8(JSCLContextHelper & helper,const char * format,ComponentLoaderInfo & info,Args...args)290 static nsresult ReportOnCallerUTF8(JSCLContextHelper& helper,
291                                    const char* format,
292                                    ComponentLoaderInfo& info, Args... args) {
293   nsCString location;
294   MOZ_TRY(info.GetLocation(location));
295 
296   UniqueChars buf = JS_smprintf(format, location.get(), args...);
297   if (!buf) {
298     return NS_ERROR_OUT_OF_MEMORY;
299   }
300 
301   helper.reportErrorAfterPop(std::move(buf));
302   return NS_ERROR_FAILURE;
303 }
304 
305 #undef BEGIN_ENSURE
306 #undef ENSURE_DEPS
307 #undef ENSURE_DEP
308 
~mozJSComponentLoader()309 mozJSComponentLoader::~mozJSComponentLoader() {
310   MOZ_ASSERT(!mInitialized,
311              "UnloadModules() was not explicitly called before cleaning up "
312              "mozJSComponentLoader");
313 
314   if (mInitialized) {
315     UnloadModules();
316   }
317 
318   sSelf = nullptr;
319 }
320 
321 StaticRefPtr<mozJSComponentLoader> mozJSComponentLoader::sSelf;
322 
323 // For terrible compatibility reasons, we need to consider both the global
324 // lexical environment and the global of modules when searching for exported
325 // symbols.
ResolveModuleObjectProperty(JSContext * aCx,HandleObject aModObj,const char * name)326 static JSObject* ResolveModuleObjectProperty(JSContext* aCx,
327                                              HandleObject aModObj,
328                                              const char* name) {
329   if (JS_HasExtensibleLexicalEnvironment(aModObj)) {
330     RootedObject lexical(aCx, JS_ExtensibleLexicalEnvironment(aModObj));
331     bool found;
332     if (!JS_HasOwnProperty(aCx, lexical, name, &found)) {
333       return nullptr;
334     }
335     if (found) {
336       return lexical;
337     }
338   }
339   return aModObj;
340 }
341 
LoadModule(FileLocation & aFile)342 const mozilla::Module* mozJSComponentLoader::LoadModule(FileLocation& aFile) {
343   if (!NS_IsMainThread()) {
344     MOZ_ASSERT(false, "Don't use JS components off the main thread");
345     return nullptr;
346   }
347 
348   nsCOMPtr<nsIFile> file = aFile.GetBaseFile();
349 
350   nsCString spec;
351   aFile.GetURIString(spec);
352   ComponentLoaderInfo info(spec);
353   nsresult rv = info.EnsureURI();
354   NS_ENSURE_SUCCESS(rv, nullptr);
355 
356   mInitialized = true;
357 
358   AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("mozJSComponentLoader::LoadModule",
359                                         OTHER, spec);
360   AUTO_PROFILER_MARKER_TEXT("JS XPCOM", JS, MarkerStack::Capture(), spec);
361 
362   ModuleEntry* mod;
363   if (mModules.Get(spec, &mod)) {
364     return mod;
365   }
366 
367   dom::AutoJSAPI jsapi;
368   jsapi.Init();
369   JSContext* cx = jsapi.cx();
370 
371   auto entry = MakeUnique<ModuleEntry>(RootingContext::get(cx));
372   RootedValue exn(cx);
373   rv = ObjectForLocation(info, file, &entry->obj, &entry->thisObjectKey,
374                          &entry->location, /* aPropagateExceptions */ false,
375                          &exn);
376   NS_ENSURE_SUCCESS(rv, nullptr);
377 
378   nsCOMPtr<nsIComponentManager> cm;
379   rv = NS_GetComponentManager(getter_AddRefs(cm));
380   if (NS_FAILED(rv)) {
381     return nullptr;
382   }
383 
384   JSAutoRealm ar(cx, entry->obj);
385   RootedObject entryObj(cx, entry->obj);
386 
387   RootedObject NSGetFactoryHolder(
388       cx, ResolveModuleObjectProperty(cx, entryObj, "NSGetFactory"));
389   RootedValue NSGetFactory_val(cx);
390   if (!NSGetFactoryHolder ||
391       !JS_GetProperty(cx, NSGetFactoryHolder, "NSGetFactory",
392                       &NSGetFactory_val) ||
393       NSGetFactory_val.isUndefined()) {
394     return nullptr;
395   }
396 
397   if (JS_TypeOfValue(cx, NSGetFactory_val) != JSTYPE_FUNCTION) {
398     /*
399      * spec's encoding is ASCII unless it's zip file, otherwise it's
400      * random encoding.  Latin1 variant is safe for random encoding.
401      */
402     JS_ReportErrorLatin1(
403         cx, "%s has NSGetFactory property that is not a function", spec.get());
404     return nullptr;
405   }
406 
407   RootedObject jsGetFactoryObj(cx);
408   if (!JS_ValueToObject(cx, NSGetFactory_val, &jsGetFactoryObj) ||
409       !jsGetFactoryObj) {
410     /* XXX report error properly */
411     return nullptr;
412   }
413 
414   rv = nsXPConnect::XPConnect()->WrapJS(cx, jsGetFactoryObj,
415                                         NS_GET_IID(xpcIJSGetFactory),
416                                         getter_AddRefs(entry->getfactoryobj));
417   if (NS_FAILED(rv)) {
418     /* XXX report error properly */
419 #ifdef DEBUG
420     fprintf(stderr, "mJCL: couldn't get nsIModule from jsval\n");
421 #endif
422     return nullptr;
423   }
424 
425 #if defined(NIGHTLY_BUILD) || defined(DEBUG)
426   if (Preferences::GetBool("browser.startup.record", false)) {
427     entry->importStack = xpc_PrintJSStack(cx, false, false, false).get();
428   }
429 #endif
430 
431   // Cache this module for later
432   mModules.InsertOrUpdate(spec, entry.get());
433 
434   // The hash owns the ModuleEntry now, forget about it
435   return entry.release();
436 }
437 
FindTargetObject(JSContext * aCx,MutableHandleObject aTargetObject)438 void mozJSComponentLoader::FindTargetObject(JSContext* aCx,
439                                             MutableHandleObject aTargetObject) {
440   aTargetObject.set(JS::GetJSMEnvironmentOfScriptedCaller(aCx));
441 
442   // The above could fail if the scripted caller is not a component/JSM (it
443   // could be a DOM scope, for instance).
444   //
445   // If the target object was not in the JSM shared global, return the global
446   // instead. This is needed when calling the subscript loader within a frame
447   // script, since it the FrameScript NSVO will have been found.
448   if (!aTargetObject ||
449       !IsLoaderGlobal(JS::GetNonCCWObjectGlobal(aTargetObject))) {
450     aTargetObject.set(JS::GetScriptedCallerGlobal(aCx));
451 
452     // Return nullptr if the scripted caller is in a different compartment.
453     if (JS::GetCompartment(aTargetObject) != js::GetContextCompartment(aCx)) {
454       aTargetObject.set(nullptr);
455     }
456   }
457 }
458 
InitStatics()459 void mozJSComponentLoader::InitStatics() {
460   MOZ_ASSERT(!sSelf);
461   sSelf = new mozJSComponentLoader();
462   RegisterWeakMemoryReporter(sSelf);
463 }
464 
Unload()465 void mozJSComponentLoader::Unload() {
466   if (sSelf) {
467     sSelf->UnloadModules();
468   }
469 }
470 
Shutdown()471 void mozJSComponentLoader::Shutdown() {
472   MOZ_ASSERT(sSelf);
473   UnregisterWeakMemoryReporter(sSelf);
474   sSelf = nullptr;
475 }
476 
477 // This requires that the keys be strings and the values be pointers.
478 template <class Key, class Data, class UserData, class Converter>
SizeOfTableExcludingThis(const nsBaseHashtable<Key,Data,UserData,Converter> & aTable,MallocSizeOf aMallocSizeOf)479 static size_t SizeOfTableExcludingThis(
480     const nsBaseHashtable<Key, Data, UserData, Converter>& aTable,
481     MallocSizeOf aMallocSizeOf) {
482   size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
483   for (const auto& entry : aTable) {
484     n += entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
485     n += entry.GetData()->SizeOfIncludingThis(aMallocSizeOf);
486   }
487   return n;
488 }
489 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)490 size_t mozJSComponentLoader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
491   size_t n = aMallocSizeOf(this);
492   n += SizeOfTableExcludingThis(mModules, aMallocSizeOf);
493   n += SizeOfTableExcludingThis(mImports, aMallocSizeOf);
494   n += mLocations.ShallowSizeOfExcludingThis(aMallocSizeOf);
495   n += SizeOfTableExcludingThis(mInProgressImports, aMallocSizeOf);
496   return n;
497 }
498 
499 // Memory report paths are split on '/', with each component displayed as a
500 // separate layer of a visual tree. Any slashes which are meant to belong to a
501 // particular path component, rather than be used to build a hierarchy,
502 // therefore need to be replaced with backslashes, which are displayed as
503 // slashes in the UI.
504 //
505 // If `aAnonymize` is true, this function also attempts to translate any file:
506 // URLs to replace the path of the GRE directory with a placeholder containing
507 // no private information, and strips all other file: URIs of everything upto
508 // their last `/`.
MangleURL(const char * aURL,bool aAnonymize)509 static nsAutoCString MangleURL(const char* aURL, bool aAnonymize) {
510   nsAutoCString url(aURL);
511 
512   if (aAnonymize) {
513     static nsCString greDirURI;
514     if (greDirURI.IsEmpty()) {
515       nsCOMPtr<nsIFile> file;
516       Unused << NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(file));
517       if (file) {
518         nsCOMPtr<nsIURI> uri;
519         NS_NewFileURI(getter_AddRefs(uri), file);
520         if (uri) {
521           uri->GetSpec(greDirURI);
522           RunOnShutdown([&]() { greDirURI.Truncate(0); });
523         }
524       }
525     }
526 
527     url.ReplaceSubstring(greDirURI, "<GREDir>/"_ns);
528 
529     if (FindInReadable("file:"_ns, url)) {
530       if (StringBeginsWith(url, "jar:file:"_ns)) {
531         int32_t idx = url.RFindChar('!');
532         url = "jar:file://<anonymized>!"_ns + Substring(url, idx);
533       } else {
534         int32_t idx = url.RFindChar('/');
535         url = "file://<anonymized>/"_ns + Substring(url, idx);
536       }
537     }
538   }
539 
540   url.ReplaceChar('/', '\\');
541   return url;
542 }
543 
544 NS_IMETHODIMP
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)545 mozJSComponentLoader::CollectReports(nsIHandleReportCallback* aHandleReport,
546                                      nsISupports* aData, bool aAnonymize) {
547   for (const auto& entry : mImports.Values()) {
548     nsAutoCString path("js-component-loader/modules/");
549     path.Append(MangleURL(entry->location, aAnonymize));
550 
551     aHandleReport->Callback(""_ns, path, KIND_NONHEAP, UNITS_COUNT, 1,
552                             "Loaded JS modules"_ns, aData);
553   }
554 
555   for (const auto& entry : mModules.Values()) {
556     nsAutoCString path("js-component-loader/components/");
557     path.Append(MangleURL(entry->location, aAnonymize));
558 
559     aHandleReport->Callback(""_ns, path, KIND_NONHEAP, UNITS_COUNT, 1,
560                             "Loaded JS components"_ns, aData);
561   }
562 
563   return NS_OK;
564 }
565 
CreateLoaderGlobal(JSContext * aCx,const nsACString & aLocation,MutableHandleObject aGlobal)566 void mozJSComponentLoader::CreateLoaderGlobal(JSContext* aCx,
567                                               const nsACString& aLocation,
568                                               MutableHandleObject aGlobal) {
569   auto backstagePass = MakeRefPtr<BackstagePass>();
570   RealmOptions options;
571 
572   options.creationOptions().setNewCompartmentInSystemZone();
573   xpc::SetPrefableRealmOptions(options);
574 
575   // Defer firing OnNewGlobalObject until after the __URI__ property has
576   // been defined so the JS debugger can tell what module the global is
577   // for
578   RootedObject global(aCx);
579   nsresult rv = xpc::InitClassesWithNewWrappedGlobal(
580       aCx, static_cast<nsIGlobalObject*>(backstagePass),
581       nsContentUtils::GetSystemPrincipal(), xpc::DONT_FIRE_ONNEWGLOBALHOOK,
582       options, &global);
583   NS_ENSURE_SUCCESS_VOID(rv);
584 
585   NS_ENSURE_TRUE_VOID(global);
586 
587   backstagePass->SetGlobalObject(global);
588 
589   JSAutoRealm ar(aCx, global);
590   if (!JS_DefineFunctions(aCx, global, gGlobalFun)) {
591     return;
592   }
593 
594   // Set the location information for the new global, so that tools like
595   // about:memory may use that information
596   xpc::SetLocationForGlobal(global, aLocation);
597 
598   aGlobal.set(global);
599 }
600 
GetSharedGlobal(JSContext * aCx)601 JSObject* mozJSComponentLoader::GetSharedGlobal(JSContext* aCx) {
602   if (!mLoaderGlobal) {
603     JS::RootedObject globalObj(aCx);
604     CreateLoaderGlobal(aCx, "shared JSM global"_ns, &globalObj);
605 
606     // If we fail to create a module global this early, we're not going to
607     // get very far, so just bail out now.
608     MOZ_RELEASE_ASSERT(globalObj);
609     mLoaderGlobal = globalObj;
610 
611     // AutoEntryScript required to invoke debugger hook, which is a
612     // Gecko-specific concept at present.
613     dom::AutoEntryScript aes(globalObj, "component loader report global");
614     JS_FireOnNewGlobalObject(aes.cx(), globalObj);
615   }
616 
617   return mLoaderGlobal;
618 }
619 
PrepareObjectForLocation(JSContext * aCx,nsIFile * aComponentFile,nsIURI * aURI,bool * aRealFile)620 JSObject* mozJSComponentLoader::PrepareObjectForLocation(
621     JSContext* aCx, nsIFile* aComponentFile, nsIURI* aURI, bool* aRealFile) {
622   nsAutoCString nativePath;
623   NS_ENSURE_SUCCESS(aURI->GetSpec(nativePath), nullptr);
624 
625   RootedObject globalObj(aCx, GetSharedGlobal(aCx));
626 
627   // |thisObj| is the object we set properties on for a particular .jsm.
628   RootedObject thisObj(aCx, globalObj);
629   NS_ENSURE_TRUE(thisObj, nullptr);
630 
631   JSAutoRealm ar(aCx, thisObj);
632 
633   thisObj = JS::NewJSMEnvironment(aCx);
634   NS_ENSURE_TRUE(thisObj, nullptr);
635 
636   *aRealFile = false;
637 
638   // need to be extra careful checking for URIs pointing to files
639   // EnsureFile may not always get called, especially on resource URIs
640   // so we need to call GetFile to make sure this is a valid file
641   {
642     // Create an extra scope so that ~nsCOMPtr will run before the returned
643     // JSObject* is placed on the stack, since otherwise a GC in the destructor
644     // would invalidate the return value.
645     nsresult rv = NS_OK;
646     nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
647     nsCOMPtr<nsIFile> testFile;
648     if (NS_SUCCEEDED(rv)) {
649       fileURL->GetFile(getter_AddRefs(testFile));
650     }
651 
652     if (testFile) {
653       *aRealFile = true;
654 
655       if (XRE_IsParentProcess()) {
656         RootedObject locationObj(aCx);
657 
658         rv = nsXPConnect::XPConnect()->WrapNative(aCx, thisObj, aComponentFile,
659                                                   NS_GET_IID(nsIFile),
660                                                   locationObj.address());
661         NS_ENSURE_SUCCESS(rv, nullptr);
662         NS_ENSURE_TRUE(locationObj, nullptr);
663 
664         if (!JS_DefineProperty(aCx, thisObj, "__LOCATION__", locationObj, 0)) {
665           return nullptr;
666         }
667       }
668     }
669   }
670 
671   // Expose the URI from which the script was imported through a special
672   // variable that we insert into the JSM.
673   RootedString exposedUri(
674       aCx, JS_NewStringCopyN(aCx, nativePath.get(), nativePath.Length()));
675   NS_ENSURE_TRUE(exposedUri, nullptr);
676 
677   if (!JS_DefineProperty(aCx, thisObj, "__URI__", exposedUri, 0)) {
678     return nullptr;
679   }
680 
681   return thisObj;
682 }
683 
ReadScript(ComponentLoaderInfo & aInfo)684 static mozilla::Result<nsCString, nsresult> ReadScript(
685     ComponentLoaderInfo& aInfo) {
686   MOZ_TRY(aInfo.EnsureScriptChannel());
687 
688   nsCOMPtr<nsIInputStream> scriptStream;
689   MOZ_TRY(NS_MaybeOpenChannelUsingOpen(aInfo.ScriptChannel(),
690                                        getter_AddRefs(scriptStream)));
691 
692   uint64_t len64;
693   uint32_t bytesRead;
694 
695   MOZ_TRY(scriptStream->Available(&len64));
696   NS_ENSURE_TRUE(len64 < UINT32_MAX, Err(NS_ERROR_FILE_TOO_BIG));
697   NS_ENSURE_TRUE(len64, Err(NS_ERROR_FAILURE));
698   uint32_t len = (uint32_t)len64;
699 
700   /* malloc an internal buf the size of the file */
701   nsCString str;
702   if (!str.SetLength(len, fallible)) {
703     return Err(NS_ERROR_OUT_OF_MEMORY);
704   }
705 
706   /* read the file in one swoop */
707   MOZ_TRY(scriptStream->Read(str.BeginWriting(), len, &bytesRead));
708   if (bytesRead != len) {
709     return Err(NS_BASE_STREAM_OSERROR);
710   }
711 
712   return std::move(str);
713 }
714 
ObjectForLocation(ComponentLoaderInfo & aInfo,nsIFile * aComponentFile,MutableHandleObject aObject,MutableHandleScript aTableScript,char ** aLocation,bool aPropagateExceptions,MutableHandleValue aException)715 nsresult mozJSComponentLoader::ObjectForLocation(
716     ComponentLoaderInfo& aInfo, nsIFile* aComponentFile,
717     MutableHandleObject aObject, MutableHandleScript aTableScript,
718     char** aLocation, bool aPropagateExceptions,
719     MutableHandleValue aException) {
720   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
721 
722   dom::AutoJSAPI jsapi;
723   jsapi.Init();
724   JSContext* cx = jsapi.cx();
725 
726   bool realFile = false;
727   nsresult rv = aInfo.EnsureURI();
728   NS_ENSURE_SUCCESS(rv, rv);
729   RootedObject obj(
730       cx, PrepareObjectForLocation(cx, aComponentFile, aInfo.URI(), &realFile));
731   NS_ENSURE_TRUE(obj, NS_ERROR_FAILURE);
732   MOZ_ASSERT(!JS_IsGlobalObject(obj));
733 
734   JSAutoRealm ar(cx, obj);
735 
736   nsAutoCString nativePath;
737   rv = aInfo.URI()->GetSpec(nativePath);
738   NS_ENSURE_SUCCESS(rv, rv);
739 
740   // Before compiling the script, first check to see if we have it in
741   // the preloader cache or the startupcache.  Note: as a rule, preloader cache
742   // errors and startupcache errors are not fatal to loading the script, since
743   // we can always slow-load.
744 
745   bool storeIntoStartupCache = false;
746   StartupCache* cache = StartupCache::GetSingleton();
747 
748   aInfo.EnsureResolvedURI();
749 
750   nsAutoCString cachePath(JS_CACHE_PREFIX("non-syntactic"));
751   rv = PathifyURI(aInfo.ResolvedURI(), cachePath);
752   NS_ENSURE_SUCCESS(rv, rv);
753 
754   JS::DecodeOptions decodeOptions;
755   ScriptPreloader::FillDecodeOptionsForCachedStencil(decodeOptions);
756 
757   RefPtr<JS::Stencil> stencil =
758       ScriptPreloader::GetSingleton().GetCachedStencil(cx, decodeOptions,
759                                                        cachePath);
760 
761   if (!stencil && cache) {
762     ReadCachedStencil(cache, cachePath, cx, decodeOptions,
763                       getter_AddRefs(stencil));
764     if (!stencil) {
765       JS_ClearPendingException(cx);
766 
767       storeIntoStartupCache = true;
768     }
769   }
770 
771   if (stencil) {
772     LOG(("Successfully loaded %s from cache\n", nativePath.get()));
773   } else {
774     // The script wasn't in the cache , so compile it now.
775     LOG(("Slow loading %s\n", nativePath.get()));
776 
777     CompileOptions options(cx);
778     ScriptPreloader::FillCompileOptionsForCachedStencil(options);
779     options.setFileAndLine(nativePath.get(), 1);
780     options.setForceStrictMode();
781     options.setNonSyntacticScope(true);
782 
783     // If we can no longer write to caches, we should stop using lazy sources
784     // and instead let normal syntax parsing occur. This can occur in content
785     // processes after the ScriptPreloader is flushed where we can read but no
786     // longer write.
787     if (!storeIntoStartupCache && !ScriptPreloader::GetSingleton().Active()) {
788       options.setSourceIsLazy(false);
789     }
790 
791     if (realFile) {
792       AutoMemMap map;
793       MOZ_TRY(map.init(aComponentFile));
794 
795       // Note: exceptions will get handled further down;
796       // don't early return for them here.
797       auto buf = map.get<char>();
798 
799       JS::SourceText<mozilla::Utf8Unit> srcBuf;
800       if (srcBuf.init(cx, buf.get(), map.size(),
801                       JS::SourceOwnership::Borrowed)) {
802         stencil = CompileGlobalScriptToStencil(cx, options, srcBuf);
803       }
804     } else {
805       nsCString str;
806       MOZ_TRY_VAR(str, ReadScript(aInfo));
807 
808       JS::SourceText<mozilla::Utf8Unit> srcBuf;
809       if (srcBuf.init(cx, str.get(), str.Length(),
810                       JS::SourceOwnership::Borrowed)) {
811         stencil = CompileGlobalScriptToStencil(cx, options, srcBuf);
812       }
813     }
814 
815 #ifdef DEBUG
816     // The above shouldn't touch any options for instantiation.
817     JS::InstantiateOptions instantiateOptions(options);
818     instantiateOptions.assertDefault();
819 #endif
820 
821     if (!stencil) {
822       // Propagate the exception, if one exists. Also, don't leave the stale
823       // exception on this context.
824       if (aPropagateExceptions && jsapi.HasException()) {
825         if (!jsapi.StealException(aException)) {
826           return NS_ERROR_OUT_OF_MEMORY;
827         }
828       }
829       return NS_ERROR_FAILURE;
830     }
831   }
832 
833   JS::InstantiateOptions instantiateOptions;
834   RootedScript script(
835       cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
836   if (!script) {
837     // Propagate the exception, if one exists. Also, don't leave the stale
838     // exception on this context.
839     if (aPropagateExceptions && jsapi.HasException()) {
840       if (!jsapi.StealException(aException)) {
841         return NS_ERROR_OUT_OF_MEMORY;
842       }
843     }
844     return NS_ERROR_FAILURE;
845   }
846 
847   // ScriptPreloader::NoteScript needs to be called unconditionally, to
848   // reflect the usage into the next session's cache.
849   ScriptPreloader::GetSingleton().NoteStencil(nativePath, cachePath, stencil);
850 
851   // Write to startup cache only when we didn't have any cache for the script
852   // and compiled it.
853   if (storeIntoStartupCache) {
854     MOZ_ASSERT(stencil);
855 
856     // We successfully compiled the script, so cache it.
857     rv = WriteCachedStencil(cache, cachePath, cx, stencil);
858 
859     // Don't treat failure to write as fatal, since we might be working
860     // with a read-only cache.
861     if (NS_SUCCEEDED(rv)) {
862       LOG(("Successfully wrote to cache\n"));
863     } else {
864       LOG(("Failed to write to cache\n"));
865     }
866   }
867 
868   // Assign aObject here so that it's available to recursive imports.
869   // See bug 384168.
870   aObject.set(obj);
871 
872   aTableScript.set(script);
873 
874   {  // Scope for AutoEntryScript
875     AutoAllowLegacyScriptExecution exemption;
876 
877     // We're going to run script via JS_ExecuteScript, so we need an
878     // AutoEntryScript. This is Gecko-specific and not in any spec.
879     dom::AutoEntryScript aes(CurrentGlobalOrNull(cx),
880                              "component loader load module");
881     JSContext* aescx = aes.cx();
882 
883     bool executeOk = false;
884     if (JS_IsGlobalObject(obj)) {
885       JS::RootedValue rval(cx);
886       executeOk = JS_ExecuteScript(aescx, script, &rval);
887     } else {
888       executeOk = JS::ExecuteInJSMEnvironment(aescx, script, obj);
889     }
890 
891     if (!executeOk) {
892       if (aPropagateExceptions && aes.HasException()) {
893         // Ignore return value because we're returning an error code
894         // anyway.
895         Unused << aes.StealException(aException);
896       }
897       aObject.set(nullptr);
898       aTableScript.set(nullptr);
899       return NS_ERROR_FAILURE;
900     }
901   }
902 
903   /* Freed when we remove from the table. */
904   *aLocation = ToNewCString(nativePath, mozilla::fallible);
905   if (!*aLocation) {
906     aObject.set(nullptr);
907     aTableScript.set(nullptr);
908     return NS_ERROR_OUT_OF_MEMORY;
909   }
910 
911   return NS_OK;
912 }
913 
UnloadModules()914 void mozJSComponentLoader::UnloadModules() {
915   mInitialized = false;
916 
917   if (mLoaderGlobal) {
918     MOZ_ASSERT(JS_HasExtensibleLexicalEnvironment(mLoaderGlobal));
919     JS::RootedObject lexicalEnv(dom::RootingCx(),
920                                 JS_ExtensibleLexicalEnvironment(mLoaderGlobal));
921     JS_SetAllNonReservedSlotsToUndefined(lexicalEnv);
922     JS_SetAllNonReservedSlotsToUndefined(mLoaderGlobal);
923     mLoaderGlobal = nullptr;
924   }
925 
926   mInProgressImports.Clear();
927   mImports.Clear();
928   mLocations.Clear();
929 
930   for (auto iter = mModules.Iter(); !iter.Done(); iter.Next()) {
931     iter.Data()->Clear();
932     iter.Remove();
933   }
934 }
935 
ImportInto(const nsACString & registryLocation,HandleValue targetValArg,JSContext * cx,uint8_t optionalArgc,MutableHandleValue retval)936 nsresult mozJSComponentLoader::ImportInto(const nsACString& registryLocation,
937                                           HandleValue targetValArg,
938                                           JSContext* cx, uint8_t optionalArgc,
939                                           MutableHandleValue retval) {
940   MOZ_ASSERT(nsContentUtils::IsCallerChrome());
941 
942   RootedValue targetVal(cx, targetValArg);
943   RootedObject targetObject(cx, nullptr);
944 
945   if (optionalArgc) {
946     // The caller passed in the optional second argument. Get it.
947     if (targetVal.isObject()) {
948       // If we're passing in something like a content DOM window, chances
949       // are the caller expects the properties to end up on the object
950       // proper and not on the Xray holder. This is dubious, but can be used
951       // during testing. Given that dumb callers can already leak JSMs into
952       // content by passing a raw content JS object (where Xrays aren't
953       // possible), we aim for consistency here. Waive xray.
954       if (WrapperFactory::IsXrayWrapper(&targetVal.toObject()) &&
955           !WrapperFactory::WaiveXrayAndWrap(cx, &targetVal)) {
956         return NS_ERROR_FAILURE;
957       }
958       targetObject = &targetVal.toObject();
959     } else if (!targetVal.isNull()) {
960       // If targetVal isNull(), we actually want to leave targetObject null.
961       // Not doing so breaks |make package|.
962       return ReportOnCallerUTF8(cx, ERROR_SCOPE_OBJ,
963                                 PromiseFlatCString(registryLocation).get());
964     }
965   } else {
966     FindTargetObject(cx, &targetObject);
967     if (!targetObject) {
968       return ReportOnCallerUTF8(cx, ERROR_NO_TARGET_OBJECT,
969                                 PromiseFlatCString(registryLocation).get());
970     }
971   }
972 
973   js::AssertSameCompartment(cx, targetObject);
974 
975   RootedObject global(cx);
976   nsresult rv = ImportInto(registryLocation, targetObject, cx, &global);
977 
978   if (global) {
979     if (!JS_WrapObject(cx, &global)) {
980       NS_ERROR("can't wrap return value");
981       return NS_ERROR_FAILURE;
982     }
983 
984     retval.setObject(*global);
985   }
986   return rv;
987 }
988 
IsModuleLoaded(const nsACString & aLocation,bool * retval)989 nsresult mozJSComponentLoader::IsModuleLoaded(const nsACString& aLocation,
990                                               bool* retval) {
991   MOZ_ASSERT(nsContentUtils::IsCallerChrome());
992 
993   mInitialized = true;
994   ComponentLoaderInfo info(aLocation);
995   *retval = !!mImports.Get(info.Key());
996   return NS_OK;
997 }
998 
GetLoadedModules(nsTArray<nsCString> & aLoadedModules)999 void mozJSComponentLoader::GetLoadedModules(
1000     nsTArray<nsCString>& aLoadedModules) {
1001   aLoadedModules.SetCapacity(mImports.Count());
1002   for (const auto& data : mImports.Values()) {
1003     aLoadedModules.AppendElement(data->location);
1004   }
1005 }
1006 
GetLoadedComponents(nsTArray<nsCString> & aLoadedComponents)1007 void mozJSComponentLoader::GetLoadedComponents(
1008     nsTArray<nsCString>& aLoadedComponents) {
1009   aLoadedComponents.SetCapacity(mModules.Count());
1010   for (const auto& data : mModules.Values()) {
1011     aLoadedComponents.AppendElement(data->location);
1012   }
1013 }
1014 
GetModuleImportStack(const nsACString & aLocation,nsACString & retval)1015 nsresult mozJSComponentLoader::GetModuleImportStack(const nsACString& aLocation,
1016                                                     nsACString& retval) {
1017 #ifdef STARTUP_RECORDER_ENABLED
1018   MOZ_ASSERT(nsContentUtils::IsCallerChrome());
1019   MOZ_ASSERT(mInitialized);
1020 
1021   ComponentLoaderInfo info(aLocation);
1022 
1023   ModuleEntry* mod;
1024   if (!mImports.Get(info.Key(), &mod)) {
1025     return NS_ERROR_FAILURE;
1026   }
1027 
1028   retval = mod->importStack;
1029   return NS_OK;
1030 #else
1031   return NS_ERROR_NOT_IMPLEMENTED;
1032 #endif
1033 }
1034 
GetComponentLoadStack(const nsACString & aLocation,nsACString & retval)1035 nsresult mozJSComponentLoader::GetComponentLoadStack(
1036     const nsACString& aLocation, nsACString& retval) {
1037 #ifdef STARTUP_RECORDER_ENABLED
1038   MOZ_ASSERT(nsContentUtils::IsCallerChrome());
1039   MOZ_ASSERT(mInitialized);
1040 
1041   ComponentLoaderInfo info(aLocation);
1042   nsresult rv = info.EnsureURI();
1043   NS_ENSURE_SUCCESS(rv, rv);
1044 
1045   ModuleEntry* mod;
1046   if (!mModules.Get(info.Key(), &mod)) {
1047     return NS_ERROR_FAILURE;
1048   }
1049 
1050   retval = mod->importStack;
1051   return NS_OK;
1052 #else
1053   return NS_ERROR_NOT_IMPLEMENTED;
1054 #endif
1055 }
1056 
ResolveModuleObjectPropertyById(JSContext * aCx,HandleObject aModObj,HandleId id)1057 static JSObject* ResolveModuleObjectPropertyById(JSContext* aCx,
1058                                                  HandleObject aModObj,
1059                                                  HandleId id) {
1060   if (JS_HasExtensibleLexicalEnvironment(aModObj)) {
1061     RootedObject lexical(aCx, JS_ExtensibleLexicalEnvironment(aModObj));
1062     bool found;
1063     if (!JS_HasOwnPropertyById(aCx, lexical, id, &found)) {
1064       return nullptr;
1065     }
1066     if (found) {
1067       return lexical;
1068     }
1069   }
1070   return aModObj;
1071 }
1072 
ImportInto(const nsACString & aLocation,HandleObject targetObj,JSContext * cx,MutableHandleObject vp)1073 nsresult mozJSComponentLoader::ImportInto(const nsACString& aLocation,
1074                                           HandleObject targetObj, JSContext* cx,
1075                                           MutableHandleObject vp) {
1076   vp.set(nullptr);
1077 
1078   JS::RootedObject exports(cx);
1079   MOZ_TRY(Import(cx, aLocation, vp, &exports, !targetObj));
1080 
1081   if (targetObj) {
1082     JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx));
1083     if (!JS_Enumerate(cx, exports, &ids)) {
1084       return NS_ERROR_OUT_OF_MEMORY;
1085     }
1086 
1087     JS::RootedValue value(cx);
1088     JS::RootedId id(cx);
1089     for (jsid idVal : ids) {
1090       id = idVal;
1091       if (!JS_GetPropertyById(cx, exports, id, &value) ||
1092           !JS_SetPropertyById(cx, targetObj, id, value)) {
1093         return NS_ERROR_FAILURE;
1094       }
1095     }
1096   }
1097 
1098   return NS_OK;
1099 }
1100 
ExtractExports(JSContext * aCx,ComponentLoaderInfo & aInfo,ModuleEntry * aMod,JS::MutableHandleObject aExports)1101 nsresult mozJSComponentLoader::ExtractExports(
1102     JSContext* aCx, ComponentLoaderInfo& aInfo, ModuleEntry* aMod,
1103     JS::MutableHandleObject aExports) {
1104   // cxhelper must be created before jsapi, so that jsapi is destroyed and
1105   // pops any context it has pushed before we report to the caller context.
1106   JSCLContextHelper cxhelper(aCx);
1107 
1108   // Even though we are calling JS_SetPropertyById on targetObj, we want
1109   // to ensure that we never run script here, so we use an AutoJSAPI and
1110   // not an AutoEntryScript.
1111   dom::AutoJSAPI jsapi;
1112   jsapi.Init();
1113   JSContext* cx = jsapi.cx();
1114   JSAutoRealm ar(cx, aMod->obj);
1115 
1116   RootedValue symbols(cx);
1117   {
1118     RootedObject obj(
1119         cx, ResolveModuleObjectProperty(cx, aMod->obj, "EXPORTED_SYMBOLS"));
1120     if (!obj || !JS_GetProperty(cx, obj, "EXPORTED_SYMBOLS", &symbols)) {
1121       return ReportOnCallerUTF8(cxhelper, ERROR_NOT_PRESENT, aInfo);
1122     }
1123   }
1124 
1125   bool isArray;
1126   if (!JS::IsArrayObject(cx, symbols, &isArray)) {
1127     return NS_ERROR_FAILURE;
1128   }
1129   if (!isArray) {
1130     return ReportOnCallerUTF8(cxhelper, ERROR_NOT_AN_ARRAY, aInfo);
1131   }
1132 
1133   RootedObject symbolsObj(cx, &symbols.toObject());
1134 
1135   // Iterate over symbols array, installing symbols on targetObj:
1136 
1137   uint32_t symbolCount = 0;
1138   if (!JS::GetArrayLength(cx, symbolsObj, &symbolCount)) {
1139     return ReportOnCallerUTF8(cxhelper, ERROR_GETTING_ARRAY_LENGTH, aInfo);
1140   }
1141 
1142 #ifdef DEBUG
1143   nsAutoCString logBuffer;
1144 #endif
1145 
1146   aExports.set(JS_NewPlainObject(cx));
1147   if (!aExports) {
1148     return NS_ERROR_OUT_OF_MEMORY;
1149   }
1150 
1151   bool missing = false;
1152 
1153   RootedValue value(cx);
1154   RootedId symbolId(cx);
1155   RootedObject symbolHolder(cx);
1156   for (uint32_t i = 0; i < symbolCount; ++i) {
1157     if (!JS_GetElement(cx, symbolsObj, i, &value) || !value.isString() ||
1158         !JS_ValueToId(cx, value, &symbolId)) {
1159       return ReportOnCallerUTF8(cxhelper, ERROR_ARRAY_ELEMENT, aInfo, i);
1160     }
1161 
1162     symbolHolder = ResolveModuleObjectPropertyById(cx, aMod->obj, symbolId);
1163     if (!symbolHolder ||
1164         !JS_GetPropertyById(cx, symbolHolder, symbolId, &value)) {
1165       RootedString symbolStr(cx, symbolId.toString());
1166       JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, symbolStr);
1167       if (!bytes) {
1168         return NS_ERROR_FAILURE;
1169       }
1170       return ReportOnCallerUTF8(cxhelper, ERROR_GETTING_SYMBOL, aInfo,
1171                                 bytes.get());
1172     }
1173 
1174     // It's possible |value| is the uninitialized lexical MagicValue when
1175     // there's a cyclic import: const obj = ChromeUtils.import("parent.jsm").
1176     if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) {
1177       RootedString symbolStr(cx, symbolId.toString());
1178       JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, symbolStr);
1179       if (!bytes) {
1180         return NS_ERROR_FAILURE;
1181       }
1182       return ReportOnCallerUTF8(cxhelper, ERROR_UNINITIALIZED_SYMBOL, aInfo,
1183                                 bytes.get());
1184     }
1185 
1186     if (value.isUndefined()) {
1187       missing = true;
1188     }
1189 
1190     if (!JS_SetPropertyById(cx, aExports, symbolId, value)) {
1191       RootedString symbolStr(cx, symbolId.toString());
1192       JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, symbolStr);
1193       if (!bytes) {
1194         return NS_ERROR_FAILURE;
1195       }
1196       return ReportOnCallerUTF8(cxhelper, ERROR_GETTING_SYMBOL, aInfo,
1197                                 bytes.get());
1198     }
1199 #ifdef DEBUG
1200     if (i == 0) {
1201       logBuffer.AssignLiteral("Installing symbols [ ");
1202     }
1203     JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, symbolId.toString());
1204     if (!!bytes) {
1205       logBuffer.Append(bytes.get());
1206     }
1207     logBuffer.Append(' ');
1208     if (i == symbolCount - 1) {
1209       nsCString location;
1210       MOZ_TRY(aInfo.GetLocation(location));
1211       LOG(("%s] from %s\n", logBuffer.get(), location.get()));
1212     }
1213 #endif
1214   }
1215 
1216   // Don't cache the exports object if any of its exported symbols are
1217   // missing. If the module hasn't finished loading yet, they may be
1218   // defined the next time we try to import it.
1219   if (!missing) {
1220     aMod->exports = aExports;
1221   }
1222   return NS_OK;
1223 }
1224 
Import(JSContext * aCx,const nsACString & aLocation,JS::MutableHandleObject aModuleGlobal,JS::MutableHandleObject aModuleExports,bool aIgnoreExports)1225 nsresult mozJSComponentLoader::Import(JSContext* aCx,
1226                                       const nsACString& aLocation,
1227                                       JS::MutableHandleObject aModuleGlobal,
1228                                       JS::MutableHandleObject aModuleExports,
1229                                       bool aIgnoreExports) {
1230   mInitialized = true;
1231 
1232   AUTO_PROFILER_MARKER_TEXT(
1233       "ChromeUtils.import", JS,
1234       MarkerOptions(MarkerStack::Capture(),
1235                     MarkerInnerWindowIdFromJSContext(aCx)),
1236       aLocation);
1237 
1238   ComponentLoaderInfo info(aLocation);
1239 
1240   nsresult rv;
1241   ModuleEntry* mod;
1242   UniquePtr<ModuleEntry> newEntry;
1243   if (!mImports.Get(info.Key(), &mod) &&
1244       !mInProgressImports.Get(info.Key(), &mod)) {
1245     // We're trying to import a new JSM, but we're late in shutdown and this
1246     // will likely not succeed and might even crash, so fail here.
1247     if (PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal)) {
1248       return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
1249     }
1250 
1251     newEntry = MakeUnique<ModuleEntry>(RootingContext::get(aCx));
1252 
1253     // Note: This implies EnsureURI().
1254     MOZ_TRY(info.EnsureResolvedURI());
1255 
1256     // get the JAR if there is one
1257     nsCOMPtr<nsIJARURI> jarURI;
1258     jarURI = do_QueryInterface(info.ResolvedURI(), &rv);
1259     nsCOMPtr<nsIFileURL> baseFileURL;
1260     if (NS_SUCCEEDED(rv)) {
1261       nsCOMPtr<nsIURI> baseURI;
1262       while (jarURI) {
1263         jarURI->GetJARFile(getter_AddRefs(baseURI));
1264         jarURI = do_QueryInterface(baseURI, &rv);
1265       }
1266       baseFileURL = do_QueryInterface(baseURI, &rv);
1267       NS_ENSURE_SUCCESS(rv, rv);
1268     } else {
1269       baseFileURL = do_QueryInterface(info.ResolvedURI(), &rv);
1270       NS_ENSURE_SUCCESS(rv, rv);
1271     }
1272 
1273     nsCOMPtr<nsIFile> sourceFile;
1274     rv = baseFileURL->GetFile(getter_AddRefs(sourceFile));
1275     NS_ENSURE_SUCCESS(rv, rv);
1276 
1277     rv = info.ResolvedURI()->GetSpec(newEntry->resolvedURL);
1278     NS_ENSURE_SUCCESS(rv, rv);
1279 
1280     nsCString* existingPath;
1281     if (mLocations.Get(newEntry->resolvedURL, &existingPath) &&
1282         *existingPath != info.Key()) {
1283       return NS_ERROR_UNEXPECTED;
1284     }
1285 
1286     mLocations.InsertOrUpdate(newEntry->resolvedURL,
1287                               MakeUnique<nsCString>(info.Key()));
1288 
1289     RootedValue exception(aCx);
1290     {
1291       mInProgressImports.InsertOrUpdate(info.Key(), newEntry.get());
1292       auto cleanup =
1293           MakeScopeExit([&]() { mInProgressImports.Remove(info.Key()); });
1294 
1295       rv = ObjectForLocation(info, sourceFile, &newEntry->obj,
1296                              &newEntry->thisObjectKey, &newEntry->location,
1297                              true, &exception);
1298     }
1299 
1300     if (NS_FAILED(rv)) {
1301       mLocations.Remove(newEntry->resolvedURL);
1302       if (!exception.isUndefined()) {
1303         // An exception was thrown during compilation. Propagate it
1304         // out to our caller so they can report it.
1305         if (!JS_WrapValue(aCx, &exception)) {
1306           return NS_ERROR_OUT_OF_MEMORY;
1307         }
1308         JS_SetPendingException(aCx, exception);
1309         return NS_ERROR_FAILURE;
1310       }
1311 
1312       // Something failed, but we don't know what it is, guess.
1313       return NS_ERROR_FILE_NOT_FOUND;
1314     }
1315 
1316 #ifdef STARTUP_RECORDER_ENABLED
1317     if (Preferences::GetBool("browser.startup.record", false)) {
1318       newEntry->importStack = xpc_PrintJSStack(aCx, false, false, false).get();
1319     }
1320 #endif
1321 
1322     mod = newEntry.get();
1323   }
1324 
1325   MOZ_ASSERT(mod->obj, "Import table contains entry with no object");
1326   aModuleGlobal.set(mod->obj);
1327 
1328   JS::RootedObject exports(aCx, mod->exports);
1329   if (!exports && !aIgnoreExports) {
1330     MOZ_TRY(ExtractExports(aCx, info, mod, &exports));
1331   }
1332 
1333   if (exports && !JS_WrapObject(aCx, &exports)) {
1334     mLocations.Remove(newEntry->resolvedURL);
1335     return NS_ERROR_FAILURE;
1336   }
1337   aModuleExports.set(exports);
1338 
1339   // Cache this module for later
1340   if (newEntry) {
1341     mImports.InsertOrUpdate(info.Key(), std::move(newEntry));
1342   }
1343 
1344   return NS_OK;
1345 }
1346 
Unload(const nsACString & aLocation)1347 nsresult mozJSComponentLoader::Unload(const nsACString& aLocation) {
1348   if (!mInitialized) {
1349     return NS_OK;
1350   }
1351 
1352   ComponentLoaderInfo info(aLocation);
1353 
1354   ModuleEntry* mod;
1355   if (mImports.Get(info.Key(), &mod)) {
1356     mLocations.Remove(mod->resolvedURL);
1357     mImports.Remove(info.Key());
1358   }
1359 
1360   // If this is the last module to be unloaded, we will leak mLoaderGlobal
1361   // until UnloadModules is called. So be it.
1362 
1363   return NS_OK;
1364 }
1365 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const1366 size_t mozJSComponentLoader::ModuleEntry::SizeOfIncludingThis(
1367     MallocSizeOf aMallocSizeOf) const {
1368   size_t n = aMallocSizeOf(this);
1369   n += aMallocSizeOf(location);
1370 
1371   return n;
1372 }
1373 
1374 /* static */
GetFactory(const mozilla::Module & module,const mozilla::Module::CIDEntry & entry)1375 already_AddRefed<nsIFactory> mozJSComponentLoader::ModuleEntry::GetFactory(
1376     const mozilla::Module& module, const mozilla::Module::CIDEntry& entry) {
1377   const ModuleEntry& self = static_cast<const ModuleEntry&>(module);
1378   MOZ_ASSERT(self.getfactoryobj, "Handing out an uninitialized module?");
1379 
1380   nsCOMPtr<nsIFactory> f;
1381   nsresult rv = self.getfactoryobj->Get(*entry.cid, getter_AddRefs(f));
1382   if (NS_FAILED(rv)) {
1383     return nullptr;
1384   }
1385 
1386   return f.forget();
1387 }
1388 
1389 //----------------------------------------------------------------------
1390 
JSCLContextHelper(JSContext * aCx)1391 JSCLContextHelper::JSCLContextHelper(JSContext* aCx)
1392     : mContext(aCx), mBuf(nullptr) {}
1393 
~JSCLContextHelper()1394 JSCLContextHelper::~JSCLContextHelper() {
1395   if (mBuf) {
1396     JS_ReportErrorUTF8(mContext, "%s", mBuf.get());
1397   }
1398 }
1399 
reportErrorAfterPop(UniqueChars && buf)1400 void JSCLContextHelper::reportErrorAfterPop(UniqueChars&& buf) {
1401   MOZ_ASSERT(!mBuf, "Already called reportErrorAfterPop");
1402   mBuf = std::move(buf);
1403 }
1404