diff options
Diffstat (limited to 'js/loader')
-rw-r--r-- | js/loader/LoadContextBase.h | 3 | ||||
-rw-r--r-- | js/loader/LoadedScript.cpp | 60 | ||||
-rw-r--r-- | js/loader/LoadedScript.h | 32 | ||||
-rw-r--r-- | js/loader/ModuleLoadRequest.cpp | 4 | ||||
-rw-r--r-- | js/loader/ModuleLoaderBase.cpp | 109 | ||||
-rw-r--r-- | js/loader/ModuleLoaderBase.h | 28 |
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); |