Bug 1364934: Ignore cached scripts from content processes which were removed in a cache flush. r?erahm
MozReview-Commit-ID: AnmsM3WiZMX
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -28,17 +28,17 @@
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "xpcpublic.h"
#define DELAYED_STARTUP_TOPIC "browser-delayed-startup-finished"
#define DOC_ELEM_INSERTED_TOPIC "document-element-inserted"
#define CLEANUP_TOPIC "xpcom-shutdown"
#define SHUTDOWN_TOPIC "quit-application-granted"
-#define CACHE_FLUSH_TOPIC "startupcache-invalidate"
+#define CACHE_INVALIDATE_TOPIC "startupcache-invalidate"
namespace mozilla {
namespace {
static LazyLogModule gLog("ScriptPreloader");
#define LOG(level, ...) MOZ_LOG(gLog, LogLevel::level, (__VA_ARGS__))
}
@@ -158,17 +158,19 @@ ScriptPreloader::InitContentChild(Conten
// that process type is not included in the next session's cache. This
// should be a sufficiently rare occurrence that it's not worth trying to
// handle specially.
auto processType = GetChildProcessType(parent.GetRemoteType());
bool wantScriptData = !cache.mInitializedProcesses.contains(processType);
cache.mInitializedProcesses += processType;
auto fd = cache.mCacheData.cloneFileDescriptor();
- if (fd.IsValid()) {
+ // Don't send original cache data to new processes if the cache has been
+ // invalidated.
+ if (fd.IsValid() && !cache.mCacheInvalidated) {
Unused << parent.SendPScriptCacheConstructor(fd, wantScriptData);
} else {
Unused << parent.SendPScriptCacheConstructor(NS_ERROR_FILE_NOT_FOUND, wantScriptData);
}
}
ProcessType
ScriptPreloader::GetChildProcessType(const nsAString& remoteType)
@@ -222,17 +224,17 @@ ScriptPreloader::ScriptPreloader()
// In the child process, we need to freeze the script cache before any
// untrusted code has been executed. The insertion of the first DOM
// document element may sometimes be earlier than is ideal, but at
// least it should always be safe.
obs->AddObserver(this, DOC_ELEM_INSERTED_TOPIC, false);
}
obs->AddObserver(this, SHUTDOWN_TOPIC, false);
obs->AddObserver(this, CLEANUP_TOPIC, false);
- obs->AddObserver(this, CACHE_FLUSH_TOPIC, false);
+ obs->AddObserver(this, CACHE_INVALIDATE_TOPIC, false);
AutoSafeJSAPI jsapi;
JS_AddExtraGCRootsTracer(jsapi.cx(), TraceOp, this);
}
void
ScriptPreloader::ForceWriteCacheFile()
{
@@ -260,20 +262,22 @@ ScriptPreloader::Cleanup()
AutoSafeJSAPI jsapi;
JS_RemoveExtraGCRootsTracer(jsapi.cx(), TraceOp, this);
UnregisterWeakMemoryReporter(this);
}
void
-ScriptPreloader::FlushCache()
+ScriptPreloader::InvalidateCache()
{
MonitorAutoLock mal(mMonitor);
+ mCacheInvalidated = true;
+
for (auto& script : IterHash(mScripts)) {
// We can only purge finished scripts here. Async scripts that are
// still being parsed off-thread have a non-refcounted reference to
// this script, which needs to stay alive until they finish parsing.
if (script->mReadyToExecute) {
script->Cancel();
script.Remove();
}
@@ -316,18 +320,18 @@ ScriptPreloader::Observe(nsISupports* su
if (mChildActor) {
mChildActor->SendScriptsAndFinalize(mScripts);
}
} else if (!strcmp(topic, SHUTDOWN_TOPIC)) {
ForceWriteCacheFile();
} else if (!strcmp(topic, CLEANUP_TOPIC)) {
Cleanup();
- } else if (!strcmp(topic, CACHE_FLUSH_TOPIC)) {
- FlushCache();
+ } else if (!strcmp(topic, CACHE_INVALIDATE_TOPIC)) {
+ InvalidateCache();
}
return NS_OK;
}
Result<nsCOMPtr<nsIFile>, nsresult>
ScriptPreloader::GetCacheFile(const nsAString& suffix)
@@ -719,16 +723,31 @@ ScriptPreloader::NoteScript(const nsCStr
script->mSize = xdrData.Length();
script->mXDRData.construct<nsTArray<uint8_t>>(Forward<nsTArray<uint8_t>>(xdrData));
auto& data = script->Array();
script->mXDRRange.emplace(data.Elements(), data.Length());
}
}
+ if (!script->mSize && !script->mScript) {
+ // If the content process is sending us a script entry for a script
+ // which was in the cache at startup, it expects us to already have this
+ // script data, so it doesn't send it.
+ //
+ // However, the cache may have been invalidated at this point (usually
+ // due to the add-on manager installing or uninstalling a legacy
+ // extension during very early startup), which means we may no longer
+ // have an entry for this script. Since that means we have no data to
+ // write to the new cache, and no JSScript to generate it from, we need
+ // to discard this entry.
+ mScripts.Remove(cachePath);
+ return;
+ }
+
script->UpdateLoadTime(loadTime);
script->mProcessTypes += processType;
}
JSScript*
ScriptPreloader::GetCachedScript(JSContext* cx, const nsCString& path)
{
// If a script is used by both the parent and the child, it's stored only
--- a/js/xpconnect/loader/ScriptPreloader.h
+++ b/js/xpconnect/loader/ScriptPreloader.h
@@ -345,17 +345,17 @@ private:
// thread for quite as long.
static constexpr int MAX_MAINTHREAD_DECODE_SIZE = 50 * 1024;
ScriptPreloader();
void ForceWriteCacheFile();
void Cleanup();
- void FlushCache();
+ void InvalidateCache();
// Opens the cache file for reading.
Result<Ok, nsresult> OpenCache();
// Writes a new cache file to disk. Must not be called on the main thread.
Result<Ok, nsresult> WriteCache();
// Prepares scripts for writing to the cache, serializing new scripts to
@@ -399,16 +399,17 @@ private:
// True after we've shown the first window, and are no longer adding new
// scripts to the cache.
bool mStartupFinished = false;
bool mCacheInitialized = false;
bool mSaveComplete = false;
bool mDataPrepared = false;
+ bool mCacheInvalidated = false;
// The process type of the current process.
static ProcessType sProcessType;
// The process types for which remote processes have been initialized, and
// are expected to send back script data.
EnumSet<ProcessType> mInitializedProcesses{};