forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			466 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			466 lines
		
	
	
	
		
			14 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 "SharedMap.h"
 | |
| #include "SharedMapChangeEvent.h"
 | |
| 
 | |
| #include "MemMapSnapshot.h"
 | |
| #include "ScriptPreloader-inl.h"
 | |
| 
 | |
| #include "mozilla/dom/AutoEntryScript.h"
 | |
| #include "mozilla/dom/BlobImpl.h"
 | |
| #include "mozilla/dom/ContentParent.h"
 | |
| #include "mozilla/dom/ContentProcessMessageManager.h"
 | |
| #include "mozilla/dom/IPCBlobUtils.h"
 | |
| #include "mozilla/dom/RootedDictionary.h"
 | |
| #include "mozilla/dom/ScriptSettings.h"
 | |
| #include "mozilla/IOBuffers.h"
 | |
| #include "mozilla/ScriptPreloader.h"
 | |
| 
 | |
| using namespace mozilla::loader;
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| using namespace ipc;
 | |
| 
 | |
| namespace dom::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() : DOMEventTargetHelper() {}
 | |
| 
 | |
| SharedMap::SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor& aMapFile,
 | |
|                      size_t aMapSize, nsTArray<RefPtr<BlobImpl>>&& aBlobs)
 | |
|     : DOMEventTargetHelper(aGlobal), mBlobImpls(std::move(aBlobs)) {
 | |
|   mMapFile.reset(new FileDescriptor(aMapFile));
 | |
|   mMapSize = aMapSize;
 | |
| }
 | |
| 
 | |
| bool SharedMap::Has(const nsACString& aName) {
 | |
|   Unused << MaybeRebuild();
 | |
|   return mEntries.Contains(aName);
 | |
| }
 | |
| 
 | |
| void SharedMap::Get(JSContext* aCx, const nsACString& aName,
 | |
|                     JS::MutableHandle<JS::Value> 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::MutableHandle<JS::Value> 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);
 | |
|     return;
 | |
|   }
 | |
|   if (mBlobCount) {
 | |
|     holder.BlobImpls().AppendElements(Blobs());
 | |
|   }
 | |
|   holder.Read(aCx, aRetVal, aRv);
 | |
| }
 | |
| 
 | |
| FileDescriptor SharedMap::CloneMapFile() const {
 | |
|   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;
 | |
|   }
 | |
|   for (auto& key : aChangedKeys) {
 | |
|     Unused << init.mChangedKeys.AppendElement(NS_ConvertUTF8toUTF16(key),
 | |
|                                               fallible);
 | |
|   }
 | |
| 
 | |
|   RefPtr<SharedMapChangeEvent> event =
 | |
|       SharedMapChangeEvent::Constructor(this, u"change"_ns, init);
 | |
|   event->SetTrusted(true);
 | |
| 
 | |
|   DispatchEvent(*event);
 | |
| }
 | |
| 
 | |
| const nsTArray<SharedMap::Entry*>& SharedMap::EntryArray() const {
 | |
|   if (mEntryArray.isNothing()) {
 | |
|     MaybeRebuild();
 | |
| 
 | |
|     mEntryArray.emplace(mEntries.Count());
 | |
|     auto& array = mEntryArray.ref();
 | |
|     for (auto& entry : mEntries) {
 | |
|       array.AppendElement(entry.GetWeak());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return mEntryArray.ref();
 | |
| }
 | |
| 
 | |
| const nsString SharedMap::GetKeyAtIndex(uint32_t aIndex) const {
 | |
|   return NS_ConvertUTF8toUTF16(EntryArray()[aIndex]->Name());
 | |
| }
 | |
| 
 | |
| bool SharedMap::GetValueAtIndex(JSContext* aCx, uint32_t aIndex,
 | |
|                                 JS::MutableHandle<JS::Value> aResult) const {
 | |
|   ErrorResult rv;
 | |
|   EntryArray()[aIndex]->Read(aCx, aResult, rv);
 | |
|   if (rv.MaybeSetPendingException(aCx)) {
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| 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,
 | |
|                                    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(uint32_t(ptr - aDestPtr) == mSize);
 | |
|   } else {
 | |
|     memcpy(aDestPtr, Data(), mSize);
 | |
|   }
 | |
| 
 | |
|   mData = AsVariant(aNewOffset);
 | |
|   mBlobOffset = aNewBlobOffset;
 | |
| }
 | |
| 
 | |
| 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: While the order of evaluation of the arguments to Put doesn't
 | |
|     // matter for this (the actual move will only happen within Put), to be
 | |
|     // clear about this, we call entry->Name() before calling Put.
 | |
|     const auto& name = entry->Name();
 | |
|     mEntries.InsertOrUpdate(name, std::move(entry));
 | |
|   }
 | |
| 
 | |
|   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) {
 | |
|     nsTArray<RefPtr<BlobImpl>> blobs(mBlobImpls.Clone());
 | |
|     mReadOnly =
 | |
|         new SharedMap(ContentProcessMessageManager::Get()->GetParentObject(),
 | |
|                       CloneMapFile(), MapSize(), std::move(blobs));
 | |
|   }
 | |
|   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);
 | |
|   size_t blobCount = 0;
 | |
| 
 | |
|   for (const auto& entry : mEntries.Values()) {
 | |
|     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 (const auto& entry : mEntries.Values()) {
 | |
|     AlignTo(&offset, kStructuredCloneAlign);
 | |
| 
 | |
|     size_t blobOffset = blobImpls.Length();
 | |
|     if (entry->BlobCount()) {
 | |
|       blobImpls.AppendElements(entry->Blobs());
 | |
|     }
 | |
| 
 | |
|     entry->ExtractData(&ptr[offset], offset, blobOffset);
 | |
|     entry->Code(header);
 | |
| 
 | |
|     offset += entry->Size();
 | |
|   }
 | |
| 
 | |
|   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();
 | |
| }
 | |
| 
 | |
| void WritableSharedMap::SendTo(ContentParent* aParent) const {
 | |
|   nsTArray<IPCBlob> blobs(mBlobImpls.Length());
 | |
| 
 | |
|   for (auto& blobImpl : mBlobImpls) {
 | |
|     nsresult rv = IPCBlobUtils::Serialize(blobImpl, *blobs.AppendElement());
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       continue;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Unused << aParent->SendUpdateSharedData(CloneMapFile(), mMap.size(), blobs,
 | |
|                                           mChangedKeys);
 | |
| }
 | |
| 
 | |
| void WritableSharedMap::BroadcastChanges() {
 | |
|   if (mChangedKeys.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!Serialize().isOk()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsTArray<ContentParent*> parents;
 | |
|   ContentParent::GetAll(parents);
 | |
|   for (auto& parent : parents) {
 | |
|     SendTo(parent);
 | |
|   }
 | |
| 
 | |
|   if (mReadOnly) {
 | |
|     nsTArray<RefPtr<BlobImpl>> blobImpls(mBlobImpls.Clone());
 | |
|     mReadOnly->Update(CloneMapFile(), mMap.size(), std::move(blobImpls),
 | |
|                       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::Handle<JS::Value> aValue, ErrorResult& aRv) {
 | |
|   StructuredCloneData holder;
 | |
| 
 | |
|   holder.Write(aCx, aValue, aRv);
 | |
|   if (aRv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!holder.InputStreams().IsEmpty()) {
 | |
|     aRv.Throw(NS_ERROR_INVALID_ARG);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Entry* entry = mEntries.GetOrInsertNew(aName, *this, aName);
 | |
|   entry->TakeData(std::move(holder));
 | |
| 
 | |
|   KeyChanged(aName);
 | |
| }
 | |
| 
 | |
| void WritableSharedMap::Flush() { BroadcastChanges(); }
 | |
| 
 | |
| void WritableSharedMap::IdleFlush() {
 | |
|   mPendingFlush = false;
 | |
|   Flush();
 | |
| }
 | |
| 
 | |
| nsresult WritableSharedMap::KeyChanged(const nsACString& aName) {
 | |
|   if (!mChangedKeys.ContainsSorted(aName)) {
 | |
|     mChangedKeys.InsertElementSorted(aName);
 | |
|   }
 | |
|   mEntryArray.reset();
 | |
| 
 | |
|   if (!mPendingFlush) {
 | |
|     MOZ_TRY(NS_DispatchToCurrentThreadQueue(
 | |
|         NewRunnableMethod("WritableSharedMap::IdleFlush", this,
 | |
|                           &WritableSharedMap::IdleFlush),
 | |
|         EventQueuePriority::Idle));
 | |
|     mPendingFlush = true;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| JSObject* SharedMap::WrapObject(JSContext* aCx,
 | |
|                                 JS::Handle<JSObject*> aGivenProto) {
 | |
|   return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto);
 | |
| }
 | |
| 
 | |
| JSObject* WritableSharedMap::WrapObject(JSContext* aCx,
 | |
|                                         JS::Handle<JSObject*> aGivenProto) {
 | |
|   return MozWritableSharedMap_Binding::Wrap(aCx, this, aGivenProto);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| already_AddRefed<SharedMapChangeEvent> SharedMapChangeEvent::Constructor(
 | |
|     EventTarget* aEventTarget, const nsAString& aType,
 | |
|     const MozSharedMapChangeEventInit& aInit) {
 | |
|   RefPtr<SharedMapChangeEvent> event = new SharedMapChangeEvent(aEventTarget);
 | |
| 
 | |
|   bool trusted = event->Init(aEventTarget);
 | |
|   event->InitEvent(aType, aInit.mBubbles, aInit.mCancelable);
 | |
|   event->SetTrusted(trusted);
 | |
|   event->SetComposed(aInit.mComposed);
 | |
| 
 | |
|   event->mChangedKeys = aInit.mChangedKeys;
 | |
| 
 | |
|   return event.forget();
 | |
| }
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableSharedMap, SharedMap, mReadOnly)
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableSharedMap)
 | |
| NS_INTERFACE_MAP_END_INHERITING(SharedMap)
 | |
| 
 | |
| NS_IMPL_ADDREF_INHERITED(WritableSharedMap, SharedMap)
 | |
| NS_IMPL_RELEASE_INHERITED(WritableSharedMap, SharedMap)
 | |
| 
 | |
| }  // namespace dom::ipc
 | |
| }  // namespace mozilla
 | 
