/* 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/. */
#ifndef js_loader_ScriptLoadRequest_h
#define js_loader_ScriptLoadRequest_h
#include "js/experimental/JSStencil.h"
#include "js/RootingAPI.h"
#include "js/SourceText.h"
#include "js/TypeDecls.h"
#include "mozilla/Assertions.h"
#include "mozilla/dom/CacheExpirationTime.h"
#include "mozilla/dom/SRIMetadata.h"
#include "mozilla/LinkedList.h"
#include "mozilla/PreloaderBase.h"
#include "mozilla/RefPtr.h"
#include "mozilla/SharedSubResourceCache.h" // mozilla::SubResourceNetworkMetadataHolder
#include "mozilla/StaticPrefs_dom.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIGlobalObject.h"
#include "LoadedScript.h"
#include "ScriptKind.h"
#include "ScriptFetchOptions.h"
namespace mozilla::dom {
class ScriptLoadContext;
class WorkerLoadContext;
class WorkletLoadContext;
enum class RequestPriority : uint8_t;
}
// namespace mozilla::dom
namespace mozilla::loader {
class SyncLoadContext;
}
// namespace mozilla::loader
namespace JS::loader {
class LoadContextBase;
class ModuleLoadRequest;
class ScriptLoadRequestList;
/*
* ScriptLoadRequest
*
* ScriptLoadRequest is a generic representation of a request/response for
* JavaScript file that will be loaded by a Script/Module loader. This
* representation is used by the following:
* - DOM ScriptLoader / ModuleLoader
* - worker ScriptLoader / ModuleLoader
* - worklet ScriptLoader
* - SyncModuleLoader
*
* The ScriptLoadRequest contains information specific to the current request,
* such as the kind of script (classic, module, etc), and the reference to the
* LoadedScript which contains the information independent of the current
* request, such as the URI, the ScriptFetchOptions, etc.
*
* Relationship to ScriptLoadContext:
*
* ScriptLoadRequest and ScriptLoadContexts have a circular pointer. A
* ScriptLoadContext augments the loading of a ScriptLoadRequest by providing
* additional information regarding the loading and evaluation behavior (see
* the ScriptLoadContext class for details). In terms of responsibility,
* the ScriptLoadRequest represents "What" is being loaded, and the
* ScriptLoadContext represents "How".
*
* TODO: see if we can use it in the jsshell script loader. We need to either
* remove ISUPPORTS or find a way to encorporate that in the jsshell. We would
* then only have one implementation of the script loader, and it would be
* tested whenever jsshell tests are run. This would mean finding another way to
* create ScriptLoadRequest lists.
*
*/
class ScriptLoadRequest :
public nsISupports,
private mozilla::LinkedListElement<ScriptLoadRequest>,
public LoadedScriptDelegate<ScriptLoadRequest> {
using super = LinkedListElement<ScriptLoadRequest>;
// Allow LinkedListElement<ScriptLoadRequest> to cast us to itself as needed.
friend class mozilla::LinkedListElement<ScriptLoadRequest>;
friend class ScriptLoadRequestList;
protected:
virtual ~ScriptLoadRequest();
public:
using SRIMetadata = mozilla::dom::SRIMetadata;
ScriptLoadRequest(ScriptKind aKind,
const SRIMetadata& aIntegrity,
nsIURI* aReferrer, LoadContextBase* aContext);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ScriptLoadRequest)
using super::getNext;
using super::isInList;
template <
typename T,
typename D = DeletePolicy<T>>
using UniquePtr = mozilla::UniquePtr<T, D>;
bool IsModuleRequest()
const {
return mKind == ScriptKind::eModule; }
bool IsImportMapRequest()
const {
return mKind == ScriptKind::eImportMap; }
ModuleLoadRequest* AsModuleRequest();
const ModuleLoadRequest* AsModuleRequest()
const;
CacheExpirationTime ExpirationTime()
const {
// The request's expiration time is used only when it's received from
// necko. For in-memory cached, case, the
// SharedSubResourceCache::CompleteSubResource::mExpirationTime field is
// used instead.
MOZ_ASSERT(!IsRetrievedFromMemoryCache());
return mExpirationTime;
}
void SetMinimumExpirationTime(
const CacheExpirationTime& aExpirationTime) {
mExpirationTime.SetMinimum(aExpirationTime);
}
virtual bool IsTopLevel()
const {
return true; };
virtual void Cancel();
virtual void SetReady();
enum class State : uint8_t {
// The initial state before checking the in-memory cache.
CheckingCache,
// One of the following:
// * Fetching the script text from the network
// * Fetching the script text from the necko cache
// * Fetching the serialized stencil from the necko cache
Fetching,
// Retrieved the already-compiled script from the in-memory cache, but not
// yet transitioned to Ready state, in order to avoid processing the script
// in the same timing as the script is inserted.
DelayingReady,
// Fetched the script text either from the network or the necko cache,
// and performing off-thread compilation.
//
// NOTE: This state is not used when the compilation is done on the
// main thread.
Compiling,
// One of the following:
// * Fetched the script text from the network or the necko cache,
// and the compilation is done
// * Fetched the serialized stencil from the necko cache, and the
// decoding is done
// * Retrieved from the stencil in-memory cache
// The script can be executed, or is already executed.
Ready,
// The request is cancelled, and the script is no longer used.
Canceled
};
// Before any attempt at fetching resources from the cache we should first
// make sure that the resource does not yet exists in the cache. In which case
// we might simply alias its LoadedScript. Otherwise a new one would be
// created.
bool IsCheckingCache()
const {
return mState == State::CheckingCache; }
// Setup and load resources, to fill the LoadedScript and make it usable by
// the JavaScript engine.
bool IsFetching()
const {
return mState == State::Fetching; }
bool IsDelayingReady()
const {
return mState == State::DelayingReady; }
bool IsCompiling()
const {
return mState == State::Compiling; }
bool IsCanceled()
const {
return mState == State::Canceled; }
// Return whether the request has been completed, either successfully or
// otherwise.
bool IsFinished()
const {
return mState == State::Ready || mState == State::Canceled;
}
mozilla::dom::ReferrerPolicy ReferrerPolicy()
const {
return FetchInfo()->ReferrerPolicy();
}
nsIURI* BaseURL()
const {
return FetchInfo()->BaseURL(); }
void SetBaseURL(nsIURI* aBaseURL) { FetchInfo()->SetBaseURL(aBaseURL); }
void SetBaseURLFromChannelAndOriginalURI(nsIChannel* aChannel,
nsIURI* aOriginalURI) {
FetchInfo()->SetBaseURLFromChannelAndOriginalURI(aChannel, aOriginalURI);
}
ScriptFetchOptions* FetchOptions()
const {
return FetchInfo()->FetchOptions();
}
mozilla::dom::RequestPriority FetchPriority()
const {
return FetchOptions()->mFetchPriority;
}
enum ParserMetadata ParserMetadata()
const {
return FetchOptions()->mParserMetadata;
}
const nsString& Nonce()
const {
return FetchOptions()->mNonce; }
nsIPrincipal* TriggeringPrincipal()
const {
return FetchOptions()->mTriggeringPrincipal;
}
// Convert a CheckingCache ScriptLoadRequest into a DelayingReady
// (for classic script and import map) or Fetching (for module script),
// by populating the script data from the cached script.
//
// The DelayingReady state should be converted into Ready state, by calling
// SetReady in the next event tick.
//
// aFetchOptions is the current request's fetch option, which can have
// different nonce value.
void CacheEntryFound(LoadedScript* aLoadedScript,
ScriptFetchOptions* aFetchOptions);
// Use the cached entry, reviving from the dirty state.
// This should be called when the request is getting fetched from necko,
// and this keeps the request in the Fetching state.
void CacheEntryRevived(LoadedScript* aLoadedScript);
// Convert a CheckingCache ScriptLoadRequest into a Fetching one, by creating
// a new LoadedScript which is matching the ScriptKind provided when
// constructing this ScriptLoadRequest.
void NoCacheEntryFound(mozilla::dom::ReferrerPolicy aReferrerPolicy,
ScriptFetchOptions* aFetchOptions, nsIURI* aURI);
private:
void SetCacheEntry(LoadedScript* aLoadedScript,
ScriptFetchOptions* aFetchOptions);
public:
bool PassedConditionForDiskCache()
const {
return mDiskCachingPlan == CachingPlan::PassedCondition;
}
bool PassedConditionForMemoryCache()
const {
return mMemoryCachingPlan == CachingPlan::PassedCondition;
}
bool PassedConditionForEitherCache()
const {
return PassedConditionForDiskCache() || PassedConditionForMemoryCache();
}
void MarkNotCacheable() {
mDiskCachingPlan = CachingPlan::NotCacheable;
mMemoryCachingPlan = CachingPlan::NotCacheable;
}
bool IsMarkedNotCacheable()
const {
MOZ_ASSERT_IF(mDiskCachingPlan == CachingPlan::NotCacheable,
mMemoryCachingPlan == CachingPlan::NotCacheable);
MOZ_ASSERT_IF(mDiskCachingPlan != CachingPlan::NotCacheable,
mMemoryCachingPlan != CachingPlan::NotCacheable);
return mDiskCachingPlan == CachingPlan::NotCacheable;
}
void MarkSkippedDiskCaching() {
MOZ_ASSERT(mDiskCachingPlan == CachingPlan::Uninitialized ||
mDiskCachingPlan == CachingPlan::PassedCondition);
mDiskCachingPlan = CachingPlan::Skipped;
}
void MarkSkippedMemoryCaching() {
MOZ_ASSERT(mMemoryCachingPlan == CachingPlan::Uninitialized ||
mMemoryCachingPlan == CachingPlan::PassedCondition);
mMemoryCachingPlan = CachingPlan::Skipped;
}
void MarkSkippedAllCaching() {
MarkSkippedDiskCaching();
MarkSkippedMemoryCaching();
}
void MarkPassedConditionForDiskCache() {
MOZ_ASSERT(mDiskCachingPlan == CachingPlan::Uninitialized);
mDiskCachingPlan = CachingPlan::PassedCondition;
}
void MarkPassedConditionForMemoryCache() {
MOZ_ASSERT(mMemoryCachingPlan == CachingPlan::Uninitialized);
mMemoryCachingPlan = CachingPlan::PassedCondition;
}
mozilla::CORSMode CORSMode()
const {
return FetchOptions()->mCORSMode; }
bool HasLoadContext()
const {
return mLoadContext; }
bool HasScriptLoadContext()
const;
bool HasWorkerLoadContext()
const;
mozilla::dom::ScriptLoadContext* GetScriptLoadContext();
const mozilla::dom::ScriptLoadContext* GetScriptLoadContext()
const;
mozilla::loader::SyncLoadContext* GetSyncLoadContext();
mozilla::dom::WorkerLoadContext* GetWorkerLoadContext();
mozilla::dom::WorkletLoadContext* GetWorkletLoadContext();
const LoadedScript* getLoadedScript()
const {
return mLoadedScript.get(); }
LoadedScript* getLoadedScript() {
return mLoadedScript.get(); }
bool HasStencil()
const {
return !!mStencil; }
JS::Stencil* GetStencil()
const {
return mStencil; }
void SetStencil(JS::Stencil* aStencil) { mStencil = aStencil; }
void ClearStencil() { mStencil = nullptr; }
bool HasSourceMapURL()
const {
return mHasSourceMapURL_; }
const nsString& GetSourceMapURL()
const {
MOZ_ASSERT(mHasSourceMapURL_);
return mMaybeSourceMapURL_;
}
void SetSourceMapURL(
const nsString& aSourceMapURL) {
MOZ_ASSERT(!mHasSourceMapURL_);
mMaybeSourceMapURL_ = aSourceMapURL;
mHasSourceMapURL_ =
true;
}
bool HasDirtyCache()
const {
return mHasDirtyCache_; }
void SetHasDirtyCache() { mHasDirtyCache_ =
true; }
bool HadPostponed()
const {
return mHadPostponed_; }
void SetHadPostponed() { mHadPostponed_ =
true; }
const ScriptFetchInfo* FetchInfo()
const {
return mFetchInfo; }
ScriptFetchInfo* FetchInfo() {
return mFetchInfo; }
// Becomes true if the actual script data is retrieved from the
// SharedScriptCache.
// This becomes true in the following two situations:
// * A valid cache is found when starting the request
// * A dirty cache is found when starting the request, and the
// cache is revived (becomes true only after revived)
//
// This is different than LoadedScript::IsCachedStencil, given that
// LoadedScript::IsCachedStencil can become true also when the
// script data is retrieved as text or serialized stencil and then
// converted to the cached stencil.
bool IsRetrievedFromMemoryCache()
const {
return mIsRetrievedFromMemoryCache;
}
// Given that the LoadedScript::mDataType field used by
// LoadedScript::IsTextSource and LoadedScript::IsSerializedStencil be set to
// eCachedStencil or eInvalidatedCachedStencil after setting to
// others, the following accessors can be called only if one of the following
// conditions is met:
// * while receiving the response from necko,
// which means the cached case never reaches the code path
// * before converting to cached stencil,
// which means only text or serialized stencil cases can reach the code
// path
// * after filtering out the cached stencil cases
bool IsFetchedAsTextSource()
const {
MOZ_ASSERT(!IsRetrievedFromMemoryCache());
MOZ_ASSERT(!getLoadedScript()->IsCachedStencil());
MOZ_ASSERT(!getLoadedScript()->IsInvalidatedCachedStencil());
return getLoadedScript()->IsTextSource();
}
bool IsRetrievedAsSerializedStencil()
const {
MOZ_ASSERT(!IsRetrievedFromMemoryCache());
MOZ_ASSERT(!getLoadedScript()->IsCachedStencil());
MOZ_ASSERT(!getLoadedScript()->IsInvalidatedCachedStencil());
return getLoadedScript()->IsSerializedStencil();
}
public:
// Fields.
// Whether this is a classic script, a module script, or an import map.
const ScriptKind mKind;
// Are we still waiting for a load to complete?
State mState;
// Request source, not cached serialized Stencil.
bool mFetchSourceOnly :
1;
// Becomes true if this has source map url.
//
// Do not access directly.
// Use HasSourceMapURL(), SetSourceMapURL(), and GetSourceMapURL().
bool mHasSourceMapURL_ :
1;
// Set to true if this response is found in the in-memory cache, but the
// cache is marked as dirty, and needs validation.
//
// This request should go to necko, and when the response is received,
// the cache should be either revived or evicted.
bool mHasDirtyCache_ :
1;
// Set to true if this script had already been postponed in the scheduling.
bool mHadPostponed_ :
1;
enum class CachingPlan : uint8_t {
// This is not yet considered for caching.
Uninitialized,
// This request is not cacheable (e.g. inline script, JSON module).
NotCacheable,
// This request is cacheable, but is marked for skipping due to
// not passing conditions.
Skipped,
// This fits the condition for the caching (e.g. file size, fetch count).
PassedCondition,
};
CachingPlan mDiskCachingPlan :
2;
CachingPlan mMemoryCachingPlan :
2;
bool mIsRetrievedFromMemoryCache :
1;
CacheExpirationTime mExpirationTime = CacheExpirationTime::Never();
RefPtr<mozilla::SubResourceNetworkMetadataHolder> mNetworkMetadata;
const SRIMetadata mIntegrity;
const nsCOMPtr<nsIURI> mReferrer;
// Holds source map url for loaded scripts.
//
// Do not access directly.
// Use HasSourceMapURL(), SetSourceMapURL(), and GetSourceMapURL().
nsString mMaybeSourceMapURL_;
nsCOMPtr<nsIPrincipal> mOriginPrincipal;
// Keep the URI's filename alive during off thread parsing.
// Also used by workers to report on errors while loading, and used by
// worklets as the file name in compile options.
nsAutoCString mURL;
// The loaded script holds the data which can be shared among similar requests
RefPtr<LoadedScript> mLoadedScript;
// Either the result of compilation/decode, or the stencil retrieved from
// cache.
// Once this field is populated, it's is guaranteed to be valid until the
// stencil is instantiated.
RefPtr<JS::Stencil> mStencil;
RefPtr<ScriptFetchInfo> mFetchInfo;
// LoadContext for augmenting the load depending on the loading
// context (DOM, Worker, etc.)
RefPtr<LoadContextBase> mLoadContext;
// EarlyHintRegistrar id to connect the http channel back to the preload, with
// a default of value of 0 indicating that this request is not an early hints
// preload.
uint64_t mEarlyHintPreloaderId;
};
}
// namespace JS::loader
#endif // js_loader_ScriptLoadRequest_h