--- a/dom/base/ProcessGlobal.cpp
+++ b/dom/base/ProcessGlobal.cpp
@@ -2,34 +2,41 @@
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "ProcessGlobal.h"
#include "nsContentCID.h"
+#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/MessageManagerBinding.h"
#include "mozilla/dom/ResolveSystemBinding.h"
+#include "mozilla/dom/ipc/SharedMap.h"
using namespace mozilla;
using namespace mozilla::dom;
bool ProcessGlobal::sWasCreated = false;
ProcessGlobal::ProcessGlobal(nsFrameMessageManager* aMessageManager)
: MessageManagerGlobal(aMessageManager),
mInitialized(false)
{
mozilla::HoldJSObjects(this);
}
ProcessGlobal::~ProcessGlobal()
{
mAnonymousGlobalScopes.Clear();
+ if (ContentChild* child = ContentChild::GetSingleton()) {
+ // Clear this now so we can be sure it's destroyed before cycle collector
+ // shutdown.
+ child->ClearSharedData();
+ }
mozilla::DropJSObjects(this);
}
bool
ProcessGlobal::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
JS::Handle<jsid> aId,
JS::MutableHandle<JS::PropertyDescriptor> aDesc)
{
@@ -67,16 +74,26 @@ ProcessGlobal::Get()
}
ProcessGlobal* global = static_cast<ProcessGlobal*>(service.get());
if (global) {
sWasCreated = true;
}
return global;
}
+already_AddRefed<mozilla::dom::ipc::SharedMap>
+ProcessGlobal::SharedData()
+{
+ if (ContentChild* child = ContentChild::GetSingleton()) {
+ return do_AddRef(child->SharedData());
+ }
+ auto* ppmm = nsFrameMessageManager::sParentProcessManager;
+ return do_AddRef(ppmm->SharedData()->GetReadOnly());
+}
+
bool
ProcessGlobal::WasCreated()
{
return sWasCreated;
}
void
ProcessGlobal::MarkForCC()
--- a/dom/base/ProcessGlobal.h
+++ b/dom/base/ProcessGlobal.h
@@ -20,16 +20,20 @@
#include "nsIScriptObjectPrincipal.h"
#include "nsServiceManagerUtils.h"
#include "nsWeakReference.h"
#include "nsWrapperCache.h"
namespace mozilla {
namespace dom {
+namespace ipc {
+ class SharedMap;
+}
+
class ProcessGlobal :
public nsIMessageSender,
public nsMessageManagerScriptExecutor,
public nsIGlobalObject,
public nsIScriptObjectPrincipal,
public nsSupportsWeakReference,
public ipc::MessageManagerCallback,
public MessageManagerGlobal,
@@ -79,16 +83,18 @@ public:
{
if (!mMessageManager) {
aError.Throw(NS_ERROR_NULL_POINTER);
return;
}
mMessageManager->GetInitialProcessData(aCx, aInitialProcessData, aError);
}
+ already_AddRefed<ipc::SharedMap> SharedData();
+
NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
virtual void LoadScript(const nsAString& aURL);
virtual JSObject* GetGlobalJSObject() override
{
return GetWrapper();
}
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -43,16 +43,17 @@
#include "mozilla/dom/ParentProcessMessageManager.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/ProcessGlobal.h"
#include "mozilla/dom/ProcessMessageManager.h"
#include "mozilla/dom/ResolveSystemBinding.h"
#include "mozilla/dom/SameProcessMessageQueue.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/ipc/SharedMap.h"
#include "mozilla/dom/ipc/StructuredCloneData.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
#include "nsPrintfCString.h"
#include "nsXULAppAPI.h"
#include "nsQueryObject.h"
#include "xpcprivate.h"
#include <algorithm>
@@ -994,16 +995,29 @@ nsFrameMessageManager::GetInitialProcess
if (!JS_WrapValue(aCx, &init)) {
aError.NoteJSContextException(aCx);
return;
}
aInitialProcessData.set(init);
}
+WritableSharedMap*
+nsFrameMessageManager::SharedData()
+{
+ if (!mChrome || !mIsProcessManager) {
+ MOZ_ASSERT(false, "Should only call this binding method on ppmm");
+ return nullptr;
+ }
+ if (!mSharedData) {
+ mSharedData = new WritableSharedMap();
+ }
+ return mSharedData;
+}
+
already_AddRefed<ProcessMessageManager>
nsFrameMessageManager::GetProcessMessageManager(ErrorResult& aError)
{
RefPtr<ProcessMessageManager> pmm;
if (mCallback) {
pmm = mCallback->GetProcessMessageManager();
}
return pmm.forget();
--- a/dom/base/nsFrameMessageManager.h
+++ b/dom/base/nsFrameMessageManager.h
@@ -32,16 +32,21 @@
#include "mozilla/dom/CallbackObject.h"
#include "mozilla/dom/SameProcessMessageQueue.h"
#include "mozilla/dom/ipc/StructuredCloneData.h"
#include "mozilla/jsipc/CpowHolder.h"
class nsFrameLoader;
namespace mozilla {
+
+namespace ipc {
+ class FileDescriptor;
+}
+
namespace dom {
class nsIContentParent;
class nsIContentChild;
class ChildProcessMessageManager;
class ChromeMessageBroadcaster;
class ClonedMessageData;
class MessageBroadcaster;
@@ -49,16 +54,18 @@ class MessageListener;
class MessageListenerManager;
class MessageManagerReporter;
template<typename T> class Optional;
class ParentProcessMessageManager;
class ProcessMessageManager;
namespace ipc {
+class WritableSharedMap;
+
// Note: we round the time we spend to the nearest millisecond. So a min value
// of 1 ms actually captures from 500us and above.
static const uint32_t kMinTelemetrySyncMessageManagerLatencyMs = 1;
enum class MessageManagerFlags {
MM_NONE = 0,
MM_CHROME = 1,
MM_GLOBAL = 2,
@@ -228,16 +235,18 @@ public:
SendMessage(aCx, aMessageName, aObj, aObjects, aPrincipal, false, aResult, aError);
}
// GlobalProcessScriptLoader
void GetInitialProcessData(JSContext* aCx,
JS::MutableHandle<JS::Value> aInitialProcessData,
mozilla::ErrorResult& aError);
+ mozilla::dom::ipc::WritableSharedMap* SharedData();
+
NS_DECL_NSIMESSAGESENDER
NS_DECL_NSICONTENTFRAMEMESSAGEMANAGER
static mozilla::dom::ProcessMessageManager* NewProcessMessageManager(bool aIsRemote);
void ReceiveMessage(nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
const nsAString& aMessage, bool aIsSync,
StructuredCloneData* aCloneData, mozilla::jsipc::CpowHolder* aCpows,
@@ -335,16 +344,17 @@ protected:
bool mHandlingMessage;
bool mClosed; // true if we can no longer send messages
bool mDisconnected;
mozilla::dom::ipc::MessageManagerCallback* mCallback;
nsAutoPtr<mozilla::dom::ipc::MessageManagerCallback> mOwnedCallback;
nsTArray<nsString> mPendingScripts;
nsTArray<bool> mPendingScriptsGlobalStates;
JS::Heap<JS::Value> mInitialProcessData;
+ RefPtr<mozilla::dom::ipc::WritableSharedMap> mSharedData;
void LoadPendingScripts(nsFrameMessageManager* aManager,
nsFrameMessageManager* aChildMM);
public:
static mozilla::dom::ParentProcessMessageManager* sParentProcessManager;
static nsFrameMessageManager* sSameProcessParentManager;
static nsTArray<nsCOMPtr<nsIRunnable> >* sPendingSameProcessAsyncMessages;
private:
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -43,16 +43,17 @@
#include "mozilla/dom/ProcessGlobal.h"
#include "mozilla/dom/PushNotifier.h"
#include "mozilla/dom/ServiceWorkerManager.h"
#include "mozilla/dom/TabGroup.h"
#include "mozilla/dom/nsIContentChild.h"
#include "mozilla/dom/URLClassifierChild.h"
#include "mozilla/dom/WorkerDebugger.h"
#include "mozilla/dom/WorkerDebuggerManager.h"
+#include "mozilla/dom/ipc/SharedMap.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/psm/PSMContentListener.h"
#include "mozilla/hal_sandbox/PHalChild.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/FileDescriptorSetChild.h"
#include "mozilla/ipc/FileDescriptorUtils.h"
#include "mozilla/ipc/GeckoChildProcessHost.h"
@@ -584,31 +585,42 @@ NS_INTERFACE_MAP_BEGIN(ContentChild)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentChild)
NS_INTERFACE_MAP_END
mozilla::ipc::IPCResult
ContentChild::RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
const StructuredCloneData& aInitialData,
nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache,
- nsTArray<SystemFontListEntry>&& aFontList)
+ nsTArray<SystemFontListEntry>&& aFontList,
+ const FileDescriptor& aSharedDataMapFile,
+ const uint32_t& aSharedDataMapSize)
{
if (!sShutdownCanary) {
return IPC_OK();
}
mLookAndFeelCache = std::move(aLookAndFeelIntCache);
mFontList = std::move(aFontList);
gfx::gfxVars::SetValuesForInitialize(aXPCOMInit.gfxNonDefaultVarUpdates());
InitXPCOM(aXPCOMInit, aInitialData);
InitGraphicsDeviceData(aXPCOMInit.contentDeviceData());
+ mSharedData = new SharedMap(ProcessGlobal::Get(), aSharedDataMapFile,
+ aSharedDataMapSize);
+
return IPC_OK();
}
+void
+ContentChild::ClearSharedData()
+{
+ mSharedData = nullptr;
+}
+
bool
ContentChild::Init(MessageLoop* aIOLoop,
base::ProcessId aParentPid,
const char* aParentBuildID,
IPC::Channel* aChannel,
uint64_t aChildID,
bool aIsForBrowser)
{
@@ -2532,16 +2544,28 @@ ContentChild::RecvAsyncMessage(const nsS
ipc::UnpackClonedMessageDataForChild(aData, data);
cpm->ReceiveMessage(cpm, nullptr, aMsg, false, &data, &cpows, aPrincipal, nullptr,
IgnoreErrors());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
+ContentChild::RecvUpdateSharedData(const FileDescriptor& aMapFile,
+ const uint32_t& aMapSize,
+ nsTArray<nsCString>&& aChangedKeys)
+{
+ if (mSharedData) {
+ mSharedData->Update(aMapFile, aMapSize, std::move(aChangedKeys));
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
ContentChild::RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition)
{
nsCOMPtr<nsIGeolocationUpdate> gs =
do_GetService("@mozilla.org/geolocation/service;1");
if (!gs) {
return IPC_OK();
}
gs->Update(aPosition);
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -61,16 +61,20 @@ nsresult GetObjDir(nsIFile **aObjDir);
namespace ipc {
class OptionalURIParams;
class URIParams;
}// namespace ipc
namespace dom {
+namespace ipc {
+class SharedMap;
+}
+
class AlertObserver;
class ConsoleListener;
class ClonedMessageData;
class TabChild;
class GetFilesHelperChild;
class FileCreatorHelper;
class ContentChild final : public PContentChild
@@ -159,16 +163,20 @@ public:
mProfileDir = aProfileDir;
}
#endif
bool IsAlive() const;
bool IsShuttingDown() const;
+ ipc::SharedMap* SharedData() { return mSharedData; };
+
+ void ClearSharedData();
+
static void AppendProcessId(nsACString& aName);
static void UpdateCookieStatus(nsIChannel *aChannel);
mozilla::ipc::IPCResult
RecvInitContentBridgeChild(Endpoint<PContentBridgeChild>&& aEndpoint) override;
mozilla::ipc::IPCResult
@@ -389,16 +397,20 @@ public:
virtual mozilla::ipc::IPCResult RecvLoadProcessScript(const nsString& aURL) override;
virtual mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& aMsg,
InfallibleTArray<CpowEntry>&& aCpows,
const IPC::Principal& aPrincipal,
const ClonedMessageData& aData) override;
+ mozilla::ipc::IPCResult RecvUpdateSharedData(const FileDescriptor& aMapFile,
+ const uint32_t& aMapSize,
+ nsTArray<nsCString>&& aChangedKeys) override;
+
virtual mozilla::ipc::IPCResult RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition) override;
virtual mozilla::ipc::IPCResult RecvGeolocationError(const uint16_t& errorCode) override;
virtual mozilla::ipc::IPCResult RecvUpdateDictionaryList(InfallibleTArray<nsString>&& aDictionaries) override;
virtual mozilla::ipc::IPCResult RecvUpdateFontList(InfallibleTArray<SystemFontListEntry>&& aFontList) override;
@@ -606,17 +618,19 @@ public:
const bool& anonymize,
const bool& minimizeMemoryUsage,
const MaybeFileDesc& DMDFile) override;
virtual mozilla::ipc::IPCResult
RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
const StructuredCloneData& aInitialData,
nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache,
- nsTArray<SystemFontListEntry>&& aFontList) override;
+ nsTArray<SystemFontListEntry>&& aFontList,
+ const FileDescriptor& aSharedDataMapFile,
+ const uint32_t& aSharedDataMapSize) override;
virtual mozilla::ipc::IPCResult
RecvProvideAnonymousTemporaryFile(const uint64_t& aID, const FileDescOrError& aFD) override;
mozilla::ipc::IPCResult
RecvSetPermissionsWithKey(const nsCString& aPermissionKey,
nsTArray<IPC::Permission>&& aPerms) override;
@@ -806,16 +820,18 @@ private:
static ContentChild* sSingleton;
class ShutdownCanary;
static StaticAutoPtr<ShutdownCanary> sShutdownCanary;
nsCOMPtr<nsIDomainPolicy> mPolicy;
nsCOMPtr<nsITimer> mForceKillTimer;
+ RefPtr<ipc::SharedMap> mSharedData;
+
#ifdef MOZ_GECKO_PROFILER
RefPtr<ChildProfilerController> mProfilerController;
#endif
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
nsCOMPtr<nsIFile> mProfileDir;
#endif
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -55,16 +55,17 @@
#include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "mozilla/dom/Permissions.h"
#include "mozilla/dom/PresentationParent.h"
#include "mozilla/dom/PPresentationParent.h"
#include "mozilla/dom/PushNotifier.h"
#include "mozilla/dom/quota/QuotaManagerService.h"
#include "mozilla/dom/URLClassifierParent.h"
+#include "mozilla/dom/ipc/SharedMap.h"
#include "mozilla/embedding/printingui/PrintingParent.h"
#include "mozilla/extensions/StreamFilterParent.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/hal_sandbox/PHalParent.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/CrashReporterHost.h"
@@ -2304,18 +2305,22 @@ ContentParent::InitInternal(ProcessPrior
// Send the dynamic scalar definitions to the new process.
TelemetryIPC::GetDynamicScalarDefinitions(xpcomInit.dynamicScalarDefs());
// Must send screen info before send initialData
ScreenManager& screenManager = ScreenManager::GetSingleton();
screenManager.CopyScreensToRemote(this);
+ ipc::WritableSharedMap* sharedData = nsFrameMessageManager::sParentProcessManager->SharedData();
+ sharedData->Flush();
+
Unused << SendSetXPCOMProcessAttributes(xpcomInit, initialData, lnfCache,
- fontList);
+ fontList, sharedData->CloneMapFile(),
+ sharedData->MapSize());
nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
nsChromeRegistryChrome* chromeRegistry =
static_cast<nsChromeRegistryChrome*>(registrySvc.get());
chromeRegistry->SendRegisteredChrome(this);
if (gAppData) {
nsCString version(gAppData->version);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -450,18 +450,22 @@ child:
async UpdateDictionaryList(nsString[] dictionaries);
async UpdateFontList(SystemFontListEntry[] fontList);
async UpdateAppLocales(nsCString[] appLocales);
async UpdateRequestedLocales(nsCString[] requestedLocales);
+
async ClearSiteDataReloadNeeded(nsString origin);
+ async UpdateSharedData(FileDescriptor mapFile, uint32_t aSize,
+ nsCString[] changedKeys);
+
// nsIPermissionManager messages
async AddPermission(Permission permission);
async FlushMemory(nsString reason);
async GarbageCollect();
async CycleCollect();
async UnlinkGhosts();
@@ -498,17 +502,19 @@ child:
* Send BlobURLRegistrationData to child process.
*/
async InitBlobURLs(BlobURLRegistrationData[] registrations);
async SetXPCOMProcessAttributes(XPCOMInitData xpcomInit,
StructuredCloneData initialData,
LookAndFeelInt[] lookAndFeelIntCache,
/* used on MacOSX and Linux only: */
- SystemFontListEntry[] systemFontList);
+ SystemFontListEntry[] systemFontList,
+ FileDescriptor sharedDataMapFile,
+ uint32_t sharedDataMapSize);
// Notify child that last-pb-context-exited notification was observed
async LastPrivateDocShellDestroyed();
async NotifyProcessPriorityChanged(ProcessPriority priority);
async MinimizeMemoryUsage();
/**
new file mode 100644
--- /dev/null
+++ b/dom/ipc/SharedMap.cpp
@@ -0,0 +1,368 @@
+/* -*- 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 "SharedMap.h"
+
+#include "MemMapSnapshot.h"
+#include "ScriptPreloader-inl.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ProcessGlobal.h"
+
+using namespace mozilla::loader;
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+namespace ipc {
+
+// Align to size of uintptr_t here, to be safe. It's probably not strictly
+// necessary, though.
+constexpr size_t kStructuredCloneAlign = sizeof(uintptr_t);
+
+
+static inline void
+AlignTo(size_t* aOffset, size_t aAlign)
+{
+ if (auto mod = *aOffset % aAlign) {
+ *aOffset += aAlign - mod;
+ }
+}
+
+
+SharedMap::SharedMap()
+{}
+
+SharedMap::SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor& aMapFile,
+ size_t aMapSize)
+{
+ mMapFile.reset(new FileDescriptor(aMapFile));
+ mMapSize = aMapSize;
+}
+
+
+bool
+SharedMap::Has(const nsACString& aName)
+{
+ return mEntries.Contains(aName);
+}
+
+void
+SharedMap::Get(JSContext* aCx,
+ const nsACString& aName,
+ JS::MutableHandleValue aRetVal,
+ ErrorResult& aRv)
+{
+ auto res = MaybeRebuild();
+ if (res.isErr()) {
+ aRv.Throw(res.unwrapErr());
+ return;
+ }
+
+ Entry* entry = mEntries.Get(aName);
+ if (!entry) {
+ aRetVal.setNull();
+ return;
+ }
+
+ entry->Read(aCx, aRetVal, aRv);
+}
+
+void
+SharedMap::Entry::Read(JSContext* aCx,
+ JS::MutableHandleValue aRetVal,
+ ErrorResult& aRv)
+{
+ if (mData.is<StructuredCloneData>()) {
+ // We have a temporary buffer for a key that was changed after the last
+ // snapshot. Just decode it directly.
+ auto& holder = mData.as<StructuredCloneData>();
+ holder.Read(aCx, aRetVal, aRv);
+ return;
+ }
+
+ // We have a pointer to a shared memory region containing our structured
+ // clone data. Create a temporary buffer to decode that data, and then
+ // discard it so that we don't keep a separate process-local copy around any
+ // longer than necessary.
+ StructuredCloneData holder;
+ if (!holder.CopyExternalData(Data(), Size())) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ holder.Read(aCx, aRetVal, aRv);
+}
+
+FileDescriptor
+SharedMap::CloneMapFile()
+{
+ if (mMap.initialized()) {
+ return mMap.cloneHandle();
+ }
+ return *mMapFile;
+}
+
+void
+SharedMap::Update(const FileDescriptor& aMapFile, size_t aMapSize,
+ nsTArray<nsCString>&& aChangedKeys)
+{
+ MOZ_DIAGNOSTIC_ASSERT(!mWritable);
+
+ mMap.reset();
+ if (mMapFile) {
+ *mMapFile = aMapFile;
+ } else {
+ mMapFile.reset(new FileDescriptor(aMapFile));
+ }
+ mMapSize = aMapSize;
+ mEntries.Clear();
+}
+
+void
+SharedMap::Entry::TakeData(StructuredCloneData&& aHolder)
+{
+ mData = AsVariant(std::move(aHolder));
+
+ mSize = Holder().Data().Size();
+}
+
+void
+SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset)
+{
+ if (mData.is<StructuredCloneData>()) {
+ char* ptr = aDestPtr;
+ Holder().Data().ForEachDataChunk([&](const char* aData, size_t aSize) {
+ memcpy(ptr, aData, aSize);
+ ptr += aSize;
+ return true;
+ });
+ MOZ_ASSERT(ptr - aDestPtr == mSize);
+ } else {
+ memcpy(aDestPtr, Data(), mSize);
+ }
+
+ mData = AsVariant(aNewOffset);
+}
+
+Result<Ok, nsresult>
+SharedMap::MaybeRebuild()
+{
+ if (!mMapFile) {
+ return Ok();
+ }
+
+ // This function maps a shared memory region created by Serialize() and reads
+ // its header block to build a new mEntries hashtable of its contents.
+ //
+ // The entries created by this function contain a pointer to this SharedMap
+ // instance, and the offsets and sizes of their structured clone data within
+ // its shared memory region. When needed, that structured clone data is
+ // retrieved directly as indexes into the SharedMap's shared memory region.
+
+ MOZ_TRY(mMap.init(*mMapFile, mMapSize));
+ mMapFile.reset();
+
+ // We should be able to pass this range as an initializer list or an immediate
+ // param, but gcc currently chokes on that if optimization is enabled, and
+ // initializes everything to 0.
+ Range<uint8_t> range(&mMap.get<uint8_t>()[0], mMap.size());
+ InputBuffer buffer(range);
+
+ uint32_t count;
+ buffer.codeUint32(count);
+
+ for (uint32_t i = 0; i < count; i++) {
+ auto entry = MakeUnique<Entry>(*this);
+ entry->Code(buffer);
+
+ // This buffer was created at runtime, during this session, so any errors
+ // indicate memory corruption, and are fatal.
+ MOZ_RELEASE_ASSERT(!buffer.error());
+
+ // Note: Order of evaluation of function arguments is not guaranteed, so we
+ // can't use entry.release() in place of entry.get() without entry->Name()
+ // sometimes resulting in a null dereference.
+ mEntries.Put(entry->Name(), entry.get());
+ Unused << entry.release();
+ }
+
+ return Ok();
+}
+
+void
+SharedMap::MaybeRebuild() const
+{
+ Unused << const_cast<SharedMap*>(this)->MaybeRebuild();
+}
+
+WritableSharedMap::WritableSharedMap()
+ : SharedMap()
+{
+ mWritable = true;
+ // Serialize the initial empty contents of the map immediately so that we
+ // always have a file descriptor to send to callers of CloneMapFile().
+ Unused << Serialize();
+}
+
+SharedMap*
+WritableSharedMap::GetReadOnly()
+{
+ if (!mReadOnly) {
+ mReadOnly = new SharedMap(ProcessGlobal::Get(), CloneMapFile(),
+ MapSize());
+ }
+ return mReadOnly;
+}
+
+Result<Ok, nsresult>
+WritableSharedMap::Serialize()
+{
+ // Serializes a new snapshot of the map, initializes a new read-only shared
+ // memory region with its contents, and updates all entries to point to that
+ // new snapshot.
+ //
+ // The layout of the snapshot is as follows:
+ //
+ // - A header containing a uint32 count field containing the number of
+ // entries in the map, followed by that number of serialized entries, as
+ // produced by Entry::Code.
+ //
+ // - A data block containing structured clone data for each of the entries'
+ // values. This data is referenced by absolute byte offsets from the start
+ // of the shared memory region, encoded in each of the entry header values.
+ //
+ // This serialization format is decoded by the MaybeRebuild() method of
+ // read-only SharedMap() instances, and used to populate their mEntries
+ // hashtables.
+ //
+ // Writable instances never read the header blocks, but instead directly
+ // update their Entry instances to point to the appropriate offsets in the
+ // shared memory region created by this function.
+
+ uint32_t count = mEntries.Count();
+
+ size_t dataSize = 0;
+ size_t headerSize = sizeof(count);
+
+ for (auto& entry : IterHash(mEntries)) {
+ headerSize += entry->HeaderSize();
+
+ dataSize += entry->Size();
+ AlignTo(&dataSize, kStructuredCloneAlign);
+ }
+
+ size_t offset = headerSize;
+ AlignTo(&offset, kStructuredCloneAlign);
+
+ OutputBuffer header;
+ header.codeUint32(count);
+
+ MemMapSnapshot mem;
+ MOZ_TRY(mem.Init(offset + dataSize));
+
+ auto ptr = mem.Get<char>();
+
+ for (auto& entry : IterHash(mEntries)) {
+ AlignTo(&offset, kStructuredCloneAlign);
+
+ entry->ExtractData(&ptr[offset], offset);
+ entry->Code(header);
+
+ offset += entry->Size();
+ }
+
+ // FIXME: We should create a separate OutputBuffer class which can encode to
+ // a static memory region rather than dynamically allocating and then
+ // copying.
+ memcpy(ptr.get(), header.Get(), header.cursor());
+
+ // We've already updated offsets at this point. We need this to succeed.
+ mMap.reset();
+ MOZ_RELEASE_ASSERT(mem.Finalize(mMap).isOk());
+
+ return Ok();
+}
+
+void
+WritableSharedMap::BroadcastChanges()
+{
+ if (mChangedKeys.IsEmpty()) {
+ return;
+ }
+
+ if (!Serialize().isOk()) {
+ return;
+ }
+
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ for (auto& parent : parents) {
+ Unused << parent->SendUpdateSharedData(CloneMapFile(), mMap.size(),
+ mChangedKeys);
+ }
+
+ if (mReadOnly) {
+ mReadOnly->Update(CloneMapFile(), mMap.size(),
+ std::move(mChangedKeys));
+ }
+
+ mChangedKeys.Clear();
+}
+
+void
+WritableSharedMap::Delete(const nsACString& aName)
+{
+ if (mEntries.Remove(aName)) {
+ KeyChanged(aName);
+ }
+}
+
+void
+WritableSharedMap::Set(JSContext* aCx,
+ const nsACString& aName,
+ JS::HandleValue aValue,
+ ErrorResult& aRv)
+{
+ StructuredCloneData holder;
+
+ holder.Write(aCx, aValue, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (!holder.BlobImpls().IsEmpty() ||
+ !holder.InputStreams().IsEmpty()) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ Entry* entry = mEntries.LookupOrAdd(aName, *this, aName);
+ entry->TakeData(std::move(holder));
+
+ KeyChanged(aName);
+}
+
+void
+WritableSharedMap::Flush()
+{
+ BroadcastChanges();
+}
+
+void
+WritableSharedMap::KeyChanged(const nsACString& aName)
+{
+ if (!mChangedKeys.ContainsSorted(aName)) {
+ mChangedKeys.InsertElementSorted(aName);
+ }
+}
+
+NS_IMPL_ISUPPORTS0(SharedMap)
+
+} // ipc
+} // dom
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/SharedMap.h
@@ -0,0 +1,305 @@
+/* -*- 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/. */
+
+#ifndef dom_ipc_SharedMap_h
+#define dom_ipc_SharedMap_h
+
+#include "mozilla/AutoMemMap.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Variant.h"
+#include "nsClassHashtable.h"
+#include "nsTArray.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+namespace ipc {
+
+/**
+ * Together, the SharedMap and WritableSharedMap classes allow sharing a
+ * dynamically-updated, shared-memory key-value store across processes.
+ *
+ * The maps may only ever be updated in the parent process, via
+ * WritableSharedMap instances. When that map changes, its entire contents are
+ * serialized into a contiguous shared memory buffer, and broadcast to all child
+ * processes, which in turn update their entire map contents wholesale.
+ *
+ * Keys are arbitrary UTF-8 strings (currently exposed to JavaScript as UTF-16),
+ * and values are structured clone buffers. Values are eagerly encoded whenever
+ * they are updated, and lazily decoded each time they're read.
+ *
+ * Updates are batched. Rather than each key change triggering an immediate
+ * update, combined updates are broadcast after a delay. Currently, this
+ * requires an explicit flush() call, or spawning a new content process. In the
+ * future, it will happen automatically in idle tasks.
+ *
+ *
+ * Whenever a read-only SharedMap is updated, it dispatches a "change" event.
+ * The event contains a "changedKeys" property with a list of all keys which
+ * were changed in the last update batch. Change events are never dispatched to
+ * WritableSharedMap instances.
+ */
+class SharedMap : public nsISupports
+{
+ using FileDescriptor = mozilla::ipc::FileDescriptor;
+
+public:
+ NS_DECL_ISUPPORTS
+
+ SharedMap();
+
+ SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor&, size_t);
+
+ // Returns true if the map contains the given (UTF-8) key.
+ bool Has(const nsACString& name);
+
+ // If the map contains the given (UTF-8) key, decodes and returns a new copy
+ // of its value. Otherwise returns null.
+ void Get(JSContext* cx, const nsACString& name, JS::MutableHandleValue aRetVal,
+ ErrorResult& aRv);
+
+
+ /**
+ * Returns a copy of the read-only file descriptor which backs the shared
+ * memory region for this map. The file descriptor may be passed between
+ * processes, and used to update corresponding instances in child processes.
+ */
+ FileDescriptor CloneMapFile();
+
+ /**
+ * Returns the size of the memory mapped region that backs this map. Must be
+ * passed to the SharedMap() constructor or Update() method along with the
+ * descriptor returned by CloneMapFile() in order to initialize or update a
+ * child SharedMap.
+ */
+ size_t MapSize() const { return mMap.size(); }
+
+ /**
+ * Updates this instance to reflect the contents of the shared memory region
+ * in the given map file, and broadcasts a change event for the given set of
+ * changed (UTF-8-encoded) keys.
+ */
+ void Update(const FileDescriptor& aMapFile, size_t aMapSize,
+ nsTArray<nsCString>&& aChangedKeys);
+
+
+protected:
+ virtual ~SharedMap() = default;
+
+ class Entry
+ {
+ public:
+ Entry(Entry&&) = delete;
+
+ explicit Entry(SharedMap& aMap, const nsACString& aName = EmptyCString())
+ : mMap(aMap)
+ , mName(aName)
+ , mData(AsVariant(uint32_t(0)))
+ {
+ }
+
+ ~Entry() = default;
+
+ /**
+ * Encodes or decodes this entry into or from the given buffer.
+ */
+ template<typename Buffer>
+ void Code(Buffer& buffer)
+ {
+ DebugOnly<size_t> startOffset = buffer.cursor();
+
+ buffer.codeString(mName);
+ buffer.codeUint32(DataOffset());
+ buffer.codeUint32(mSize);
+
+ MOZ_ASSERT(buffer.cursor() == startOffset + HeaderSize());
+ }
+
+ /**
+ * Returns the size that this entry will take up in the map header. This
+ * must be equal to the number of bytes encoded by Code().
+ */
+ size_t HeaderSize() const
+ {
+ return (sizeof(uint16_t) + mName.Length() +
+ sizeof(DataOffset()) +
+ sizeof(mSize));
+ }
+
+ /**
+ * Updates the value of this entry to the given structured clone data, of
+ * which it takes ownership. The passed StructuredCloneData object may not
+ * be used after this call.
+ */
+ void TakeData(StructuredCloneData&&);
+
+ /**
+ * This is called while building a new snapshot of the SharedMap. aDestPtr
+ * must point to a buffer within the new snapshot with Size() bytes reserved
+ * for it, and `aNewOffset` must be the offset of that buffer from the start
+ * of the snapshot's memory region.
+ *
+ * This function copies the raw structured clone data for the entry's value
+ * to the new buffer, and updates its internal state for use with the new
+ * data. Its offset is updated to aNewOffset, and any StructuredCloneData
+ * object it holds is destroyed.
+ *
+ * After this call, the entry is only valid in reference to the new
+ * snapshot, and may not be accessed again until the SharedMap mMap has been
+ * updated to point to it.
+ */
+ void ExtractData(char* aDestPtr, uint32_t aNewOffset);
+
+ // Returns the UTF-8-encoded name of the entry, which is used as its key in
+ // the map.
+ const nsCString& Name() const { return mName; }
+
+ // Decodes the entry's value into the current Realm of the given JS context
+ // and puts the result in aRetVal on success.
+ void Read(JSContext* aCx, JS::MutableHandleValue aRetVal,
+ ErrorResult& aRv);
+
+ // Returns the byte size size of the entry's raw structured clone data.
+ uint32_t Size() const { return mSize; }
+
+ private:
+ // Returns a pointer to the entry value's structured clone data within the
+ // SharedMap's mapped memory region. This is *only* valid shen mData
+ // contains a uint32_t.
+ const char* Data() const
+ {
+ return mMap.Data() + DataOffset();
+ }
+
+ // Returns the offset of the entry value's structured clone data within the
+ // SharedMap's mapped memory region. This is *only* valid shen mData
+ // contains a uint32_t.
+ uint32_t& DataOffset()
+ {
+ return mData.as<uint32_t>();
+ }
+ const uint32_t& DataOffset() const
+ {
+ return mData.as<uint32_t>();
+ }
+
+ // Returns the temporary StructuredCloneData object containing the entry's
+ // value. This is *only* value when mData contains a StructuredCloneDAta
+ // object.
+ const StructuredCloneData& Holder() const
+ {
+ return mData.as<StructuredCloneData>();
+ }
+
+ SharedMap& mMap;
+
+ // The entry's (UTF-8 encoded) name, which serves as its key in the map.
+ nsCString mName;
+
+ /**
+ * This member provides a reference to the entry's structured clone data.
+ * Its type varies depending on the state of the entry:
+ *
+ * - For entries which have been snapshotted into a shared memory region,
+ * this is a uint32_t offset into the parent SharedMap's Data() buffer.
+ *
+ * - For entries which have been changed in a WritableSharedMap instance,
+ * but not serialized to a shared memory snapshot yet, this is a
+ * StructuredCloneData instance, containing a process-local copy of the
+ * data. This will be discarded the next time the map is serialized, and
+ * replaced with a buffer offset, as described above.
+ */
+ Variant<uint32_t, StructuredCloneData> mData;
+
+ // The size, in bytes, of the entry's structured clone data.
+ uint32_t mSize = 0;
+ };
+
+ // Rebuilds the entry hashtable mEntries from the values serialized in the
+ // current snapshot, if necessary. The hashtable is rebuilt lazily after
+ // construction and after every Update() call, so this function must be called
+ // before any attempt to access mEntries.
+ Result<Ok, nsresult> MaybeRebuild();
+ void MaybeRebuild() const;
+
+ // Note: This header is included by WebIDL binding headers, and therefore
+ // can't include "windows.h". Since FileDescriptor.h does include "windows.h"
+ // on Windows, we can only forward declare FileDescriptor, and can't include
+ // it as an inline member.
+ UniquePtr<FileDescriptor> mMapFile;
+ // The size of the memory-mapped region backed by mMapFile, in bytes.
+ size_t mMapSize = 0;
+
+ mutable nsClassHashtable<nsCStringHashKey, Entry> mEntries;
+
+ // Manages the memory mapping of the current snapshot. This is initialized
+ // lazily after each SharedMap construction or updated, based on the values in
+ // mMapFile and mMapSize.
+ loader::AutoMemMap mMap;
+
+ bool mWritable = false;
+
+ // Returns a pointer to the beginning of the memory mapped snapshot. Entry
+ // offsets are relative to this pointer, and Entry objects access their
+ // structured clone data by indexing this pointer.
+ char* Data() { return mMap.get<char>().get(); }
+};
+
+class WritableSharedMap final : public SharedMap
+{
+public:
+ WritableSharedMap();
+
+ // Sets the value of the given (UTF-8 encoded) key to a structured clone
+ // snapshot of the given value.
+ void Set(JSContext* cx, const nsACString& name, JS::HandleValue value, ErrorResult& aRv);
+
+ // Deletes the given (UTF-8 encoded) key from the map.
+ void Delete(const nsACString& name);
+
+ // Flushes any queued changes to a new snapshot, and broadcasts it to all
+ // child SharedMap instances.
+ void Flush();
+
+
+ /**
+ * Returns the read-only SharedMap instance corresponding to this
+ * WritableSharedMap for use in the parent process.
+ */
+ SharedMap* GetReadOnly();
+
+protected:
+ ~WritableSharedMap() override = default;
+
+private:
+ // The set of (UTF-8 encoded) keys which have changed, or been deleted, since
+ // the last snapshot.
+ nsTArray<nsCString> mChangedKeys;
+
+ RefPtr<SharedMap> mReadOnly;
+
+ // Creates a new snapshot of the map, and updates all Entry instance to
+ // reference its data.
+ Result<Ok, nsresult> Serialize();
+
+ // If there have been any changes since the last snapshot, creates a new
+ // serialization and broadcasts it to all child SharedMap instances.
+ void BroadcastChanges();
+
+ // Marks the given (UTF-8 encoded) key as having changed. This adds it to
+ // mChangedKeys, if not already present. In the future, it will also schedule
+ // a flush the next time the event loop is idle.
+ void KeyChanged(const nsACString& aName);
+};
+
+} // ipc
+} // dom
+} // mozilla
+
+#endif // dom_ipc_SharedMap_h
--- a/dom/ipc/StructuredCloneData.cpp
+++ b/dom/ipc/StructuredCloneData.cpp
@@ -7,29 +7,35 @@
#include "StructuredCloneData.h"
#include "nsIMutable.h"
#include "nsIXPConnect.h"
#include "ipc/IPCMessageUtils.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/File.h"
#include "mozilla/dom/IPCBlobUtils.h"
-#include "mozilla/dom/File.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "nsContentUtils.h"
#include "nsJSEnvironment.h"
#include "MainThreadUtils.h"
#include "StructuredCloneTags.h"
#include "jsapi.h"
namespace mozilla {
namespace dom {
namespace ipc {
+using mozilla::ipc::AutoIPCStream;
+using mozilla::ipc::IPCStream;
+using mozilla::ipc::PBackgroundChild;
+using mozilla::ipc::PBackgroundParent;
+
StructuredCloneData::StructuredCloneData()
: StructuredCloneData(StructuredCloneHolder::TransferringSupported)
{}
StructuredCloneData::StructuredCloneData(StructuredCloneData&& aOther)
: StructuredCloneData(StructuredCloneHolder::TransferringSupported)
{
*this = std::move(aOther);
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -10,16 +10,17 @@ with Files("**"):
XPIDL_SOURCES += [
'nsIHangReport.idl',
]
XPIDL_MODULE = 'dom'
EXPORTS.mozilla.dom.ipc += [
'IdType.h',
+ 'SharedMap.h',
'StructuredCloneData.h',
]
EXPORTS.mozilla.dom += [
'CoalescedInputData.h',
'CoalescedMouseData.h',
'CoalescedWheelData.h',
'ContentBridgeChild.h',
@@ -63,16 +64,17 @@ UNIFIED_SOURCES += [
'FilePickerParent.cpp',
'MemMapSnapshot.cpp',
'MemoryReportRequest.cpp',
'nsIContentChild.cpp',
'nsIContentParent.cpp',
'PermissionMessageUtils.cpp',
'PreallocatedProcessManager.cpp',
'ProcessPriorityManager.cpp',
+ 'SharedMap.cpp',
'StructuredCloneData.cpp',
'TabChild.cpp',
'TabContext.cpp',
'TabMessageUtils.cpp',
'TabParent.cpp',
'URLClassifierParent.cpp',
]
--- a/js/xpconnect/loader/AutoMemMap.cpp
+++ b/js/xpconnect/loader/AutoMemMap.cpp
@@ -3,16 +3,17 @@
/* 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 "AutoMemMap.h"
#include "ScriptPreloader-inl.h"
#include "mozilla/Unused.h"
+#include "mozilla/ipc/FileDescriptor.h"
#include "nsIFile.h"
#include <private/pprio.h>
namespace mozilla {
namespace loader {
using namespace mozilla::ipc;
--- a/js/xpconnect/loader/AutoMemMap.h
+++ b/js/xpconnect/loader/AutoMemMap.h
@@ -5,24 +5,27 @@
#ifndef loader_AutoMemMap_h
#define loader_AutoMemMap_h
#include "mozilla/FileUtils.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/RangedPtr.h"
#include "mozilla/Result.h"
-#include "mozilla/ipc/FileDescriptor.h"
#include "nsIMemoryReporter.h"
#include <prio.h>
class nsIFile;
namespace mozilla {
+namespace ipc {
+ class FileDescriptor;
+}
+
namespace loader {
using mozilla::ipc::FileDescriptor;
class AutoMemMap
{
public:
AutoMemMap() = default;
--- a/js/xpconnect/loader/moz.build
+++ b/js/xpconnect/loader/moz.build
@@ -20,16 +20,17 @@ SOURCES += [
'mozJSComponentLoader.cpp'
]
IPDL_SOURCES += [
'PScriptCache.ipdl',
]
EXPORTS.mozilla += [
+ 'AutoMemMap.h',
'ScriptPreloader.h',
'URLPreloader.h',
]
EXPORTS.mozilla.dom += [
'PrecompiledScript.h',
]