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