Bug 1463587: Part 2 - Add a shared-memory structured clone key-value store. r=erahm,bz

This class allows one read-write copy of a map in the parent process to share
data with multiple read-only copies in child processes. The maps only hold
onto data as structured clone blobs, and deserialize them each time a key is
read.

This commit only provides the bare-bones data structures. Follow-ups will add
bindings, change events, and automatic flushes.

MozReview-Commit-ID: LimwfmFBNOi

--HG--
extra : rebase_source : e43985c39bd1cfd05a2ad536b0d7f74db494a753
extra : source : c27295337b4c16e2a178106a3aa873d2a0e5a1f4
This commit is contained in:
Kris Maglione 2018-06-22 20:35:49 -07:00
parent 5754c092dc
commit 03bf0b5bdd
14 changed files with 784 additions and 10 deletions

View File

@ -7,8 +7,10 @@
#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;
@ -72,6 +74,16 @@ ProcessGlobal::Get()
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()
{

View File

@ -25,6 +25,10 @@
namespace mozilla {
namespace dom {
namespace ipc {
class SharedMap;
}
class ProcessGlobal :
public nsIMessageSender,
public nsMessageManagerScriptExecutor,
@ -84,6 +88,8 @@ public:
mMessageManager->GetInitialProcessData(aCx, aInitialProcessData, aError);
}
already_AddRefed<ipc::SharedMap> SharedData();
NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
virtual void LoadScript(const nsAString& aURL);

View File

@ -48,6 +48,7 @@
#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"
@ -999,6 +1000,19 @@ nsFrameMessageManager::GetInitialProcessData(JSContext* aCx,
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)
{

View File

@ -37,6 +37,11 @@
class nsFrameLoader;
namespace mozilla {
namespace ipc {
class FileDescriptor;
}
namespace dom {
class nsIContentParent;
@ -54,6 +59,8 @@ 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;
@ -233,6 +240,8 @@ public:
JS::MutableHandle<JS::Value> aInitialProcessData,
mozilla::ErrorResult& aError);
mozilla::dom::ipc::WritableSharedMap* SharedData();
NS_DECL_NSIMESSAGESENDER
NS_DECL_NSICONTENTFRAMEMESSAGEMANAGER
@ -340,6 +349,7 @@ protected:
nsTArray<nsString> mPendingScripts;
nsTArray<bool> mPendingScriptsGlobalStates;
JS::Heap<JS::Value> mInitialProcessData;
RefPtr<mozilla::dom::ipc::WritableSharedMap> mSharedData;
void LoadPendingScripts(nsFrameMessageManager* aManager,
nsFrameMessageManager* aChildMM);

View File

@ -48,6 +48,7 @@
#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"
@ -592,7 +593,9 @@ 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();
@ -604,6 +607,9 @@ ContentChild::RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
InitXPCOM(aXPCOMInit, aInitialData);
InitGraphicsDeviceData(aXPCOMInit.contentDeviceData());
mSharedData = new SharedMap(ProcessGlobal::Get(), aSharedDataMapFile,
aSharedDataMapSize);
return IPC_OK();
}
@ -2378,6 +2384,8 @@ ContentChild::ActorDestroy(ActorDestroyReason why)
BlobURLProtocolHandler::RemoveDataEntries();
mSharedData = nullptr;
mAlertObservers.Clear();
mIdleObservers.Clear();
@ -2557,6 +2565,18 @@ ContentChild::RecvRegisterStringBundles(nsTArray<mozilla::dom::StringBundleDescr
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)
{

View File

@ -66,6 +66,10 @@ class URIParams;
namespace dom {
namespace ipc {
class SharedMap;
}
class AlertObserver;
class ConsoleListener;
class ClonedMessageData;
@ -164,6 +168,8 @@ public:
bool IsShuttingDown() const;
ipc::SharedMap* SharedData() { return mSharedData; };
static void AppendProcessId(nsACString& aName);
static void UpdateCookieStatus(nsIChannel *aChannel);
@ -396,6 +402,10 @@ public:
mozilla::ipc::IPCResult RecvRegisterStringBundles(nsTArray<StringBundleDescriptor>&& stringBundles) 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;
@ -615,7 +625,9 @@ public:
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;
@ -815,6 +827,8 @@ private:
nsCOMPtr<nsIDomainPolicy> mPolicy;
nsCOMPtr<nsITimer> mForceKillTimer;
RefPtr<ipc::SharedMap> mSharedData;
#ifdef MOZ_GECKO_PROFILER
RefPtr<ChildProfilerController> mProfilerController;
#endif

View File

@ -61,6 +61,7 @@
#include "mozilla/dom/quota/QuotaManagerService.h"
#include "mozilla/dom/ServiceWorkerUtils.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"
@ -2323,8 +2324,12 @@ ContentParent::InitInternal(ProcessPriority aInitialPriority)
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 =
@ -2873,6 +2878,7 @@ ContentParent::Observe(nsISupports* aSubject,
// Okay to call ShutDownProcess multiple times.
ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
MarkAsDead();
// Wait for shutdown to complete, so that we receive any shutdown
// data (e.g. telemetry) from the child before we quit.

View File

@ -466,6 +466,9 @@ child:
async RegisterStringBundles(StringBundleDescriptor[] stringBundles);
async UpdateSharedData(FileDescriptor mapFile, uint32_t aSize,
nsCString[] changedKeys);
// nsIPermissionManager messages
async AddPermission(Permission permission);
async RemoveAllPermissions();
@ -513,7 +516,9 @@ child:
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();

371
dom/ipc/SharedMap.cpp Normal file
View File

@ -0,0 +1,371 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 "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.initWithHandle(*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();
MOZ_RELEASE_ASSERT(mMap.initialized());
}
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 entry headers,
// 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.
// Each entry's data is aligned to kStructuredCloneAlign, and therefore may
// have alignment padding before it.
//
// 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

306
dom/ipc/SharedMap.h Normal file
View File

@ -0,0 +1,306 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
#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 OutputBuffer or
* InputBuffer.
*/
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 must 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 must 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 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

View File

@ -29,6 +29,11 @@ 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)
{}

View File

@ -15,6 +15,7 @@ XPIDL_MODULE = 'dom'
EXPORTS.mozilla.dom.ipc += [
'IdType.h',
'SharedMap.h',
'SharedStringMap.h',
'StructuredCloneData.h',
]
@ -69,6 +70,7 @@ UNIFIED_SOURCES += [
'PermissionMessageUtils.cpp',
'PreallocatedProcessManager.cpp',
'ProcessPriorityManager.cpp',
'SharedMap.cpp',
'SharedStringMap.cpp',
'StructuredCloneData.cpp',
'TabChild.cpp',

View File

@ -8,6 +8,7 @@
#include "ScriptPreloader-inl.h"
#include "mozilla/Unused.h"
#include "mozilla/ipc/FileDescriptor.h"
#include "nsIFile.h"
#include <private/pprio.h>
@ -142,12 +143,11 @@ AutoMemMap::cloneHandle() const
void
AutoMemMap::reset()
{
if (addr && !persistent_) {
Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS);
addr = nullptr;
}
if (fileMap) {
if (addr && !persistent_) {
Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS);
addr = nullptr;
}
Unused << NS_WARN_IF(PR_CloseFileMap(fileMap) != PR_SUCCESS);
fileMap = nullptr;
}

View File

@ -10,7 +10,6 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/RangedPtr.h"
#include "mozilla/Result.h"
#include "mozilla/ipc/FileDescriptor.h"
#include "nsIMemoryReporter.h"
#include <prio.h>
@ -18,6 +17,10 @@
class nsIFile;
namespace mozilla {
namespace ipc {
class FileDescriptor;
}
namespace loader {
using mozilla::ipc::FileDescriptor;