From d5d7bb5e470b6789a5da41824034497982f2e4cb Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Apr 2023 01:59:14 -0500 Subject: [PATCH] Issue #1691 - Part 7d: Allow dynamic import in cases where there's no referencing script or module. Support dynamic import from classic scripts by creating ClassicScript objects and associating them with the compiled. https://bugzilla.mozilla.org/show_bug.cgi?id=1342012 This patch is incomplete, some code in ScriptLoader::EvaluateScript() could not be applied for missing dependencies. Part 7e will apply the missing dependencies and finish the patch. (cherry picked from commit ce31a906e801b72f06385b0755710317ccf3658b) --- dom/script/ModuleLoadRequest.cpp | 15 +-- dom/script/ModuleLoadRequest.h | 7 +- dom/script/ModuleScript.cpp | 52 ++++++---- dom/script/ModuleScript.h | 13 ++- dom/script/ScriptLoader.cpp | 171 ++++++++++++++++++++++--------- dom/script/ScriptLoader.h | 22 ++++ 6 files changed, 190 insertions(+), 90 deletions(-) diff --git a/dom/script/ModuleLoadRequest.cpp b/dom/script/ModuleLoadRequest.cpp index 9144258c35..9055e2a57d 100644 --- a/dom/script/ModuleLoadRequest.cpp +++ b/dom/script/ModuleLoadRequest.cpp @@ -20,13 +20,13 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleLoadRequest) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ModuleLoadRequest, ScriptLoadRequest) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mBaseURL, mLoader, mModuleScript, mImports) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader, mModuleScript, mImports) tmp->ClearDynamicImport(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ModuleLoadRequest, ScriptLoadRequest) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBaseURL, mLoader, mModuleScript, mImports) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoader, mModuleScript, mImports) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ModuleLoadRequest, @@ -69,17 +69,18 @@ static VisitedURLSet* NewVisitedSetForTopLevelImport(nsIURI* aURI) { } /* static */ ModuleLoadRequest* ModuleLoadRequest::CreateDynamicImport( - nsIURI* aURI, ModuleScript* aScript, - JS::Handle aReferencingPrivate, JS::Handle aSpecifier, - JS::Handle aPromise) { + nsIURI* aURI, ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL, + ScriptLoader* aLoader, JS::Handle aReferencingPrivate, + JS::Handle aSpecifier, JS::Handle aPromise) +{ MOZ_ASSERT(aSpecifier); MOZ_ASSERT(aPromise); auto request = new ModuleLoadRequest( - aURI, aScript->FetchOptions(), SRIMetadata(), aScript->BaseURL(), + aURI, aFetchOptions, SRIMetadata(), aBaseURL, true, /* is top level */ true, /* is dynamic import */ - aScript->Loader(), NewVisitedSetForTopLevelImport(aURI)); + aLoader, NewVisitedSetForTopLevelImport(aURI)); request->mIsInline = false; request->mScriptMode = ScriptMode::eAsync; diff --git a/dom/script/ModuleLoadRequest.h b/dom/script/ModuleLoadRequest.h index 996bac14d7..0b5f331bff 100644 --- a/dom/script/ModuleLoadRequest.h +++ b/dom/script/ModuleLoadRequest.h @@ -60,8 +60,8 @@ public: // Create a module load request for dynamic module import. static ModuleLoadRequest* CreateDynamicImport( - nsIURI* aURI, ModuleScript* aScript, - JS::Handle aReferencingPrivate, + nsIURI* aURI, ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL, + ScriptLoader* aLoader, JS::Handle aReferencingPrivate, JS::Handle aSpecifier, JS::Handle aPromise); bool IsTopLevel() const override { @@ -90,9 +90,6 @@ public: // Is this the top level request for a dynamic module import? const bool mIsDynamicImport; - // The base URL used for resolving relative module imports. - nsCOMPtr mBaseURL; - // Pointer to the script loader, used to trigger actions when the module load // finishes. RefPtr mLoader; diff --git a/dom/script/ModuleScript.cpp b/dom/script/ModuleScript.cpp index 35dec499c8..e8f2802e14 100644 --- a/dom/script/ModuleScript.cpp +++ b/dom/script/ModuleScript.cpp @@ -26,13 +26,11 @@ NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_CLASS(LoadedScript) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LoadedScript) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchOptions) NS_IMPL_CYCLE_COLLECTION_UNLINK(mBaseURL) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LoadedScript) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoader) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END @@ -42,28 +40,52 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTING_ADDREF(LoadedScript) NS_IMPL_CYCLE_COLLECTING_RELEASE(LoadedScript) -LoadedScript::LoadedScript(ScriptKind aKind, ScriptLoader* aLoader, +LoadedScript::LoadedScript(ScriptKind aKind, ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL) : mKind(aKind), - mLoader(aLoader), mFetchOptions(aFetchOptions), mBaseURL(aBaseURL) { - MOZ_ASSERT(mLoader); MOZ_ASSERT(mFetchOptions); MOZ_ASSERT(mBaseURL); } LoadedScript::~LoadedScript() { DropJSObjects(this); } +void LoadedScript::AssociateWithScript(JSScript* aScript) { + // Set a JSScript's private value to point to this object and + // increment our reference count. This is decremented by + // HostFinalizeTopLevelScript() below when the JSScript dies. + + MOZ_ASSERT(JS::GetScriptPrivate(aScript).isUndefined()); + JS::SetScriptPrivate(aScript, JS::PrivateValue(this)); + AddRef(); +} + +void HostFinalizeTopLevelScript(JSFreeOp* aFop, const JS::Value& aPrivate) { + // Decrement the reference count of a LoadedScript object that is + // pointed to by a dying JSScript. The reference count was + // originally incremented by AssociateWithScript() above. + + auto script = static_cast(aPrivate.toPrivate()); + +#ifdef DEBUG + if (script->IsModuleScript()) { + JSObject* module = script->AsModuleScript()->mModuleRecord.unbarrieredGet(); + MOZ_ASSERT(JS::GetModulePrivate(module) == aPrivate); + } +#endif + + script->Release(); +} + ////////////////////////////////////////////////////////////// // ClassicScript ////////////////////////////////////////////////////////////// -ClassicScript::ClassicScript(ScriptLoader* aLoader, - ScriptFetchOptions* aFetchOptions, +ClassicScript::ClassicScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL) - : LoadedScript(ScriptKind::Classic, aLoader, aFetchOptions, aBaseURL) {} + : LoadedScript(ScriptKind::Classic, aFetchOptions, aBaseURL) {} ////////////////////////////////////////////////////////////// // ModuleScript @@ -92,9 +114,8 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_ADDREF_INHERITED(ModuleScript, LoadedScript) NS_IMPL_RELEASE_INHERITED(ModuleScript, LoadedScript) -ModuleScript::ModuleScript(ScriptLoader* aLoader, - ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL) - : LoadedScript(ScriptKind::Module, aLoader, aFetchOptions, aBaseURL) { +ModuleScript::ModuleScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL) + : LoadedScript(ScriptKind::Module, aFetchOptions, aBaseURL) { MOZ_ASSERT(!ModuleRecord()); MOZ_ASSERT(!HasParseError()); MOZ_ASSERT(!HasErrorToRethrow()); @@ -150,15 +171,6 @@ ModuleScript::SetParseError(const JS::Value& aError) AddRef(); } -void HostFinalizeTopLevelScript(JSFreeOp* aFop, const JS::Value& aPrivate) { - auto script = static_cast(aPrivate.toPrivate()); - if (script) { - MOZ_ASSERT(JS::GetModulePrivate(script->mModuleRecord.unbarrieredGet()) == - aPrivate); - script->UnlinkModuleRecord(); - } -} - void ModuleScript::SetErrorToRethrow(const JS::Value& aError) { diff --git a/dom/script/ModuleScript.h b/dom/script/ModuleScript.h index 95b4edbb94..b3eb841778 100644 --- a/dom/script/ModuleScript.h +++ b/dom/script/ModuleScript.h @@ -31,8 +31,8 @@ class LoadedScript : public nsISupports nsCOMPtr mBaseURL; protected: - LoadedScript(ScriptKind aKind, ScriptLoader* aLoader, - ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL); + LoadedScript(ScriptKind aKind, + ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL); virtual ~LoadedScript(); @@ -45,9 +45,10 @@ class LoadedScript : public nsISupports inline ClassicScript* AsClassicScript(); inline ModuleScript* AsModuleScript(); - ScriptLoader* Loader() const { return mLoader; } ScriptFetchOptions* FetchOptions() const { return mFetchOptions; } nsIURI* BaseURL() const { return mBaseURL; } + + void AssociateWithScript(JSScript* aScript); }; class ClassicScript final : public LoadedScript @@ -55,8 +56,7 @@ class ClassicScript final : public LoadedScript ~ClassicScript() = default; public: - ClassicScript(ScriptLoader* aLoader, ScriptFetchOptions* aFetchOptions, - nsIURI* aBaseURL); + ClassicScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL); }; // A single module script. May be used to satisfy multiple load requests. @@ -74,8 +74,7 @@ public: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ModuleScript, LoadedScript) - ModuleScript(ScriptLoader* aLoader, ScriptFetchOptions* aFetchOptions, - nsIURI* aBaseURL); + ModuleScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL); void SetModuleRecord(JS::Handle aModuleRecord); void SetParseError(const JS::Value& aError); diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp index e84ebcf151..64a80891bb 100644 --- a/dom/script/ScriptLoader.cpp +++ b/dom/script/ScriptLoader.cpp @@ -20,6 +20,7 @@ #include "mozilla/dom/SRILogHelper.h" #include "nsGkAtoms.h" #include "nsNetUtil.h" +#include "nsGlobalWindow.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptContext.h" #include "nsIScriptSecurityManager.h" @@ -638,7 +639,7 @@ ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest) MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr)); RefPtr moduleScript = - new ModuleScript(this, aRequest->mFetchOptions, aRequest->mBaseURL); + new ModuleScript(aRequest->mFetchOptions, aRequest->mBaseURL); aRequest->mModuleScript = moduleScript; if (!module) { @@ -711,8 +712,7 @@ HandleResolveFailure(JSContext* aCx, ModuleScript* aScript, } static already_AddRefed -ResolveModuleSpecifier(ModuleScript* aScript, - const nsAString& aSpecifier) +ResolveModuleSpecifier(ScriptLoader* aLoader, LoadedScript* aScript, const nsAString& aSpecifier) { // The following module specifiers are allowed by the spec: // - a valid absolute URL @@ -737,7 +737,15 @@ ResolveModuleSpecifier(ModuleScript* aScript, return nullptr; } - rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, aScript->BaseURL()); + // Get the document's base URL if we don't have a referencing script here. + nsCOMPtr baseURL; + if (aScript) { + baseURL = aScript->BaseURL(); + } else { + baseURL = aLoader->GetDocument()->GetDocBaseURI(); + } + + rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, baseURL); if (NS_SUCCEEDED(rv)) { return uri.forget(); } @@ -776,7 +784,8 @@ ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray* aUrlsOu } // Let url be the result of resolving a module specifier given module script and requested. - nsCOMPtr uri = ResolveModuleSpecifier(ms, specifier); + nsCOMPtr uri = + ResolveModuleSpecifier(aRequest->mLoader, ms, specifier); if (!uri) { nsresult rv = HandleResolveFailure(cx, ms, specifier); NS_ENSURE_SUCCESS(rv, rv); @@ -869,34 +878,83 @@ ScriptLoader::StartFetchingModuleAndDependencies(ModuleLoadRequest* aParent, return ready; } +static ScriptLoader* GetCurrentScriptLoader(JSContext* aCx) { + JSObject* object = JS::CurrentGlobalOrNull(aCx); + if (!object) { + return nullptr; + } + + nsIGlobalObject* global = xpc::NativeGlobal(object); + if (!global) { + return nullptr; + } + + nsCOMPtr win = do_QueryInterface(global); + nsGlobalWindow* window = nsGlobalWindow::Cast(win); + if (!window) { + return nullptr; + } + + nsIDocument* document = window->GetDocument(); + if (!document) { + return nullptr; + } + + ScriptLoader* loader = document->ScriptLoader(); + if (!loader) { + return nullptr; + } + + return loader; +} + +static LoadedScript* GetLoadedScriptOrNull( + JSContext* aCx, JS::Handle aReferencingPrivate) { + if (aReferencingPrivate.isUndefined()) { + return nullptr; + } + + auto script = static_cast(aReferencingPrivate.toPrivate()); + MOZ_ASSERT_IF( + script->IsModuleScript(), + JS::GetModulePrivate(script->AsModuleScript()->ModuleRecord()) == + aReferencingPrivate); + + return script; +} + // 8.1.3.8.1 HostResolveImportedModule(referencingModule, specifier) JSObject* HostResolveImportedModule(JSContext* aCx, JS::Handle aReferencingPrivate, JS::Handle aSpecifier) { - // Let referencing module script be referencingModule.[[HostDefined]]. - if (aReferencingPrivate.isUndefined()) { - JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr, - JSMSG_IMPORT_SCRIPT_NOT_FOUND); - return nullptr; - } + JS::Rooted module(aCx); + ScriptLoader::ResolveImportedModule(aCx, aReferencingPrivate, aSpecifier, + &module); + return module; +} - auto script = static_cast(aReferencingPrivate.toPrivate()); - MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate); +/* static */ void ScriptLoader::ResolveImportedModule( + JSContext* aCx, JS::Handle aReferencingPrivate, + JS::Handle aSpecifier, JS::MutableHandle aModuleOut) { + MOZ_ASSERT(!aModuleOut); + + RefPtr script(GetLoadedScriptOrNull(aCx, aReferencingPrivate)); // Let url be the result of resolving a module specifier given referencing // module script and specifier. nsAutoJSString string; if (!string.init(aCx, aSpecifier)) { - return nullptr; - } - if (!script || !aCx) { - // Our module context was ripped out from under us... - return nullptr; + return; } - nsCOMPtr uri = ResolveModuleSpecifier(script, string); + RefPtr loader = GetCurrentScriptLoader(aCx); + if (!loader) { + return; + } + + nsCOMPtr uri = ResolveModuleSpecifier(loader, script, string); // This cannot fail because resolving a module specifier must have been // previously successful with these same two arguments. @@ -905,17 +963,13 @@ HostResolveImportedModule(JSContext* aCx, // Let resolved module script be moduleMap[url]. (This entry must exist for us // to have gotten to this point.) - ModuleScript* ms = script->Loader()->GetFetchedModule(uri); + ModuleScript* ms = loader->GetFetchedModule(uri); MOZ_ASSERT(ms, "Resolved module not found in module map"); - if (!ms) { - // Already-resolved module has been removed from the map/unloaded... - return nullptr; - } MOZ_ASSERT(!ms->HasParseError()); MOZ_ASSERT(ms->ModuleRecord()); - return ms->ModuleRecord(); + aModuleOut.set(ms->ModuleRecord()); } bool @@ -950,15 +1004,7 @@ bool HostImportModuleDynamically(JSContext* aCx, JS::Handle aReferencingPrivate, JS::Handle aSpecifier, JS::Handle aPromise) { - if (aReferencingPrivate.isUndefined()) { - JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr, - JSMSG_IMPORT_SCRIPT_NOT_FOUND); - return false; - } - - auto script = static_cast(aReferencingPrivate.toPrivate()); - MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) == - aReferencingPrivate); + RefPtr script(GetLoadedScriptOrNull(aCx, aReferencingPrivate)); // Attempt to resolve the module specifier. nsAutoJSString string; @@ -966,7 +1012,12 @@ bool HostImportModuleDynamically(JSContext* aCx, return false; } - nsCOMPtr uri = ResolveModuleSpecifier(script, string); + RefPtr loader = GetCurrentScriptLoader(aCx); + if (!loader) { + return false; + } + + nsCOMPtr uri = ResolveModuleSpecifier(loader, script, string); if (!uri) { JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr, JSMSG_BAD_MODULE_SPECIFIER, string.get()); @@ -974,10 +1025,27 @@ bool HostImportModuleDynamically(JSContext* aCx, } // Create a new top-level load request. - RefPtr request = ModuleLoadRequest::CreateDynamicImport( - uri, script, aReferencingPrivate, aSpecifier, aPromise); + ScriptFetchOptions* options; + nsIURI* baseURL; + if (script) { + options = script->FetchOptions(); + baseURL = script->BaseURL(); + } else { + // We don't have a referencing script so fall back on using + // options from the document. This can happen when the user + // triggers an inline event handler, as there is no active script + // there. + nsIDocument* document = loader->GetDocument(); + options = new ScriptFetchOptions(mozilla::CORS_NONE, + document->GetReferrerPolicy(), nullptr, + document->NodePrincipal()); + baseURL = document->GetDocBaseURI(); + } - script->Loader()->StartDynamicImport(request); + RefPtr request = ModuleLoadRequest::CreateDynamicImport( + uri, options, baseURL, loader, aReferencingPrivate, aSpecifier, aPromise); + + loader->StartDynamicImport(request); return true; } @@ -1700,7 +1768,8 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement) if (request->IsModuleRequest()) { ModuleLoadRequest* modReq = request->AsModuleRequest(); - modReq->mBaseURL = mDocument->GetDocBaseURI(); + + request->mBaseURL = mDocument->GetDocBaseURI(); if (aElement->GetScriptAsync()) { AddAsyncRequest(modReq); @@ -2801,6 +2870,18 @@ ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest, mParserBlockingRequest == aRequest, "aRequest should be pending!"); + nsCOMPtr uri; + rv = channel->GetOriginalURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + // Fixup moz-extension: and resource: URIs, because the channel URI will + // point to file:, which won't be allowed to load. + if (uri && IsInternalURIScheme(uri)) { + aRequest->mBaseURL = uri; + } else { + channel->GetURI(getter_AddRefs(aRequest->mBaseURL)); + } + if (aRequest->IsModuleRequest()) { ModuleLoadRequest* request = aRequest->AsModuleRequest(); @@ -2813,18 +2894,6 @@ ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest, return NS_ERROR_FAILURE; } - nsCOMPtr uri; - rv = channel->GetOriginalURI(getter_AddRefs(uri)); - NS_ENSURE_SUCCESS(rv, rv); - - // Fixup internal scheme URIs like resource:, because the channel URI - // will point to file: which won't be allowed to load. - if (uri && IsInternalURIScheme(uri)) { - request->mBaseURL = uri; - } else { - channel->GetURI(getter_AddRefs(request->mBaseURL)); - } - // Attempt to compile off main thread. rv = AttemptAsyncScriptCompile(request); if (NS_SUCCEEDED(rv)) { diff --git a/dom/script/ScriptLoader.h b/dom/script/ScriptLoader.h index da39d0a630..53d5ddb4e0 100644 --- a/dom/script/ScriptLoader.h +++ b/dom/script/ScriptLoader.h @@ -258,6 +258,9 @@ public: // Holds the top-level JSScript that corresponds to the current source, once // it is parsed, and planned to be saved in the bytecode cache. JS::Heap mScript; + + // The base URL used for resolving relative module imports. + nsCOMPtr mBaseURL; }; class ScriptLoadRequestList : private mozilla::LinkedList @@ -561,11 +564,30 @@ public: */ void ClearModuleMap(); + /** + * Implement the HostResolveImportedModule abstract operation. + * + * Resolve a module specifier string and look this up in the module + * map, returning the result. This is only called for previously + * loaded modules and always succeeds. + * + * @param aReferencingPrivate A JS::Value which is either undefined + * or contains a LoadedScript private pointer. + * @param aSpecifier The module specifier. + * @param aModuleOut This is set to the module found. + */ + static void ResolveImportedModule(JSContext* aCx, + JS::Handle aReferencingPrivate, + JS::Handle aSpecifier, + JS::MutableHandle aModuleOut); + void StartDynamicImport(ModuleLoadRequest* aRequest); void FinishDynamicImport(ModuleLoadRequest* aRequest, nsresult aResult); void FinishDynamicImport(JSContext* aCx, ModuleLoadRequest* aRequest, nsresult aResult); + nsIDocument* GetDocument() const { return mDocument; } + private: virtual ~ScriptLoader();