Bug 1471091: Avoid cloning and caching process scripts. r=mccr8

We only run process scripts once per process, so there's no need to compile
them for the compilation scope, or to keep a separate cloned copy alive for
the length of the session.

This patch changes the caching behavior of message managers to compile
single-use scripts directly for the target global, and avoid caching them for
the rest of the session. It also changes the preloader to drop references to
these scripts after they've been executed and/or encoded, as appropriate.

MozReview-Commit-ID: EfKo2aYbBxl

--HG--
extra : rebase_source : aebc5812bef4413d497ac4fdb59aced00a5a4c8e
extra : absorb_source : cf5795eb0bff47d08b1ab45f002afb3f05109c93
extra : histedit_source : e92619d2a818095241c52d8f1961ccad38300d32
This commit is contained in:
Kris Maglione 2018-06-29 18:07:46 -07:00
Родитель cd92626ded
Коммит 8751f930ce
5 изменённых файлов: 98 добавлений и 24 удалений

Просмотреть файл

@ -94,6 +94,11 @@ public:
}
virtual nsIPrincipal* GetPrincipal() override { return mPrincipal; }
bool IsProcessScoped() const override
{
return true;
}
void SetInitialProcessData(JS::HandleValue aInitialData);
protected:

Просмотреть файл

@ -1258,7 +1258,7 @@ nsMessageManagerScriptExecutor::LoadScriptInternal(JS::Handle<JSObject*> aGlobal
// with a different WillRunInGlobalScope() value.
bool shouldCache = !holder;
TryCacheLoadAndCompileScript(aURL, aRunInGlobalScope,
shouldCache, &script);
shouldCache, aGlobal, &script);
}
AutoEntryScript aes(aGlobal, "message manager script load");
@ -1283,6 +1283,7 @@ nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
const nsAString& aURL,
bool aRunInGlobalScope,
bool aShouldCache,
JS::Handle<JSObject*> aGlobal,
JS::MutableHandle<JSScript*> aScriptp)
{
nsCString url = NS_ConvertUTF16toUTF8(aURL);
@ -1301,10 +1302,17 @@ nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
return;
}
// Compile the script in the compilation scope instead of the current global
// to avoid keeping the current compartment alive.
// If this script won't be cached, or there is only one of this type of
// message manager per process, treat this script as run-once. Run-once
// scripts can be compiled directly for the target global, and will be dropped
// from the preloader cache after they're executed and serialized.
bool isRunOnce = !aShouldCache || IsProcessScoped();
// If the script will be reused in this session, compile it in the compilation
// scope instead of the current global to avoid keeping the current
// compartment alive.
AutoJSAPI jsapi;
if (!jsapi.Init(xpc::CompilationScope())) {
if (!jsapi.Init(isRunOnce ? aGlobal : xpc::CompilationScope())) {
return;
}
JSContext* cx = jsapi.cx();
@ -1371,20 +1379,16 @@ nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
uri->GetScheme(scheme);
// We don't cache data: scripts!
if (aShouldCache && !scheme.EqualsLiteral("data")) {
ScriptPreloader::GetChildSingleton().NoteScript(url, url, script);
// Root the object also for caching.
auto* holder = new nsMessageManagerScriptHolder(cx, script, aRunInGlobalScope);
sCachedScripts->Put(aURL, holder);
}
}
ScriptPreloader::GetChildSingleton().NoteScript(url, url, script, isRunOnce);
void
nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
const nsAString& aURL,
bool aRunInGlobalScope)
{
JS::Rooted<JSScript*> script(RootingCx());
TryCacheLoadAndCompileScript(aURL, aRunInGlobalScope, true, &script);
// If this script will only run once per process, only cache it in the
// preloader cache, not the session cache.
if (!isRunOnce) {
// Root the object also for caching.
auto* holder = new nsMessageManagerScriptHolder(cx, script, aRunInGlobalScope);
sCachedScripts->Put(aURL, holder);
}
}
}
void

Просмотреть файл

@ -431,9 +431,8 @@ protected:
void TryCacheLoadAndCompileScript(const nsAString& aURL,
bool aRunInGlobalScope,
bool aShouldCache,
JS::Handle<JSObject*> aGlobal,
JS::MutableHandle<JSScript*> aScriptp);
void TryCacheLoadAndCompileScript(const nsAString& aURL,
bool aRunInGlobalScope);
bool InitChildGlobalInternal(const nsACString& aID);
virtual bool WrapGlobalObject(JSContext* aCx,
JS::RealmOptions& aOptions,
@ -443,6 +442,14 @@ protected:
nsCOMPtr<nsIPrincipal> mPrincipal;
AutoTArray<JS::Heap<JSObject*>, 2> mAnonymousGlobalScopes;
// Returns true if this is a process message manager. There should only be a
// single process message manager per session, so instances of this type will
// optimize their script loading to avoid unnecessary duplication.
virtual bool IsProcessScoped() const
{
return false;
}
static nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>* sCachedScripts;
static mozilla::StaticRefPtr<nsScriptCacheCleaner> sScriptCacheCleaner;
};

Просмотреть файл

@ -407,6 +407,12 @@ ScriptPreloader::FinishContentStartup()
}
}
bool
ScriptPreloader::WillWriteScripts()
{
return Active() && (XRE_IsParentProcess() || mChildActor);
}
Result<nsCOMPtr<nsIFile>, nsresult>
ScriptPreloader::GetCacheFile(const nsAString& suffix)
{
@ -797,11 +803,21 @@ ScriptPreloader::Run()
void
ScriptPreloader::NoteScript(const nsCString& url, const nsCString& cachePath,
JS::HandleScript jsscript)
JS::HandleScript jsscript, bool isRunOnce)
{
if (!Active()) {
if (isRunOnce) {
if (auto script = mScripts.Get(cachePath)) {
script->mIsRunOnce = true;
script->MaybeDropScript();
}
}
return;
}
// Don't bother trying to cache any URLs with cache-busting query
// parameters.
if (!Active() || cachePath.FindChar('?') >= 0) {
if (cachePath.FindChar('?') >= 0) {
return;
}
@ -812,8 +828,11 @@ ScriptPreloader::NoteScript(const nsCString& url, const nsCString& cachePath,
}
auto script = mScripts.LookupOrAdd(cachePath, *this, url, cachePath, jsscript);
if (isRunOnce) {
script->mIsRunOnce = true;
}
if (!script->mScript) {
if (!script->MaybeDropScript() && !script->mScript) {
MOZ_ASSERT(jsscript);
script->mScript = jsscript;
script->mReadyToExecute = true;
@ -1113,6 +1132,10 @@ ScriptPreloader::CachedScript::CachedScript(ScriptPreloader& cache, InputBuffer&
bool
ScriptPreloader::CachedScript::XDREncode(JSContext* cx)
{
auto cleanup = MakeScopeExit([&] () {
MaybeDropScript();
});
JSAutoRealm ar(cx, mScript);
JS::RootedScript jsscript(cx, mScript);

Просмотреть файл

@ -82,7 +82,12 @@ public:
// Notes the execution of a script with the given URL and cache key.
// Depending on the stage of startup, the script may be serialized and
// stored to the startup script cache.
void NoteScript(const nsCString& url, const nsCString& cachePath, JS::HandleScript script);
//
// If isRunOnce is true, this script is expected to run only once per
// process per browser session. A cached instance will not be kept alive
// for repeated execution.
void NoteScript(const nsCString& url, const nsCString& cachePath, JS::HandleScript script,
bool isRunOnce = false);
void NoteScript(const nsCString& url, const nsCString& cachePath,
ProcessType processType, nsTArray<uint8_t>&& xdrData,
@ -149,12 +154,14 @@ private:
public:
CachedScript(CachedScript&&) = delete;
CachedScript(ScriptPreloader& cache, const nsCString& url, const nsCString& cachePath, JSScript* script)
CachedScript(ScriptPreloader& cache, const nsCString& url, const nsCString& cachePath,
JSScript* script)
: mCache(cache)
, mURL(url)
, mCachePath(cachePath)
, mScript(script)
, mReadyToExecute(true)
, mIsRunOnce(false)
{}
inline CachedScript(ScriptPreloader& cache, InputBuffer& buf);
@ -213,6 +220,23 @@ private:
}
}
// Checks whether the cached JSScript for this entry will be needed
// again and, if not, drops it and returns true. This is the case for
// run-once scripts that do not still need to be encoded into the
// cache.
//
// If this method returns false, callers may set mScript to a cached
// JSScript instance for this entry. If it returns true, they should
// not.
bool MaybeDropScript()
{
if (mIsRunOnce && (HasRange() || !mCache.WillWriteScripts())) {
mScript = nullptr;
return true;
}
return false;
}
// Encodes this script into XDR data, and stores the result in mXDRData.
// Returns true on success, false on failure.
bool XDREncode(JSContext* cx);
@ -304,6 +328,11 @@ private:
// whenever it is first executed.
bool mReadyToExecute = false;
// True if this script is expected to run once per process. If so, its
// JSScript instance will be dropped as soon as the script has
// executed and been encoded into the cache.
bool mIsRunOnce = false;
// The set of processes in which this script has been used.
EnumSet<ProcessType> mProcessTypes{};
@ -383,6 +412,12 @@ private:
void FinishContentStartup();
// Returns true if scripts added to the cache now will be encoded and
// written to the cache. If we've passed the startup script loading
// window, or this is a content process which hasn't been asked to return
// script bytecode, this will return false.
bool WillWriteScripts();
// Returns a file pointer for the cache file with the given name in the
// current profile.
Result<nsCOMPtr<nsIFile>, nsresult>