Bug 1463587: Part 4 - Add blob support to SharedMap. r=erahm,baku
I was hoping to avoid supporting blobs here, but some parts of the
WebExtensions framework rely on being able to store Blobs in
initialProcessData, and can't be migrated without adding blob support.
This patch adds an ordered array of BlobImpls for all extant keys, clones them
to all child processes when updating the serialized maps, and initializes
StructuredCloneData instances with indexes into the combined array.
MozReview-Commit-ID: IdSv5FHbPbE
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2546,20 +2546,28 @@ ContentChild::RecvAsyncMessage(const nsS
IgnoreErrors());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentChild::RecvUpdateSharedData(const FileDescriptor& aMapFile,
const uint32_t& aMapSize,
+ nsTArray<IPCBlob>&& aBlobs,
nsTArray<nsCString>&& aChangedKeys)
{
if (mSharedData) {
- mSharedData->Update(aMapFile, aMapSize, std::move(aChangedKeys));
+ nsTArray<RefPtr<BlobImpl>> blobImpls(aBlobs.Length());
+ for (auto& ipcBlob : aBlobs) {
+ blobImpls.AppendElement(IPCBlobUtils::Deserialize(ipcBlob));
+ }
+
+ mSharedData->Update(aMapFile, aMapSize,
+ std::move(blobImpls),
+ std::move(aChangedKeys));
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentChild::RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition)
{
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -399,16 +399,17 @@ public:
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<IPCBlob>&& aBlobs,
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;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -454,16 +454,17 @@ child:
async UpdateAppLocales(nsCString[] appLocales);
async UpdateRequestedLocales(nsCString[] requestedLocales);
async ClearSiteDataReloadNeeded(nsString origin);
async UpdateSharedData(FileDescriptor mapFile, uint32_t aSize,
+ IPCBlob[] blobs,
nsCString[] changedKeys);
// nsIPermissionManager messages
async AddPermission(Permission permission);
async FlushMemory(nsString reason);
async GarbageCollect();
--- a/dom/ipc/SharedMap.cpp
+++ b/dom/ipc/SharedMap.cpp
@@ -6,16 +6,17 @@
#include "SharedMap.h"
#include "SharedMapChangeEvent.h"
#include "MemMapSnapshot.h"
#include "ScriptPreloader-inl.h"
#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/ProcessGlobal.h"
#include "mozilla/dom/ScriptSettings.h"
using namespace mozilla::loader;
namespace mozilla {
using namespace ipc;
@@ -92,46 +93,52 @@ SharedMap::Entry::Read(JSContext* aCx,
// 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);
+ return;
}
-
+ if (mBlobCount) {
+ holder.BlobImpls().AppendElements(Blobs());
+ }
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<RefPtr<BlobImpl>>&& aBlobs,
nsTArray<nsCString>&& aChangedKeys)
{
MOZ_DIAGNOSTIC_ASSERT(!mWritable);
mMap.reset();
if (mMapFile) {
*mMapFile = aMapFile;
} else {
mMapFile.reset(new FileDescriptor(aMapFile));
}
mMapSize = aMapSize;
mEntries.Clear();
mEntryArray.reset();
+ mBlobImpls = std::move(aBlobs);
+
AutoEntryScript aes(GetParentObject(), "SharedMap change event");
JSContext* cx = aes.cx();
RootedDictionary<MozSharedMapChangeEventInit> init(cx);
if (!init.mChangedKeys.SetCapacity(aChangedKeys.Length(), fallible)) {
NS_WARNING("Failed to dispatch SharedMap change event");
return;
@@ -193,34 +200,36 @@ SharedMap::GetValueAtIndex(uint32_t aInd
}
void
SharedMap::Entry::TakeData(StructuredCloneData&& aHolder)
{
mData = AsVariant(std::move(aHolder));
mSize = Holder().Data().Size();
+ mBlobCount = Holder().BlobImpls().Length();
}
void
-SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset)
+SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset, uint16_t aNewBlobOffset)
{
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);
+ mBlobOffset = aNewBlobOffset;
}
Result<Ok, nsresult>
SharedMap::MaybeRebuild()
{
if (!mMapFile) {
return Ok();
}
@@ -312,47 +321,61 @@ WritableSharedMap::Serialize()
// 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);
+ size_t blobCount = 0;
for (auto& entry : IterHash(mEntries)) {
headerSize += entry->HeaderSize();
+ blobCount += entry->BlobCount();
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>();
+ // We need to build the new array of blobs before we overwrite the existing
+ // one, since previously-serialized entries will store their blob references
+ // as indexes into our blobs array.
+ nsTArray<RefPtr<BlobImpl>> blobImpls(blobCount);
+
for (auto& entry : IterHash(mEntries)) {
AlignTo(&offset, kStructuredCloneAlign);
- entry->ExtractData(&ptr[offset], offset);
+ entry->ExtractData(&ptr[offset], offset, blobImpls.Length());
entry->Code(header);
offset += entry->Size();
+
+ if (entry->BlobCount()) {
+ mBlobImpls.AppendElements(entry->Blobs());
+ }
}
+ mBlobImpls = std::move(blobImpls);
+
// FIXME: We should create a separate OutputBuffer class which can encode to
// a static memory region rather than dynamically allocating and then
// copying.
+ MOZ_ASSERT(header.cursor() == headerSize);
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();
}
@@ -366,22 +389,34 @@ WritableSharedMap::BroadcastChanges()
if (!Serialize().isOk()) {
return;
}
nsTArray<ContentParent*> parents;
ContentParent::GetAll(parents);
for (auto& parent : parents) {
+ nsTArray<IPCBlob> blobs(mBlobImpls.Length());
+
+ for (auto& blobImpl : mBlobImpls) {
+ nsresult rv = IPCBlobUtils::Serialize(blobImpl, parent,
+ *blobs.AppendElement());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ }
+
Unused << parent->SendUpdateSharedData(CloneMapFile(), mMap.size(),
- mChangedKeys);
+ blobs, mChangedKeys);
}
if (mReadOnly) {
+ nsTArray<RefPtr<BlobImpl>> blobImpls(mBlobImpls);
mReadOnly->Update(CloneMapFile(), mMap.size(),
+ std::move(blobImpls),
std::move(mChangedKeys));
}
mChangedKeys.Clear();
}
void
WritableSharedMap::Delete(const nsACString& aName)
@@ -399,18 +434,17 @@ WritableSharedMap::Set(JSContext* aCx,
{
StructuredCloneData holder;
holder.Write(aCx, aValue, aRv);
if (aRv.Failed()) {
return;
}
- if (!holder.BlobImpls().IsEmpty() ||
- !holder.InputStreams().IsEmpty()) {
+ if (!holder.InputStreams().IsEmpty()) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
Entry* entry = mEntries.LookupOrAdd(aName, *this, aName);
entry->TakeData(std::move(holder));
KeyChanged(aName);
--- a/dom/ipc/SharedMap.h
+++ b/dom/ipc/SharedMap.h
@@ -117,16 +117,17 @@ public:
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<RefPtr<BlobImpl>>&& aBlobs,
nsTArray<nsCString>&& aChangedKeys);
JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
protected:
~SharedMap() override = default;
@@ -150,29 +151,33 @@ protected:
template<typename Buffer>
void Code(Buffer& buffer)
{
DebugOnly<size_t> startOffset = buffer.cursor();
buffer.codeString(mName);
buffer.codeUint32(DataOffset());
buffer.codeUint32(mSize);
+ buffer.codeUint16(mBlobOffset);
+ buffer.codeUint16(mBlobCount);
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));
+ sizeof(mSize) +
+ sizeof(mBlobOffset) +
+ sizeof(mBlobCount));
}
/**
* 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&&);
@@ -187,17 +192,17 @@ protected:
* 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);
+ void ExtractData(char* aDestPtr, uint32_t aNewOffset, uint16_t aNewBlobOffset);
// 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,
@@ -222,16 +227,29 @@ protected:
{
return mData.as<uint32_t>();
}
const uint32_t& DataOffset() const
{
return mData.as<uint32_t>();
}
+ public:
+ uint16_t BlobOffset() const { return mBlobOffset; }
+ uint16_t BlobCount() const { return mBlobCount; }
+
+ Span<const RefPtr<BlobImpl>> Blobs()
+ {
+ if (mData.is<StructuredCloneData>()) {
+ return mData.as<StructuredCloneData>().BlobImpls();
+ }
+ return {&mMap.mBlobImpls[mBlobOffset], BlobCount()};
+ }
+
+ private:
// 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>();
}
@@ -252,20 +270,25 @@ protected:
* 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;
+
+ uint16_t mBlobOffset = 0;
+ uint16_t mBlobCount = 0;
};
const nsTArray<Entry*>& EntryArray() const;
+ nsTArray<RefPtr<BlobImpl>> mBlobImpls;
+
// 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