Bug 1471089: Improve handling of pre-loaded content processes in script preloader. r?erahm draft
authorKris Maglione <maglione.k@gmail.com>
Mon, 25 Jun 2018 17:45:34 -0700
changeset 811123 73c54dc5651d3e1b1fa8e4362e150a91c40ed11f
parent 810520 8477472996e06d06a21d8e602e4a92d0ec130ea3
push id114194
push usermaglione.k@gmail.com
push dateTue, 26 Jun 2018 23:19:30 +0000
reviewerserahm
bugs1471089
milestone63.0a1
Bug 1471089: Improve handling of pre-loaded content processes in script preloader. r?erahm MozReview-Commit-ID: GwZzQ0ic5Et
js/xpconnect/loader/ScriptPreloader.cpp
js/xpconnect/loader/ScriptPreloader.h
--- 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;
 };