Bug 1361900: Part 6 - Add content process support for the script preloader. r=erahm,gabor
☠☠ backed out by 563613c57b60 ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Tue, 02 May 2017 17:17:52 -0700
changeset 358124 4227bcda00babd2d5980dca3b42cb4b467da38c4
parent 358123 540ecb4c1f0fc25725375c7cc5a399a1d2bea5fe
child 358125 3c716b3b45417e45100d5480241921f44c601270
push id90271
push usermaglione.k@gmail.com
push dateFri, 12 May 2017 21:27:19 +0000
treeherdermozilla-inbound@75b28187fa5e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerserahm, gabor
bugs1361900
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -1815,16 +1817,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;
@@ -236,16 +238,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;
 
@@ -2260,25 +2263,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);
 
@@ -3116,16 +3121,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;
@@ -906,16 +908,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;
@@ -306,16 +307,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
@@ -401,16 +403,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'