gecko-dev/dom/ipc/StructuredCloneData.cpp
Kris Maglione 2bbae5374b Bug 1470365: Part 1 - Add a compact, read-only, shared-memory string map class. r=erahm
This class implements a shared memory key-value store that fits into a single
memory mapped segment. All of the runtime data for its instances are stored in
the shared memory region, which means that memory overhead for each instance
in each process is only a few bytes.

Importantly, the key and value strings returned by this class are also
pointers into the shared memory region, which means that once an instance is
created, its memory cannot be unmapped until process shutdown.

For the uses I intend to put it to, this is a reasonable constraint. If we
need to use it for shorter-lived maps in the future, we can add an option to
return non-literal dependent strings that will be copied if they need to be
kept alive long term.

MozReview-Commit-ID: 5BwAaDsb7HS

--HG--
extra : rebase_source : b472fe628018f88a2c4d6b3de4b7143aeca55e14
extra : absorb_source : 5cdeb568cfd2b4a5a767191402e699e61e653b3b
2018-06-29 22:50:41 -07:00

479 lines
13 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "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/ipc/BackgroundParent.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "nsContentUtils.h"
#include "nsJSEnvironment.h"
#include "MainThreadUtils.h"
#include "StructuredCloneTags.h"
#include "jsapi.h"
using namespace mozilla::ipc;
namespace mozilla {
namespace dom {
namespace ipc {
StructuredCloneData::StructuredCloneData()
: StructuredCloneData(StructuredCloneHolder::TransferringSupported)
{}
StructuredCloneData::StructuredCloneData(StructuredCloneData&& aOther)
: StructuredCloneData(StructuredCloneHolder::TransferringSupported)
{
*this = std::move(aOther);
}
StructuredCloneData::StructuredCloneData(TransferringSupport aSupportsTransferring)
: StructuredCloneHolder(StructuredCloneHolder::CloningSupported,
aSupportsTransferring,
StructuredCloneHolder::StructuredCloneScope::DifferentProcess)
, mExternalData(JS::StructuredCloneScope::DifferentProcess)
, mInitialized(false)
{}
StructuredCloneData::~StructuredCloneData()
{}
StructuredCloneData&
StructuredCloneData::operator=(StructuredCloneData&& aOther)
{
mExternalData = std::move(aOther.mExternalData);
mSharedData = std::move(aOther.mSharedData);
mIPCStreams = std::move(aOther.mIPCStreams);
mInitialized = aOther.mInitialized;
return *this;
}
bool
StructuredCloneData::Copy(const StructuredCloneData& aData)
{
if (!aData.mInitialized) {
return true;
}
if (aData.SharedData()) {
mSharedData = aData.SharedData();
} else {
mSharedData =
SharedJSAllocatedData::CreateFromExternalData(aData.Data());
NS_ENSURE_TRUE(mSharedData, false);
}
if (mSupportsTransferring) {
PortIdentifiers().AppendElements(aData.PortIdentifiers());
}
MOZ_ASSERT(BlobImpls().IsEmpty());
BlobImpls().AppendElements(aData.BlobImpls());
MOZ_ASSERT(GetSurfaces().IsEmpty());
MOZ_ASSERT(WasmModules().IsEmpty());
MOZ_ASSERT(InputStreams().IsEmpty());
InputStreams().AppendElements(aData.InputStreams());
mInitialized = true;
return true;
}
void
StructuredCloneData::Read(JSContext* aCx,
JS::MutableHandle<JS::Value> aValue,
ErrorResult &aRv)
{
MOZ_ASSERT(mInitialized);
nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
MOZ_ASSERT(global);
ReadFromBuffer(global, aCx, Data(), aValue, aRv);
}
void
StructuredCloneData::Write(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult &aRv)
{
Write(aCx, aValue, JS::UndefinedHandleValue, aRv);
}
void
StructuredCloneData::Write(JSContext* aCx,
JS::Handle<JS::Value> aValue,
JS::Handle<JS::Value> aTransfer,
ErrorResult &aRv)
{
MOZ_ASSERT(!mInitialized);
StructuredCloneHolder::Write(aCx, aValue, aTransfer,
JS::CloneDataPolicy().denySharedArrayBuffer(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
JSStructuredCloneData data(mBuffer->scope());
mBuffer->abandon();
mBuffer->steal(&data);
mBuffer = nullptr;
mSharedData = new SharedJSAllocatedData(std::move(data));
mInitialized = true;
}
enum ActorFlavorEnum {
Parent = 0,
Child,
};
enum ManagerFlavorEnum {
ContentProtocol = 0,
BackgroundProtocol
};
template<typename M>
bool
BuildClonedMessageData(M* aManager, StructuredCloneData& aData,
ClonedMessageData& aClonedData)
{
SerializedStructuredCloneBuffer& buffer = aClonedData.data();
auto iter = aData.Data().Start();
size_t size = aData.Data().Size();
bool success;
buffer.data = aData.Data().Borrow(iter, size, &success);
if (NS_WARN_IF(!success)) {
return false;
}
if (aData.SupportsTransferring()) {
aClonedData.identfiers().AppendElements(aData.PortIdentifiers());
}
const nsTArray<RefPtr<BlobImpl>>& blobImpls = aData.BlobImpls();
if (!blobImpls.IsEmpty()) {
if (NS_WARN_IF(!aClonedData.blobs().SetLength(blobImpls.Length(), fallible))) {
return false;
}
for (uint32_t i = 0; i < blobImpls.Length(); ++i) {
nsresult rv = IPCBlobUtils::Serialize(blobImpls[i], aManager,
aClonedData.blobs()[i]);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
}
}
const nsTArray<nsCOMPtr<nsIInputStream>>& inputStreams = aData.InputStreams();
if (!inputStreams.IsEmpty()) {
if (NS_WARN_IF(!aData.IPCStreams().SetCapacity(inputStreams.Length(),
fallible))) {
return false;
}
InfallibleTArray<IPCStream>& streams = aClonedData.inputStreams();
uint32_t length = inputStreams.Length();
streams.SetCapacity(length);
for (uint32_t i = 0; i < length; ++i) {
AutoIPCStream* stream = aData.IPCStreams().AppendElement(fallible);
if (NS_WARN_IF(!stream)) {
return false;
}
if (!stream->Serialize(inputStreams[i], aManager)) {
return false;
}
streams.AppendElement(stream->TakeValue());
}
}
return true;
}
bool
StructuredCloneData::BuildClonedMessageDataForParent(
nsIContentParent* aParent,
ClonedMessageData& aClonedData)
{
return BuildClonedMessageData(aParent, *this, aClonedData);
}
bool
StructuredCloneData::BuildClonedMessageDataForChild(
nsIContentChild* aChild,
ClonedMessageData& aClonedData)
{
return BuildClonedMessageData(aChild, *this, aClonedData);
}
bool
StructuredCloneData::BuildClonedMessageDataForBackgroundParent(
PBackgroundParent* aParent,
ClonedMessageData& aClonedData)
{
return BuildClonedMessageData(aParent, *this, aClonedData);
}
bool
StructuredCloneData::BuildClonedMessageDataForBackgroundChild(
PBackgroundChild* aChild,
ClonedMessageData& aClonedData)
{
return BuildClonedMessageData(aChild, *this, aClonedData);
}
// See the StructuredCloneData class block comment for the meanings of each val.
enum MemoryFlavorEnum {
BorrowMemory = 0,
CopyMemory,
StealMemory
};
template<MemoryFlavorEnum>
struct MemoryTraits
{ };
template<>
struct MemoryTraits<BorrowMemory>
{
typedef const mozilla::dom::ClonedMessageData ClonedMessageType;
static void ProvideBuffer(const ClonedMessageData& aClonedData,
StructuredCloneData& aData)
{
const SerializedStructuredCloneBuffer& buffer = aClonedData.data();
aData.UseExternalData(buffer.data);
}
};
template<>
struct MemoryTraits<CopyMemory>
{
typedef const mozilla::dom::ClonedMessageData ClonedMessageType;
static void ProvideBuffer(const ClonedMessageData& aClonedData,
StructuredCloneData& aData)
{
const SerializedStructuredCloneBuffer& buffer = aClonedData.data();
aData.CopyExternalData(buffer.data);
}
};
template<>
struct MemoryTraits<StealMemory>
{
// note: not const!
typedef mozilla::dom::ClonedMessageData ClonedMessageType;
static void ProvideBuffer(ClonedMessageData& aClonedData,
StructuredCloneData& aData)
{
SerializedStructuredCloneBuffer& buffer = aClonedData.data();
aData.StealExternalData(buffer.data);
}
};
// Note that there isn't actually a difference between Parent/BackgroundParent
// and Child/BackgroundChild in this implementation. The calling methods,
// however, do maintain the distinction for code-reading purposes and are backed
// by assertions to enforce there is no misuse.
template<MemoryFlavorEnum MemoryFlavor, ActorFlavorEnum Flavor>
static void
UnpackClonedMessageData(typename MemoryTraits<MemoryFlavor>::ClonedMessageType& aClonedData,
StructuredCloneData& aData)
{
const InfallibleTArray<MessagePortIdentifier>& identifiers = aClonedData.identfiers();
MemoryTraits<MemoryFlavor>::ProvideBuffer(aClonedData, aData);
if (aData.SupportsTransferring()) {
aData.PortIdentifiers().AppendElements(identifiers);
}
const nsTArray<IPCBlob>& blobs = aClonedData.blobs();
if (!blobs.IsEmpty()) {
uint32_t length = blobs.Length();
aData.BlobImpls().SetCapacity(length);
for (uint32_t i = 0; i < length; ++i) {
RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(blobs[i]);
MOZ_ASSERT(blobImpl);
aData.BlobImpls().AppendElement(blobImpl);
}
}
const InfallibleTArray<IPCStream>& streams = aClonedData.inputStreams();
if (!streams.IsEmpty()) {
uint32_t length = streams.Length();
aData.InputStreams().SetCapacity(length);
for (uint32_t i = 0; i < length; ++i) {
nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(streams[i]);
aData.InputStreams().AppendElement(stream);
}
}
}
void
StructuredCloneData::BorrowFromClonedMessageDataForParent(const ClonedMessageData& aClonedData)
{
// PContent parent is always main thread and actor constraints demand we're
// likewise on that thread.
MOZ_ASSERT(NS_IsMainThread());
UnpackClonedMessageData<BorrowMemory, Parent>(aClonedData, *this);
}
void
StructuredCloneData::BorrowFromClonedMessageDataForChild(const ClonedMessageData& aClonedData)
{
// PContent child is always main thread and actor constraints demand we're
// likewise on that thread.
MOZ_ASSERT(NS_IsMainThread());
UnpackClonedMessageData<BorrowMemory, Child>(aClonedData, *this);
}
void
StructuredCloneData::BorrowFromClonedMessageDataForBackgroundParent(const ClonedMessageData& aClonedData)
{
MOZ_ASSERT(IsOnBackgroundThread());
UnpackClonedMessageData<BorrowMemory, Parent>(aClonedData, *this);
}
void
StructuredCloneData::BorrowFromClonedMessageDataForBackgroundChild(const ClonedMessageData& aClonedData)
{
// No thread assertion; BackgroundChild can happen on any thread.
UnpackClonedMessageData<BorrowMemory, Child>(aClonedData, *this);
}
void
StructuredCloneData::CopyFromClonedMessageDataForParent(const ClonedMessageData& aClonedData)
{
MOZ_ASSERT(NS_IsMainThread());
UnpackClonedMessageData<CopyMemory, Parent>(aClonedData, *this);
}
void
StructuredCloneData::CopyFromClonedMessageDataForChild(const ClonedMessageData& aClonedData)
{
MOZ_ASSERT(NS_IsMainThread());
UnpackClonedMessageData<CopyMemory, Child>(aClonedData, *this);
}
void
StructuredCloneData::CopyFromClonedMessageDataForBackgroundParent(const ClonedMessageData& aClonedData)
{
MOZ_ASSERT(IsOnBackgroundThread());
UnpackClonedMessageData<CopyMemory, Parent>(aClonedData, *this);
}
void
StructuredCloneData::CopyFromClonedMessageDataForBackgroundChild(const ClonedMessageData& aClonedData)
{
UnpackClonedMessageData<CopyMemory, Child>(aClonedData, *this);
}
void
StructuredCloneData::StealFromClonedMessageDataForParent(ClonedMessageData& aClonedData)
{
MOZ_ASSERT(NS_IsMainThread());
UnpackClonedMessageData<StealMemory, Parent>(aClonedData, *this);
}
void
StructuredCloneData::StealFromClonedMessageDataForChild(ClonedMessageData& aClonedData)
{
MOZ_ASSERT(NS_IsMainThread());
UnpackClonedMessageData<StealMemory, Child>(aClonedData, *this);
}
void
StructuredCloneData::StealFromClonedMessageDataForBackgroundParent(ClonedMessageData& aClonedData)
{
MOZ_ASSERT(IsOnBackgroundThread());
UnpackClonedMessageData<StealMemory, Parent>(aClonedData, *this);
}
void
StructuredCloneData::StealFromClonedMessageDataForBackgroundChild(ClonedMessageData& aClonedData)
{
UnpackClonedMessageData<StealMemory, Child>(aClonedData, *this);
}
void
StructuredCloneData::WriteIPCParams(IPC::Message* aMsg) const
{
WriteParam(aMsg, Data());
}
bool
StructuredCloneData::ReadIPCParams(const IPC::Message* aMsg,
PickleIterator* aIter)
{
MOZ_ASSERT(!mInitialized);
JSStructuredCloneData data(JS::StructuredCloneScope::DifferentProcess);
if (!ReadParam(aMsg, aIter, &data)) {
return false;
}
mSharedData = new SharedJSAllocatedData(std::move(data));
mInitialized = true;
return true;
}
bool
StructuredCloneData::CopyExternalData(const char* aData,
size_t aDataLength)
{
MOZ_ASSERT(!mInitialized);
mSharedData = SharedJSAllocatedData::CreateFromExternalData(aData,
aDataLength);
NS_ENSURE_TRUE(mSharedData, false);
mInitialized = true;
return true;
}
bool
StructuredCloneData::CopyExternalData(const JSStructuredCloneData& aData)
{
MOZ_ASSERT(!mInitialized);
mSharedData = SharedJSAllocatedData::CreateFromExternalData(aData);
NS_ENSURE_TRUE(mSharedData, false);
mInitialized = true;
return true;
}
bool
StructuredCloneData::StealExternalData(JSStructuredCloneData& aData)
{
MOZ_ASSERT(!mInitialized);
mSharedData = new SharedJSAllocatedData(std::move(aData));
mInitialized = true;
return true;
}
already_AddRefed<SharedJSAllocatedData>
StructuredCloneData::TakeSharedData()
{
return mSharedData.forget();
}
} // namespace ipc
} // namespace dom
} // namespace mozilla