--- 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'