Bug 1361900: Part 6 - Add content process support for the script preloader. r?erahm,gabor draft
authorKris Maglione <maglione.k@gmail.com>
Tue, 02 May 2017 17:17:52 -0700
changeset 577043 0e803b6361f6cd87ef1d8ba41d7eae82919f18b2
parent 577042 32ed7211fce8c6916ef25ff6dfbc18cb190c371c
child 577044 995219df7ebf66f3c34f3332ec4e35c846812df2
push id58588
push usermaglione.k@gmail.com
push dateFri, 12 May 2017 19:00:00 +0000
reviewerserahm, gabor
bugs1361900
milestone55.0a1
Bug 1361900: Part 6 - Add content process support for the script preloader. r?erahm,gabor MozReview-Commit-ID: 6hDQAI52bKC
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
js/xpconnect/loader/PScriptCache.ipdl
js/xpconnect/loader/ScriptCacheActors.cpp
js/xpconnect/loader/ScriptCacheActors.h
js/xpconnect/loader/ScriptPreloader-inl.h
js/xpconnect/loader/ScriptPreloader.cpp
js/xpconnect/loader/ScriptPreloader.h
js/xpconnect/loader/moz.build
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -54,16 +54,17 @@
 #include "mozilla/intl/LocaleService.h"
 #include "mozilla/ipc/TestShellChild.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/ContentProcessController.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layout/RenderFrameChild.h"
+#include "mozilla/loader/ScriptCacheActors.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/net/CaptivePortalService.h"
 #include "mozilla/Omnijar.h"
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/plugins/PluginModuleParent.h"
 #include "mozilla/widget/ScreenManager.h"
 #include "mozilla/widget/WidgetMessageUtils.h"
 #include "nsBaseDragService.h"
@@ -230,16 +231,17 @@ using namespace mozilla::layout;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
 using namespace mozilla::psm;
 using namespace mozilla::widget;
 #if defined(MOZ_WIDGET_GONK)
 using namespace mozilla::system;
 #endif
 using namespace mozilla::widget;
+using mozilla::loader::PScriptCacheChild;
 
 namespace mozilla {
 
 namespace dom {
 
 // IPC sender for remote GC/CC logging.
 class CycleCollectWithLogsChild final
   : public PCycleCollectWithLogsChild
@@ -1764,16 +1766,41 @@ ContentChild::GetCPOWManager()
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvPTestShellConstructor(PTestShellChild* actor)
 {
   return IPC_OK();
 }
 
+PScriptCacheChild*
+ContentChild::AllocPScriptCacheChild(const FileDescOrError& cacheFile, const bool& wantCacheData)
+{
+  return new loader::ScriptCacheChild();
+}
+
+bool
+ContentChild::DeallocPScriptCacheChild(PScriptCacheChild* cache)
+{
+  delete static_cast<loader::ScriptCacheChild*>(cache);
+  return true;
+}
+
+mozilla::ipc::IPCResult
+ContentChild::RecvPScriptCacheConstructor(PScriptCacheChild* actor, const FileDescOrError& cacheFile, const bool& wantCacheData)
+{
+  Maybe<FileDescriptor> fd;
+  if (cacheFile.type() == cacheFile.TFileDescriptor) {
+    fd.emplace(cacheFile.get_FileDescriptor());
+  }
+
+  static_cast<loader::ScriptCacheChild*>(actor)->Init(fd, wantCacheData);
+  return IPC_OK();
+}
+
 PNeckoChild*
 ContentChild::AllocPNeckoChild()
 {
   return new NeckoChild();
 }
 
 bool
 ContentChild::DeallocPNeckoChild(PNeckoChild* necko)
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -31,16 +31,18 @@ struct SubstitutionMapping;
 struct OverrideMapping;
 class nsIDomainPolicy;
 class nsIURIClassifierCallback;
 struct LookAndFeelInt;
 
 namespace mozilla {
 class RemoteSpellcheckEngineChild;
 
+using mozilla::loader::PScriptCacheChild;
+
 namespace ipc {
 class OptionalURIParams;
 class URIParams;
 }// namespace ipc
 
 namespace dom {
 
 class AlertObserver;
@@ -234,16 +236,27 @@ public:
   DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor) override;
 
   virtual PTestShellChild* AllocPTestShellChild() override;
 
   virtual bool DeallocPTestShellChild(PTestShellChild*) override;
 
   virtual mozilla::ipc::IPCResult RecvPTestShellConstructor(PTestShellChild*) override;
 
+  virtual PScriptCacheChild*
+  AllocPScriptCacheChild(const FileDescOrError& cacheFile,
+                         const bool& wantCacheData) override;
+
+  virtual bool DeallocPScriptCacheChild(PScriptCacheChild*) override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvPScriptCacheConstructor(PScriptCacheChild*,
+                              const FileDescOrError& cacheFile,
+                              const bool& wantCacheData) override;
+
   jsipc::CPOWManager* GetCPOWManager() override;
 
   virtual PNeckoChild* AllocPNeckoChild() override;
 
   virtual bool DeallocPNeckoChild(PNeckoChild*) override;
 
   virtual PPrintingChild* AllocPPrintingChild() override;
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -80,25 +80,27 @@
 #include "mozilla/ipc/IPCStreamUtils.h"
 #include "mozilla/intl/LocaleService.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/PAPZParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
 #include "mozilla/layout/RenderFrameParent.h"
+#include "mozilla/loader/ScriptCacheActors.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/media/MediaParent.h"
 #include "mozilla/Move.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 #include "mozilla/ScopeExit.h"
+#include "mozilla/ScriptPreloader.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TelemetryIPC.h"
 #include "mozilla/WebBrowserPersistDocumentParent.h"
 #include "mozilla/widget/ScreenManager.h"
 #include "mozilla/Unused.h"
 #include "nsAnonymousTemporaryFile.h"
@@ -279,16 +281,17 @@ using namespace mozilla::hal;
 using namespace mozilla::ipc;
 using namespace mozilla::intl;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
 using namespace mozilla::psm;
 using namespace mozilla::widget;
+using mozilla::loader::PScriptCacheParent;
 
 // XXX Workaround for bug 986973 to maintain the existing broken semantics
 template<>
 struct nsIConsoleService::COMTypeInfo<nsConsoleService, void> {
   static const nsIID kIID;
 };
 const nsIID nsIConsoleService::COMTypeInfo<nsConsoleService, void>::kIID = NS_ICONSOLESERVICE_IID;
 
@@ -2262,25 +2265,27 @@ ContentParent::InitInternal(ProcessPrior
     nsCString UAName(gAppData->UAName);
     nsCString ID(gAppData->ID);
     nsCString vendor(gAppData->vendor);
 
     // Sending all information to content process.
     Unused << SendAppInfo(version, buildID, name, UAName, ID, vendor);
   }
 
-  // Initialize the message manager (and load delayed scripts) now that we
-  // have established communications with the child.
-  mMessageManager->InitWithCallback(this);
-
   // Send the child its remote type. On Mac, this needs to be sent prior
   // to the message we send to enable the Sandbox (SendStartProcessSandbox)
   // because different remote types require different sandbox privileges.
   Unused << SendRemoteType(mRemoteType);
 
+  ScriptPreloader::InitContentChild(*this);
+
+  // Initialize the message manager (and load delayed scripts) now that we
+  // have established communications with the child.
+  mMessageManager->InitWithCallback(this);
+
   // Set the subprocess's priority.  We do this early on because we're likely
   // /lowering/ the process's CPU and memory priority, which it has inherited
   // from this process.
   //
   // This call can cause us to send IPC messages to the child process, so it
   // must come after the Open() call above.
   ProcessPriorityManager::SetProcessPriority(this, aInitialPriority);
 
@@ -3112,16 +3117,29 @@ ContentParent::AllocPTestShellParent()
 
 bool
 ContentParent::DeallocPTestShellParent(PTestShellParent* shell)
 {
   delete shell;
   return true;
 }
 
+PScriptCacheParent*
+ContentParent::AllocPScriptCacheParent(const FileDescOrError& cacheFile, const bool& wantCacheData)
+{
+  return new loader::ScriptCacheParent(wantCacheData);
+}
+
+bool
+ContentParent::DeallocPScriptCacheParent(PScriptCacheParent* cache)
+{
+  delete static_cast<loader::ScriptCacheParent*>(cache);
+  return true;
+}
+
 PNeckoParent*
 ContentParent::AllocPNeckoParent()
 {
   return new NeckoParent();
 }
 
 bool
 ContentParent::DeallocPNeckoParent(PNeckoParent* necko)
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -62,16 +62,18 @@ class CrossProcessProfilerController;
 
 #if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX)
 class SandboxBroker;
 class SandboxBrokerPolicyFactory;
 #endif
 
 class PreallocatedProcessManagerImpl;
 
+using mozilla::loader::PScriptCacheParent;
+
 namespace embedding {
 class PrintingParent;
 }
 
 namespace ipc {
 class OptionalURIParams;
 class PFileDescriptorSetParent;
 class URIParams;
@@ -904,16 +906,22 @@ private:
 
   virtual bool
   DeallocPCycleCollectWithLogsParent(PCycleCollectWithLogsParent* aActor) override;
 
   virtual PTestShellParent* AllocPTestShellParent() override;
 
   virtual bool DeallocPTestShellParent(PTestShellParent* shell) override;
 
+  virtual PScriptCacheParent*
+  AllocPScriptCacheParent(const FileDescOrError& cacheFile,
+                          const bool& wantCacheData) override;
+
+  virtual bool DeallocPScriptCacheParent(PScriptCacheParent* shell) override;
+
   virtual bool DeallocPNeckoParent(PNeckoParent* necko) override;
 
   virtual PPSMContentDownloaderParent*
   AllocPPSMContentDownloaderParent(const uint32_t& aCertType) override;
 
   virtual bool
   DeallocPPSMContentDownloaderParent(PPSMContentDownloaderParent* aDownloader) override;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -40,16 +40,17 @@ include protocol PRemoteSpellcheckEngine
 include protocol PWebBrowserPersistDocument;
 include protocol PWebrtcGlobal;
 include protocol PPresentation;
 include protocol PURLClassifier;
 include protocol PURLClassifierLocal;
 include protocol PVRManager;
 include protocol PVideoDecoderManager;
 include protocol PFlyWebPublishedServer;
+include protocol PScriptCache;
 include DOMTypes;
 include JavaScriptTypes;
 include IPCBlob;
 include IPCStream;
 include PTabContext;
 include URIParams;
 include PluginTypes;
 include ProtocolTypes;
@@ -307,16 +308,17 @@ nested(upto inside_cpow) sync protocol P
     manages PJavaScript;
     manages PRemoteSpellcheckEngine;
     manages PWebBrowserPersistDocument;
     manages PWebrtcGlobal;
     manages PPresentation;
     manages PFlyWebPublishedServer;
     manages PURLClassifier;
     manages PURLClassifierLocal;
+    manages PScriptCache;
 
 both:
     // Depending on exactly how the new browser is being created, it might be
     // created from either the child or parent process!
     //
     // The child creates the PBrowser as part of
     // TabChild::BrowserFrameProvideWindow (which happens when the child's
     // content calls window.open()), and the parent creates the PBrowser as part
@@ -399,16 +401,18 @@ child:
      * nsIMemoryInfoDumper.idl
      */
     async PCycleCollectWithLogs(bool dumpAllTraces,
                                 FileDescriptor gcLog,
                                 FileDescriptor ccLog);
 
     async PTestShell();
 
+    async PScriptCache(FileDescOrError cacheFile, bool wantCacheData);
+
     async RegisterChrome(ChromePackage[] packages, SubstitutionMapping[] substitutions,
                          OverrideMapping[] overrides, nsCString locale, bool reset);
     async RegisterChromeItem(ChromeRegistryItem item);
 
     async ClearImageCache(bool privateLoader, bool chrome);
 
     async SetOffline(bool offline);
     async SetConnectivity(bool connectivity);
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/PScriptCache.ipdl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
+/* 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/. */
+
+include protocol PContent;
+
+using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
+using mozilla::void_t from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace loader {
+
+struct ScriptData {
+    nsCString url;
+    nsCString cachePath;
+    TimeStamp loadTime;
+    // This will be an empty array if script data is present in the previous
+    // session's cache.
+    uint8_t[] xdrData;
+};
+
+protocol PScriptCache
+{
+    manager PContent;
+
+parent:
+    async __delete__(ScriptData[] scripts);
+};
+
+} // namespace loader
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/ScriptCacheActors.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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/. */
+
+#include "mozilla/ScriptPreloader.h"
+#include "ScriptPreloader-inl.h"
+#include "mozilla/loader/ScriptCacheActors.h"
+
+#include "mozilla/dom/ContentParent.h"
+
+namespace mozilla {
+namespace loader {
+
+void
+ScriptCacheChild::Init(const Maybe<FileDescriptor>& cacheFile, bool wantCacheData)
+{
+    mWantCacheData = wantCacheData;
+
+    auto& cache = ScriptPreloader::GetChildSingleton();
+    Unused << cache.InitCache(cacheFile, this);
+
+    if (!wantCacheData) {
+        // If the parent process isn't expecting any cache data from us, we're
+        // done.
+        Send__delete__(this, AutoTArray<ScriptData, 0>());
+    }
+}
+
+// Finalize the script cache for the content process, and send back data about
+// any scripts executed up to this point.
+void
+ScriptCacheChild::Finalize(LinkedList<ScriptPreloader::CachedScript>& scripts)
+{
+    MOZ_ASSERT(mWantCacheData);
+
+    AutoSafeJSAPI jsapi;
+
+    nsTArray<ScriptData> dataArray;
+    for (auto script : scripts) {
+        if (!script->mSize && !script->XDREncode(jsapi.cx())) {
+            continue;
+        }
+
+        auto data = dataArray.AppendElement();
+
+        data->url() = script->mURL;
+        data->cachePath() = script->mCachePath;
+
+        if (script->HasBuffer()) {
+            auto& xdrData = script->Buffer();
+            data->xdrData().AppendElements(xdrData.begin(), xdrData.length());
+            script->FreeData();
+        }
+    }
+
+    Send__delete__(this, dataArray);
+}
+
+void
+ScriptCacheChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+    auto& cache = ScriptPreloader::GetChildSingleton();
+    cache.mChildActor = nullptr;
+}
+
+
+IPCResult
+ScriptCacheParent::Recv__delete__(nsTArray<ScriptData>&& scripts)
+{
+    if (!mWantCacheData && scripts.Length()) {
+        return IPC_FAIL(this, "UnexpectedScriptData");
+    }
+
+    // We don't want any more data from the process at this point.
+    mWantCacheData = false;
+
+    // Merge the child's script data with the parent's.
+    auto parent = static_cast<dom::ContentParent*>(Manager());
+    auto processType = ScriptPreloader::GetChildProcessType(parent->GetRemoteType());
+
+    auto& cache = ScriptPreloader::GetChildSingleton();
+    for (auto& script : scripts) {
+        cache.NoteScript(script.url(), script.cachePath(), processType,
+                         Move(script.xdrData()));
+    }
+
+    return IPC_OK();
+}
+
+void
+ScriptCacheParent::ActorDestroy(ActorDestroyReason aWhy)
+{}
+
+} // namespace loader
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/ScriptCacheActors.h
@@ -0,0 +1,62 @@
+/* -*-  Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
+/* 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 ScriptCache_h
+#define ScriptCache_h
+
+#include "mozilla/ScriptPreloader.h"
+#include "mozilla/loader/PScriptCacheChild.h"
+#include "mozilla/loader/PScriptCacheParent.h"
+
+namespace mozilla {
+namespace ipc {
+    class FileDescriptor;
+}
+
+
+namespace loader {
+
+using mozilla::ipc::FileDescriptor;
+using mozilla::ipc::IPCResult;
+
+class ScriptCacheParent final : public PScriptCacheParent
+{
+public:
+    explicit ScriptCacheParent(bool wantCacheData)
+        : mWantCacheData(wantCacheData)
+    {}
+
+protected:
+    virtual IPCResult Recv__delete__(nsTArray<ScriptData>&& scripts) override;
+
+    virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+    bool mWantCacheData;
+};
+
+class ScriptCacheChild final : public PScriptCacheChild
+{
+    friend class mozilla::ScriptPreloader;
+
+public:
+    ScriptCacheChild() = default;
+
+    void Init(const Maybe<FileDescriptor>& cacheFile, bool wantCacheData);
+
+protected:
+    virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+    void Finalize(LinkedList<ScriptPreloader::CachedScript>& scripts);
+
+private:
+    bool mWantCacheData = false;
+};
+
+
+} // namespace loader
+} // namespace mozilla
+
+#endif // ScriptCache_h
--- a/js/xpconnect/loader/ScriptPreloader-inl.h
+++ b/js/xpconnect/loader/ScriptPreloader-inl.h
@@ -8,24 +8,32 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/Range.h"
 #include "mozilla/Result.h"
 #include "mozilla/Unused.h"
+#include "mozilla/dom/ScriptSettings.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 #include <prio.h>
 
 namespace mozilla {
 namespace loader {
 
+using mozilla::dom::AutoJSAPI;
+
+struct MOZ_RAII AutoSafeJSAPI : public AutoJSAPI
+{
+    AutoSafeJSAPI() { Init(); }
+};
+
 static inline Result<Ok, nsresult>
 WrapNSResult(PRStatus aRv)
 {
     if (aRv != PR_SUCCESS) {
         return Err(NS_ERROR_FAILURE);
     }
     return Ok();
 }
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -1,51 +1,54 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=8 sts=4 et sw=4 tw=99: */
 /* 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/. */
 
 #include "mozilla/ScriptPreloader.h"
 #include "ScriptPreloader-inl.h"
+#include "mozilla/loader/ScriptCacheActors.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Services.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
-#include "mozilla/dom/ScriptSettings.h"
 
 #include "MainThreadUtils.h"
 #include "nsDebug.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsJSUtils.h"
 #include "nsProxyRelease.h"
 #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"
 
 namespace mozilla {
 namespace {
 static LazyLogModule gLog("ScriptPreloader");
 
 #define LOG(level, ...) MOZ_LOG(gLog, LogLevel::level, (__VA_ARGS__))
 }
 
 using mozilla::dom::AutoJSAPI;
+using mozilla::dom::ContentChild;
+using mozilla::dom::ContentParent;
 using namespace mozilla::loader;
 
 ProcessType ScriptPreloader::sProcessType;
 
 
 nsresult
 ScriptPreloader::CollectReports(nsIHandleReportCallback* aHandleReport,
                                 nsISupports* aData, bool aAnonymize)
@@ -130,34 +133,59 @@ ScriptPreloader::GetChildSingleton()
             Unused << singleton->InitCache(NS_LITERAL_STRING("scriptCache-child"));
         }
         ClearOnShutdown(&singleton);
     }
 
     return *singleton;
 }
 
+void
+ScriptPreloader::InitContentChild(ContentParent& parent)
+{
+    auto& cache = GetChildSingleton();
+
+    // We want startup script data from the first process of a given type.
+    // That process sends back its script data before it executes any
+    // untrusted code, and then we never accept further script data for that
+    // type of process for the rest of the session.
+    //
+    // The script data from each process type is merged with the data from the
+    // parent process's frame and process scripts, and shared between all
+    // content process types in the next session.
+    //
+    // Note that if the first process of a given type crashes or shuts down
+    // before sending us its script data, we silently ignore it, and data for
+    // 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()) {
+        Unused << parent.SendPScriptCacheConstructor(fd, wantScriptData);
+    } else {
+        Unused << parent.SendPScriptCacheConstructor(NS_ERROR_FILE_NOT_FOUND, wantScriptData);
+    }
+}
 
 ProcessType
 ScriptPreloader::GetChildProcessType(const nsAString& remoteType)
 {
     if (remoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE)) {
         return ProcessType::Extension;
     }
     return ProcessType::Web;
 }
 
+
 namespace {
 
-struct MOZ_RAII AutoSafeJSAPI : public AutoJSAPI
-{
-    AutoSafeJSAPI() { Init(); }
-};
-
-
 static void
 TraceOp(JSTracer* trc, void* data)
 {
     auto preloader = static_cast<ScriptPreloader*>(data);
 
     preloader->Trace(trc);
 }
 
@@ -183,17 +211,28 @@ ScriptPreloader::ScriptPreloader()
     if (XRE_IsParentProcess()) {
         sProcessType = ProcessType::Parent;
     } else {
         sProcessType = GetChildProcessType(dom::ContentChild::GetSingleton()->GetRemoteType());
     }
 
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     MOZ_RELEASE_ASSERT(obs);
-    obs->AddObserver(this, DELAYED_STARTUP_TOPIC, false);
+
+    if (XRE_IsParentProcess()) {
+        // In the parent process, we want to freeze the script cache as soon
+        // as delayed startup for the first browser window has completed.
+        obs->AddObserver(this, DELAYED_STARTUP_TOPIC, false);
+    } else {
+        // 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);
 
     AutoSafeJSAPI jsapi;
     JS_AddExtraGCRootsTracer(jsapi.cx(), TraceOp, this);
 }
 
@@ -266,27 +305,38 @@ ScriptPreloader::FlushCache()
         Unused << NS_NewNamedThread("SaveScripts",
                                     getter_AddRefs(mSaveThread), this);
     }
 }
 
 nsresult
 ScriptPreloader::Observe(nsISupports* subject, const char* topic, const char16_t* data)
 {
+    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     if (!strcmp(topic, DELAYED_STARTUP_TOPIC)) {
-        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
         obs->RemoveObserver(this, DELAYED_STARTUP_TOPIC);
 
+        MOZ_ASSERT(XRE_IsParentProcess());
+
         mStartupFinished = true;
 
-
-        if (XRE_IsParentProcess() && mChildCache) {
+        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());
+
+        mStartupFinished = true;
+
+        if (mChildActor) {
+            mChildActor->Finalize(mSavedScripts);
+        }
     } else if (!strcmp(topic, SHUTDOWN_TOPIC)) {
         ForceWriteCacheFile();
     } else if (!strcmp(topic, CLEANUP_TOPIC)) {
         Cleanup();
     } else if (!strcmp(topic, CACHE_FLUSH_TOPIC)) {
         FlushCache();
     }
 
@@ -346,16 +396,41 @@ ScriptPreloader::InitCache(const nsAStri
     RegisterWeakMemoryReporter(this);
 
     if (!XRE_IsParentProcess()) {
         return Ok();
     }
 
     MOZ_TRY(OpenCache());
 
+    return InitCacheInternal();
+}
+
+Result<Ok, nsresult>
+ScriptPreloader::InitCache(const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild)
+{
+    MOZ_ASSERT(XRE_IsContentProcess());
+
+    mCacheInitialized = true;
+    mChildActor = cacheChild;
+
+    RegisterWeakMemoryReporter(this);
+
+    if (cacheFile.isNothing()){
+        return Ok();
+    }
+
+    MOZ_TRY(mCacheData.init(cacheFile.ref()));
+
+    return InitCacheInternal();
+}
+
+Result<Ok, nsresult>
+ScriptPreloader::InitCacheInternal()
+{
     auto size = mCacheData.size();
 
     uint32_t headerSize;
     if (size < sizeof(MAGIC) + sizeof(headerSize)) {
         return Err(NS_ERROR_UNEXPECTED);
     }
 
     auto data = mCacheData.get<uint8_t>();
@@ -462,17 +537,17 @@ ScriptPreloader::PrepareCacheWrite()
     }
 
     if (mRestoredScripts.isEmpty()) {
         // Check for any new scripts that we need to save. If there aren't
         // any, and there aren't any saved scripts that we need to remove,
         // don't bother writing out a new cache file.
         bool found = false;
         for (auto script : mSavedScripts) {
-            if (script->mXDRRange.isNothing()) {
+            if (!script->HasRange() || script->HasArray()) {
                 found = true;
                 break;
             }
         }
         if (!found) {
             mSaveComplete = true;
             return;
         }
@@ -579,17 +654,20 @@ ScriptPreloader::WriteCache()
     LittleEndian::writeUint32(headerSize, buf.cursor());
 
     MOZ_TRY(Write(fd, MAGIC, sizeof(MAGIC)));
     MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
     MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
 
     for (auto script : mSavedScripts) {
         MOZ_TRY(Write(fd, script->Range().begin().get(), script->mSize));
-        script->mXDRData.reset();
+
+        if (script->mScript) {
+            script->FreeData();
+        }
     }
 
     NS_TRY(cacheFile->MoveTo(nullptr, mBaseName + NS_LITERAL_STRING(".bin")));
 
     return Ok();
 }
 
 // Runs in the mSaveThread thread, and writes out the cache file for the next
@@ -624,55 +702,82 @@ ScriptPreloader::FindScript(LinkedList<C
             return script;
         }
     }
     return nullptr;
 }
 
 void
 ScriptPreloader::NoteScript(const nsCString& url, const nsCString& cachePath,
-                            JS::HandleScript script)
+                            JS::HandleScript jsscript)
 {
-    if (mStartupFinished || !mCacheInitialized) {
-        return;
-    }
     // Don't bother trying to cache any URLs with cache-busting query
     // parameters.
-    if (cachePath.FindChar('?') >= 0) {
+    if (mStartupFinished || !mCacheInitialized || cachePath.FindChar('?') >= 0) {
         return;
     }
 
     // Don't bother caching files that belong to the mochitest harness.
     NS_NAMED_LITERAL_CSTRING(mochikitPrefix, "chrome://mochikit/");
     if (StringHead(url, mochikitPrefix.Length()) == mochikitPrefix) {
         return;
     }
 
-    bool exists = mScripts.Get(cachePath);
-
-    CachedScript* restored = nullptr;
-    if (exists) {
-        restored = FindScript(mRestoredScripts, cachePath);
-    }
+    CachedScript* script = mScripts.Get(cachePath);
+    bool restored = script && FindScript(mRestoredScripts, cachePath);
 
     if (restored) {
-        restored->remove();
-        mSavedScripts.insertBack(restored);
+        script->remove();
+        mSavedScripts.insertBack(script);
+
+        MOZ_ASSERT(jsscript);
+        script->mScript = jsscript;
+        script->mReadyToExecute = true;
+    } else if (!script) {
+        script = new CachedScript(*this, url, cachePath, jsscript);
+        mSavedScripts.insertBack(script);
+        mScripts.Put(cachePath, script);
+    } else {
+        return;
+    }
+
+    script->mProcessTypes += CurrentProcessType();
+}
+
+void
+ScriptPreloader::NoteScript(const nsCString& url, const nsCString& cachePath,
+                            ProcessType processType, nsTArray<uint8_t>&& xdrData)
+{
+    CachedScript* script = mScripts.Get(cachePath);
+    bool restored = script && FindScript(mRestoredScripts, cachePath);
 
-        MOZ_ASSERT(script);
-        restored->mProcesses += CurrentProcessType();
-        restored->mScript = script;
-        restored->mReadyToExecute = true;
-    } else if (!exists) {
-        auto cachedScript = new CachedScript(*this, url, cachePath, script);
-        cachedScript->mProcesses += CurrentProcessType();
+    if (restored) {
+        script->remove();
+        mSavedScripts.insertBack(script);
+
+        script->mReadyToExecute = true;
+    } else {
+        if (!script) {
+            script = new CachedScript(this, url, cachePath, nullptr);
+            mSavedScripts.insertBack(script);
+            mScripts.Put(cachePath, script);
+        }
 
-        mSavedScripts.insertBack(cachedScript);
-        mScripts.Put(cachePath, cachedScript);
+        if (!script->HasRange()) {
+            MOZ_ASSERT(!script->HasArray());
+
+            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());
+        }
     }
+
+    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
     // in the child cache.
     if (mChildCache) {
@@ -770,21 +875,21 @@ ScriptPreloader::CachedScript::CachedScr
 }
 
 bool
 ScriptPreloader::CachedScript::XDREncode(JSContext* cx)
 {
     JSAutoCompartment ac(cx, mScript);
     JS::RootedScript jsscript(cx, mScript);
 
-    mXDRData.emplace();
+    mXDRData.construct<JS::TranscodeBuffer>();
 
-    JS::TranscodeResult code = JS::EncodeScript(cx, Data(), jsscript);
+    JS::TranscodeResult code = JS::EncodeScript(cx, Buffer(), jsscript);
     if (code == JS::TranscodeResult_Ok) {
-        mXDRRange.emplace(Data().begin(), Data().length());
+        mXDRRange.emplace(Buffer().begin(), Buffer().length());
         return true;
     }
     JS_ClearPendingException(cx);
     return false;
 }
 
 void
 ScriptPreloader::CachedScript::Cancel()
@@ -809,21 +914,25 @@ ScriptPreloader::CachedScript::GetJSScri
     }
 
     // If we have no token at this point, the script was too small to decode
     // off-thread, or it was needed before the off-thread compilation was
     // finished, and is small enough to decode on the main thread rather than
     // wait for the off-thread decoding to finish. In either case, we decode
     // it synchronously the first time it's needed.
     if (!mToken) {
-        MOZ_ASSERT(mXDRRange.isSome());
+        MOZ_ASSERT(HasRange());
 
         JS::RootedScript script(cx);
         if (JS::DecodeScript(cx, Range(), &script)) {
             mScript = script;
+
+            if (mCache.mSaveComplete) {
+                FreeData();
+            }
         }
 
         return mScript;
     }
 
     Maybe<JSAutoCompartment> ac;
     if (JS::CompartmentCreationOptionsRef(cx).addonIdOrNull()) {
         // Make sure we never try to finish the parse in a compartment with an
--- a/js/xpconnect/loader/ScriptPreloader.h
+++ b/js/xpconnect/loader/ScriptPreloader.h
@@ -6,50 +6,60 @@
 #ifndef ScriptPreloader_h
 #define ScriptPreloader_h
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/MaybeOneOf.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Range.h"
 #include "mozilla/Vector.h"
 #include "mozilla/Result.h"
 #include "mozilla/loader/AutoMemMap.h"
 #include "nsDataHashtable.h"
 #include "nsIFile.h"
 #include "nsIMemoryReporter.h"
 #include "nsIObserver.h"
 #include "nsIThread.h"
 
 #include "jsapi.h"
 
 #include <prio.h>
 
 namespace mozilla {
+namespace dom {
+    class ContentParent;
+}
+namespace ipc {
+    class FileDescriptor;
+}
 namespace loader {
     class InputBuffer;
+    class ScriptCacheChild;
 
     enum class ProcessType : uint8_t {
         Parent,
         Web,
         Extension,
     };
 }
 
 using namespace mozilla::loader;
 
 class ScriptPreloader : public nsIObserver
                       , public nsIMemoryReporter
                       , public nsIRunnable
 {
     MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
 
+    friend class mozilla::loader::ScriptCacheChild;
+
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIOBSERVER
     NS_DECL_NSIMEMORYREPORTER
     NS_DECL_NSIRUNNABLE
 
     static ScriptPreloader& GetSingleton();
     static ScriptPreloader& GetChildSingleton();
@@ -60,26 +70,37 @@ public:
     // Returns null if the script is not cached.
     JSScript* GetCachedScript(JSContext* cx, const nsCString& name);
 
     // 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);
 
+    void NoteScript(const nsCString& url, const nsCString& cachePath,
+                    ProcessType processType, nsTArray<uint8_t>&& xdrData);
+
     // Initializes the script cache from the startup script cache file.
     Result<Ok, nsresult> InitCache(const nsAString& = NS_LITERAL_STRING("scriptCache"));
 
+    Result<Ok, nsresult> InitCache(const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild);
+
+private:
+    Result<Ok, nsresult> InitCacheInternal();
+
+public:
     void Trace(JSTracer* trc);
 
     static ProcessType CurrentProcessType()
     {
         return sProcessType;
     }
 
+    static void InitContentChild(dom::ContentParent& parent);
+
 protected:
     virtual ~ScriptPreloader() = default;
 
 private:
     // Represents a cached JS script, either initially read from the script
     // cache file, to be added to the next session's script cache file, or
     // both.
     //
@@ -124,16 +145,26 @@ private:
             auto hashValue = mCache->mScripts.Get(mCachePath);
             MOZ_ASSERT_IF(hashValue, hashValue == this);
 #endif
             mCache->mScripts.Remove(mCachePath);
         }
 
         void Cancel();
 
+        void FreeData()
+        {
+            // If the script data isn't mmapped, we need to release both it
+            // and the Range that points to it at the same time.
+            if (!mXDRData.empty()) {
+                mXDRRange.reset();
+                mXDRData.destroy();
+            }
+        }
+
         // Encodes this script into XDR data, and stores the result in mXDRData.
         // Returns true on success, false on failure.
         bool XDREncode(JSContext* cx);
 
         // Encodes or decodes this script, in the storage format required by the
         // script cache file.
         template<typename Buffer>
         void Code(Buffer& buffer)
@@ -142,39 +173,58 @@ private:
             buffer.codeString(mCachePath);
             buffer.codeUint32(mOffset);
             buffer.codeUint32(mSize);
             buffer.codeUint8(mProcessTypes);
         }
 
         // Returns the XDR data generated for this script during this session. See
         // mXDRData.
-        JS::TranscodeBuffer& Data()
+        JS::TranscodeBuffer& Buffer()
         {
-            MOZ_ASSERT(mXDRData.isSome());
-            return mXDRData.ref();
+            MOZ_ASSERT(HasBuffer());
+            return mXDRData.ref<JS::TranscodeBuffer>();
         }
 
+        bool HasBuffer() { return mXDRData.constructed<JS::TranscodeBuffer>(); }
+
         // Returns the read-only XDR data for this script. See mXDRRange.
         const JS::TranscodeRange& Range()
         {
-            MOZ_ASSERT(mXDRRange.isSome());
+            MOZ_ASSERT(HasRange());
             return mXDRRange.ref();
         }
 
+        bool HasRange() { return mXDRRange.isSome(); }
+
+        nsTArray<uint8_t>& Array()
+        {
+            MOZ_ASSERT(HasArray());
+            return mXDRData.ref<nsTArray<uint8_t>>();
+        }
+
+        bool HasArray() { return mXDRData.constructed<nsTArray<uint8_t>>(); }
+
+
         JSScript* GetJSScript(JSContext* cx);
 
         size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
         {
             auto size = mallocSizeOf(this);
-            if (mXDRData.isSome()) {
-                size += (mXDRData->sizeOfExcludingThis(mallocSizeOf) +
-                         mURL.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
-                         mCachePath.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
+
+            if (HasArray()) {
+                size += Array().ShallowSizeOfExcludingThis(mallocSizeOf);
+            } else if (HasBuffer()) {
+                size += Buffer().sizeOfExcludingThis(mallocSizeOf);
+            } else {
+                return size;
             }
+
+            size += (mURL.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
+                     mCachePath.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
             return size;
         }
 
         ScriptPreloader& mCache;
 
         // The URL from which this script was initially read and compiled.
         nsCString mURL;
         // A unique identifier for this script's filesystem location, used as a
@@ -204,17 +254,17 @@ private:
 
         // The read-only XDR data for this script, which was either read from an
         // existing cache file, or generated by encoding a script which was
         // compiled during this session.
         Maybe<JS::TranscodeRange> mXDRRange;
 
         // XDR data which was generated from a script compiled during this
         // session, and will be written to the cache file.
-        Maybe<JS::TranscodeBuffer> mXDRData;
+        MaybeOneOf<JS::TranscodeBuffer, nsTArray<uint8_t>> mXDRData;
     };
 
     // There's a trade-off between the time it takes to setup an off-thread
     // decode and the time we save by doing the decode off-thread. At this
     // point, the setup is quite expensive, and 20K is about where we start to
     // see an improvement rather than a regression.
     //
     // This also means that we get much better performance loading one big
@@ -297,17 +347,22 @@ private:
 
     bool mCacheInitialized = false;
     bool mSaveComplete = false;
     bool mDataPrepared = 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{};
+
     RefPtr<ScriptPreloader> mChildCache;
+    ScriptCacheChild* mChildActor = nullptr;
 
     nsString mBaseName;
 
     nsCOMPtr<nsIFile> mProfD;
     nsCOMPtr<nsIThread> mSaveThread;
 
     // The mmapped cache data from this session's cache file.
     AutoMemMap mCacheData;
--- a/js/xpconnect/loader/moz.build
+++ b/js/xpconnect/loader/moz.build
@@ -4,35 +4,41 @@
 # 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/.
 
 UNIFIED_SOURCES += [
     'AutoMemMap.cpp',
     'ChromeScriptLoader.cpp',
     'mozJSLoaderUtils.cpp',
     'mozJSSubScriptLoader.cpp',
+    'ScriptCacheActors.cpp',
     'ScriptPreloader.cpp',
 ]
 
 # mozJSComponentLoader.cpp cannot be built in unified mode because it uses
 # windows.h
 SOURCES += [
     'mozJSComponentLoader.cpp'
 ]
 
+IPDL_SOURCES += [
+    'PScriptCache.ipdl',
+]
+
 EXPORTS.mozilla += [
     'ScriptPreloader.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'PrecompiledScript.h',
 ]
 
 EXPORTS.mozilla.loader += [
     'AutoMemMap.h',
+    'ScriptCacheActors.h',
 ]
 
 EXTRA_JS_MODULES += [
     'ISO8601DateUtils.jsm',
     'XPCOMUtils.jsm',
 ]
 
 FINAL_LIBRARY = 'xul'