SharedMap.cpp (13832B)
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 #include "SharedMap.h" 8 9 #include "MemMapSnapshot.h" 10 #include "ScriptPreloader-inl.h" 11 #include "SharedMapChangeEvent.h" 12 #include "mozilla/IOBuffers.h" 13 #include "mozilla/RefPtr.h" 14 #include "mozilla/ScriptPreloader.h" 15 #include "mozilla/Try.h" 16 #include "mozilla/dom/AutoEntryScript.h" 17 #include "mozilla/dom/BlobImpl.h" 18 #include "mozilla/dom/ContentParent.h" 19 #include "mozilla/dom/ContentProcessMessageManager.h" 20 #include "mozilla/dom/IPCBlobUtils.h" 21 #include "mozilla/dom/RootedDictionary.h" 22 #include "mozilla/dom/ScriptSettings.h" 23 24 using namespace mozilla::loader; 25 26 namespace mozilla { 27 28 using namespace ipc; 29 30 namespace dom::ipc { 31 32 // Align to size of uintptr_t here, to be safe. It's probably not strictly 33 // necessary, though. 34 constexpr size_t kStructuredCloneAlign = sizeof(uintptr_t); 35 36 static inline void AlignTo(size_t* aOffset, size_t aAlign) { 37 if (auto mod = *aOffset % aAlign) { 38 *aOffset += aAlign - mod; 39 } 40 } 41 42 SharedMap::SharedMap() = default; 43 44 SharedMap::SharedMap(nsIGlobalObject* aGlobal, SharedMemoryHandle&& aMapHandle, 45 nsTArray<RefPtr<BlobImpl>>&& aBlobs) 46 : DOMEventTargetHelper(aGlobal), 47 mBlobImpls(std::move(aBlobs)), 48 mHandle(std::move(aMapHandle)) {} 49 50 bool SharedMap::Has(const nsACString& aName) { 51 (void)MaybeRebuild(); 52 return mEntries.Contains(aName); 53 } 54 55 void SharedMap::Get(JSContext* aCx, const nsACString& aName, 56 JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) { 57 auto res = MaybeRebuild(); 58 if (res.isErr()) { 59 aRv.Throw(res.unwrapErr()); 60 return; 61 } 62 63 Entry* entry = mEntries.Get(aName); 64 if (!entry) { 65 aRetVal.setNull(); 66 return; 67 } 68 69 entry->Read(aCx, aRetVal, aRv); 70 } 71 72 void SharedMap::Entry::Read(JSContext* aCx, 73 JS::MutableHandle<JS::Value> aRetVal, 74 ErrorResult& aRv) { 75 if (mData.is<UniquePtr<StructuredCloneData>>()) { 76 // We have a temporary buffer for a key that was changed after the last 77 // snapshot. Just decode it directly. 78 auto& holder = mData.as<UniquePtr<StructuredCloneData>>(); 79 holder->Read(aCx, aRetVal, aRv); 80 return; 81 } 82 83 // We have a pointer to a shared memory region containing our structured 84 // clone data. Create a temporary buffer to decode that data, and then 85 // discard it so that we don't keep a separate process-local copy around any 86 // longer than necessary. 87 StructuredCloneData holder; 88 if (!holder.CopyExternalData(Data(), Size())) { 89 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 90 return; 91 } 92 if (mBlobCount) { 93 holder.BlobImpls().AppendElements(Blobs()); 94 } 95 holder.Read(aCx, aRetVal, aRv); 96 } 97 98 void SharedMap::Update(SharedMemoryHandle&& aMapHandle, 99 nsTArray<RefPtr<BlobImpl>>&& aBlobs, 100 nsTArray<nsCString>&& aChangedKeys) { 101 MOZ_DIAGNOSTIC_ASSERT(!mWritable); 102 103 mMapping = nullptr; 104 mHandle = std::move(aMapHandle); 105 mEntries.Clear(); 106 mEntryArray.reset(); 107 108 mBlobImpls = std::move(aBlobs); 109 110 AutoEntryScript aes(GetParentObject(), "SharedMap change event"); 111 JSContext* cx = aes.cx(); 112 113 RootedDictionary<MozSharedMapChangeEventInit> init(cx); 114 if (!init.mChangedKeys.SetCapacity(aChangedKeys.Length(), fallible)) { 115 NS_WARNING("Failed to dispatch SharedMap change event"); 116 return; 117 } 118 for (auto& key : aChangedKeys) { 119 (void)init.mChangedKeys.AppendElement(NS_ConvertUTF8toUTF16(key), fallible); 120 } 121 122 RefPtr<SharedMapChangeEvent> event = 123 SharedMapChangeEvent::Constructor(this, u"change"_ns, init); 124 event->SetTrusted(true); 125 126 DispatchEvent(*event); 127 } 128 129 const nsTArray<SharedMap::Entry*>& SharedMap::EntryArray() const { 130 if (mEntryArray.isNothing()) { 131 MaybeRebuild(); 132 133 mEntryArray.emplace(mEntries.Count()); 134 auto& array = mEntryArray.ref(); 135 for (auto& entry : mEntries) { 136 array.AppendElement(entry.GetWeak()); 137 } 138 } 139 140 return mEntryArray.ref(); 141 } 142 143 const nsString SharedMap::GetKeyAtIndex(uint32_t aIndex) const { 144 return NS_ConvertUTF8toUTF16(EntryArray()[aIndex]->Name()); 145 } 146 147 bool SharedMap::GetValueAtIndex(JSContext* aCx, uint32_t aIndex, 148 JS::MutableHandle<JS::Value> aResult) const { 149 ErrorResult rv; 150 EntryArray()[aIndex]->Read(aCx, aResult, rv); 151 if (rv.MaybeSetPendingException(aCx)) { 152 return false; 153 } 154 return true; 155 } 156 157 void SharedMap::Entry::TakeData(UniquePtr<StructuredCloneData> aHolder) { 158 mData = AsVariant(std::move(aHolder)); 159 160 mSize = Holder().Data().Size(); 161 mBlobCount = Holder().BlobImpls().Length(); 162 } 163 164 void SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset, 165 uint16_t aNewBlobOffset) { 166 if (mData.is<UniquePtr<StructuredCloneData>>()) { 167 char* ptr = aDestPtr; 168 Holder().Data().ForEachDataChunk([&](const char* aData, size_t aSize) { 169 memcpy(ptr, aData, aSize); 170 ptr += aSize; 171 return true; 172 }); 173 MOZ_ASSERT(uint32_t(ptr - aDestPtr) == mSize); 174 } else { 175 memcpy(aDestPtr, Data(), mSize); 176 } 177 178 mData = AsVariant(aNewOffset); 179 mBlobOffset = aNewBlobOffset; 180 } 181 182 Result<Ok, nsresult> SharedMap::MaybeRebuild() { 183 if (mMapping || !mHandle) { 184 return Ok(); 185 } 186 187 MOZ_DIAGNOSTIC_ASSERT(!mWritable); 188 189 // This function maps a shared memory region created by Serialize() and reads 190 // its header block to build a new mEntries hashtable of its contents. 191 // 192 // The entries created by this function contain a pointer to this SharedMap 193 // instance, and the offsets and sizes of their structured clone data within 194 // its shared memory region. When needed, that structured clone data is 195 // retrieved directly as indexes into the SharedMap's shared memory region. 196 197 mMapping = mHandle.Map(); 198 if (!mMapping) { 199 return Err(NS_ERROR_FAILURE); 200 } 201 mHandle = nullptr; 202 203 Range<const uint8_t> inputRange(mMapping.DataAsSpan<uint8_t>()); 204 InputBuffer buffer(inputRange); 205 206 uint32_t count; 207 buffer.codeUint32(count); 208 209 MOZ_ASSERT(mEntries.IsEmpty()); 210 MOZ_ASSERT(mEntryArray.isNothing()); 211 for (uint32_t i = 0; i < count; i++) { 212 auto entry = MakeUnique<Entry>(*this); 213 entry->Code(buffer); 214 215 // This buffer was created at runtime, during this session, so any errors 216 // indicate memory corruption, and are fatal. 217 MOZ_RELEASE_ASSERT(!buffer.error()); 218 219 // Note: While the order of evaluation of the arguments to Put doesn't 220 // matter for this (the actual move will only happen within Put), to be 221 // clear about this, we call entry->Name() before calling Put. 222 const auto& name = entry->Name(); 223 mEntries.InsertOrUpdate(name, std::move(entry)); 224 } 225 226 return Ok(); 227 } 228 229 void SharedMap::MaybeRebuild() const { 230 (void)const_cast<SharedMap*>(this)->MaybeRebuild(); 231 } 232 233 WritableSharedMap::WritableSharedMap() { 234 mWritable = true; 235 // Serialize the initial empty contents of the map immediately so that we 236 // always have a file descriptor to send. 237 (void)Serialize(); 238 MOZ_RELEASE_ASSERT(mHandle.IsValid() && mMapping.IsValid()); 239 } 240 241 SharedMap* WritableSharedMap::GetReadOnly() { 242 if (!mReadOnly) { 243 nsTArray<RefPtr<BlobImpl>> blobs(mBlobImpls.Clone()); 244 mReadOnly = 245 new SharedMap(ContentProcessMessageManager::Get()->GetParentObject(), 246 mHandle.Clone(), std::move(blobs)); 247 } 248 return mReadOnly; 249 } 250 251 Result<Ok, nsresult> WritableSharedMap::Serialize() { 252 // Serializes a new snapshot of the map, initializes a new read-only shared 253 // memory region with its contents, and updates all entries to point to that 254 // new snapshot. 255 // 256 // The layout of the snapshot is as follows: 257 // 258 // - A header containing a uint32 count field containing the number of 259 // entries in the map, followed by that number of serialized entry headers, 260 // as produced by Entry::Code. 261 // 262 // - A data block containing structured clone data for each of the entries' 263 // values. This data is referenced by absolute byte offsets from the start 264 // of the shared memory region, encoded in each of the entry header values. 265 // Each entry's data is aligned to kStructuredCloneAlign, and therefore may 266 // have alignment padding before it. 267 // 268 // This serialization format is decoded by the MaybeRebuild() method of 269 // read-only SharedMap() instances, and used to populate their mEntries 270 // hashtables. 271 // 272 // Writable instances never read the header blocks, but instead directly 273 // update their Entry instances to point to the appropriate offsets in the 274 // shared memory region created by this function. 275 276 uint32_t count = mEntries.Count(); 277 278 size_t dataSize = 0; 279 size_t headerSize = sizeof(count); 280 size_t blobCount = 0; 281 282 for (const auto& entry : mEntries.Values()) { 283 headerSize += entry->HeaderSize(); 284 blobCount += entry->BlobCount(); 285 286 dataSize += entry->Size(); 287 AlignTo(&dataSize, kStructuredCloneAlign); 288 } 289 290 size_t offset = headerSize; 291 AlignTo(&offset, kStructuredCloneAlign); 292 293 OutputBuffer header; 294 header.codeUint32(count); 295 296 MemMapSnapshot mem; 297 MOZ_TRY(mem.Init(offset + dataSize)); 298 299 auto ptr = mem.Get<char>(); 300 301 // We need to build the new array of blobs before we overwrite the existing 302 // one, since previously-serialized entries will store their blob references 303 // as indexes into our blobs array. 304 nsTArray<RefPtr<BlobImpl>> blobImpls(blobCount); 305 306 for (const auto& entry : mEntries.Values()) { 307 AlignTo(&offset, kStructuredCloneAlign); 308 309 size_t blobOffset = blobImpls.Length(); 310 if (entry->BlobCount()) { 311 blobImpls.AppendElements(entry->Blobs()); 312 } 313 314 entry->ExtractData(&ptr[offset], offset, blobOffset); 315 entry->Code(header); 316 317 offset += entry->Size(); 318 } 319 320 mBlobImpls = std::move(blobImpls); 321 322 // FIXME: We should create a separate OutputBuffer class which can encode to 323 // a static memory region rather than dynamically allocating and then 324 // copying. 325 MOZ_ASSERT(header.cursor() == headerSize); 326 memcpy(ptr.get(), header.Get(), header.cursor()); 327 328 // We've already updated offsets at this point. We need this to succeed. 329 auto result = mem.Finalize(); 330 MOZ_RELEASE_ASSERT(result.isOk()); 331 mHandle = result.unwrap(); 332 mMapping = mHandle.Map(); 333 MOZ_RELEASE_ASSERT(mMapping.IsValid()); 334 335 return Ok(); 336 } 337 338 void WritableSharedMap::SendTo(ContentParent* aParent) const { 339 nsTArray<IPCBlob> blobs(mBlobImpls.Length()); 340 341 for (auto& blobImpl : mBlobImpls) { 342 nsresult rv = IPCBlobUtils::Serialize(blobImpl, *blobs.AppendElement()); 343 if (NS_WARN_IF(NS_FAILED(rv))) { 344 continue; 345 } 346 } 347 348 (void)aParent->SendUpdateSharedData(mHandle.Clone(), blobs, mChangedKeys); 349 } 350 351 void WritableSharedMap::BroadcastChanges() { 352 if (mChangedKeys.IsEmpty()) { 353 return; 354 } 355 356 if (!Serialize().isOk()) { 357 return; 358 } 359 360 nsTArray<ContentParent*> parents; 361 ContentParent::GetAll(parents); 362 for (auto& parent : parents) { 363 SendTo(parent); 364 } 365 366 if (mReadOnly) { 367 nsTArray<RefPtr<BlobImpl>> blobImpls(mBlobImpls.Clone()); 368 mReadOnly->Update(mHandle.Clone(), std::move(blobImpls), 369 std::move(mChangedKeys)); 370 } 371 372 mChangedKeys.Clear(); 373 } 374 375 void WritableSharedMap::Delete(const nsACString& aName) { 376 if (mEntries.Remove(aName)) { 377 KeyChanged(aName); 378 } 379 } 380 381 void WritableSharedMap::Set(JSContext* aCx, const nsACString& aName, 382 JS::Handle<JS::Value> aValue, ErrorResult& aRv) { 383 auto holder = MakeUnique<StructuredCloneData>(); 384 385 holder->Write(aCx, aValue, aRv); 386 if (aRv.Failed()) { 387 return; 388 } 389 390 if (!holder->InputStreams().IsEmpty()) { 391 aRv.Throw(NS_ERROR_INVALID_ARG); 392 return; 393 } 394 395 Entry* entry = mEntries.GetOrInsertNew(aName, *this, aName); 396 entry->TakeData(std::move(holder)); 397 398 KeyChanged(aName); 399 } 400 401 void WritableSharedMap::Flush() { BroadcastChanges(); } 402 403 void WritableSharedMap::IdleFlush() { 404 mPendingFlush = false; 405 Flush(); 406 } 407 408 nsresult WritableSharedMap::KeyChanged(const nsACString& aName) { 409 if (!mChangedKeys.ContainsSorted(aName)) { 410 mChangedKeys.InsertElementSorted(aName); 411 } 412 mEntryArray.reset(); 413 414 if (!mPendingFlush) { 415 MOZ_TRY(NS_DispatchToCurrentThreadQueue( 416 NewRunnableMethod("WritableSharedMap::IdleFlush", this, 417 &WritableSharedMap::IdleFlush), 418 EventQueuePriority::Idle)); 419 mPendingFlush = true; 420 } 421 return NS_OK; 422 } 423 424 JSObject* SharedMap::WrapObject(JSContext* aCx, 425 JS::Handle<JSObject*> aGivenProto) { 426 return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto); 427 } 428 429 JSObject* WritableSharedMap::WrapObject(JSContext* aCx, 430 JS::Handle<JSObject*> aGivenProto) { 431 return MozWritableSharedMap_Binding::Wrap(aCx, this, aGivenProto); 432 } 433 434 /* static */ 435 already_AddRefed<SharedMapChangeEvent> SharedMapChangeEvent::Constructor( 436 EventTarget* aEventTarget, const nsAString& aType, 437 const MozSharedMapChangeEventInit& aInit) { 438 RefPtr<SharedMapChangeEvent> event = new SharedMapChangeEvent(aEventTarget); 439 440 bool trusted = event->Init(aEventTarget); 441 event->InitEvent(aType, aInit.mBubbles, aInit.mCancelable); 442 event->SetTrusted(trusted); 443 event->SetComposed(aInit.mComposed); 444 445 event->mChangedKeys = aInit.mChangedKeys; 446 447 return event.forget(); 448 } 449 450 NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableSharedMap, SharedMap, mReadOnly) 451 452 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableSharedMap) 453 NS_INTERFACE_MAP_END_INHERITING(SharedMap) 454 455 NS_IMPL_ADDREF_INHERITED(WritableSharedMap, SharedMap) 456 NS_IMPL_RELEASE_INHERITED(WritableSharedMap, SharedMap) 457 458 } // namespace dom::ipc 459 } // namespace mozilla