mozJSModuleLoader.cpp (36388B)
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 "ScriptLoadRequest.h" 8 #include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_ASSERT_IF 9 #include "mozilla/Attributes.h" 10 #include "mozilla/RefPtr.h" // RefPtr, mozilla::StaticRefPtr 11 #include "mozilla/Utf8.h" // mozilla::Utf8Unit 12 13 #include "mozilla/Logging.h" 14 #include "mozilla/dom/RequestBinding.h" 15 #ifdef ANDROID 16 # include <android/log.h> 17 #endif 18 #ifdef XP_WIN 19 # include <windows.h> 20 #endif 21 22 #include "jsapi.h" 23 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject 24 #include "js/CharacterEncoding.h" 25 #include "js/CompilationAndEvaluation.h" 26 #include "js/CompileOptions.h" // JS::CompileOptions 27 #include "js/ErrorReport.h" // JS_ReportErrorUTF8, JSErrorReport 28 #include "js/Exception.h" // JS_ErrorFromException 29 #include "js/friend/JSMEnvironment.h" // JS::ExecuteInJSMEnvironment, JS::GetJSMEnvironmentOfScriptedCaller, JS::NewJSMEnvironment 30 #include "js/friend/ErrorMessages.h" // JSMSG_* 31 #include "js/loader/ModuleLoadRequest.h" 32 #include "js/Object.h" // JS::GetCompartment 33 #include "js/Printf.h" 34 #include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_DefineProperty, JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_HasOwnProperty, JS_HasOwnPropertyById, JS_SetProperty, JS_SetPropertyById 35 #include "js/PropertySpec.h" 36 #include "js/SourceText.h" // JS::SourceText 37 #include "nsCOMPtr.h" 38 #include "nsDirectoryServiceDefs.h" 39 #include "nsDirectoryServiceUtils.h" 40 #include "nsIFile.h" 41 #include "mozJSModuleLoader.h" 42 #include "mozJSLoaderUtils.h" 43 #include "nsIFileURL.h" 44 #include "nsIJARURI.h" 45 #include "nsIChannel.h" 46 #include "nsIStreamListener.h" 47 #include "nsNetUtil.h" 48 #include "nsJSUtils.h" 49 #include "xpcprivate.h" 50 #include "xpcpublic.h" 51 #include "nsContentUtils.h" 52 #include "nsContentSecurityUtils.h" 53 #include "nsXULAppAPI.h" 54 #include "WrapperFactory.h" 55 #include "JSServices.h" 56 57 #include "mozilla/scache/StartupCache.h" 58 #include "mozilla/scache/StartupCacheUtils.h" 59 #include "mozilla/ClearOnShutdown.h" 60 #include "mozilla/MacroForEach.h" 61 #include "mozilla/Preferences.h" 62 #include "mozilla/ProfilerLabels.h" 63 #include "mozilla/ProfilerMarkers.h" 64 #include "mozilla/ResultExtensions.h" 65 #include "mozilla/ScriptPreloader.h" 66 #include "mozilla/StaticPrefs_browser.h" 67 #include "mozilla/Try.h" 68 #include "mozilla/dom/AutoEntryScript.h" 69 #include "mozilla/dom/ReferrerPolicyBinding.h" 70 #include "mozilla/dom/ScriptSettings.h" 71 #include "mozilla/dom/WorkerCommon.h" // dom::GetWorkerPrivateFromContext 72 #include "mozilla/dom/WorkerPrivate.h" // dom::WorkerPrivate, dom::AutoSyncLoopHolder 73 #include "mozilla/dom/WorkerRef.h" // dom::StrongWorkerRef, dom::ThreadSafeWorkerRef 74 #include "mozilla/dom/WorkerRunnable.h" // dom::MainThreadStopSyncLoopRunnable 75 76 using namespace mozilla; 77 using namespace mozilla::scache; 78 using namespace mozilla::loader; 79 using namespace xpc; 80 using namespace JS; 81 82 #define JS_CACHE_PREFIX(aScopeType, aCompilationTarget) \ 83 "jsloader/" aScopeType "/" aCompilationTarget 84 85 // MOZ_LOG=JSModuleLoader:5 86 static LazyLogModule gJSCLLog("JSModuleLoader"); 87 88 #define LOG(args) MOZ_LOG(gJSCLLog, mozilla::LogLevel::Debug, args) 89 90 static bool Dump(JSContext* cx, unsigned argc, Value* vp) { 91 if (!nsJSUtils::DumpEnabled()) { 92 return true; 93 } 94 95 CallArgs args = CallArgsFromVp(argc, vp); 96 97 if (args.length() == 0) { 98 return true; 99 } 100 101 RootedString str(cx, JS::ToString(cx, args[0])); 102 if (!str) { 103 return false; 104 } 105 106 JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str); 107 if (!utf8str) { 108 return false; 109 } 110 111 MOZ_LOG(nsContentUtils::DOMDumpLog(), mozilla::LogLevel::Debug, 112 ("[SystemGlobal.Dump] %s", utf8str.get())); 113 #ifdef ANDROID 114 __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.get()); 115 #endif 116 #ifdef XP_WIN 117 if (IsDebuggerPresent()) { 118 nsAutoJSString wstr; 119 if (!wstr.init(cx, str)) { 120 return false; 121 } 122 OutputDebugStringW(wstr.get()); 123 } 124 #endif 125 fputs(utf8str.get(), stdout); 126 fflush(stdout); 127 return true; 128 } 129 130 static bool Debug(JSContext* cx, unsigned argc, Value* vp) { 131 #ifdef DEBUG 132 return Dump(cx, argc, vp); 133 #else 134 return true; 135 #endif 136 } 137 138 static const JSFunctionSpec gGlobalFun[] = { 139 JS_FN("dump", Dump, 1, 0), JS_FN("debug", Debug, 1, 0), 140 JS_FN("atob", Atob, 1, 0), JS_FN("btoa", Btoa, 1, 0), JS_FS_END}; 141 142 mozJSModuleLoader::mozJSModuleLoader() 143 : 144 #ifdef STARTUP_RECORDER_ENABLED 145 mImportStacks(16), 146 #endif 147 mInitialized(false), 148 mLoaderGlobal(dom::RootingCx()), 149 mServicesObj(dom::RootingCx()) { 150 } 151 152 #define ENSURE_DEP(name) \ 153 { \ 154 nsresult rv = Ensure##name(); \ 155 NS_ENSURE_SUCCESS(rv, rv); \ 156 } 157 #define ENSURE_DEPS(...) MOZ_FOR_EACH(ENSURE_DEP, (), (__VA_ARGS__)); 158 #define BEGIN_ENSURE(self, ...) \ 159 { \ 160 if (m##self) return NS_OK; \ 161 ENSURE_DEPS(__VA_ARGS__); \ 162 } 163 164 class MOZ_STACK_CLASS ModuleLoaderInfo { 165 public: 166 explicit ModuleLoaderInfo(const nsACString& aLocation) 167 : mLocation(&aLocation) {} 168 explicit ModuleLoaderInfo(JS::loader::ModuleLoadRequest* aRequest) 169 : mLocation(nullptr), mURI(aRequest->URI()) {} 170 171 nsIIOService* IOService() { 172 MOZ_ASSERT(mIOService); 173 return mIOService; 174 } 175 nsresult EnsureIOService() { 176 if (mIOService) { 177 return NS_OK; 178 } 179 nsresult rv; 180 mIOService = do_GetIOService(&rv); 181 return rv; 182 } 183 184 nsIURI* URI() { 185 MOZ_ASSERT(mURI); 186 return mURI; 187 } 188 nsresult EnsureURI() { 189 BEGIN_ENSURE(URI, IOService); 190 MOZ_ASSERT(mLocation); 191 return mIOService->NewURI(*mLocation, nullptr, nullptr, 192 getter_AddRefs(mURI)); 193 } 194 195 nsIChannel* ScriptChannel() { 196 MOZ_ASSERT(mScriptChannel); 197 return mScriptChannel; 198 } 199 nsresult EnsureScriptChannel() { 200 BEGIN_ENSURE(ScriptChannel, IOService, URI); 201 202 return NS_NewChannel( 203 getter_AddRefs(mScriptChannel), mURI, 204 nsContentUtils::GetSystemPrincipal(), 205 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 206 nsIContentPolicy::TYPE_SCRIPT, 207 /* aCookieJarSettings = */ nullptr, 208 /* aPerformanceStorage = */ nullptr, 209 /* aLoadGroup = */ nullptr, /* aCallbacks = */ nullptr, 210 nsIRequest::LOAD_NORMAL, mIOService, /* aSandboxFlags = */ 0); 211 } 212 213 nsIURI* ResolvedURI() { 214 MOZ_ASSERT(mResolvedURI); 215 return mResolvedURI; 216 } 217 nsresult EnsureResolvedURI() { 218 BEGIN_ENSURE(ResolvedURI, URI); 219 return ResolveURI(mURI, getter_AddRefs(mResolvedURI)); 220 } 221 222 private: 223 const nsACString* mLocation; 224 nsCOMPtr<nsIIOService> mIOService; 225 nsCOMPtr<nsIURI> mURI; 226 nsCOMPtr<nsIChannel> mScriptChannel; 227 nsCOMPtr<nsIURI> mResolvedURI; 228 }; 229 230 #undef BEGIN_ENSURE 231 #undef ENSURE_DEPS 232 #undef ENSURE_DEP 233 234 mozJSModuleLoader::~mozJSModuleLoader() { 235 MOZ_ASSERT(!mInitialized, 236 "UnloadModules() was not explicitly called before cleaning up " 237 "mozJSModuleLoader"); 238 239 if (mInitialized) { 240 UnloadModules(); 241 } 242 } 243 244 StaticRefPtr<mozJSModuleLoader> mozJSModuleLoader::sSelf; 245 StaticRefPtr<mozJSModuleLoader> mozJSModuleLoader::sDevToolsLoader; 246 247 void mozJSModuleLoader::FindTargetObject(JSContext* aCx, 248 MutableHandleObject aTargetObject) { 249 aTargetObject.set(JS::GetJSMEnvironmentOfScriptedCaller(aCx)); 250 251 // The above could fail if the scripted caller is not a JSM (it could be a DOM 252 // scope, for instance). 253 // 254 // If the target object was not in the JSM shared global, return the global 255 // instead. This is needed when calling the subscript loader within a frame 256 // script, since it the FrameScript NSVO will have been found. 257 if (!aTargetObject || 258 !IsLoaderGlobal(JS::GetNonCCWObjectGlobal(aTargetObject))) { 259 aTargetObject.set(JS::GetScriptedCallerGlobal(aCx)); 260 261 // Return nullptr if the scripted caller is in a different compartment. 262 if (JS::GetCompartment(aTargetObject) != js::GetContextCompartment(aCx)) { 263 aTargetObject.set(nullptr); 264 } 265 } 266 } 267 268 void mozJSModuleLoader::InitStatics() { 269 MOZ_ASSERT(!sSelf); 270 sSelf = new mozJSModuleLoader(); 271 272 dom::AutoJSAPI jsapi; 273 jsapi.Init(); 274 JSContext* cx = jsapi.cx(); 275 sSelf->InitSharedGlobal(cx); 276 277 NonSharedGlobalSyncModuleLoaderScope::InitStatics(); 278 } 279 280 void mozJSModuleLoader::UnloadLoaders() { 281 if (sSelf) { 282 sSelf->Unload(); 283 } 284 if (sDevToolsLoader) { 285 sDevToolsLoader->Unload(); 286 } 287 } 288 289 void mozJSModuleLoader::Unload() { 290 UnloadModules(); 291 292 if (mModuleLoader) { 293 mModuleLoader->Shutdown(); 294 mModuleLoader = nullptr; 295 } 296 } 297 298 void mozJSModuleLoader::ShutdownLoaders() { 299 MOZ_ASSERT(sSelf); 300 sSelf = nullptr; 301 302 if (sDevToolsLoader) { 303 sDevToolsLoader = nullptr; 304 } 305 } 306 307 mozJSModuleLoader* mozJSModuleLoader::GetOrCreateDevToolsLoader( 308 JSContext* aCx) { 309 if (sDevToolsLoader) { 310 return sDevToolsLoader; 311 } 312 sDevToolsLoader = new mozJSModuleLoader(); 313 314 sDevToolsLoader->InitSharedGlobal(aCx); 315 316 return sDevToolsLoader; 317 } 318 319 void mozJSModuleLoader::InitSyncModuleLoaderForGlobal( 320 nsIGlobalObject* aGlobal) { 321 MOZ_ASSERT(!mLoaderGlobal); 322 MOZ_ASSERT(!mModuleLoader); 323 324 RefPtr<SyncScriptLoader> scriptLoader = new SyncScriptLoader; 325 mModuleLoader = new SyncModuleLoader(scriptLoader, aGlobal); 326 mLoaderGlobal = aGlobal->GetGlobalJSObject(); 327 } 328 329 void mozJSModuleLoader::DisconnectSyncModuleLoaderFromGlobal() { 330 MOZ_ASSERT(mLoaderGlobal); 331 MOZ_ASSERT(mModuleLoader); 332 333 mLoaderGlobal = nullptr; 334 Unload(); 335 } 336 337 /* static */ 338 bool mozJSModuleLoader::IsSharedSystemGlobal(nsIGlobalObject* aGlobal) { 339 return sSelf->IsLoaderGlobal(aGlobal->GetGlobalJSObject()); 340 } 341 342 /* static */ 343 bool mozJSModuleLoader::IsDevToolsLoaderGlobal(nsIGlobalObject* aGlobal) { 344 return sDevToolsLoader && 345 sDevToolsLoader->IsLoaderGlobal(aGlobal->GetGlobalJSObject()); 346 } 347 348 #ifdef STARTUP_RECORDER_ENABLED 349 template <class Key, class Data, class UserData, class Converter> 350 static size_t SizeOfStringTableExcludingThis( 351 const nsBaseHashtable<Key, Data, UserData, Converter>& aTable, 352 MallocSizeOf aMallocSizeOf) { 353 size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf); 354 for (const auto& entry : aTable) { 355 n += entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); 356 n += entry.GetData().SizeOfExcludingThisIfUnshared(aMallocSizeOf); 357 } 358 return n; 359 } 360 #endif 361 362 size_t mozJSModuleLoader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { 363 size_t n = aMallocSizeOf(this); 364 #ifdef STARTUP_RECORDER_ENABLED 365 n += SizeOfStringTableExcludingThis(mImportStacks, aMallocSizeOf); 366 #endif 367 return n; 368 } 369 370 void mozJSModuleLoader::CreateLoaderGlobal(JSContext* aCx, 371 const nsACString& aLocation, 372 MutableHandleObject aGlobal) { 373 auto systemGlobal = MakeRefPtr<SystemGlobal>(); 374 RealmOptions options; 375 auto& creationOptions = options.creationOptions(); 376 377 creationOptions.setFreezeBuiltins(true).setNewCompartmentInSystemZone(); 378 if (IsDevToolsLoader()) { 379 creationOptions.setInvisibleToDebugger(true); 380 } 381 xpc::SetPrefableRealmOptions(options); 382 383 // Defer firing OnNewGlobalObject until after the __URI__ property has 384 // been defined so the JS debugger can tell what module the global is 385 // for 386 RootedObject global(aCx); 387 388 #ifdef DEBUG 389 // See mozJSModuleLoader::DefineJSServices. 390 mIsInitializingLoaderGlobal = true; 391 #endif 392 nsresult rv = xpc::InitClassesWithNewWrappedGlobal( 393 aCx, static_cast<nsIGlobalObject*>(systemGlobal), 394 nsContentUtils::GetSystemPrincipal(), xpc::DONT_FIRE_ONNEWGLOBALHOOK, 395 options, &global); 396 #ifdef DEBUG 397 mIsInitializingLoaderGlobal = false; 398 #endif 399 NS_ENSURE_SUCCESS_VOID(rv); 400 401 NS_ENSURE_TRUE_VOID(global); 402 403 systemGlobal->SetGlobalObject(global); 404 405 JSAutoRealm ar(aCx, global); 406 if (!JS_DefineFunctions(aCx, global, gGlobalFun)) { 407 return; 408 } 409 410 if (!CreateJSServices(aCx)) { 411 return; 412 } 413 414 if (!DefineJSServices(aCx, global)) { 415 return; 416 } 417 418 // Set the location information for the new global, so that tools like 419 // about:memory may use that information 420 xpc::SetLocationForGlobal(global, aLocation); 421 422 MOZ_ASSERT(!mModuleLoader); 423 RefPtr<SyncScriptLoader> scriptLoader = new SyncScriptLoader; 424 mModuleLoader = new SyncModuleLoader(scriptLoader, systemGlobal); 425 systemGlobal->InitModuleLoader(mModuleLoader); 426 427 aGlobal.set(global); 428 } 429 430 void mozJSModuleLoader::InitSharedGlobal(JSContext* aCx) { 431 JS::RootedObject globalObj(aCx); 432 433 CreateLoaderGlobal( 434 aCx, IsDevToolsLoader() ? "DevTools global"_ns : "shared JSM global"_ns, 435 &globalObj); 436 437 // If we fail to create a module global this early, we're not going to 438 // get very far, so just bail out now. 439 MOZ_RELEASE_ASSERT(globalObj); 440 mLoaderGlobal = globalObj; 441 442 // AutoEntryScript required to invoke debugger hook, which is a 443 // Gecko-specific concept at present. 444 dom::AutoEntryScript aes(globalObj, "module loader report global"); 445 JS_FireOnNewGlobalObject(aes.cx(), globalObj); 446 } 447 448 // Read script file on the main thread and pass it back to worker. 449 class ScriptReaderRunnable final : public nsIRunnable, 450 public nsINamed, 451 public nsIStreamListener { 452 public: 453 ScriptReaderRunnable(RefPtr<dom::ThreadSafeWorkerRef>&& aWorkerRef, 454 nsIEventTarget* aSyncLoopTarget, 455 const nsCString& aLocation) 456 : mLocation(aLocation), 457 mRv(NS_ERROR_FAILURE), 458 mWorkerRef(std::move(aWorkerRef)), 459 mSyncLoopTarget(aSyncLoopTarget) {} 460 461 NS_DECL_THREADSAFE_ISUPPORTS 462 463 nsCString& Data() { return mData; } 464 465 nsresult Result() const { return mRv; } 466 467 // nsIRunnable 468 469 NS_IMETHOD 470 Run() override { 471 MOZ_ASSERT(NS_IsMainThread()); 472 473 nsresult rv = StartReadScriptFromLocation(); 474 if (NS_FAILED(rv)) { 475 OnComplete(rv); 476 } 477 478 return NS_OK; 479 } 480 481 // nsINamed 482 483 NS_IMETHOD 484 GetName(nsACString& aName) override { 485 aName.AssignLiteral("ScriptReaderRunnable"); 486 return NS_OK; 487 } 488 489 // nsIStreamListener 490 491 NS_IMETHOD OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream, 492 uint64_t aOffset, uint32_t aCount) override { 493 uint32_t read = 0; 494 return aInputStream->ReadSegments(AppendSegmentToData, this, aCount, &read); 495 } 496 497 // nsIRequestObserver 498 499 NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override { return NS_OK; } 500 501 NS_IMETHOD OnStopRequest(nsIRequest* aRequest, 502 nsresult aStatusCode) override { 503 OnComplete(aStatusCode); 504 return NS_OK; 505 } 506 507 private: 508 ~ScriptReaderRunnable() = default; 509 510 static nsresult AppendSegmentToData(nsIInputStream* aInputStream, 511 void* aClosure, const char* aRawSegment, 512 uint32_t aToOffset, uint32_t aCount, 513 uint32_t* outWrittenCount) { 514 auto* self = static_cast<ScriptReaderRunnable*>(aClosure); 515 self->mData.Append(aRawSegment, aCount); 516 *outWrittenCount = aCount; 517 return NS_OK; 518 } 519 520 void OnComplete(nsresult aRv) { 521 MOZ_ASSERT(NS_IsMainThread()); 522 MOZ_ASSERT(mWorkerRef); 523 524 mRv = aRv; 525 526 RefPtr<dom::MainThreadStopSyncLoopRunnable> runnable = 527 new dom::MainThreadStopSyncLoopRunnable(std::move(mSyncLoopTarget), 528 mRv); 529 MOZ_ALWAYS_TRUE(runnable->Dispatch(mWorkerRef->Private())); 530 531 mWorkerRef = nullptr; 532 mSyncLoopTarget = nullptr; 533 } 534 535 nsresult StartReadScriptFromLocation() { 536 MOZ_ASSERT(NS_IsMainThread()); 537 538 ModuleLoaderInfo info(mLocation); 539 nsresult rv = info.EnsureScriptChannel(); 540 NS_ENSURE_SUCCESS(rv, rv); 541 542 return info.ScriptChannel()->AsyncOpen(this); 543 } 544 545 private: 546 nsAutoCString mLocation; 547 nsCString mData; 548 nsresult mRv; 549 550 RefPtr<dom::ThreadSafeWorkerRef> mWorkerRef; 551 552 nsCOMPtr<nsIEventTarget> mSyncLoopTarget; 553 }; 554 555 NS_IMPL_ISUPPORTS(ScriptReaderRunnable, nsIRunnable, nsINamed, 556 nsIStreamListener) 557 558 /* static */ 559 nsresult mozJSModuleLoader::ReadScriptOnMainThread(JSContext* aCx, 560 const nsCString& aLocation, 561 nsCString& aData) { 562 dom::WorkerPrivate* workerPrivate = dom::GetWorkerPrivateFromContext(aCx); 563 MOZ_ASSERT(workerPrivate); 564 565 dom::AutoSyncLoopHolder syncLoop(workerPrivate, dom::WorkerStatus::Canceling); 566 nsCOMPtr<nsISerialEventTarget> syncLoopTarget = 567 syncLoop.GetSerialEventTarget(); 568 if (!syncLoopTarget) { 569 return NS_ERROR_DOM_INVALID_STATE_ERR; 570 } 571 572 RefPtr<dom::StrongWorkerRef> workerRef = dom::StrongWorkerRef::Create( 573 workerPrivate, "mozJSModuleLoader::ScriptReaderRunnable", nullptr); 574 if (!workerRef) { 575 return NS_ERROR_DOM_INVALID_STATE_ERR; 576 } 577 RefPtr<dom::ThreadSafeWorkerRef> tsWorkerRef = 578 MakeRefPtr<dom::ThreadSafeWorkerRef>(workerRef); 579 580 RefPtr<ScriptReaderRunnable> runnable = new ScriptReaderRunnable( 581 std::move(tsWorkerRef), syncLoopTarget, aLocation); 582 583 if (NS_FAILED(NS_DispatchToMainThread(runnable))) { 584 return NS_ERROR_FAILURE; 585 } 586 587 syncLoop.Run(); 588 589 if (NS_FAILED(runnable->Result())) { 590 return runnable->Result(); 591 } 592 593 aData = std::move(runnable->Data()); 594 595 return NS_OK; 596 } 597 598 /* static */ 599 nsresult mozJSModuleLoader::LoadSingleModuleScriptOnWorker( 600 SyncModuleLoader* aModuleLoader, JSContext* aCx, 601 JS::loader::ModuleLoadRequest* aRequest, MutableHandleScript aScriptOut) { 602 nsAutoCString location; 603 nsresult rv = aRequest->URI()->GetSpec(location); 604 NS_ENSURE_SUCCESS(rv, rv); 605 606 nsCString data; 607 rv = ReadScriptOnMainThread(aCx, location, data); 608 NS_ENSURE_SUCCESS(rv, rv); 609 610 CompileOptions options(aCx); 611 // NOTE: ScriptPreloader::FillCompileOptionsForCachedStencil shouldn't be 612 // used here because the module is put into the worker global's 613 // module map, instead of the shared global's module map, where the 614 // worker module loader doesn't support lazy source. 615 // Accessing the source requires the synchronous communication with the 616 // main thread, and supporting it requires too much complexity compared 617 // to the benefit. 618 options.setNoScriptRval(true); 619 options.setFileAndLine(location.BeginReading(), 1); 620 SetModuleOptions(options); 621 622 // Worker global doesn't have the source hook. 623 MOZ_ASSERT(!options.sourceIsLazy); 624 625 JS::SourceText<mozilla::Utf8Unit> srcBuf; 626 if (!srcBuf.init(aCx, data.get(), data.Length(), 627 JS::SourceOwnership::Borrowed)) { 628 return NS_ERROR_FAILURE; 629 } 630 631 RefPtr<JS::Stencil> stencil = 632 CompileModuleScriptToStencil(aCx, options, srcBuf); 633 if (!stencil) { 634 return NS_ERROR_FAILURE; 635 } 636 637 aScriptOut.set(InstantiateStencil(aCx, stencil)); 638 639 return NS_OK; 640 } 641 642 /* static */ 643 nsresult mozJSModuleLoader::LoadSingleModuleScript( 644 SyncModuleLoader* aModuleLoader, JSContext* aCx, 645 JS::loader::ModuleLoadRequest* aRequest, MutableHandleScript aScriptOut) { 646 AUTO_PROFILER_MARKER_TEXT( 647 "ChromeUtils.importESModule static import", JS, 648 MarkerOptions(MarkerStack::Capture(), 649 MarkerInnerWindowIdFromJSContext(aCx)), 650 nsContentUtils::TruncatedURLForDisplay(aRequest->URI())); 651 652 if (!NS_IsMainThread()) { 653 return LoadSingleModuleScriptOnWorker(aModuleLoader, aCx, aRequest, 654 aScriptOut); 655 } 656 657 ModuleLoaderInfo info(aRequest); 658 nsresult rv = info.EnsureResolvedURI(); 659 NS_ENSURE_SUCCESS(rv, rv); 660 661 nsCOMPtr<nsIFile> sourceFile; 662 rv = GetSourceFile(info.ResolvedURI(), getter_AddRefs(sourceFile)); 663 NS_ENSURE_SUCCESS(rv, rv); 664 665 bool realFile = LocationIsRealFile(aRequest->URI()); 666 667 RootedScript script(aCx); 668 rv = GetScriptForLocation(aCx, info, sourceFile, realFile, aScriptOut); 669 NS_ENSURE_SUCCESS(rv, rv); 670 671 #ifdef STARTUP_RECORDER_ENABLED 672 if (aModuleLoader == sSelf->mModuleLoader) { 673 sSelf->RecordImportStack(aCx, aRequest); 674 } else if (sDevToolsLoader && 675 aModuleLoader == sDevToolsLoader->mModuleLoader) { 676 sDevToolsLoader->RecordImportStack(aCx, aRequest); 677 } else { 678 // NOTE: Do not record import stack for non-shared globals, given the 679 // loader is associated with the global only while importing. 680 } 681 #endif 682 683 return NS_OK; 684 } 685 686 /* static */ 687 nsresult mozJSModuleLoader::GetSourceFile(nsIURI* aResolvedURI, 688 nsIFile** aSourceFileOut) { 689 // Get the JAR if there is one. 690 nsCOMPtr<nsIJARURI> jarURI; 691 nsresult rv = NS_OK; 692 jarURI = do_QueryInterface(aResolvedURI, &rv); 693 nsCOMPtr<nsIFileURL> baseFileURL; 694 if (NS_SUCCEEDED(rv)) { 695 nsCOMPtr<nsIURI> baseURI; 696 while (jarURI) { 697 jarURI->GetJARFile(getter_AddRefs(baseURI)); 698 jarURI = do_QueryInterface(baseURI, &rv); 699 } 700 baseFileURL = do_QueryInterface(baseURI, &rv); 701 NS_ENSURE_SUCCESS(rv, rv); 702 } else { 703 baseFileURL = do_QueryInterface(aResolvedURI, &rv); 704 NS_ENSURE_SUCCESS(rv, rv); 705 } 706 707 return baseFileURL->GetFile(aSourceFileOut); 708 } 709 710 /* static */ 711 bool mozJSModuleLoader::LocationIsRealFile(nsIURI* aURI) { 712 // We need to be extra careful checking for URIs pointing to files. 713 // EnsureFile may not always get called, especially on resource URIs so we 714 // need to call GetFile to make sure this is a valid file. 715 nsresult rv = NS_OK; 716 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv); 717 nsCOMPtr<nsIFile> testFile; 718 if (NS_SUCCEEDED(rv)) { 719 fileURL->GetFile(getter_AddRefs(testFile)); 720 } 721 722 return bool(testFile); 723 } 724 725 static mozilla::Result<nsCString, nsresult> ReadScript( 726 ModuleLoaderInfo& aInfo) { 727 MOZ_TRY(aInfo.EnsureScriptChannel()); 728 729 nsCOMPtr<nsIInputStream> scriptStream; 730 MOZ_TRY(aInfo.ScriptChannel()->Open(getter_AddRefs(scriptStream))); 731 732 uint64_t len64; 733 uint32_t bytesRead; 734 735 MOZ_TRY(scriptStream->Available(&len64)); 736 NS_ENSURE_TRUE(len64 < UINT32_MAX, Err(NS_ERROR_FILE_TOO_BIG)); 737 NS_ENSURE_TRUE(len64, Err(NS_ERROR_FAILURE)); 738 uint32_t len = (uint32_t)len64; 739 740 /* malloc an internal buf the size of the file */ 741 nsCString str; 742 if (!str.SetLength(len, fallible)) { 743 return Err(NS_ERROR_OUT_OF_MEMORY); 744 } 745 746 /* read the file in one swoop */ 747 MOZ_TRY(scriptStream->Read(str.BeginWriting(), len, &bytesRead)); 748 if (bytesRead != len) { 749 return Err(NS_BASE_STREAM_OSERROR); 750 } 751 752 return std::move(str); 753 } 754 755 /* static */ 756 void mozJSModuleLoader::SetModuleOptions(CompileOptions& aOptions) { 757 aOptions.setModule(); 758 759 // Top level await is not supported in synchronously loaded modules. 760 aOptions.topLevelAwait = false; 761 } 762 763 /* static */ 764 nsresult mozJSModuleLoader::GetScriptForLocation( 765 JSContext* aCx, ModuleLoaderInfo& aInfo, nsIFile* aModuleFile, 766 bool aUseMemMap, MutableHandleScript aScriptOut, char** aLocationOut) { 767 // JS compilation errors are returned via an exception on the context. 768 MOZ_ASSERT(!JS_IsExceptionPending(aCx)); 769 770 aScriptOut.set(nullptr); 771 772 nsAutoCString nativePath; 773 nsresult rv = aInfo.URI()->GetSpec(nativePath); 774 NS_ENSURE_SUCCESS(rv, rv); 775 776 // Before compiling the script, first check to see if we have it in 777 // the preloader cache or the startupcache. Note: as a rule, preloader cache 778 // errors and startupcache errors are not fatal to loading the script, since 779 // we can always slow-load. 780 781 bool storeIntoStartupCache = false; 782 StartupCache* cache = StartupCache::GetSingleton(); 783 784 aInfo.EnsureResolvedURI(); 785 786 nsAutoCString cachePath; 787 rv = PathifyURI(JS_CACHE_PREFIX("non-syntactic", "module"), 788 aInfo.ResolvedURI(), cachePath); 789 NS_ENSURE_SUCCESS(rv, rv); 790 791 JS::DecodeOptions decodeOptions; 792 ScriptPreloader::FillDecodeOptionsForCachedStencil(decodeOptions); 793 794 RefPtr<JS::Stencil> stencil = 795 ScriptPreloader::GetSingleton().GetCachedStencil(aCx, decodeOptions, 796 cachePath); 797 798 if (!stencil && cache) { 799 ReadCachedStencil(cache, cachePath, aCx, decodeOptions, 800 getter_AddRefs(stencil)); 801 if (!stencil) { 802 JS_ClearPendingException(aCx); 803 804 storeIntoStartupCache = true; 805 } 806 } 807 808 if (stencil) { 809 LOG(("Successfully loaded %s from cache\n", nativePath.get())); 810 } else { 811 // The script wasn't in the cache , so compile it now. 812 LOG(("Slow loading %s\n", nativePath.get())); 813 814 CompileOptions options(aCx); 815 ScriptPreloader::FillCompileOptionsForCachedStencil(options); 816 options.setFileAndLine(nativePath.get(), 1); 817 SetModuleOptions(options); 818 819 // If we can no longer write to caches, we should stop using lazy sources 820 // and instead let normal syntax parsing occur. This can occur in content 821 // processes after the ScriptPreloader is flushed where we can read but no 822 // longer write. 823 if (!storeIntoStartupCache && !ScriptPreloader::GetSingleton().Active()) { 824 options.setSourceIsLazy(false); 825 } 826 827 if (aUseMemMap) { 828 AutoMemMap map; 829 MOZ_TRY(map.init(aModuleFile)); 830 831 // Note: exceptions will get handled further down; 832 // don't early return for them here. 833 auto buf = map.get<char>(); 834 835 JS::SourceText<mozilla::Utf8Unit> srcBuf; 836 if (srcBuf.init(aCx, buf.get(), map.size(), 837 JS::SourceOwnership::Borrowed)) { 838 stencil = CompileModuleScriptToStencil(aCx, options, srcBuf); 839 } 840 } else { 841 nsCString str = MOZ_TRY(ReadScript(aInfo)); 842 843 JS::SourceText<mozilla::Utf8Unit> srcBuf; 844 if (srcBuf.init(aCx, str.get(), str.Length(), 845 JS::SourceOwnership::Borrowed)) { 846 stencil = CompileModuleScriptToStencil(aCx, options, srcBuf); 847 } 848 } 849 850 #ifdef DEBUG 851 // The above shouldn't touch any options for instantiation. 852 JS::InstantiateOptions instantiateOptions(options); 853 instantiateOptions.assertDefault(); 854 #endif 855 856 if (!stencil) { 857 return NS_ERROR_FAILURE; 858 } 859 } 860 861 aScriptOut.set(InstantiateStencil(aCx, stencil)); 862 if (!aScriptOut) { 863 return NS_ERROR_FAILURE; 864 } 865 866 // ScriptPreloader::NoteScript needs to be called unconditionally, to 867 // reflect the usage into the next session's cache. 868 ScriptPreloader::GetSingleton().NoteStencil(nativePath, cachePath, stencil); 869 870 // Write to startup cache only when we didn't have any cache for the script 871 // and compiled it. 872 if (storeIntoStartupCache) { 873 MOZ_ASSERT(stencil); 874 875 // We successfully compiled the script, so cache it. 876 rv = WriteCachedStencil(cache, cachePath, aCx, stencil); 877 878 // Don't treat failure to write as fatal, since we might be working 879 // with a read-only cache. 880 if (NS_SUCCEEDED(rv)) { 881 LOG(("Successfully wrote to cache\n")); 882 } else { 883 LOG(("Failed to write to cache\n")); 884 } 885 } 886 887 /* Owned by ModuleEntry. Freed when we remove from the table. */ 888 if (aLocationOut) { 889 *aLocationOut = ToNewCString(nativePath, mozilla::fallible); 890 if (!*aLocationOut) { 891 return NS_ERROR_OUT_OF_MEMORY; 892 } 893 } 894 895 return NS_OK; 896 } 897 898 void mozJSModuleLoader::UnloadModules() { 899 MOZ_ASSERT(!mIsUnloaded); 900 901 mInitialized = false; 902 mIsUnloaded = true; 903 904 if (mLoaderGlobal) { 905 MOZ_ASSERT(JS_HasExtensibleLexicalEnvironment(mLoaderGlobal)); 906 JS::RootedObject lexicalEnv(dom::RootingCx(), 907 JS_ExtensibleLexicalEnvironment(mLoaderGlobal)); 908 JS_SetAllNonReservedSlotsToUndefined(lexicalEnv); 909 JS_SetAllNonReservedSlotsToUndefined(mLoaderGlobal); 910 mLoaderGlobal = nullptr; 911 } 912 mServicesObj = nullptr; 913 914 #ifdef STARTUP_RECORDER_ENABLED 915 mImportStacks.Clear(); 916 #endif 917 } 918 919 /* static */ 920 JSScript* mozJSModuleLoader::InstantiateStencil(JSContext* aCx, 921 JS::Stencil* aStencil) { 922 JS::InstantiateOptions instantiateOptions; 923 924 RootedObject module(aCx); 925 module = JS::InstantiateModuleStencil(aCx, instantiateOptions, aStencil); 926 if (!module) { 927 return nullptr; 928 } 929 930 return JS::GetModuleScript(module); 931 } 932 933 nsresult mozJSModuleLoader::IsESModuleLoaded(const nsACString& aLocation, 934 bool* retval) { 935 MOZ_ASSERT(nsContentUtils::IsCallerChrome()); 936 937 if (mIsUnloaded) { 938 *retval = false; 939 return NS_OK; 940 } 941 942 mInitialized = true; 943 ModuleLoaderInfo info(aLocation); 944 945 nsresult rv = info.EnsureURI(); 946 NS_ENSURE_SUCCESS(rv, rv); 947 948 if (mModuleLoader->IsModuleFetched( 949 JS::loader::ModuleMapKey(info.URI(), ModuleType::JavaScript))) { 950 *retval = true; 951 return NS_OK; 952 } 953 954 *retval = false; 955 return NS_OK; 956 } 957 958 nsresult mozJSModuleLoader::GetLoadedESModules( 959 nsTArray<nsCString>& aLoadedModules) { 960 return mModuleLoader->GetFetchedModuleURLs(aLoadedModules); 961 } 962 963 #ifdef STARTUP_RECORDER_ENABLED 964 void mozJSModuleLoader::RecordImportStack( 965 JSContext* aCx, JS::loader::ModuleLoadRequest* aRequest) { 966 if (!StaticPrefs::browser_startup_record()) { 967 return; 968 } 969 970 nsAutoCString location; 971 nsresult rv = aRequest->URI()->GetSpec(location); 972 if (NS_FAILED(rv)) { 973 return; 974 } 975 976 auto recordJSStackOnly = [&]() { 977 mImportStacks.InsertOrUpdate( 978 location, xpc_PrintJSStack(aCx, false, false, false).get()); 979 }; 980 981 if (aRequest->IsTopLevel()) { 982 recordJSStackOnly(); 983 return; 984 } 985 986 nsAutoCString importerSpec; 987 rv = aRequest->mReferrer->GetSpec(importerSpec); 988 if (NS_FAILED(rv)) { 989 recordJSStackOnly(); 990 return; 991 } 992 993 auto importerStack = mImportStacks.Lookup(importerSpec); 994 if (!importerStack) { 995 // The importer's stack is not collected, possibly due to OOM. 996 recordJSStackOnly(); 997 return; 998 } 999 1000 nsAutoCString stack; 1001 1002 stack += "* import [\""; 1003 stack += importerSpec; 1004 stack += "\"]\n"; 1005 stack += *importerStack; 1006 1007 mImportStacks.InsertOrUpdate(location, stack); 1008 } 1009 #endif 1010 1011 nsresult mozJSModuleLoader::GetModuleImportStack(const nsACString& aLocation, 1012 nsACString& retval) { 1013 #ifdef STARTUP_RECORDER_ENABLED 1014 MOZ_ASSERT(nsContentUtils::IsCallerChrome()); 1015 1016 // When querying the DevTools loader, it may not be initialized yet 1017 if (!mInitialized) { 1018 return NS_ERROR_FAILURE; 1019 } 1020 1021 auto str = mImportStacks.Lookup(aLocation); 1022 if (!str) { 1023 return NS_ERROR_FAILURE; 1024 } 1025 1026 retval = *str; 1027 return NS_OK; 1028 #else 1029 return NS_ERROR_NOT_IMPLEMENTED; 1030 #endif 1031 } 1032 1033 nsresult mozJSModuleLoader::ImportESModule( 1034 JSContext* aCx, const nsACString& aLocation, 1035 JS::MutableHandleObject aModuleNamespace) { 1036 using namespace JS::loader; 1037 1038 if (mIsUnloaded) { 1039 JS_ReportErrorASCII(aCx, "Module loaded is already unloaded"); 1040 return NS_ERROR_FAILURE; 1041 } 1042 1043 mInitialized = true; 1044 1045 // Called from ChromeUtils::ImportESModule. 1046 nsCString str(aLocation); 1047 1048 AUTO_PROFILER_MARKER_TEXT( 1049 "ChromeUtils.importESModule", JS, 1050 MarkerOptions(MarkerStack::Capture(), 1051 MarkerInnerWindowIdFromJSContext(aCx)), 1052 Substring(aLocation, 0, std::min(size_t(128), aLocation.Length()))); 1053 1054 RootedObject globalObj(aCx, GetSharedGlobal()); 1055 MOZ_ASSERT(globalObj); 1056 MOZ_ASSERT_IF(NS_IsMainThread(), 1057 xpc::Scriptability::Get(globalObj).Allowed()); 1058 1059 // The module loader should be instantiated when fetching the shared global 1060 MOZ_ASSERT(mModuleLoader); 1061 1062 JSAutoRealm ar(aCx, globalObj); 1063 1064 nsCOMPtr<nsIURI> uri; 1065 nsresult rv = NS_NewURI(getter_AddRefs(uri), aLocation); 1066 NS_ENSURE_SUCCESS(rv, rv); 1067 1068 if (!nsContentSecurityUtils::IsTrustedScheme(uri)) { 1069 JS_ReportErrorASCII(aCx, 1070 "System modules must be loaded from a trusted scheme"); 1071 return NS_ERROR_FAILURE; 1072 } 1073 1074 nsCOMPtr<nsIPrincipal> principal = 1075 mModuleLoader->GetGlobalObject()->PrincipalOrNull(); 1076 MOZ_ASSERT(principal); 1077 1078 RefPtr<ScriptFetchOptions> options = new ScriptFetchOptions( 1079 CORS_NONE, /* aNonce = */ u""_ns, dom::RequestPriority::Auto, 1080 ParserMetadata::NotParserInserted, principal); 1081 1082 RefPtr<SyncLoadContext> context = new SyncLoadContext(); 1083 1084 RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest( 1085 JS::ModuleType::JavaScript, dom::SRIMetadata(), 1086 /* aReferrer = */ nullptr, context, ModuleLoadRequest::Kind::TopLevel, 1087 mModuleLoader, nullptr); 1088 1089 request->NoCacheEntryFound(dom::ReferrerPolicy::No_referrer, options, uri); 1090 1091 rv = request->StartModuleLoad(); 1092 if (NS_FAILED(rv)) { 1093 mModuleLoader->MaybeReportLoadError(aCx); 1094 return rv; 1095 } 1096 1097 rv = mModuleLoader->ProcessRequests(); 1098 if (NS_FAILED(rv)) { 1099 mModuleLoader->MaybeReportLoadError(aCx); 1100 return rv; 1101 } 1102 1103 MOZ_ASSERT(request->IsFinished()); 1104 if (!request->mModuleScript) { 1105 mModuleLoader->MaybeReportLoadError(aCx); 1106 return NS_ERROR_FAILURE; 1107 } 1108 1109 // All modules are loaded. MaybeReportLoadError isn't necessary from here. 1110 1111 if (!request->mModuleScript->HasErrorToRethrow() && 1112 !request->InstantiateModuleGraph()) { 1113 return NS_ERROR_FAILURE; 1114 } 1115 1116 rv = mModuleLoader->EvaluateModuleInContext(aCx, request, 1117 JS::ThrowModuleErrorsSync); 1118 NS_ENSURE_SUCCESS(rv, rv); 1119 if (JS_IsExceptionPending(aCx)) { 1120 return NS_ERROR_FAILURE; 1121 } 1122 1123 RefPtr<ModuleScript> moduleScript = request->mModuleScript; 1124 JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord()); 1125 aModuleNamespace.set(JS::GetModuleNamespace(aCx, module)); 1126 1127 return NS_OK; 1128 } 1129 1130 bool mozJSModuleLoader::CreateJSServices(JSContext* aCx) { 1131 JSObject* services = NewJSServices(aCx); 1132 if (!services) { 1133 return false; 1134 } 1135 1136 mServicesObj = services; 1137 return true; 1138 } 1139 1140 bool mozJSModuleLoader::DefineJSServices(JSContext* aCx, 1141 JS::Handle<JSObject*> aGlobal) { 1142 if (!mServicesObj) { 1143 // This function is called whenever creating a new global that needs 1144 // `Services`, including the loader's shared global. 1145 // 1146 // This function is no-op if it's called during creating the loader's 1147 // shared global. 1148 // 1149 // See also CreateAndDefineJSServices. 1150 MOZ_ASSERT(!mLoaderGlobal); 1151 MOZ_ASSERT(mIsInitializingLoaderGlobal); 1152 return true; 1153 } 1154 1155 JS::Rooted<JS::Value> services(aCx, ObjectValue(*mServicesObj)); 1156 if (!JS_WrapValue(aCx, &services)) { 1157 return false; 1158 } 1159 1160 JS::Rooted<JS::PropertyKey> servicesId( 1161 aCx, XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_SERVICES)); 1162 return JS_DefinePropertyById(aCx, aGlobal, servicesId, services, 0); 1163 } 1164 1165 size_t mozJSModuleLoader::ModuleEntry::SizeOfIncludingThis( 1166 MallocSizeOf aMallocSizeOf) const { 1167 size_t n = aMallocSizeOf(this); 1168 n += aMallocSizeOf(location); 1169 1170 return n; 1171 } 1172 1173 //---------------------------------------------------------------------- 1174 1175 /* static */ 1176 MOZ_THREAD_LOCAL(mozJSModuleLoader*) 1177 NonSharedGlobalSyncModuleLoaderScope::sTlsActiveLoader; 1178 1179 void NonSharedGlobalSyncModuleLoaderScope::InitStatics() { 1180 sTlsActiveLoader.infallibleInit(); 1181 } 1182 1183 NonSharedGlobalSyncModuleLoaderScope::NonSharedGlobalSyncModuleLoaderScope( 1184 JSContext* aCx, nsIGlobalObject* aGlobal) { 1185 MOZ_ASSERT_IF(NS_IsMainThread(), 1186 !mozJSModuleLoader::IsSharedSystemGlobal(aGlobal)); 1187 MOZ_ASSERT_IF(NS_IsMainThread(), 1188 !mozJSModuleLoader::IsDevToolsLoaderGlobal(aGlobal)); 1189 1190 mAsyncModuleLoader = aGlobal->GetModuleLoader(aCx); 1191 MOZ_ASSERT(mAsyncModuleLoader, 1192 "The consumer should guarantee the global returns non-null module " 1193 "loader"); 1194 1195 mLoader = new mozJSModuleLoader(); 1196 mLoader->InitSyncModuleLoaderForGlobal(aGlobal); 1197 1198 mAsyncModuleLoader->CopyModulesTo(mLoader->mModuleLoader); 1199 1200 mMaybeOverride.emplace(mAsyncModuleLoader, mLoader->mModuleLoader); 1201 1202 MOZ_ASSERT(!sTlsActiveLoader.get()); 1203 sTlsActiveLoader.set(mLoader); 1204 } 1205 1206 NonSharedGlobalSyncModuleLoaderScope::~NonSharedGlobalSyncModuleLoaderScope() { 1207 MOZ_ASSERT(sTlsActiveLoader.get() == mLoader); 1208 sTlsActiveLoader.set(nullptr); 1209 1210 mLoader->DisconnectSyncModuleLoaderFromGlobal(); 1211 } 1212 1213 void NonSharedGlobalSyncModuleLoaderScope::Finish() { 1214 mLoader->mModuleLoader->MoveModulesTo(mAsyncModuleLoader); 1215 } 1216 1217 /* static */ 1218 bool NonSharedGlobalSyncModuleLoaderScope::IsActive() { 1219 return !!sTlsActiveLoader.get(); 1220 } 1221 1222 /*static */ 1223 mozJSModuleLoader* NonSharedGlobalSyncModuleLoaderScope::ActiveLoader() { 1224 return sTlsActiveLoader.get(); 1225 }