Files
tubestation/dom/ipc/SharedMap.cpp
Sandor Molnar abe7f841ca Backed out 4 changesets (bug 1454816) for causing build bustages. CLOSED TREE
Backed out changeset 3e8d2c47138c (bug 1454816)
Backed out changeset 80ff20241831 (bug 1454816)
Backed out changeset 28c2d6d2a683 (bug 1454816)
Backed out changeset 236943ab4142 (bug 1454816)
2024-10-21 20:15:13 +03:00

468 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