Issue #618 - Keep track of which modules in a graph have been fetched using a visited set

Ref: BZ 1365187
This commit is contained in:
Gaming4JC 2020-08-03 10:44:39 -04:00 • committed by Roy Tam
commit 6639e9cdac
4 changed files with 92 additions and 72 deletions

View file

@ -17,26 +17,48 @@ NS_INTERFACE_MAP_END_INHERITING(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_INHERITED(ModuleLoadRequest, ScriptLoadRequest, NS_IMPL_CYCLE_COLLECTION_INHERITED(ModuleLoadRequest, ScriptLoadRequest,
mBaseURL, mBaseURL,
mLoader, mLoader,
mParent,
mModuleScript, mModuleScript,
mImports) mImports)
NS_IMPL_ADDREF_INHERITED(ModuleLoadRequest, ScriptLoadRequest) NS_IMPL_ADDREF_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
NS_IMPL_RELEASE_INHERITED(ModuleLoadRequest, ScriptLoadRequest) NS_IMPL_RELEASE_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
ModuleLoadRequest::ModuleLoadRequest(nsIScriptElement* aElement, ModuleLoadRequest::ModuleLoadRequest(nsIURI* aURI,
nsIScriptElement* aElement,
uint32_t aVersion, uint32_t aVersion,
CORSMode aCORSMode, CORSMode aCORSMode,
const SRIMetadata &aIntegrity, const SRIMetadata &aIntegrity,
ScriptLoader* aLoader) ScriptLoader* aLoader)
: ScriptLoadRequest(ScriptKind::Module, : ScriptLoadRequest(ScriptKind::Module,
aURI,
aElement, aElement,
aVersion, aVersion,
aCORSMode, aCORSMode,
aIntegrity), aIntegrity),
mIsTopLevel(true), mIsTopLevel(true),
mLoader(aLoader) mLoader(aLoader),
{} mVisitedSet(new VisitedURLSet())
{
mVisitedSet->PutEntry(aURI);
}
ModuleLoadRequest::ModuleLoadRequest(nsIURI* aURI,
ModuleLoadRequest* aParent)
: ScriptLoadRequest(ScriptKind::Module,
aURI,
aParent->mElement,
aParent->mJSVersion,
aParent->mCORSMode,
aParent->mIntegrity),
mIsTopLevel(false),
mLoader(aParent->mLoader),
mVisitedSet(aParent->mVisitedSet)
{
MOZ_ASSERT(mVisitedSet->Contains(aURI));
mIsInline = false;
mReferrerPolicy = aParent->mReferrerPolicy;
}
void ModuleLoadRequest::Cancel() void ModuleLoadRequest::Cancel()
{ {
@ -132,8 +154,7 @@ ModuleLoadRequest::LoadFinished()
{ {
mLoader->ProcessLoadedModuleTree(this); mLoader->ProcessLoadedModuleTree(this);
mLoader = nullptr; mLoader = nullptr;
mParent = nullptr;
} }
} // dom namespace } // dom namespace
} // mozilla namespace } // mozilla namespace

View file

@ -8,6 +8,7 @@
#define mozilla_dom_ModuleLoadRequest_h #define mozilla_dom_ModuleLoadRequest_h
#include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/ScriptLoader.h"
#include "nsURIHashKey.h"
#include "mozilla/MozPromise.h" #include "mozilla/MozPromise.h"
namespace mozilla { namespace mozilla {
@ -16,6 +17,16 @@ namespace dom {
class ModuleScript; class ModuleScript;
class ScriptLoader; class ScriptLoader;
// A reference counted set of URLs we have visited in the process of loading a
// module graph.
class VisitedURLSet : public nsTHashtable<nsURIHashKey>
{
NS_INLINE_DECL_REFCOUNTING(VisitedURLSet)
private:
~VisitedURLSet() = default;
};
// A load request for a module, created for every top level module script and // A load request for a module, created for every top level module script and
// every module import. Load request can share a ModuleScript if there are // every module import. Load request can share a ModuleScript if there are
// multiple imports of the same module. // multiple imports of the same module.
@ -31,12 +42,18 @@ public:
NS_DECL_ISUPPORTS_INHERITED NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ModuleLoadRequest, ScriptLoadRequest) NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
ModuleLoadRequest(nsIScriptElement* aElement, // Create a top-level module load request.
ModuleLoadRequest(nsIURI* aURI,
nsIScriptElement* aElement,
uint32_t aVersion, uint32_t aVersion,
CORSMode aCORSMode, CORSMode aCORSMode,
const SRIMetadata& aIntegrity, const SRIMetadata& aIntegrity,
ScriptLoader* aLoader); ScriptLoader* aLoader);
// Create a module load request for an imported module.
ModuleLoadRequest(nsIURI* aURI,
ModuleLoadRequest* aParent);
bool IsTopLevel() const { bool IsTopLevel() const {
return mIsTopLevel; return mIsTopLevel;
} }
@ -55,7 +72,7 @@ private:
public: public:
// Is this a request for a top level module script or an import? // Is this a request for a top level module script or an import?
bool mIsTopLevel; const bool mIsTopLevel;
// The base URL used for resolving relative module imports. // The base URL used for resolving relative module imports.
nsCOMPtr<nsIURI> mBaseURL; nsCOMPtr<nsIURI> mBaseURL;
@ -64,10 +81,6 @@ public:
// finishes. // finishes.
RefPtr<ScriptLoader> mLoader; RefPtr<ScriptLoader> mLoader;
// The importing module, or nullptr for top level module scripts. Used to
// implement the ancestor list checked when fetching module dependencies.
RefPtr<ModuleLoadRequest> mParent;
// Set to a module script object after a successful load or nullptr on // Set to a module script object after a successful load or nullptr on
// failure. // failure.
RefPtr<ModuleScript> mModuleScript; RefPtr<ModuleScript> mModuleScript;
@ -79,9 +92,13 @@ public:
// Array of imported modules. // Array of imported modules.
nsTArray<RefPtr<ModuleLoadRequest>> mImports; nsTArray<RefPtr<ModuleLoadRequest>> mImports;
// Set of module URLs visited while fetching the module graph this request is
// part of.
RefPtr<VisitedURLSet> mVisitedSet;
}; };
} // dom namespace } // dom namespace
} // mozilla namespace } // mozilla namespace
#endif // mozilla_dom_ModuleLoadRequest_h #endif // mozilla_dom_ModuleLoadRequest_h

View file

@ -501,7 +501,7 @@ ScriptLoader::ProcessFetchedModuleSource(ModuleLoadRequest* aRequest)
} }
static nsresult static nsresult
ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray<nsIURI>& aUrls); ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray<nsIURI>* aUrlsOut);
nsresult nsresult
ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest) ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest)
@ -577,8 +577,7 @@ ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest)
// Validate requested modules and treat failure to resolve module specifiers // Validate requested modules and treat failure to resolve module specifiers
// the same as a parse error. // the same as a parse error.
nsCOMArray<nsIURI> urls; rv = ResolveRequestedModules(aRequest, nullptr);
rv = ResolveRequestedModules(aRequest, urls);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
aRequest->ModuleErrored(); aRequest->ModuleErrored();
return NS_OK; return NS_OK;
@ -665,33 +664,7 @@ ResolveModuleSpecifier(ModuleScript* aScript,
} }
static nsresult static nsresult
RequestedModuleIsInAncestorList(ModuleLoadRequest* aRequest, nsIURI* aURL, bool* aResult) ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray<nsIURI>* aUrlsOut)
{
const size_t ImportDepthLimit = 100;
*aResult = false;
size_t depth = 0;
while (aRequest) {
if (depth++ == ImportDepthLimit) {
return NS_ERROR_FAILURE;
}
bool equal;
nsresult rv = aURL->Equals(aRequest->mURI, &equal);
NS_ENSURE_SUCCESS(rv, rv);
if (equal) {
*aResult = true;
return NS_OK;
}
aRequest = aRequest->mParent;
}
return NS_OK;
}
static nsresult
ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray<nsIURI> &aUrls)
{ {
ModuleScript* ms = aRequest->mModuleScript; ModuleScript* ms = aRequest->mModuleScript;
@ -728,11 +701,8 @@ ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray<nsIURI> &aUrls)
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
bool isAncestor; if (aUrlsOut) {
nsresult rv = RequestedModuleIsInAncestorList(aRequest, uri, &isAncestor); aUrlsOut->AppendElement(uri.forget());
NS_ENSURE_SUCCESS(rv, rv);
if (!isAncestor) {
aUrls.AppendElement(uri.forget());
} }
} }
@ -746,17 +716,33 @@ ScriptLoader::StartFetchingModuleDependencies(ModuleLoadRequest* aRequest)
MOZ_ASSERT(!aRequest->mModuleScript->HasParseError()); MOZ_ASSERT(!aRequest->mModuleScript->HasParseError());
MOZ_ASSERT(!aRequest->IsReadyToRun()); MOZ_ASSERT(!aRequest->IsReadyToRun());
auto visitedSet = aRequest->mVisitedSet;
MOZ_ASSERT(visitedSet->Contains(aRequest->mURI));
aRequest->mProgress = ModuleLoadRequest::Progress::FetchingImports; aRequest->mProgress = ModuleLoadRequest::Progress::FetchingImports;
nsCOMArray<nsIURI> urls; nsCOMArray<nsIURI> urls;
nsresult rv = ResolveRequestedModules(aRequest, urls); nsresult rv = ResolveRequestedModules(aRequest, &urls);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
aRequest->ModuleErrored(); aRequest->ModuleErrored();
return; return;
} }
if (urls.Length() == 0) { // Remove already visited URLs from the list. Put unvisited URLs into the
// There are no descendents to load so this request is ready. // visited set.
int32_t i = 0;
while (i < urls.Count()) {
nsIURI* url = urls[i];
if (visitedSet->Contains(url)) {
urls.RemoveObjectAt(i);
} else {
visitedSet->PutEntry(url);
i++;
}
}
if (urls.Count() == 0) {
// There are no descendants to load so this request is ready.
aRequest->DependenciesLoaded(); aRequest->DependenciesLoaded();
return; return;
} }
@ -779,21 +765,14 @@ ScriptLoader::StartFetchingModuleDependencies(ModuleLoadRequest* aRequest)
} }
RefPtr<GenericPromise> RefPtr<GenericPromise>
ScriptLoader::StartFetchingModuleAndDependencies(ModuleLoadRequest* aRequest, ScriptLoader::StartFetchingModuleAndDependencies(ModuleLoadRequest* aParent,
nsIURI* aURI) nsIURI* aURI)
{ {
MOZ_ASSERT(aURI); MOZ_ASSERT(aURI);
RefPtr<ModuleLoadRequest> childRequest = RefPtr<ModuleLoadRequest> childRequest = new ModuleLoadRequest(aURI, aParent);
new ModuleLoadRequest(aRequest->mElement, aRequest->mJSVersion,
aRequest->mCORSMode, aRequest->mIntegrity, this);
childRequest->mIsTopLevel = false; aParent->mImports.AppendElement(childRequest);
childRequest->mURI = aURI;
childRequest->mIsInline = false;
childRequest->mReferrerPolicy = aRequest->mReferrerPolicy;
childRequest->mParent = aRequest;
aRequest->mImports.AppendElement(childRequest);
RefPtr<GenericPromise> ready = childRequest->mReady.Ensure(__func__); RefPtr<GenericPromise> ready = childRequest->mReady.Ensure(__func__);
@ -1201,17 +1180,19 @@ CSPAllowsInlineScript(nsIScriptElement *aElement, nsIDocument *aDocument)
ScriptLoadRequest* ScriptLoadRequest*
ScriptLoader::CreateLoadRequest(ScriptKind aKind, ScriptLoader::CreateLoadRequest(ScriptKind aKind,
nsIURI* aURI,
nsIScriptElement* aElement, nsIScriptElement* aElement,
uint32_t aVersion, CORSMode aCORSMode, uint32_t aVersion, CORSMode aCORSMode,
const SRIMetadata &aIntegrity) const SRIMetadata &aIntegrity)
{ {
if (aKind == ScriptKind::Classic) { if (aKind == ScriptKind::Classic) {
return new ScriptLoadRequest(aKind, aElement, aVersion, aCORSMode, return new ScriptLoadRequest(aKind, aURI, aElement,
aVersion,aCORSMode,
aIntegrity); aIntegrity);
} }
MOZ_ASSERT(aKind == ScriptKind::Module); MOZ_ASSERT(aKind == ScriptKind::Module);
return new ModuleLoadRequest(aElement, aVersion, aCORSMode, aIntegrity, this); return new ModuleLoadRequest(aURI, aElement, aVersion, aCORSMode, aIntegrity, this);
} }
bool bool
@ -1343,9 +1324,8 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
} }
} }
request = CreateLoadRequest(scriptKind, aElement, version, ourCORSMode, request = CreateLoadRequest(scriptKind, scriptURI, aElement,
sriMetadata); version, ourCORSMode, sriMetadata);
request->mURI = scriptURI;
request->mIsInline = false; request->mIsInline = false;
request->mReferrerPolicy = ourRefPolicy; request->mReferrerPolicy = ourRefPolicy;
@ -1466,11 +1446,11 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
} }
// Inline scripts ignore ther CORS mode and are always CORS_NONE // Inline scripts ignore ther CORS mode and are always CORS_NONE
request = CreateLoadRequest(scriptKind, aElement, version, CORS_NONE, request = CreateLoadRequest(scriptKind, mDocument->GetDocumentURI(), aElement,
version, CORS_NONE,
SRIMetadata()); // SRI doesn't apply SRIMetadata()); // SRI doesn't apply
request->mJSVersion = version; request->mJSVersion = version;
request->mIsInline = true; request->mIsInline = true;
request->mURI = mDocument->GetDocumentURI();
request->mLineNo = aElement->GetScriptLineNumber(); request->mLineNo = aElement->GetScriptLineNumber();
if (request->IsModuleRequest()) { if (request->IsModuleRequest()) {
@ -2603,9 +2583,8 @@ ScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
} }
RefPtr<ScriptLoadRequest> request = RefPtr<ScriptLoadRequest> request =
CreateLoadRequest(ScriptKind::Classic, nullptr, 0, CreateLoadRequest(ScriptKind::Classic, aURI, nullptr, 0,
Element::StringToCORSMode(aCrossOrigin), sriMetadata); Element::StringToCORSMode(aCrossOrigin), sriMetadata);
request->mURI = aURI;
request->mIsInline = false; request->mIsInline = false;
request->mReferrerPolicy = aReferrerPolicy; request->mReferrerPolicy = aReferrerPolicy;

View file

@ -62,6 +62,7 @@ protected:
public: public:
ScriptLoadRequest(ScriptKind aKind, ScriptLoadRequest(ScriptKind aKind,
nsIURI* aURI,
nsIScriptElement* aElement, nsIScriptElement* aElement,
uint32_t aVersion, uint32_t aVersion,
mozilla::CORSMode aCORSMode, mozilla::CORSMode aCORSMode,
@ -81,6 +82,7 @@ public:
mScriptTextBuf(nullptr), mScriptTextBuf(nullptr),
mScriptTextLength(0), mScriptTextLength(0),
mJSVersion(aVersion), mJSVersion(aVersion),
mURI(aURI),
mLineNo(1), mLineNo(1),
mCORSMode(aCORSMode), mCORSMode(aCORSMode),
mIntegrity(aIntegrity), mIntegrity(aIntegrity),
@ -165,7 +167,7 @@ public:
char16_t* mScriptTextBuf; // Holds script text for non-inline scripts. Don't char16_t* mScriptTextBuf; // Holds script text for non-inline scripts. Don't
size_t mScriptTextLength; // use nsString so we can give ownership to jsapi. size_t mScriptTextLength; // use nsString so we can give ownership to jsapi.
uint32_t mJSVersion; uint32_t mJSVersion;
nsCOMPtr<nsIURI> mURI; const nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIPrincipal> mOriginPrincipal; nsCOMPtr<nsIPrincipal> mOriginPrincipal;
nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing. nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing.
int32_t mLineNo; int32_t mLineNo;
@ -470,6 +472,7 @@ private:
ScriptLoadRequest* CreateLoadRequest( ScriptLoadRequest* CreateLoadRequest(
ScriptKind aKind, ScriptKind aKind,
nsIURI* aURI,
nsIScriptElement* aElement, nsIScriptElement* aElement,
uint32_t aVersion, uint32_t aVersion,
mozilla::CORSMode aCORSMode, mozilla::CORSMode aCORSMode,
@ -588,7 +591,7 @@ private:
void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest); void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
RefPtr<mozilla::GenericPromise> RefPtr<mozilla::GenericPromise>
StartFetchingModuleAndDependencies(ModuleLoadRequest* aRequest, nsIURI* aURI); StartFetchingModuleAndDependencies(ModuleLoadRequest* aParent, nsIURI* aURI);
nsIDocument* mDocument; // [WEAK] nsIDocument* mDocument; // [WEAK]
nsCOMArray<nsIScriptLoaderObserver> mObservers; nsCOMArray<nsIScriptLoaderObserver> mObservers;