mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	More on: https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-equals-default.html Command: L=$(hg export -r tip|grep +++|cut -d/ -f2-) ./mach static-analysis check --checks="-*, modernize-use-equals-default" --fix $L Differential Revision: https://phabricator.services.mozilla.com/D191016
		
			
				
	
	
		
			467 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			467 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"
 | 
						|
#include "mozilla/Try.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() = default;
 | 
						|
 | 
						|
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() {
 | 
						|
  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
 |