Bug 1463587: Part 4 - Add blob support to SharedMap. r=erahm,baku draft
authorKris Maglione <maglione.k@gmail.com>
Wed, 27 Jun 2018 16:35:53 -0700
changeset 816388 0a2f2757853e5b557f7435ff394dbbd9b0c19e30
parent 816387 468a65f46075e9d23333427ec643e6058f42ce7d
child 816389 9688e45dbecc09414e10db048b8e4b89ed456897
push id115826
push usermaglione.k@gmail.com
push dateWed, 11 Jul 2018 05:19:48 +0000
reviewerserahm, baku
bugs1463587
milestone63.0a1
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
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/PContent.ipdl
dom/ipc/SharedMap.cpp
dom/ipc/SharedMap.h
--- 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