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