mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
b8156bbc41
Differential Revision: https://phabricator.services.mozilla.com/D156348
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"
|
|
|
|
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
|