summaryrefslogtreecommitdiffstats
path: root/js/loader
diff options
context:
space:
mode:
Diffstat (limited to 'js/loader')
-rw-r--r--js/loader/LoadContextBase.h3
-rw-r--r--js/loader/LoadedScript.cpp60
-rw-r--r--js/loader/LoadedScript.h32
-rw-r--r--js/loader/ModuleLoadRequest.cpp4
-rw-r--r--js/loader/ModuleLoaderBase.cpp109
-rw-r--r--js/loader/ModuleLoaderBase.h28
6 files changed, 198 insertions, 38 deletions
diff --git a/js/loader/LoadContextBase.h b/js/loader/LoadContextBase.h
index a8ce045546..5dc78070cb 100644
--- a/js/loader/LoadContextBase.h
+++ b/js/loader/LoadContextBase.h
@@ -52,6 +52,9 @@ class LoadContextBase : public nsISupports {
// Used to output a string for the Gecko Profiler.
virtual void GetProfilerLabel(nsACString& aOutString);
+ // Whether this is a preload, for contexts that support them.
+ virtual bool IsPreload() const { return false; }
+
// Casting to the different contexts
bool IsWindowContext() const { return mKind == ContextKind::Window; }
mozilla::dom::ScriptLoadContext* AsWindowContext();
diff --git a/js/loader/LoadedScript.cpp b/js/loader/LoadedScript.cpp
index 7ddd04168e..c84ed3ac55 100644
--- a/js/loader/LoadedScript.cpp
+++ b/js/loader/LoadedScript.cpp
@@ -20,6 +20,8 @@ namespace JS::loader {
// LoadedScript
//////////////////////////////////////////////////////////////
+MOZ_DEFINE_MALLOC_SIZE_OF(LoadedScriptMallocSizeOf)
+
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LoadedScript)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
@@ -52,7 +54,59 @@ LoadedScript::LoadedScript(ScriptKind aKind,
MOZ_ASSERT(mURI);
}
-LoadedScript::~LoadedScript() { mozilla::DropJSObjects(this); }
+LoadedScript::~LoadedScript() {
+ mozilla::UnregisterWeakMemoryReporter(this);
+ mozilla::DropJSObjects(this);
+}
+
+void LoadedScript::RegisterMemoryReport() {
+ mozilla::RegisterWeakMemoryReporter(this);
+}
+
+NS_IMETHODIMP
+LoadedScript::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+#define COLLECT_REPORT(path, kind) \
+ MOZ_COLLECT_REPORT(path, KIND_HEAP, UNITS_BYTES, \
+ SizeOfIncludingThis(LoadedScriptMallocSizeOf), \
+ "Memory used for LoadedScript to hold on " kind \
+ " across documents")
+
+ switch (mKind) {
+ case ScriptKind::eClassic:
+ COLLECT_REPORT("explicit/js/script/loaded-script/classic", "scripts");
+ break;
+ case ScriptKind::eImportMap:
+ COLLECT_REPORT("explicit/js/script/loaded-script/import-map",
+ "import-maps");
+ break;
+ case ScriptKind::eModule:
+ COLLECT_REPORT("explicit/js/script/loaded-script/module", "modules");
+ break;
+ case ScriptKind::eEvent:
+ COLLECT_REPORT("explicit/js/script/loaded-script/event", "event scripts");
+ break;
+ }
+
+#undef COLLECT_REPORT
+ return NS_OK;
+}
+
+size_t LoadedScript::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t bytes = aMallocSizeOf(this);
+
+ if (IsTextSource()) {
+ if (IsUTF16Text()) {
+ bytes += ScriptText<char16_t>().sizeOfExcludingThis(aMallocSizeOf);
+ } else {
+ bytes += ScriptText<Utf8Unit>().sizeOfExcludingThis(aMallocSizeOf);
+ }
+ }
+
+ bytes += mScriptBytecode.sizeOfExcludingThis(aMallocSizeOf);
+ return bytes;
+}
void LoadedScript::AssociateWithScript(JSScript* aScript) {
// Verify that the rewritten URL is available when manipulating LoadedScript.
@@ -207,6 +261,7 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END
ModuleScript::ModuleScript(mozilla::dom::ReferrerPolicy aReferrerPolicy,
ScriptFetchOptions* aFetchOptions, nsIURI* aURI)
: LoadedScript(ScriptKind::eModule, aReferrerPolicy, aFetchOptions, aURI),
+ mHadImportMap(false),
mDebuggerDataInitialized(false) {
MOZ_ASSERT(!ModuleRecord());
MOZ_ASSERT(!HasParseError());
@@ -279,6 +334,9 @@ void ModuleScript::SetErrorToRethrow(const JS::Value& aError) {
mErrorToRethrow = aError;
}
+void ModuleScript::SetForPreload(bool aValue) { mForPreload = aValue; }
+void ModuleScript::SetHadImportMap(bool aValue) { mHadImportMap = aValue; }
+
void ModuleScript::SetDebuggerDataInitialized() {
MOZ_ASSERT(ModuleRecord());
MOZ_ASSERT(!mDebuggerDataInitialized);
diff --git a/js/loader/LoadedScript.h b/js/loader/LoadedScript.h
index ca6d1fc179..5759800c27 100644
--- a/js/loader/LoadedScript.h
+++ b/js/loader/LoadedScript.h
@@ -12,12 +12,14 @@
#include "mozilla/Maybe.h"
#include "mozilla/MaybeOneOf.h"
+#include "mozilla/MemoryReporting.h"
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "mozilla/Variant.h"
#include "mozilla/Vector.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
+#include "nsIMemoryReporter.h"
#include "jsapi.h"
#include "ScriptKind.h"
@@ -39,7 +41,17 @@ class ModuleScript;
class EventScript;
class LoadContextBase;
-class LoadedScript : public nsISupports {
+// A LoadedScript is a place where the Script is stored once it is loaded. It is
+// not unique to a load, and can be shared across loads as long as it is
+// properly ref-counted by each load instance.
+//
+// When the load is not performed, the URI represents the resource to be loaded,
+// and it is replaced by the absolute resource location once loaded.
+//
+// As the LoadedScript can be shared, using the SharedSubResourceCache, it is
+// exposed to the memory reporter such that sharing might be accounted for
+// properly.
+class LoadedScript : public nsIMemoryReporter {
ScriptKind mKind;
const mozilla::dom::ReferrerPolicy mReferrerPolicy;
RefPtr<ScriptFetchOptions> mFetchOptions;
@@ -53,7 +65,17 @@ class LoadedScript : public nsISupports {
virtual ~LoadedScript();
public:
- NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ // When the memory should be reported, register it using RegisterMemoryReport,
+ // and make sure to call SizeOfIncludingThis in the enclosing container.
+ //
+ // Each reported script would be listed under
+ // `explicit/js/script/loaded-script/<kind>`.
+ void RegisterMemoryReport();
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS;
+ NS_DECL_NSIMEMORYREPORTER;
NS_DECL_CYCLE_COLLECTION_CLASS(LoadedScript)
bool IsClassicScript() const { return mKind == ScriptKind::eClassic; }
@@ -332,6 +354,8 @@ class ModuleScript final : public LoadedScript {
JS::Heap<JSObject*> mModuleRecord;
JS::Heap<JS::Value> mParseError;
JS::Heap<JS::Value> mErrorToRethrow;
+ bool mForPreload;
+ bool mHadImportMap;
bool mDebuggerDataInitialized;
~ModuleScript();
@@ -352,6 +376,8 @@ class ModuleScript final : public LoadedScript {
void SetModuleRecord(JS::Handle<JSObject*> aModuleRecord);
void SetParseError(const JS::Value& aError);
void SetErrorToRethrow(const JS::Value& aError);
+ void SetForPreload(bool aValue);
+ void SetHadImportMap(bool aValue);
void SetDebuggerDataInitialized();
JSObject* ModuleRecord() const { return mModuleRecord; }
@@ -360,6 +386,8 @@ class ModuleScript final : public LoadedScript {
JS::Value ErrorToRethrow() const { return mErrorToRethrow; }
bool HasParseError() const { return !mParseError.isUndefined(); }
bool HasErrorToRethrow() const { return !mErrorToRethrow.isUndefined(); }
+ bool ForPreload() const { return mForPreload; }
+ bool HadImportMap() const { return mHadImportMap; }
bool DebuggerDataInitialized() const { return mDebuggerDataInitialized; }
void Shutdown();
diff --git a/js/loader/ModuleLoadRequest.cpp b/js/loader/ModuleLoadRequest.cpp
index 7e188160fc..7313563df9 100644
--- a/js/loader/ModuleLoadRequest.cpp
+++ b/js/loader/ModuleLoadRequest.cpp
@@ -205,6 +205,7 @@ void ModuleLoadRequest::CheckModuleDependenciesLoaded() {
if (!mModuleScript || mModuleScript->HasParseError()) {
return;
}
+
for (const auto& childRequest : mImports) {
ModuleScript* childScript = childRequest->mModuleScript;
if (!childScript) {
@@ -213,6 +214,9 @@ void ModuleLoadRequest::CheckModuleDependenciesLoaded() {
childRequest.get()));
return;
}
+
+ MOZ_DIAGNOSTIC_ASSERT(mModuleScript->HadImportMap() ==
+ childScript->HadImportMap());
}
LOG(("ScriptLoadRequest (%p): all ok", this));
diff --git a/js/loader/ModuleLoaderBase.cpp b/js/loader/ModuleLoaderBase.cpp
index 228c96ad69..020542754d 100644
--- a/js/loader/ModuleLoaderBase.cpp
+++ b/js/loader/ModuleLoaderBase.cpp
@@ -55,17 +55,17 @@ mozilla::LazyLogModule ModuleLoaderBase::gModuleLoaderBaseLog(
MOZ_LOG_TEST(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug)
//////////////////////////////////////////////////////////////
-// ModuleLoaderBase::WaitingRequests
+// ModuleLoaderBase::LoadingRequest
//////////////////////////////////////////////////////////////
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase::WaitingRequests)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase::LoadingRequest)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
-NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase::WaitingRequests, mWaiting)
+NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase::LoadingRequest, mRequest, mWaiting)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase::WaitingRequests)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase::WaitingRequests)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase::LoadingRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase::LoadingRequest)
//////////////////////////////////////////////////////////////
// ModuleLoaderBase
@@ -308,6 +308,12 @@ bool ModuleLoaderBase::HostImportModuleDynamically(
return false;
}
+ if (!loader->IsDynamicImportSupported()) {
+ JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
+ JSMSG_DYNAMIC_IMPORT_NOT_SUPPORTED);
+ return false;
+ }
+
auto result = loader->ResolveModuleSpecifier(script, specifier);
if (result.isErr()) {
JS::Rooted<JS::Value> error(aCx);
@@ -509,7 +515,9 @@ void ModuleLoaderBase::SetModuleFetchStarted(ModuleLoadRequest* aRequest) {
MOZ_ASSERT(aRequest->IsFetching() || aRequest->IsPendingFetchingError());
MOZ_ASSERT(!ModuleMapContainsURL(aRequest->mURI));
- mFetchingModules.InsertOrUpdate(aRequest->mURI, nullptr);
+ RefPtr<LoadingRequest> loadingRequest = new LoadingRequest();
+ loadingRequest->mRequest = aRequest;
+ mFetchingModules.InsertOrUpdate(aRequest->mURI, loadingRequest);
}
void ModuleLoaderBase::SetModuleFetchFinishedAndResumeWaitingRequests(
@@ -526,9 +534,8 @@ void ModuleLoaderBase::SetModuleFetchFinishedAndResumeWaitingRequests(
"%u)",
aRequest, aRequest->mModuleScript.get(), unsigned(aResult)));
- RefPtr<WaitingRequests> waitingRequests;
- if (!mFetchingModules.Remove(aRequest->mURI,
- getter_AddRefs(waitingRequests))) {
+ auto entry = mFetchingModules.Lookup(aRequest->mURI);
+ if (!entry) {
LOG(
("ScriptLoadRequest (%p): Key not found in mFetchingModules, "
"assuming we have an inline module or have finished fetching already",
@@ -536,20 +543,35 @@ void ModuleLoaderBase::SetModuleFetchFinishedAndResumeWaitingRequests(
return;
}
+ // It's possible for a request to be cancelled and removed from the fetching
+ // modules map and a new request started for the same URI and added to the
+ // map. In this case we don't want the first cancelled request to complete the
+ // later request (which will cause it to fail) so we ignore it.
+ RefPtr<LoadingRequest> loadingRequest = entry.Data();
+ if (loadingRequest->mRequest != aRequest) {
+ MOZ_ASSERT(aRequest->IsCanceled());
+ LOG(
+ ("ScriptLoadRequest (%p): Ignoring completion of cancelled request "
+ "that was removed from the map",
+ aRequest));
+ return;
+ }
+
+ MOZ_ALWAYS_TRUE(mFetchingModules.Remove(aRequest->mURI));
+
RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);
mFetchedModules.InsertOrUpdate(aRequest->mURI, RefPtr{moduleScript});
- if (waitingRequests) {
- LOG(("ScriptLoadRequest (%p): Resuming waiting requests", aRequest));
- ResumeWaitingRequests(waitingRequests, bool(moduleScript));
- }
+ LOG(("ScriptLoadRequest (%p): Resuming waiting requests", aRequest));
+ MOZ_ASSERT(loadingRequest->mRequest == aRequest);
+ ResumeWaitingRequests(loadingRequest, bool(moduleScript));
}
-void ModuleLoaderBase::ResumeWaitingRequests(WaitingRequests* aWaitingRequests,
+void ModuleLoaderBase::ResumeWaitingRequests(LoadingRequest* aLoadingRequest,
bool aSuccess) {
- for (ModuleLoadRequest* request : aWaitingRequests->mWaiting) {
+ for (ModuleLoadRequest* request : aLoadingRequest->mWaiting) {
ResumeWaitingRequest(request, aSuccess);
}
}
@@ -568,13 +590,8 @@ void ModuleLoaderBase::WaitForModuleFetch(ModuleLoadRequest* aRequest) {
MOZ_ASSERT(ModuleMapContainsURL(uri));
if (auto entry = mFetchingModules.Lookup(uri)) {
- RefPtr<WaitingRequests> waitingRequests = entry.Data();
- if (!waitingRequests) {
- waitingRequests = new WaitingRequests();
- mFetchingModules.InsertOrUpdate(uri, waitingRequests);
- }
-
- waitingRequests->mWaiting.AppendElement(aRequest);
+ RefPtr<LoadingRequest> loadingRequest = entry.Data();
+ loadingRequest->mWaiting.AppendElement(aRequest);
return;
}
@@ -678,6 +695,9 @@ nsresult ModuleLoaderBase::CreateModuleScript(ModuleLoadRequest* aRequest) {
aRequest->mLoadedScript->AsModuleScript();
aRequest->mModuleScript = moduleScript;
+ moduleScript->SetForPreload(aRequest->mLoadContext->IsPreload());
+ moduleScript->SetHadImportMap(HasImportMapRegistered());
+
if (!module) {
LOG(("ScriptLoadRequest (%p): compilation failed (%d)", aRequest,
unsigned(rv)));
@@ -769,6 +789,7 @@ ResolveResult ModuleLoaderBase::ResolveModuleSpecifier(
// Import Maps are not supported on workers/worklets.
// See https://github.com/WICG/import-maps/issues/2
MOZ_ASSERT_IF(!NS_IsMainThread(), mImportMap == nullptr);
+
// Forward to the updated 'Resolve a module specifier' algorithm defined in
// the Import Maps spec.
return ImportMap::ResolveModuleSpecifier(mImportMap.get(), mLoader, aScript,
@@ -1034,9 +1055,9 @@ void ModuleLoaderBase::Shutdown() {
CancelAndClearDynamicImports();
for (const auto& entry : mFetchingModules) {
- RefPtr<WaitingRequests> waitingRequests(entry.GetData());
- if (waitingRequests) {
- ResumeWaitingRequests(waitingRequests, false);
+ RefPtr<LoadingRequest> loadingRequest(entry.GetData());
+ if (loadingRequest) {
+ ResumeWaitingRequests(loadingRequest, false);
}
}
@@ -1100,6 +1121,9 @@ JS::Value ModuleLoaderBase::FindFirstParseError(ModuleLoadRequest* aRequest) {
}
for (ModuleLoadRequest* childRequest : aRequest->mImports) {
+ MOZ_DIAGNOSTIC_ASSERT(moduleScript->HadImportMap() ==
+ childRequest->mModuleScript->HadImportMap());
+
JS::Value error = FindFirstParseError(childRequest);
if (!error.isUndefined()) {
return error;
@@ -1393,6 +1417,41 @@ void ModuleLoaderBase::RegisterImportMap(UniquePtr<ImportMap> aImportMap) {
// Step 3. Set global's import map to result's import map.
mImportMap = std::move(aImportMap);
+
+ // Any import resolution has been invalidated by the addition of the import
+ // map. If speculative preloading is currently fetching any modules then
+ // cancel their requests and remove them from the map.
+ //
+ // The cancelled requests will still complete later so we have to check this
+ // in SetModuleFetchFinishedAndResumeWaitingRequests.
+ for (const auto& entry : mFetchingModules) {
+ LoadingRequest* loadingRequest = entry.GetData();
+ MOZ_DIAGNOSTIC_ASSERT(loadingRequest->mRequest->mLoadContext->IsPreload());
+ loadingRequest->mRequest->Cancel();
+ for (const auto& request : loadingRequest->mWaiting) {
+ MOZ_DIAGNOSTIC_ASSERT(request->mLoadContext->IsPreload());
+ request->Cancel();
+ }
+ }
+ mFetchingModules.Clear();
+
+ // If speculative preloading has added modules to the module map, remove
+ // them.
+ for (const auto& entry : mFetchedModules) {
+ ModuleScript* script = entry.GetData();
+ if (script) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ script->ForPreload(),
+ "Non-preload module loads should block import maps");
+ MOZ_DIAGNOSTIC_ASSERT(!script->HadImportMap(),
+ "Only one import map can be registered");
+ if (JSObject* module = script->ModuleRecord()) {
+ MOZ_DIAGNOSTIC_ASSERT(!JS::ModuleIsLinked(module));
+ }
+ script->Shutdown();
+ }
+ }
+ mFetchedModules.Clear();
}
void ModuleLoaderBase::CopyModulesTo(ModuleLoaderBase* aDest) {
diff --git a/js/loader/ModuleLoaderBase.h b/js/loader/ModuleLoaderBase.h
index 2c2c385a30..89d23e12bc 100644
--- a/js/loader/ModuleLoaderBase.h
+++ b/js/loader/ModuleLoaderBase.h
@@ -165,20 +165,30 @@ class ScriptLoaderInterface : public nsISupports {
*/
class ModuleLoaderBase : public nsISupports {
/*
- * The set of requests that are waiting for an ongoing fetch to complete.
+ * Represents an ongoing load operation for a URI initiated for one request
+ * and which may have other requests waiting for it to complete.
+ *
+ * These are tracked in the mFetchingModules map.
*/
- class WaitingRequests final : public nsISupports {
- virtual ~WaitingRequests() = default;
+ class LoadingRequest final : public nsISupports {
+ virtual ~LoadingRequest() = default;
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
- NS_DECL_CYCLE_COLLECTION_CLASS(WaitingRequests)
+ NS_DECL_CYCLE_COLLECTION_CLASS(LoadingRequest)
+
+ // The request that initiated the load and which is currently fetching or
+ // being compiled.
+ RefPtr<ModuleLoadRequest> mRequest;
+ // A list of any other requests for the same URI that are waiting for the
+ // initial load to complete. These will be resumed by ResumeWaitingRequests
+ // when that happens.
nsTArray<RefPtr<ModuleLoadRequest>> mWaiting;
};
// Module map
- nsRefPtrHashtable<nsURIHashKey, WaitingRequests> mFetchingModules;
+ nsRefPtrHashtable<nsURIHashKey, LoadingRequest> mFetchingModules;
nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
// List of dynamic imports that are currently being loaded.
@@ -241,6 +251,8 @@ class ModuleLoaderBase : public nsISupports {
JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
JS::Handle<JSString*> aSpecifier, JS::Handle<JSObject*> aPromise) = 0;
+ virtual bool IsDynamicImportSupported() { return true; }
+
// Called when dynamic import started successfully.
virtual void OnDynamicImportStarted(ModuleLoadRequest* aRequest) {}
@@ -333,10 +345,6 @@ class ModuleLoaderBase : public nsISupports {
nsresult GetFetchedModuleURLs(nsTArray<nsCString>& aURLs);
- // Removed a fetched module from the module map. Asserts that the module is
- // unlinked. Extreme care should be taken when calling this method.
- bool RemoveFetchedModule(nsIURI* aURL);
-
// Override the module loader with given loader until ResetOverride is called.
// While overridden, ModuleLoaderBase::GetCurrentModuleLoader returns aLoader.
//
@@ -421,7 +429,7 @@ class ModuleLoaderBase : public nsISupports {
void SetModuleFetchFinishedAndResumeWaitingRequests(
ModuleLoadRequest* aRequest, nsresult aResult);
- void ResumeWaitingRequests(WaitingRequests* aWaitingRequests, bool aSuccess);
+ void ResumeWaitingRequests(LoadingRequest* aLoadingRequest, bool aSuccess);
void ResumeWaitingRequest(ModuleLoadRequest* aRequest, bool aSuccess);
void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);