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