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