1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
|
/* -*- 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/. */
#ifndef dom_ipc_SharedMap_h
#define dom_ipc_SharedMap_h
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Variant.h"
#include "mozilla/dom/MozSharedMapBinding.h"
#include "mozilla/dom/ipc/StructuredCloneData.h"
#include "mozilla/ipc/SharedMemoryHandle.h"
#include "mozilla/ipc/SharedMemoryMapping.h"
#include "nsClassHashtable.h"
#include "nsTArray.h"
class nsIGlobalObject;
namespace mozilla::dom {
class ContentParent;
namespace ipc {
/**
* Together, the SharedMap and WritableSharedMap classes allow sharing a
* dynamically-updated, shared-memory key-value store across processes.
*
* The maps may only ever be updated in the parent process, via
* WritableSharedMap instances. When that map changes, its entire contents are
* serialized into a contiguous shared memory buffer, and broadcast to all child
* processes, which in turn update their entire map contents wholesale.
*
* Keys are arbitrary UTF-8 strings (currently exposed to JavaScript as UTF-16),
* and values are structured clone buffers. Values are eagerly encoded whenever
* they are updated, and lazily decoded each time they're read.
*
* Updates are batched. Rather than each key change triggering an immediate
* update, combined updates are broadcast after a delay. Changes are flushed
* immediately any time a new process is created. Additionally, any time a key
* is changed, a flush task is scheduled for the next time the event loop
* becomes idle. Changes can be flushed immediately by calling the flush()
* method.
*
*
* Whenever a read-only SharedMap is updated, it dispatches a "change" event.
* The event contains a "changedKeys" property with a list of all keys which
* were changed in the last update batch. Change events are never dispatched to
* WritableSharedMap instances.
*/
class SharedMap : public DOMEventTargetHelper {
protected:
using SharedMemoryMapping = mozilla::ipc::ReadOnlySharedMemoryMapping;
using SharedMemoryHandle = mozilla::ipc::ReadOnlySharedMemoryHandle;
public:
SharedMap();
SharedMap(nsIGlobalObject* aGlobal, SharedMemoryHandle&&,
nsTArray<RefPtr<BlobImpl>>&& aBlobs);
// Returns true if the map contains the given (UTF-8) key.
bool Has(const nsACString& name);
// If the map contains the given (UTF-8) key, decodes and returns a new copy
// of its value. Otherwise returns null.
void Get(JSContext* cx, const nsACString& name,
JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv);
// Conversion helpers for WebIDL callers
bool Has(const nsAString& aName) { return Has(NS_ConvertUTF16toUTF8(aName)); }
void Get(JSContext* aCx, const nsAString& aName,
JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) {
return Get(aCx, NS_ConvertUTF16toUTF8(aName), aRetVal, aRv);
}
/**
* WebIDL iterator glue.
*/
uint32_t GetIterableLength() const { return EntryArray().Length(); }
/**
* These functions return the key or value, respectively, at the given index.
* The index *must* be less than the value returned by GetIterableLength(), or
* the program will crash.
*/
const nsString GetKeyAtIndex(uint32_t aIndex) const;
bool GetValueAtIndex(JSContext* aCx, uint32_t aIndex,
JS::MutableHandle<JS::Value> aResult) const;
/**
* Returns the size of the memory mapped region that backs this map.
*/
size_t MapSize() const { return mMapping.Size(); }
/**
* Updates this instance to reflect the contents of the shared memory region
* in the given map handle, and broadcasts a change event for the given set of
* changed (UTF-8-encoded) keys.
*/
void Update(SharedMemoryHandle&& aMapHandle,
nsTArray<RefPtr<BlobImpl>>&& aBlobs,
nsTArray<nsCString>&& aChangedKeys);
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
protected:
~SharedMap() override = default;
class Entry {
public:
Entry(Entry&&) = delete;
explicit Entry(SharedMap& aMap, const nsACString& aName = ""_ns)
: mMap(aMap), mName(aName), mData(AsVariant(uint32_t(0))) {}
~Entry() = default;
/**
* Encodes or decodes this entry into or from the given OutputBuffer or
* InputBuffer.
*/
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(mBlobOffset) + sizeof(mBlobCount));
}
/**
* Updates the value of this entry to the given structured clone data, of
* which it takes ownership. The passed StructuredCloneData object must not
* be used after this call.
*/
void TakeData(StructuredCloneData&&);
/**
* This is called while building a new snapshot of the SharedMap. aDestPtr
* must point to a buffer within the new snapshot with Size() bytes reserved
* for it, and `aNewOffset` must be the offset of that buffer from the start
* of the snapshot's memory region.
*
* This function copies the raw structured clone data for the entry's value
* 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 must not be accessed again until the SharedMap mMap has
* been updated to point to it.
*/
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::MutableHandle<JS::Value> aRetVal,
ErrorResult& aRv);
// Returns the byte size of the entry's raw structured clone data.
uint32_t Size() const { return mSize; }
private:
// Returns a pointer to the entry value's structured clone data within the
// SharedMap's mapped memory region. This is *only* valid shen mData
// contains a uint32_t.
const char* Data() const { return mMap.Data() + DataOffset(); }
// Returns the offset of the entry value's structured clone data within the
// SharedMap's mapped memory region. This is *only* valid shen mData
// contains a uint32_t.
uint32_t& DataOffset() { 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>();
}
SharedMap& mMap;
// The entry's (UTF-8 encoded) name, which serves as its key in the map.
nsCString mName;
/**
* This member provides a reference to the entry's structured clone data.
* Its type varies depending on the state of the entry:
*
* - For entries which have been snapshotted into a shared memory region,
* this is a uint32_t offset into the parent SharedMap's Data() buffer.
*
* - For entries which have been changed in a WritableSharedMap instance,
* but not serialized to a shared memory snapshot yet, this is a
* 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;
mutable nsClassHashtable<nsCStringHashKey, Entry> mEntries;
mutable Maybe<nsTArray<Entry*>> mEntryArray;
// Manages the memory mapping of the current snapshot. This is initialized
// lazily after each SharedMap construction or update.
SharedMemoryHandle mHandle;
SharedMemoryMapping mMapping;
bool mWritable = false;
// Returns a pointer to the beginning of the memory mapped snapshot. Entry
// offsets are relative to this pointer, and Entry objects access their
// structured clone data by indexing this pointer.
const char* Data() { return mMapping.DataAs<char>(); }
};
class WritableSharedMap final : public SharedMap {
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WritableSharedMap, SharedMap)
WritableSharedMap();
// Sets the value of the given (UTF-8 encoded) key to a structured clone
// snapshot of the given value.
void Set(JSContext* cx, const nsACString& name, JS::Handle<JS::Value> value,
ErrorResult& aRv);
// Deletes the given (UTF-8 encoded) key from the map.
void Delete(const nsACString& name);
// Conversion helpers for WebIDL callers
void Set(JSContext* aCx, const nsAString& aName, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
return Set(aCx, NS_ConvertUTF16toUTF8(aName), aValue, aRv);
}
void Delete(const nsAString& aName) {
return Delete(NS_ConvertUTF16toUTF8(aName));
}
// Flushes any queued changes to a new snapshot, and broadcasts it to all
// child SharedMap instances.
void Flush();
// Sends the current set of shared map data to the given content process.
void SendTo(ContentParent* aContentParent) const;
/**
* Returns the read-only SharedMap instance corresponding to this
* WritableSharedMap for use in the parent process.
*/
SharedMap* GetReadOnly();
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
protected:
~WritableSharedMap() override = default;
private:
// The set of (UTF-8 encoded) keys which have changed, or been deleted, since
// the last snapshot.
nsTArray<nsCString> mChangedKeys;
RefPtr<SharedMap> mReadOnly;
bool mPendingFlush = false;
// Creates a new snapshot of the map, and updates all Entry instance to
// reference its data.
Result<Ok, nsresult> Serialize();
void IdleFlush();
// If there have been any changes since the last snapshot, creates a new
// serialization and broadcasts it to all child SharedMap instances.
void BroadcastChanges();
// Marks the given (UTF-8 encoded) key as having changed. This adds it to
// mChangedKeys, if not already present, and schedules a flush for the next
// time the event loop is idle.
nsresult KeyChanged(const nsACString& aName);
};
} // namespace ipc
} // namespace mozilla::dom
#endif // dom_ipc_SharedMap_h
|