Bug 1471089: Improve handling of pre-loaded content processes in script preloader. r?erahm
MozReview-Commit-ID: GwZzQ0ic5Et
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -21,28 +21,33 @@
#include "mozilla/dom/ContentParent.h"
#include "MainThreadUtils.h"
#include "nsDebug.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIFile.h"
#include "nsIObserverService.h"
#include "nsJSUtils.h"
+#include "nsNetUtil.h"
#include "nsProxyRelease.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "xpcpublic.h"
#define STARTUP_COMPLETE_TOPIC "browser-delayed-startup-finished"
#define DOC_ELEM_INSERTED_TOPIC "document-element-inserted"
#define CACHE_WRITE_TOPIC "browser-idle-startup-tasks-finished"
#define CLEANUP_TOPIC "xpcom-shutdown"
#define SHUTDOWN_TOPIC "quit-application-granted"
#define CACHE_INVALIDATE_TOPIC "startupcache-invalidate"
+// The maximum time we'll wait for a child process to finish starting up before
+// we send its script data back to the parent.
+constexpr uint32_t CHILD_STARTUP_TIMEOUT_MS = 8000;
+
namespace mozilla {
namespace {
static LazyLogModule gLog("ScriptPreloader");
#define LOG(level, ...) MOZ_LOG(gLog, LogLevel::level, (__VA_ARGS__))
}
using mozilla::dom::AutoJSAPI;
@@ -350,36 +355,59 @@ ScriptPreloader::Observe(nsISupports* su
MOZ_ASSERT(mStartupFinished);
MOZ_ASSERT(XRE_IsParentProcess());
if (mChildCache) {
Unused << NS_NewNamedThread("SaveScripts",
getter_AddRefs(mSaveThread), this);
}
} else if (!strcmp(topic, DOC_ELEM_INSERTED_TOPIC)) {
- obs->RemoveObserver(this, DOC_ELEM_INSERTED_TOPIC);
-
- MOZ_ASSERT(XRE_IsContentProcess());
+ // If this is an uninitialized about:blank viewer or a chrome: document
+ // (which should always be an XBL binding document), ignore it. We don't
+ // have to worry about it loading malicious content.
+ if (nsCOMPtr<nsIDocument> doc = do_QueryInterface(subject)) {
+ nsCOMPtr<nsIURI> uri = doc->GetDocumentURI();
- mStartupFinished = true;
-
- if (mChildActor) {
- mChildActor->SendScriptsAndFinalize(mScripts);
+ bool schemeIs;
+ if ((NS_IsAboutBlank(uri) &&
+ doc->GetReadyStateEnum() == doc->READYSTATE_UNINITIALIZED) ||
+ (NS_SUCCEEDED(uri->SchemeIs("chrome", &schemeIs)) && schemeIs)) {
+ return NS_OK;
+ }
}
+ FinishContentStartup();
+ } else if (!strcmp(topic, "timer-callback")) {
+ FinishContentStartup();
} else if (!strcmp(topic, SHUTDOWN_TOPIC)) {
ForceWriteCacheFile();
} else if (!strcmp(topic, CLEANUP_TOPIC)) {
Cleanup();
} else if (!strcmp(topic, CACHE_INVALIDATE_TOPIC)) {
InvalidateCache();
}
return NS_OK;
}
+void
+ScriptPreloader::FinishContentStartup()
+{
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ obs->RemoveObserver(this, DOC_ELEM_INSERTED_TOPIC);
+
+ mSaveTimer = nullptr;
+
+ mStartupFinished = true;
+
+ if (mChildActor) {
+ mChildActor->SendScriptsAndFinalize(mScripts);
+ }
+}
Result<nsCOMPtr<nsIFile>, nsresult>
ScriptPreloader::GetCacheFile(const nsAString& suffix)
{
NS_ENSURE_TRUE(mProfD, Err(NS_ERROR_NOT_INITIALIZED));
nsCOMPtr<nsIFile> cacheFile;
MOZ_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
@@ -452,16 +480,29 @@ ScriptPreloader::InitCache(const Maybe<i
{
MOZ_ASSERT(XRE_IsContentProcess());
mCacheInitialized = true;
mChildActor = cacheChild;
RegisterWeakMemoryReporter(this);
+ auto cleanup = MakeScopeExit([&] {
+ // If the parent is expecting cache data from us, make sure we send it
+ // before it writes out its cache file. For normal proceses, this isn't
+ // a concern, since they begin loading documents quite early. For the
+ // preloaded process, we may end up waiting a long time (or, indeed,
+ // never loading a document), so we need an additional timeout.
+ if (cacheChild) {
+ NS_NewTimerWithObserver(getter_AddRefs(mSaveTimer),
+ this, CHILD_STARTUP_TIMEOUT_MS,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ });
+
if (cacheFile.isNothing()){
return Ok();
}
MOZ_TRY(mCacheData.init(cacheFile.ref()));
return InitCacheInternal();
}
--- a/js/xpconnect/loader/ScriptPreloader.h
+++ b/js/xpconnect/loader/ScriptPreloader.h
@@ -17,16 +17,17 @@
#include "mozilla/Vector.h"
#include "mozilla/Result.h"
#include "mozilla/loader/AutoMemMap.h"
#include "nsClassHashtable.h"
#include "nsIFile.h"
#include "nsIMemoryReporter.h"
#include "nsIObserver.h"
#include "nsIThread.h"
+#include "nsITimer.h"
#include "jsapi.h"
#include "js/GCAnnotations.h"
#include <prio.h>
namespace mozilla {
namespace dom {
@@ -374,16 +375,18 @@ private:
Result<Ok, nsresult> WriteCache();
// Prepares scripts for writing to the cache, serializing new scripts to
// XDR, and calculating their size-based offsets.
void PrepareCacheWrite();
void PrepareCacheWriteInternal();
+ void FinishContentStartup();
+
// Returns a file pointer for the cache file with the given name in the
// current profile.
Result<nsCOMPtr<nsIFile>, nsresult>
GetCacheFile(const nsAString& suffix);
// Waits for the given cached script to finish compiling off-thread, or
// decodes it synchronously on the main thread, as appropriate.
JSScript* WaitForCachedScript(JSContext* cx, CachedScript* script);
@@ -449,16 +452,17 @@ private:
RefPtr<ScriptPreloader> mChildCache;
ScriptCacheChild* mChildActor = nullptr;
nsString mBaseName;
nsCOMPtr<nsIFile> mProfD;
nsCOMPtr<nsIThread> mSaveThread;
+ nsCOMPtr<nsITimer> mSaveTimer;
// The mmapped cache data from this session's cache file.
AutoMemMap mCacheData;
Monitor mMonitor;
Monitor mSaveMonitor;
};