Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ScriptLoadRequest.h"
#include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_ASSERT_IF
#include "mozilla/Attributes.h"
#include "mozilla/ArrayUtils.h" // mozilla::ArrayLength
#include "mozilla/RefPtr.h" // RefPtr, mozilla::StaticRefPtr
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include <cstdarg>
#include "mozilla/Logging.h"
#include "mozilla/dom/RequestBinding.h"
#ifdef ANDROID
# include <android/log.h>
#endif
#ifdef XP_WIN
# include <windows.h>
#endif
#include "jsapi.h"
#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
#include "js/CharacterEncoding.h"
#include "js/CompilationAndEvaluation.h"
#include "js/CompileOptions.h" // JS::CompileOptions
#include "js/ErrorReport.h" // JS_ReportErrorUTF8, JSErrorReport
#include "js/Exception.h" // JS_ErrorFromException
#include "js/friend/JSMEnvironment.h" // JS::ExecuteInJSMEnvironment, JS::GetJSMEnvironmentOfScriptedCaller, JS::NewJSMEnvironment
#include "js/friend/ErrorMessages.h" // JSMSG_*
#include "js/loader/ModuleLoadRequest.h"
#include "js/Object.h" // JS::GetCompartment
#include "js/Printf.h"
#include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_DefineProperty, JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_HasOwnProperty, JS_HasOwnPropertyById, JS_SetProperty, JS_SetPropertyById
#include "js/PropertySpec.h"
#include "js/SourceText.h" // JS::SourceText
#include "nsCOMPtr.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIFile.h"
#include "mozJSModuleLoader.h"
#include "mozJSLoaderUtils.h"
#include "nsIFileURL.h"
#include "nsIJARURI.h"
#include "nsIChannel.h"
#include "nsIStreamListener.h"
#include "nsNetUtil.h"
#include "nsJSUtils.h"
#include "xpcprivate.h"
#include "xpcpublic.h"
#include "nsContentUtils.h"
#include "nsXULAppAPI.h"
#include "WrapperFactory.h"
#include "JSMEnvironmentProxy.h"
#include "ModuleEnvironmentProxy.h"
#include "JSServices.h"
#include "mozilla/scache/StartupCache.h"
#include "mozilla/scache/StartupCacheUtils.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/MacroForEach.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/ScriptPreloader.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Try.h"
#include "mozilla/dom/AutoEntryScript.h"
#include "mozilla/dom/ReferrerPolicyBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/WorkerCommon.h" // dom::GetWorkerPrivateFromContext
#include "mozilla/dom/WorkerPrivate.h" // dom::WorkerPrivate, dom::AutoSyncLoopHolder
#include "mozilla/dom/WorkerRunnable.h" // dom::MainThreadStopSyncLoopRunnable
#include "mozilla/Unused.h"
using namespace mozilla;
using namespace mozilla::scache;
using namespace mozilla::loader;
using namespace xpc;
using namespace JS;
#define JS_CACHE_PREFIX(aScopeType, aCompilationTarget) \
"jsloader/" aScopeType "/" aCompilationTarget
/**
* Buffer sizes for serialization and deserialization of scripts.
* FIXME: bug #411579 (tune this macro!) Last updated: Jan 2008
*/
#define XPC_SERIALIZATION_BUFFER_SIZE (64 * 1024)
#define XPC_DESERIALIZATION_BUFFER_SIZE (12 * 8192)
// MOZ_LOG=JSModuleLoader:5
static LazyLogModule gJSCLLog("JSModuleLoader");
#define LOG(args) MOZ_LOG(gJSCLLog, mozilla::LogLevel::Debug, args)
// Components.utils.import error messages
#define ERROR_SCOPE_OBJ "%s - Second argument must be an object."
#define ERROR_NO_TARGET_OBJECT "%s - Couldn't find target object for import."
#define ERROR_NOT_PRESENT "%s - EXPORTED_SYMBOLS is not present."
#define ERROR_NOT_AN_ARRAY "%s - EXPORTED_SYMBOLS is not an array."
#define ERROR_GETTING_ARRAY_LENGTH \
"%s - Error getting array length of EXPORTED_SYMBOLS."
#define ERROR_ARRAY_ELEMENT "%s - EXPORTED_SYMBOLS[%d] is not a string."
#define ERROR_GETTING_SYMBOL "%s - Could not get symbol '%s'."
#define ERROR_SETTING_SYMBOL "%s - Could not set symbol '%s' on target object."
#define ERROR_UNINITIALIZED_SYMBOL \
"%s - Symbol '%s' accessed before initialization. Cyclic import?"
static constexpr char JSM_Suffix[] = ".jsm";
static constexpr size_t JSM_SuffixLength = mozilla::ArrayLength(JSM_Suffix) - 1;
static constexpr char JSM_JS_Suffix[] = ".jsm.js";
static constexpr size_t JSM_JS_SuffixLength =
mozilla::ArrayLength(JSM_JS_Suffix) - 1;
static constexpr char JS_Suffix[] = ".js";
static constexpr size_t JS_SuffixLength = mozilla::ArrayLength(JS_Suffix) - 1;
static constexpr char MJS_Suffix[] = ".sys.mjs";
static constexpr size_t MJS_SuffixLength = mozilla::ArrayLength(MJS_Suffix) - 1;
static bool IsJSM(const nsACString& aLocation) {
if (aLocation.Length() < JSM_SuffixLength) {
return false;
}
const auto ext = Substring(aLocation, aLocation.Length() - JSM_SuffixLength);
return ext == JSM_Suffix;
}
static bool IsJS(const nsACString& aLocation) {
if (aLocation.Length() < JS_SuffixLength) {
return false;
}
const auto ext = Substring(aLocation, aLocation.Length() - JS_SuffixLength);
return ext == JS_Suffix;
}
static bool IsJSM_JS(const nsACString& aLocation) {
if (aLocation.Length() < JSM_JS_SuffixLength) {
return false;
}
const auto ext =
Substring(aLocation, aLocation.Length() - JSM_JS_SuffixLength);
return ext == JSM_JS_Suffix;
}
static bool IsMJS(const nsACString& aLocation) {
if (aLocation.Length() < MJS_SuffixLength) {
return false;
}
const auto ext = Substring(aLocation, aLocation.Length() - MJS_SuffixLength);
return ext == MJS_Suffix;
}
static void MJSToJSM(const nsACString& aLocation, nsAutoCString& aOut) {
MOZ_ASSERT(IsMJS(aLocation));
aOut = Substring(aLocation, 0, aLocation.Length() - MJS_SuffixLength);
aOut += JSM_Suffix;
}
static bool TryToMJS(const nsACString& aLocation, nsAutoCString& aOut) {
if (IsJSM(aLocation)) {
aOut = Substring(aLocation, 0, aLocation.Length() - JSM_SuffixLength);
aOut += MJS_Suffix;
return true;
}
if (IsJSM_JS(aLocation)) {
aOut = Substring(aLocation, 0, aLocation.Length() - JSM_JS_SuffixLength);
aOut += MJS_Suffix;
return true;
}
if (IsJS(aLocation)) {
aOut = Substring(aLocation, 0, aLocation.Length() - JS_SuffixLength);
aOut += MJS_Suffix;
return true;
}
return false;
}
static bool Dump(JSContext* cx, unsigned argc, Value* vp) {
if (!nsJSUtils::DumpEnabled()) {
return true;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() == 0) {
return true;
}
RootedString str(cx, JS::ToString(cx, args[0]));
if (!str) {
return false;
}
JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
if (!utf8str) {
return false;
}
MOZ_LOG(nsContentUtils::DOMDumpLog(), mozilla::LogLevel::Debug,
("[Backstage.Dump] %s", utf8str.get()));
#ifdef ANDROID
__android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.get());
#endif
#ifdef XP_WIN
if (IsDebuggerPresent()) {
nsAutoJSString wstr;
if (!wstr.init(cx, str)) {
return false;
}
OutputDebugStringW(wstr.get());
}
#endif
fputs(utf8str.get(), stdout);
fflush(stdout);
return true;
}
static bool Debug(JSContext* cx, unsigned argc, Value* vp) {
#ifdef DEBUG
return Dump(cx, argc, vp);
#else
return true;
#endif
}
static const JSFunctionSpec gGlobalFun[] = {
JS_FN("dump", Dump, 1, 0), JS_FN("debug", Debug, 1, 0),
JS_FN("atob", Atob, 1, 0), JS_FN("btoa", Btoa, 1, 0), JS_FS_END};
class MOZ_STACK_CLASS JSCLContextHelper {
public:
explicit JSCLContextHelper(JSContext* aCx);
~JSCLContextHelper();
void reportErrorAfterPop(UniqueChars&& buf);
private:
JSContext* mContext;
UniqueChars mBuf;
// prevent copying and assignment
JSCLContextHelper(const JSCLContextHelper&) = delete;
const JSCLContextHelper& operator=(const JSCLContextHelper&) = delete;
};
static nsresult MOZ_FORMAT_PRINTF(2, 3)
ReportOnCallerUTF8(JSContext* callerContext, const char* format, ...) {
if (!callerContext) {
return NS_ERROR_FAILURE;
}
va_list ap;
va_start(ap, format);
UniqueChars buf = JS_vsmprintf(format, ap);
if (!buf) {
va_end(ap);
return NS_ERROR_OUT_OF_MEMORY;
}
JS_ReportErrorUTF8(callerContext, "%s", buf.get());
va_end(ap);
return NS_OK;
}
NS_IMPL_ISUPPORTS(mozJSModuleLoader, nsIMemoryReporter)
mozJSModuleLoader::mozJSModuleLoader()
: mImports(16),
mInProgressImports(16),
mFallbackImports(16),
#ifdef STARTUP_RECORDER_ENABLED
mImportStacks(16),
#endif
mLocations(16),
mInitialized(false),
mLoaderGlobal(dom::RootingCx()),
mServicesObj(dom::RootingCx()) {
}
#define ENSURE_DEP(name) \
{ \
nsresult rv = Ensure##name(); \
NS_ENSURE_SUCCESS(rv, rv); \
}
#define ENSURE_DEPS(...) MOZ_FOR_EACH(ENSURE_DEP, (), (__VA_ARGS__));
#define BEGIN_ENSURE(self, ...) \
{ \
if (m##self) return NS_OK; \
ENSURE_DEPS(__VA_ARGS__); \
}
class MOZ_STACK_CLASS ModuleLoaderInfo {
public:
explicit ModuleLoaderInfo(const nsACString& aLocation,
SkipCheckForBrokenURLOrZeroSized aSkipCheck =
SkipCheckForBrokenURLOrZeroSized::No)
: mLocation(&aLocation), mIsModule(false), mSkipCheck(aSkipCheck) {}
explicit ModuleLoaderInfo(JS::loader::ModuleLoadRequest* aRequest)
: mLocation(nullptr),
mURI(aRequest->mURI),
mIsModule(true),
mSkipCheck(aRequest->GetSyncLoadContext()->mSkipCheck) {}
SkipCheckForBrokenURLOrZeroSized getSkipCheckForBrokenURLOrZeroSized() const {
return mSkipCheck;
}
void resetChannelWithCheckForBrokenURLOrZeroSized() {
MOZ_ASSERT(mSkipCheck == SkipCheckForBrokenURLOrZeroSized::Yes);
mSkipCheck = SkipCheckForBrokenURLOrZeroSized::No;
mScriptChannel = nullptr;
}
nsIIOService* IOService() {
MOZ_ASSERT(mIOService);
return mIOService;
}
nsresult EnsureIOService() {
if (mIOService) {
return NS_OK;
}
nsresult rv;
mIOService = do_GetIOService(&rv);
return rv;
}
nsIURI* URI() {
MOZ_ASSERT(mURI);
return mURI;
}
nsresult EnsureURI() {
BEGIN_ENSURE(URI, IOService);
MOZ_ASSERT(mLocation);
return mIOService->NewURI(*mLocation, nullptr, nullptr,
getter_AddRefs(mURI));
}
nsIChannel* ScriptChannel() {
MOZ_ASSERT(mScriptChannel);
return mScriptChannel;
}
nsresult EnsureScriptChannel() {
BEGIN_ENSURE(ScriptChannel, IOService, URI);
// Skip check for missing URL when handling JSM-to-ESM fallback.
return NS_NewChannel(
getter_AddRefs(mScriptChannel), mURI,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_SCRIPT,
/* aCookieJarSettings = */ nullptr,
/* aPerformanceStorage = */ nullptr,
/* aLoadGroup = */ nullptr, /* aCallbacks = */ nullptr,
nsIRequest::LOAD_NORMAL, mIOService, /* aSandboxFlags = */ 0,
mSkipCheck == SkipCheckForBrokenURLOrZeroSized::Yes);
}
nsIURI* ResolvedURI() {
MOZ_ASSERT(mResolvedURI);
return mResolvedURI;
}
nsresult EnsureResolvedURI() {
BEGIN_ENSURE(ResolvedURI, URI);
return ResolveURI(mURI, getter_AddRefs(mResolvedURI));
}
const nsACString& Key() {
MOZ_ASSERT(mLocation);
return *mLocation;
}
[[nodiscard]] nsresult GetLocation(nsCString& aLocation) {
nsresult rv = EnsureURI();
NS_ENSURE_SUCCESS(rv, rv);
return mURI->GetSpec(aLocation);
}
bool IsModule() const { return mIsModule; }
private:
const nsACString* mLocation;
nsCOMPtr<nsIIOService> mIOService;
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIChannel> mScriptChannel;
nsCOMPtr<nsIURI> mResolvedURI;
const bool mIsModule;
SkipCheckForBrokenURLOrZeroSized mSkipCheck;
};
template <typename... Args>
static nsresult ReportOnCallerUTF8(JSCLContextHelper& helper,
const char* format, ModuleLoaderInfo& info,
Args... args) {
nsCString location;
MOZ_TRY(info.GetLocation(location));
UniqueChars buf = JS_smprintf(format, location.get(), args...);
if (!buf) {
return NS_ERROR_OUT_OF_MEMORY;
}
helper.reportErrorAfterPop(std::move(buf));
return NS_ERROR_FAILURE;
}
#undef BEGIN_ENSURE
#undef ENSURE_DEPS
#undef ENSURE_DEP
mozJSModuleLoader::~mozJSModuleLoader() {
MOZ_ASSERT(!mInitialized,
"UnloadModules() was not explicitly called before cleaning up "
"mozJSModuleLoader");
if (mInitialized) {
UnloadModules();
}
}
StaticRefPtr<mozJSModuleLoader> mozJSModuleLoader::sSelf;
StaticRefPtr<mozJSModuleLoader> mozJSModuleLoader::sDevToolsLoader;
void mozJSModuleLoader::FindTargetObject(JSContext* aCx,
MutableHandleObject aTargetObject) {
aTargetObject.set(JS::GetJSMEnvironmentOfScriptedCaller(aCx));
// The above could fail if the scripted caller is not a JSM (it could be a DOM
// scope, for instance).
//
// If the target object was not in the JSM shared global, return the global
// instead. This is needed when calling the subscript loader within a frame
// script, since it the FrameScript NSVO will have been found.
if (!aTargetObject ||
!IsLoaderGlobal(JS::GetNonCCWObjectGlobal(aTargetObject))) {
aTargetObject.set(JS::GetScriptedCallerGlobal(aCx));
// Return nullptr if the scripted caller is in a different compartment.
if (JS::GetCompartment(aTargetObject) != js::GetContextCompartment(aCx)) {
aTargetObject.set(nullptr);
}
}
}
void mozJSModuleLoader::InitStatics() {
MOZ_ASSERT(!sSelf);
sSelf = new mozJSModuleLoader();
RegisterWeakMemoryReporter(sSelf);
dom::AutoJSAPI jsapi;
jsapi.Init();
JSContext* cx = jsapi.cx();
sSelf->InitSharedGlobal(cx);
NonSharedGlobalSyncModuleLoaderScope::InitStatics();
}
void mozJSModuleLoader::UnloadLoaders() {
if (sSelf) {
sSelf->Unload();
}
if (sDevToolsLoader) {
sDevToolsLoader->Unload();
}
}
void mozJSModuleLoader::Unload() {
UnloadModules();
if (mModuleLoader) {
mModuleLoader->Shutdown();
mModuleLoader = nullptr;
}
}
void mozJSModuleLoader::ShutdownLoaders() {
MOZ_ASSERT(sSelf);
UnregisterWeakMemoryReporter(sSelf);
sSelf = nullptr;
if (sDevToolsLoader) {
UnregisterWeakMemoryReporter(sDevToolsLoader);
sDevToolsLoader = nullptr;
}
}
mozJSModuleLoader* mozJSModuleLoader::GetOrCreateDevToolsLoader(
JSContext* aCx) {
if (sDevToolsLoader) {
return sDevToolsLoader;
}
sDevToolsLoader = new mozJSModuleLoader();
RegisterWeakMemoryReporter(sDevToolsLoader);
sDevToolsLoader->InitSharedGlobal(aCx);
return sDevToolsLoader;
}
void mozJSModuleLoader::InitSyncModuleLoaderForGlobal(
nsIGlobalObject* aGlobal) {
MOZ_ASSERT(!mLoaderGlobal);
MOZ_ASSERT(!mModuleLoader);
RefPtr<SyncScriptLoader> scriptLoader = new SyncScriptLoader;
mModuleLoader = new SyncModuleLoader(scriptLoader, aGlobal);
mLoaderGlobal = aGlobal->GetGlobalJSObject();
}
void mozJSModuleLoader::DisconnectSyncModuleLoaderFromGlobal() {
MOZ_ASSERT(mLoaderGlobal);
MOZ_ASSERT(mModuleLoader);
mLoaderGlobal = nullptr;
Unload();
}
/* static */
bool mozJSModuleLoader::IsSharedSystemGlobal(nsIGlobalObject* aGlobal) {
return sSelf->IsLoaderGlobal(aGlobal->GetGlobalJSObject());
}
/* static */
bool mozJSModuleLoader::IsDevToolsLoaderGlobal(nsIGlobalObject* aGlobal) {
return sDevToolsLoader &&
sDevToolsLoader->IsLoaderGlobal(aGlobal->GetGlobalJSObject());
}
// This requires that the keys be strings and the values be pointers.
template <class Key, class Data, class UserData, class Converter>
static size_t SizeOfTableExcludingThis(
const nsBaseHashtable<Key, Data, UserData, Converter>& aTable,
MallocSizeOf aMallocSizeOf) {
size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (const auto& entry : aTable) {
n += entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
n += entry.GetData()->SizeOfIncludingThis(aMallocSizeOf);
}
return n;
}
#ifdef STARTUP_RECORDER_ENABLED
template <class Key, class Data, class UserData, class Converter>
static size_t SizeOfStringTableExcludingThis(
const nsBaseHashtable<Key, Data, UserData, Converter>& aTable,
MallocSizeOf aMallocSizeOf) {
size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (const auto& entry : aTable) {
n += entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
n += entry.GetData().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
}
return n;
}
#endif
size_t mozJSModuleLoader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
size_t n = aMallocSizeOf(this);
n += SizeOfTableExcludingThis(mImports, aMallocSizeOf);
n += mLocations.ShallowSizeOfExcludingThis(aMallocSizeOf);
n += SizeOfTableExcludingThis(mInProgressImports, aMallocSizeOf);
n += SizeOfTableExcludingThis(mFallbackImports, aMallocSizeOf);
#ifdef STARTUP_RECORDER_ENABLED
n += SizeOfStringTableExcludingThis(mImportStacks, aMallocSizeOf);
#endif
return n;
}
// Memory report paths are split on '/', with each module displayed as a
// separate layer of a visual tree. Any slashes which are meant to belong to a
// particular path module, rather than be used to build a hierarchy, therefore
// need to be replaced with backslashes, which are displayed as slashes in the
// UI.
//
// If `aAnonymize` is true, this function also attempts to translate any file:
// URLs to replace the path of the GRE directory with a placeholder containing
// no private information, and strips all other file: URIs of everything upto
// their last `/`.
static nsAutoCString MangleURL(const char* aURL, bool aAnonymize) {
nsAutoCString url(aURL);
if (aAnonymize) {
static nsCString greDirURI;
if (greDirURI.IsEmpty()) {
nsCOMPtr<nsIFile> file;
Unused << NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(file));
if (file) {
nsCOMPtr<nsIURI> uri;
NS_NewFileURI(getter_AddRefs(uri), file);
if (uri) {
uri->GetSpec(greDirURI);
RunOnShutdown([&]() { greDirURI.Truncate(0); });
}
}
}
url.ReplaceSubstring(greDirURI, "<GREDir>/"_ns);
if (FindInReadable("file:"_ns, url)) {
if (StringBeginsWith(url, "jar:file:"_ns)) {
int32_t idx = url.RFindChar('!');
url = "jar:file://<anonymized>!"_ns + Substring(url, idx);
} else {
int32_t idx = url.RFindChar('/');
url = "file://<anonymized>/"_ns + Substring(url, idx);
}
}
}
url.ReplaceChar('/', '\\');
return url;
}
NS_IMETHODIMP
mozJSModuleLoader::CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) {
for (const auto& entry : mImports.Values()) {
nsAutoCString path("js-module-loader/modules/");
path.Append(MangleURL(entry->location, aAnonymize));
aHandleReport->Callback(""_ns, path, KIND_NONHEAP, UNITS_COUNT, 1,
"Loaded JS modules"_ns, aData);
}
return NS_OK;
}
void mozJSModuleLoader::CreateLoaderGlobal(JSContext* aCx,
const nsACString& aLocation,
MutableHandleObject aGlobal) {
auto backstagePass = MakeRefPtr<BackstagePass>();
RealmOptions options;
auto& creationOptions = options.creationOptions();
creationOptions.setFreezeBuiltins(true).setNewCompartmentInSystemZone();
if (IsDevToolsLoader()) {
creationOptions.setInvisibleToDebugger(true);
}
xpc::SetPrefableRealmOptions(options);
// Defer firing OnNewGlobalObject until after the __URI__ property has
// been defined so the JS debugger can tell what module the global is
// for
RootedObject global(aCx);
#ifdef DEBUG
// See mozJSModuleLoader::DefineJSServices.
mIsInitializingLoaderGlobal = true;
#endif
nsresult rv = xpc::InitClassesWithNewWrappedGlobal(
aCx, static_cast<nsIGlobalObject*>(backstagePass),
nsContentUtils::GetSystemPrincipal(), xpc::DONT_FIRE_ONNEWGLOBALHOOK,
options, &global);
#ifdef DEBUG
mIsInitializingLoaderGlobal = false;
#endif
NS_ENSURE_SUCCESS_VOID(rv);
NS_ENSURE_TRUE_VOID(global);
backstagePass->SetGlobalObject(global);
JSAutoRealm ar(aCx, global);
if (!JS_DefineFunctions(aCx, global, gGlobalFun)) {
return;
}
if (!CreateJSServices(aCx)) {
return;
}
if (!DefineJSServices(aCx, global)) {
return;
}
// Set the location information for the new global, so that tools like
// about:memory may use that information
xpc::SetLocationForGlobal(global, aLocation);
MOZ_ASSERT(!mModuleLoader);
RefPtr<SyncScriptLoader> scriptLoader = new SyncScriptLoader;
mModuleLoader = new SyncModuleLoader(scriptLoader, backstagePass);
backstagePass->InitModuleLoader(mModuleLoader);
aGlobal.set(global);
}
void mozJSModuleLoader::InitSharedGlobal(JSContext* aCx) {
JS::RootedObject globalObj(aCx);
CreateLoaderGlobal(
aCx, IsDevToolsLoader() ? "DevTools global"_ns : "shared JSM global"_ns,
&globalObj);
// If we fail to create a module global this early, we're not going to
// get very far, so just bail out now.
MOZ_RELEASE_ASSERT(globalObj);
mLoaderGlobal = globalObj;
// AutoEntryScript required to invoke debugger hook, which is a
// Gecko-specific concept at present.
dom::AutoEntryScript aes(globalObj, "module loader report global");
JS_FireOnNewGlobalObject(aes.cx(), globalObj);
}
// Read script file on the main thread and pass it back to worker.
class ScriptReaderRunnable final : public nsIRunnable,
public nsINamed,
public nsIStreamListener {
public:
ScriptReaderRunnable(dom::WorkerPrivate* aWorkerPrivate,
nsIEventTarget* aSyncLoopTarget,
const nsCString& aLocation)
: mLocation(aLocation),
mRv(NS_ERROR_FAILURE),
mWorkerPrivate(aWorkerPrivate),
mSyncLoopTarget(aSyncLoopTarget) {}
NS_DECL_THREADSAFE_ISUPPORTS
nsCString& Data() { return mData; }
nsresult Result() const { return mRv; }
// nsIRunnable
NS_IMETHOD
Run() override {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = StartReadScriptFromLocation();
if (NS_FAILED(rv)) {
OnComplete(rv);
}
return NS_OK;
}
// nsINamed
NS_IMETHOD
GetName(nsACString& aName) override {
aName.AssignLiteral("ScriptReaderRunnable");
return NS_OK;
}
// nsIStreamListener
NS_IMETHOD OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
uint64_t aOffset, uint32_t aCount) override {
uint32_t read = 0;
return aInputStream->ReadSegments(AppendSegmentToData, this, aCount, &read);
}
// nsIRequestObserver
NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override { return NS_OK; }
NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
nsresult aStatusCode) override {
OnComplete(aStatusCode);
return NS_OK;
}
private:
~ScriptReaderRunnable() = default;
static nsresult AppendSegmentToData(nsIInputStream* aInputStream,
void* aClosure, const char* aRawSegment,
uint32_t aToOffset, uint32_t aCount,
uint32_t* outWrittenCount) {
auto* self = static_cast<ScriptReaderRunnable*>(aClosure);
self->mData.Append(aRawSegment, aCount);
*outWrittenCount = aCount;
return NS_OK;
}
void OnComplete(nsresult aRv) {
MOZ_ASSERT(NS_IsMainThread());
mRv = aRv;
RefPtr<dom::MainThreadStopSyncLoopRunnable> runnable =
new dom::MainThreadStopSyncLoopRunnable(
mWorkerPrivate, std::move(mSyncLoopTarget), mRv);
MOZ_ALWAYS_TRUE(runnable->Dispatch());
mWorkerPrivate = nullptr;
mSyncLoopTarget = nullptr;
}
nsresult StartReadScriptFromLocation() {
MOZ_ASSERT(NS_IsMainThread());
ModuleLoaderInfo info(mLocation);
nsresult rv = info.EnsureScriptChannel();
NS_ENSURE_SUCCESS(rv, rv);
return info.ScriptChannel()->AsyncOpen(this);
}
private:
nsAutoCString mLocation;
nsCString mData;
nsresult mRv;
// This pointer is guaranteed to be alive until OnComplete, given
// the worker thread is synchronously waiting with AutoSyncLoopHolder::Run
// until the corresponding WorkerPrivate::StopSyncLoop is called by
// MainThreadStopSyncLoopRunnable, which is dispatched from OnComplete.
dom::WorkerPrivate* mWorkerPrivate;
nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
};
NS_IMPL_ISUPPORTS(ScriptReaderRunnable, nsIRunnable, nsINamed,
nsIStreamListener)
/* static */
nsresult mozJSModuleLoader::ReadScriptOnMainThread(JSContext* aCx,
const nsCString& aLocation,
nsCString& aData) {
dom::WorkerPrivate* workerPrivate = dom::GetWorkerPrivateFromContext(aCx);
MOZ_ASSERT(workerPrivate);
dom::AutoSyncLoopHolder syncLoop(workerPrivate, dom::WorkerStatus::Canceling);
nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
syncLoop.GetSerialEventTarget();
if (!syncLoopTarget) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
RefPtr<ScriptReaderRunnable> runnable =
new ScriptReaderRunnable(workerPrivate, syncLoopTarget, aLocation);
if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
return NS_ERROR_FAILURE;
}
syncLoop.Run();
if (NS_FAILED(runnable->Result())) {
return runnable->Result();
}
aData = std::move(runnable->Data());
return NS_OK;
}
/* static */
nsresult mozJSModuleLoader::LoadSingleModuleScriptOnWorker(
SyncModuleLoader* aModuleLoader, JSContext* aCx,
JS::loader::ModuleLoadRequest* aRequest, MutableHandleScript aScriptOut) {
nsAutoCString location;
nsresult rv = aRequest->mURI->GetSpec(location);
NS_ENSURE_SUCCESS(rv, rv);
nsCString data;
rv = ReadScriptOnMainThread(aCx, location, data);
NS_ENSURE_SUCCESS(rv, rv);
CompileOptions options(aCx);
ScriptPreloader::FillCompileOptionsForCachedStencil(options);
options.setFileAndLine(location.BeginReading(), 1);
SetModuleOptions(options);
JS::SourceText<mozilla::Utf8Unit> srcBuf;
if (!srcBuf.init(aCx, data.get(), data.Length(),
JS::SourceOwnership::Borrowed)) {
return NS_ERROR_FAILURE;
}
RefPtr<JS::Stencil> stencil =
CompileStencil(aCx, options, srcBuf, /* aIsModule = */ true);
if (!stencil) {
return NS_ERROR_FAILURE;
}
aScriptOut.set(InstantiateStencil(aCx, stencil, /* aIsModule = */ true));
return NS_OK;
}
/* static */
nsresult mozJSModuleLoader::LoadSingleModuleScript(
SyncModuleLoader* aModuleLoader, JSContext* aCx,
JS::loader::ModuleLoadRequest* aRequest, MutableHandleScript aScriptOut) {
AUTO_PROFILER_MARKER_TEXT(
"ChromeUtils.importESModule static import", JS,
MarkerOptions(MarkerStack::Capture(),
MarkerInnerWindowIdFromJSContext(aCx)),
nsContentUtils::TruncatedURLForDisplay(aRequest->mURI));
if (!NS_IsMainThread()) {
return LoadSingleModuleScriptOnWorker(aModuleLoader, aCx, aRequest,
aScriptOut);
}
ModuleLoaderInfo info(aRequest);
nsresult rv = info.EnsureResolvedURI();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> sourceFile;
rv = GetSourceFile(info.ResolvedURI(), getter_AddRefs(sourceFile));
NS_ENSURE_SUCCESS(rv, rv);
bool realFile = LocationIsRealFile(aRequest->mURI);
RootedScript script(aCx);
rv = GetScriptForLocation(aCx, info, sourceFile, realFile, aScriptOut);
NS_ENSURE_SUCCESS(rv, rv);
#ifdef STARTUP_RECORDER_ENABLED
if (aModuleLoader == sSelf->mModuleLoader) {
sSelf->RecordImportStack(aCx, aRequest);
} else if (sDevToolsLoader &&
aModuleLoader == sDevToolsLoader->mModuleLoader) {
sDevToolsLoader->RecordImportStack(aCx, aRequest);
} else {
// NOTE: Do not record import stack for non-shared globals, given the
// loader is associated with the global only while importing.
}
#endif
return NS_OK;
}
/* static */
nsresult mozJSModuleLoader::GetSourceFile(nsIURI* aResolvedURI,
nsIFile** aSourceFileOut) {
// Get the JAR if there is one.
nsCOMPtr<nsIJARURI> jarURI;
nsresult rv = NS_OK;
jarURI = do_QueryInterface(aResolvedURI, &rv);
nsCOMPtr<nsIFileURL> baseFileURL;
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIURI> baseURI;
while (jarURI) {
jarURI->GetJARFile(getter_AddRefs(baseURI));
jarURI = do_QueryInterface(baseURI, &rv);
}
baseFileURL = do_QueryInterface(baseURI, &rv);
NS_ENSURE_SUCCESS(rv, rv);
} else {
baseFileURL = do_QueryInterface(aResolvedURI, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
return baseFileURL->GetFile(aSourceFileOut);
}
/* static */
bool mozJSModuleLoader::LocationIsRealFile(nsIURI* aURI) {
// We need to be extra careful checking for URIs pointing to files.
// EnsureFile may not always get called, especially on resource URIs so we
// need to call GetFile to make sure this is a valid file.
nsresult rv = NS_OK;
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
nsCOMPtr<nsIFile> testFile;
if (NS_SUCCEEDED(rv)) {
fileURL->GetFile(getter_AddRefs(testFile));
}
return bool(testFile);
}
JSObject* mozJSModuleLoader::PrepareObjectForLocation(JSContext* aCx,
nsIFile* aModuleFile,
nsIURI* aURI,
bool aRealFile) {
RootedObject globalObj(aCx, GetSharedGlobal());
MOZ_ASSERT(globalObj);
JSAutoRealm ar(aCx, globalObj);
// |thisObj| is the object we set properties on for a particular .jsm.
RootedObject thisObj(aCx, JS::NewJSMEnvironment(aCx));
NS_ENSURE_TRUE(thisObj, nullptr);
if (aRealFile) {
if (XRE_IsParentProcess()) {
RootedObject locationObj(aCx);
nsresult rv = nsXPConnect::XPConnect()->WrapNative(
aCx, thisObj, aModuleFile, NS_GET_IID(nsIFile),
locationObj.address());
NS_ENSURE_SUCCESS(rv, nullptr);
NS_ENSURE_TRUE(locationObj, nullptr);
if (!JS_DefineProperty(aCx, thisObj, "__LOCATION__", locationObj, 0)) {
return nullptr;
}
}
}
// Expose the URI from which the script was imported through a special
// variable that we insert into the JSM.
nsAutoCString nativePath;
NS_ENSURE_SUCCESS(aURI->GetSpec(nativePath), nullptr);
RootedString exposedUri(
aCx, JS_NewStringCopyN(aCx, nativePath.get(), nativePath.Length()));
NS_ENSURE_TRUE(exposedUri, nullptr);
if (!JS_DefineProperty(aCx, thisObj, "__URI__", exposedUri, 0)) {
return nullptr;
}
return thisObj;
}
static mozilla::Result<nsCString, nsresult> ReadScript(
ModuleLoaderInfo& aInfo) {
MOZ_TRY(aInfo.EnsureScriptChannel());
nsCOMPtr<nsIInputStream> scriptStream;
MOZ_TRY(aInfo.ScriptChannel()->Open(getter_AddRefs(scriptStream)));
uint64_t len64;
uint32_t bytesRead;
MOZ_TRY(scriptStream->Available(&len64));
NS_ENSURE_TRUE(len64 < UINT32_MAX, Err(NS_ERROR_FILE_TOO_BIG));
NS_ENSURE_TRUE(len64, Err(NS_ERROR_FAILURE));
uint32_t len = (uint32_t)len64;
/* malloc an internal buf the size of the file */
nsCString str;
if (!str.SetLength(len, fallible)) {
return Err(NS_ERROR_OUT_OF_MEMORY);
}
/* read the file in one swoop */
MOZ_TRY(scriptStream->Read(str.BeginWriting(), len, &bytesRead));
if (bytesRead != len) {
return Err(NS_BASE_STREAM_OSERROR);
}
return std::move(str);
}
nsresult mozJSModuleLoader::ObjectForLocation(
ModuleLoaderInfo& aInfo, nsIFile* aModuleFile, MutableHandleObject aObject,
MutableHandleScript aTableScript, char** aLocation,
bool aPropagateExceptions, MutableHandleValue aException) {
MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
dom::AutoJSAPI jsapi;
jsapi.Init();
JSContext* cx = jsapi.cx();
nsresult rv = aInfo.EnsureURI();
NS_ENSURE_SUCCESS(rv, rv);
bool realFile = LocationIsRealFile(aInfo.URI());
RootedObject obj(
cx, PrepareObjectForLocation(cx, aModuleFile, aInfo.URI(), realFile));
NS_ENSURE_TRUE(obj, NS_ERROR_FAILURE);
MOZ_ASSERT(!JS_IsGlobalObject(obj));
JSAutoRealm ar(cx, obj);
RootedScript script(cx);
rv = GetScriptForLocation(cx, aInfo, aModuleFile, realFile, &script,
aLocation);
if (NS_FAILED(rv)) {
// Propagate the exception, if one exists. Also, don't leave the stale
// exception on this context.
if (aPropagateExceptions && jsapi.HasException()) {
if (!jsapi.StealException(aException)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
return rv;
}
// Assign aObject here so that it's available to recursive imports.
// See bug 384168.
aObject.set(obj);
aTableScript.set(script);
{ // Scope for AutoEntryScript
AutoAllowLegacyScriptExecution exemption;
// We're going to run script via JS_ExecuteScript, so we need an
// AutoEntryScript. This is Gecko-specific and not in any spec.
dom::AutoEntryScript aes(CurrentGlobalOrNull(cx),
"module loader load module");
JSContext* aescx = aes.cx();
bool executeOk = false;
if (JS_IsGlobalObject(obj)) {
JS::RootedValue rval(cx);
executeOk = JS_ExecuteScript(aescx, script, &rval);
} else {
executeOk = JS::ExecuteInJSMEnvironment(aescx, script, obj);
}
if (!executeOk) {
if (aPropagateExceptions && aes.HasException()) {
// Ignore return value because we're returning an error code
// anyway.
Unused << aes.StealException(aException);
}
aObject.set(nullptr);
aTableScript.set(nullptr);
return NS_ERROR_FAILURE;
}
}
return rv;
}
/* static */
void mozJSModuleLoader::SetModuleOptions(CompileOptions& aOptions) {
aOptions.setModule();
// Top level await is not supported in synchronously loaded modules.
aOptions.topLevelAwait = false;
// Make all top-level `vars` available in `ModuleEnvironmentObject`.
aOptions.deoptimizeModuleGlobalVars = true;
}
/* static */
nsresult mozJSModuleLoader::GetScriptForLocation(
JSContext* aCx, ModuleLoaderInfo& aInfo, nsIFile* aModuleFile,
bool aUseMemMap, MutableHandleScript aScriptOut, char** aLocationOut) {
// JS compilation errors are returned via an exception on the context.
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
aScriptOut.set(nullptr);
nsAutoCString nativePath;
nsresult rv = aInfo.URI()->GetSpec(nativePath);
NS_ENSURE_SUCCESS(rv, rv);
// Before compiling the script, first check to see if we have it in
// the preloader cache or the startupcache. Note: as a rule, preloader cache
// errors and startupcache errors are not fatal to loading the script, since
// we can always slow-load.
bool storeIntoStartupCache = false;
StartupCache* cache = StartupCache::GetSingleton();
aInfo.EnsureResolvedURI();
nsAutoCString cachePath;
if (aInfo.IsModule()) {
rv = PathifyURI(JS_CACHE_PREFIX("non-syntactic", "module"),
aInfo.ResolvedURI(), cachePath);
} else {
rv = PathifyURI(JS_CACHE_PREFIX("non-syntactic", "script"),
aInfo.ResolvedURI(), cachePath);
}
NS_ENSURE_SUCCESS(rv, rv);
JS::DecodeOptions decodeOptions;
ScriptPreloader::FillDecodeOptionsForCachedStencil(decodeOptions);
RefPtr<JS::Stencil> stencil =
ScriptPreloader::GetSingleton().GetCachedStencil(aCx, decodeOptions,
cachePath);
if (!stencil && cache) {
ReadCachedStencil(cache, cachePath, aCx, decodeOptions,
getter_AddRefs(stencil));
if (!stencil) {
JS_ClearPendingException(aCx);
storeIntoStartupCache = true;
}
}
if (stencil) {
LOG(("Successfully loaded %s from cache\n", nativePath.get()));
} else {
// The script wasn't in the cache , so compile it now.
LOG(("Slow loading %s\n", nativePath.get()));
CompileOptions options(aCx);
ScriptPreloader::FillCompileOptionsForCachedStencil(options);
options.setFileAndLine(nativePath.get(), 1);
if (aInfo.IsModule()) {
SetModuleOptions(options);
} else {
options.setForceStrictMode();
options.setNonSyntacticScope(true);
}
// If we can no longer write to caches, we should stop using lazy sources
// and instead let normal syntax parsing occur. This can occur in content
// processes after the ScriptPreloader is flushed where we can read but no
// longer write.
if (!storeIntoStartupCache && !ScriptPreloader::GetSingleton().Active()) {
options.setSourceIsLazy(false);
}
if (aUseMemMap) {
AutoMemMap map;
MOZ_TRY(map.init(aModuleFile));
// Note: exceptions will get handled further down;
// don't early return for them here.
auto buf = map.get<char>();
JS::SourceText<mozilla::Utf8Unit> srcBuf;
if (srcBuf.init(aCx, buf.get(), map.size(),
JS::SourceOwnership::Borrowed)) {
stencil = CompileStencil(aCx, options, srcBuf, aInfo.IsModule());
}
} else {
nsCString str;
MOZ_TRY_VAR(str, ReadScript(aInfo));
JS::SourceText<mozilla::Utf8Unit> srcBuf;
if (srcBuf.init(aCx, str.get(), str.Length(),
JS::SourceOwnership::Borrowed)) {
stencil = CompileStencil(aCx, options, srcBuf, aInfo.IsModule());
}
}
#ifdef DEBUG
// The above shouldn't touch any options for instantiation.
JS::InstantiateOptions instantiateOptions(options);
instantiateOptions.assertDefault();
#endif
if (!stencil) {
return NS_ERROR_FAILURE;
}
}
aScriptOut.set(InstantiateStencil(aCx, stencil, aInfo.IsModule()));
if (!aScriptOut) {
return NS_ERROR_FAILURE;
}
// ScriptPreloader::NoteScript needs to be called unconditionally, to
// reflect the usage into the next session's cache.
ScriptPreloader::GetSingleton().NoteStencil(nativePath, cachePath, stencil);
// Write to startup cache only when we didn't have any cache for the script
// and compiled it.
if (storeIntoStartupCache) {
MOZ_ASSERT(stencil);
// We successfully compiled the script, so cache it.
rv = WriteCachedStencil(cache, cachePath, aCx, stencil);
// Don't treat failure to write as fatal, since we might be working
// with a read-only cache.
if (NS_SUCCEEDED(rv)) {
LOG(("Successfully wrote to cache\n"));
} else {
LOG(("Failed to write to cache\n"));
}
}
/* Owned by ModuleEntry. Freed when we remove from the table. */
if (aLocationOut) {
*aLocationOut = ToNewCString(nativePath, mozilla::fallible);
if (!*aLocationOut) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
return NS_OK;
}
void mozJSModuleLoader::UnloadModules() {
MOZ_ASSERT(!mIsUnloaded);
mInitialized = false;
mIsUnloaded = true;
if (mLoaderGlobal) {
MOZ_ASSERT(JS_HasExtensibleLexicalEnvironment(mLoaderGlobal));
JS::RootedObject lexicalEnv(dom::RootingCx(),
JS_ExtensibleLexicalEnvironment(mLoaderGlobal));
JS_SetAllNonReservedSlotsToUndefined(lexicalEnv);
JS_SetAllNonReservedSlotsToUndefined(mLoaderGlobal);
mLoaderGlobal = nullptr;
}
mServicesObj = nullptr;
#ifdef STARTUP_RECORDER_ENABLED
mImportStacks.Clear();
#endif
mFallbackImports.Clear();
mInProgressImports.Clear();
mImports.Clear();
mLocations.Clear();
}
/* static */
already_AddRefed<Stencil> mozJSModuleLoader::CompileStencil(
JSContext* aCx, const JS::CompileOptions& aOptions,
JS::SourceText<mozilla::Utf8Unit>& aSource, bool aIsModule) {
if (aIsModule) {
return CompileModuleScriptToStencil(aCx, aOptions, aSource);
}
return CompileGlobalScriptToStencil(aCx, aOptions, aSource);
}
/* static */
JSScript* mozJSModuleLoader::InstantiateStencil(JSContext* aCx,
JS::Stencil* aStencil,
bool aIsModule) {
JS::InstantiateOptions instantiateOptions;
if (aIsModule) {
RootedObject module(aCx);
module = JS::InstantiateModuleStencil(aCx, instantiateOptions, aStencil);
if (!module) {
return nullptr;
}
return JS::GetModuleScript(module);
}
return JS::InstantiateGlobalStencil(aCx, instantiateOptions, aStencil);
}
nsresult mozJSModuleLoader::ImportInto(const nsACString& registryLocation,
HandleValue targetValArg, JSContext* cx,
uint8_t optionalArgc,
MutableHandleValue retval) {
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
RootedValue targetVal(cx, targetValArg);
RootedObject targetObject(cx, nullptr);
if (optionalArgc) {
// The caller passed in the optional second argument. Get it.
if (targetVal.isObject()) {
// If we're passing in something like a content DOM window, chances
// are the caller expects the properties to end up on the object
// proper and not on the Xray holder. This is dubious, but can be used
// during testing. Given that dumb callers can already leak JSMs into
// content by passing a raw content JS object (where Xrays aren't
// possible), we aim for consistency here. Waive xray.
if (WrapperFactory::IsXrayWrapper(&targetVal.toObject()) &&
!WrapperFactory::WaiveXrayAndWrap(cx, &targetVal)) {
return NS_ERROR_FAILURE;
}
targetObject = &targetVal.toObject();
} else if (!targetVal.isNull()) {
// If targetVal isNull(), we actually want to leave targetObject null.
// Not doing so breaks |make package|.
return ReportOnCallerUTF8(cx, ERROR_SCOPE_OBJ,
PromiseFlatCString(registryLocation).get());
}
} else {
FindTargetObject(cx, &targetObject);
if (!targetObject) {
return ReportOnCallerUTF8(cx, ERROR_NO_TARGET_OBJECT,
PromiseFlatCString(registryLocation).get());
}
}
js::AssertSameCompartment(cx, targetObject);
RootedObject global(cx);
nsresult rv = ImportInto(registryLocation, targetObject, cx, &global);
if (global) {
if (!JS_WrapObject(cx, &global)) {
NS_ERROR("can't wrap return value");
return NS_ERROR_FAILURE;
}
retval.setObject(*global);
}
return rv;
}
nsresult mozJSModuleLoader::IsModuleLoaded(const nsACString& aLocation,
bool* retval) {
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
if (mIsUnloaded) {
*retval = false;
return NS_OK;
}
mInitialized = true;
ModuleLoaderInfo info(aLocation);
if (mImports.Get(info.Key())) {
*retval = true;
return NS_OK;
}
if (mModuleLoader) {
nsAutoCString mjsLocation;
if (!TryToMJS(aLocation, mjsLocation)) {
*retval = false;
return NS_OK;
}
ModuleLoaderInfo mjsInfo(mjsLocation);
nsresult rv = mjsInfo.EnsureURI();
NS_ENSURE_SUCCESS(rv, rv);
if (mModuleLoader->IsModuleFetched(mjsInfo.URI())) {
*retval = true;
return NS_OK;
}
}
*retval = false;
return NS_OK;
}
nsresult mozJSModuleLoader::IsJSModuleLoaded(const nsACString& aLocation,
bool* retval) {
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
if (mIsUnloaded) {
*retval = false;
return NS_OK;
}
mInitialized = true;
ModuleLoaderInfo info(aLocation);
if (mImports.Get(info.Key())) {
*retval = true;
return NS_OK;
}
*retval = false;
return NS_OK;
}
nsresult mozJSModuleLoader::IsESModuleLoaded(const nsACString& aLocation,
bool* retval) {
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
if (mIsUnloaded) {
*retval = false;
return NS_OK;
}
mInitialized = true;
ModuleLoaderInfo info(aLocation);
nsresult rv = info.EnsureURI();
NS_ENSURE_SUCCESS(rv, rv);
if (mModuleLoader->IsModuleFetched(info.URI())) {
*retval = true;
return NS_OK;
}
*retval = false;
return NS_OK;
}
void mozJSModuleLoader::GetLoadedModules(nsTArray<nsCString>& aLoadedModules) {
aLoadedModules.SetCapacity(mImports.Count());
for (const auto& data : mImports.Values()) {
aLoadedModules.AppendElement(data->location);
}
}
nsresult mozJSModuleLoader::GetLoadedESModules(
nsTArray<nsCString>& aLoadedModules) {
return mModuleLoader->GetFetchedModuleURLs(aLoadedModules);
}
nsresult mozJSModuleLoader::GetLoadedJSAndESModules(
nsTArray<nsCString>& aLoadedModules) {
GetLoadedModules(aLoadedModules);
nsTArray<nsCString> modules;
nsresult rv = GetLoadedESModules(modules);
NS_ENSURE_SUCCESS(rv, rv);
for (const auto& location : modules) {
if (IsMJS(location)) {
nsAutoCString jsmLocation;
// NOTE: Unconditionally convert to *.jsm. This doesn't cover *.js case
// but given `Cu.loadedModules` is rarely used for system modules,
// this won't cause much compat issue.
MJSToJSM(location, jsmLocation);
aLoadedModules.AppendElement(jsmLocation);
}
}
return NS_OK;
}
#ifdef STARTUP_RECORDER_ENABLED
void mozJSModuleLoader::RecordImportStack(JSContext* aCx,