Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / ipc / SharedMap.cpp
blobdc6602d3770771d6904816217b8dfa41eeaa1efe
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/. */
7 #include "SharedMap.h"
8 #include "SharedMapChangeEvent.h"
10 #include "MemMapSnapshot.h"
11 #include "ScriptPreloader-inl.h"
13 #include "mozilla/dom/AutoEntryScript.h"
14 #include "mozilla/dom/BlobImpl.h"
15 #include "mozilla/dom/ContentParent.h"
16 #include "mozilla/dom/ContentProcessMessageManager.h"
17 #include "mozilla/dom/IPCBlobUtils.h"
18 #include "mozilla/dom/RootedDictionary.h"
19 #include "mozilla/dom/ScriptSettings.h"
20 #include "mozilla/IOBuffers.h"
21 #include "mozilla/ScriptPreloader.h"
22 #include "mozilla/Try.h"
24 using namespace mozilla::loader;
26 namespace mozilla {
28 using namespace ipc;
30 namespace dom::ipc {
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);
36 static inline void AlignTo(size_t* aOffset, size_t aAlign) {
37 if (auto mod = *aOffset % aAlign) {
38 *aOffset += aAlign - mod;
42 SharedMap::SharedMap() = default;
44 SharedMap::SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor& aMapFile,
45 size_t aMapSize, nsTArray<RefPtr<BlobImpl>>&& aBlobs)
46 : DOMEventTargetHelper(aGlobal), mBlobImpls(std::move(aBlobs)) {
47 mMapFile.reset(new FileDescriptor(aMapFile));
48 mMapSize = aMapSize;
51 bool SharedMap::Has(const nsACString& aName) {
52 Unused << MaybeRebuild();
53 return mEntries.Contains(aName);
56 void SharedMap::Get(JSContext* aCx, const nsACString& aName,
57 JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) {
58 auto res = MaybeRebuild();
59 if (res.isErr()) {
60 aRv.Throw(res.unwrapErr());
61 return;
64 Entry* entry = mEntries.Get(aName);
65 if (!entry) {
66 aRetVal.setNull();
67 return;
70 entry->Read(aCx, aRetVal, aRv);
73 void SharedMap::Entry::Read(JSContext* aCx,
74 JS::MutableHandle<JS::Value> aRetVal,
75 ErrorResult& aRv) {
76 if (mData.is<StructuredCloneData>()) {
77 // We have a temporary buffer for a key that was changed after the last
78 // snapshot. Just decode it directly.
79 auto& holder = mData.as<StructuredCloneData>();
80 holder.Read(aCx, aRetVal, aRv);
81 return;
84 // We have a pointer to a shared memory region containing our structured
85 // clone data. Create a temporary buffer to decode that data, and then
86 // discard it so that we don't keep a separate process-local copy around any
87 // longer than necessary.
88 StructuredCloneData holder;
89 if (!holder.CopyExternalData(Data(), Size())) {
90 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
91 return;
93 if (mBlobCount) {
94 holder.BlobImpls().AppendElements(Blobs());
96 holder.Read(aCx, aRetVal, aRv);
99 FileDescriptor SharedMap::CloneMapFile() const {
100 if (mMap.initialized()) {
101 return mMap.cloneHandle();
103 return *mMapFile;
106 void SharedMap::Update(const FileDescriptor& aMapFile, size_t aMapSize,
107 nsTArray<RefPtr<BlobImpl>>&& aBlobs,
108 nsTArray<nsCString>&& aChangedKeys) {
109 MOZ_DIAGNOSTIC_ASSERT(!mWritable);
111 mMap.reset();
112 if (mMapFile) {
113 *mMapFile = aMapFile;
114 } else {
115 mMapFile.reset(new FileDescriptor(aMapFile));
117 mMapSize = aMapSize;
118 mEntries.Clear();
119 mEntryArray.reset();
121 mBlobImpls = std::move(aBlobs);
123 AutoEntryScript aes(GetParentObject(), "SharedMap change event");
124 JSContext* cx = aes.cx();
126 RootedDictionary<MozSharedMapChangeEventInit> init(cx);
127 if (!init.mChangedKeys.SetCapacity(aChangedKeys.Length(), fallible)) {
128 NS_WARNING("Failed to dispatch SharedMap change event");
129 return;
131 for (auto& key : aChangedKeys) {
132 Unused << init.mChangedKeys.AppendElement(NS_ConvertUTF8toUTF16(key),
133 fallible);
136 RefPtr<SharedMapChangeEvent> event =
137 SharedMapChangeEvent::Constructor(this, u"change"_ns, init);
138 event->SetTrusted(true);
140 DispatchEvent(*event);
143 const nsTArray<SharedMap::Entry*>& SharedMap::EntryArray() const {
144 if (mEntryArray.isNothing()) {
145 MaybeRebuild();
147 mEntryArray.emplace(mEntries.Count());
148 auto& array = mEntryArray.ref();
149 for (auto& entry : mEntries) {
150 array.AppendElement(entry.GetWeak());
154 return mEntryArray.ref();
157 const nsString SharedMap::GetKeyAtIndex(uint32_t aIndex) const {
158 return NS_ConvertUTF8toUTF16(EntryArray()[aIndex]->Name());
161 bool SharedMap::GetValueAtIndex(JSContext* aCx, uint32_t aIndex,
162 JS::MutableHandle<JS::Value> aResult) const {
163 ErrorResult rv;
164 EntryArray()[aIndex]->Read(aCx, aResult, rv);
165 if (rv.MaybeSetPendingException(aCx)) {
166 return false;
168 return true;
171 void SharedMap::Entry::TakeData(StructuredCloneData&& aHolder) {
172 mData = AsVariant(std::move(aHolder));
174 mSize = Holder().Data().Size();
175 mBlobCount = Holder().BlobImpls().Length();
178 void SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset,
179 uint16_t aNewBlobOffset) {
180 if (mData.is<StructuredCloneData>()) {
181 char* ptr = aDestPtr;
182 Holder().Data().ForEachDataChunk([&](const char* aData, size_t aSize) {
183 memcpy(ptr, aData, aSize);
184 ptr += aSize;
185 return true;
187 MOZ_ASSERT(uint32_t(ptr - aDestPtr) == mSize);
188 } else {
189 memcpy(aDestPtr, Data(), mSize);
192 mData = AsVariant(aNewOffset);
193 mBlobOffset = aNewBlobOffset;
196 Result<Ok, nsresult> SharedMap::MaybeRebuild() {
197 if (!mMapFile) {
198 return Ok();
201 // This function maps a shared memory region created by Serialize() and reads
202 // its header block to build a new mEntries hashtable of its contents.
204 // The entries created by this function contain a pointer to this SharedMap
205 // instance, and the offsets and sizes of their structured clone data within
206 // its shared memory region. When needed, that structured clone data is
207 // retrieved directly as indexes into the SharedMap's shared memory region.
209 MOZ_TRY(mMap.initWithHandle(*mMapFile, mMapSize));
210 mMapFile.reset();
212 // We should be able to pass this range as an initializer list or an immediate
213 // param, but gcc currently chokes on that if optimization is enabled, and
214 // initializes everything to 0.
215 Range<uint8_t> range(&mMap.get<uint8_t>()[0], mMap.size());
216 InputBuffer buffer(range);
218 uint32_t count;
219 buffer.codeUint32(count);
221 for (uint32_t i = 0; i < count; i++) {
222 auto entry = MakeUnique<Entry>(*this);
223 entry->Code(buffer);
225 // This buffer was created at runtime, during this session, so any errors
226 // indicate memory corruption, and are fatal.
227 MOZ_RELEASE_ASSERT(!buffer.error());
229 // Note: While the order of evaluation of the arguments to Put doesn't
230 // matter for this (the actual move will only happen within Put), to be
231 // clear about this, we call entry->Name() before calling Put.
232 const auto& name = entry->Name();
233 mEntries.InsertOrUpdate(name, std::move(entry));
236 return Ok();
239 void SharedMap::MaybeRebuild() const {
240 Unused << const_cast<SharedMap*>(this)->MaybeRebuild();
243 WritableSharedMap::WritableSharedMap() {
244 mWritable = true;
245 // Serialize the initial empty contents of the map immediately so that we
246 // always have a file descriptor to send to callers of CloneMapFile().
247 Unused << Serialize();
248 MOZ_RELEASE_ASSERT(mMap.initialized());
251 SharedMap* WritableSharedMap::GetReadOnly() {
252 if (!mReadOnly) {
253 nsTArray<RefPtr<BlobImpl>> blobs(mBlobImpls.Clone());
254 mReadOnly =
255 new SharedMap(ContentProcessMessageManager::Get()->GetParentObject(),
256 CloneMapFile(), MapSize(), std::move(blobs));
258 return mReadOnly;
261 Result<Ok, nsresult> WritableSharedMap::Serialize() {
262 // Serializes a new snapshot of the map, initializes a new read-only shared
263 // memory region with its contents, and updates all entries to point to that
264 // new snapshot.
266 // The layout of the snapshot is as follows:
268 // - A header containing a uint32 count field containing the number of
269 // entries in the map, followed by that number of serialized entry headers,
270 // as produced by Entry::Code.
272 // - A data block containing structured clone data for each of the entries'
273 // values. This data is referenced by absolute byte offsets from the start
274 // of the shared memory region, encoded in each of the entry header values.
275 // Each entry's data is aligned to kStructuredCloneAlign, and therefore may
276 // have alignment padding before it.
278 // This serialization format is decoded by the MaybeRebuild() method of
279 // read-only SharedMap() instances, and used to populate their mEntries
280 // hashtables.
282 // Writable instances never read the header blocks, but instead directly
283 // update their Entry instances to point to the appropriate offsets in the
284 // shared memory region created by this function.
286 uint32_t count = mEntries.Count();
288 size_t dataSize = 0;
289 size_t headerSize = sizeof(count);
290 size_t blobCount = 0;
292 for (const auto& entry : mEntries.Values()) {
293 headerSize += entry->HeaderSize();
294 blobCount += entry->BlobCount();
296 dataSize += entry->Size();
297 AlignTo(&dataSize, kStructuredCloneAlign);
300 size_t offset = headerSize;
301 AlignTo(&offset, kStructuredCloneAlign);
303 OutputBuffer header;
304 header.codeUint32(count);
306 MemMapSnapshot mem;
307 MOZ_TRY(mem.Init(offset + dataSize));
309 auto ptr = mem.Get<char>();
311 // We need to build the new array of blobs before we overwrite the existing
312 // one, since previously-serialized entries will store their blob references
313 // as indexes into our blobs array.
314 nsTArray<RefPtr<BlobImpl>> blobImpls(blobCount);
316 for (const auto& entry : mEntries.Values()) {
317 AlignTo(&offset, kStructuredCloneAlign);
319 size_t blobOffset = blobImpls.Length();
320 if (entry->BlobCount()) {
321 blobImpls.AppendElements(entry->Blobs());
324 entry->ExtractData(&ptr[offset], offset, blobOffset);
325 entry->Code(header);
327 offset += entry->Size();
330 mBlobImpls = std::move(blobImpls);
332 // FIXME: We should create a separate OutputBuffer class which can encode to
333 // a static memory region rather than dynamically allocating and then
334 // copying.
335 MOZ_ASSERT(header.cursor() == headerSize);
336 memcpy(ptr.get(), header.Get(), header.cursor());
338 // We've already updated offsets at this point. We need this to succeed.
339 mMap.reset();
340 MOZ_RELEASE_ASSERT(mem.Finalize(mMap).isOk());
342 return Ok();
345 void WritableSharedMap::SendTo(ContentParent* aParent) const {
346 nsTArray<IPCBlob> blobs(mBlobImpls.Length());
348 for (auto& blobImpl : mBlobImpls) {
349 nsresult rv = IPCBlobUtils::Serialize(blobImpl, *blobs.AppendElement());
350 if (NS_WARN_IF(NS_FAILED(rv))) {
351 continue;
355 Unused << aParent->SendUpdateSharedData(CloneMapFile(), mMap.size(), blobs,
356 mChangedKeys);
359 void WritableSharedMap::BroadcastChanges() {
360 if (mChangedKeys.IsEmpty()) {
361 return;
364 if (!Serialize().isOk()) {
365 return;
368 nsTArray<ContentParent*> parents;
369 ContentParent::GetAll(parents);
370 for (auto& parent : parents) {
371 SendTo(parent);
374 if (mReadOnly) {
375 nsTArray<RefPtr<BlobImpl>> blobImpls(mBlobImpls.Clone());
376 mReadOnly->Update(CloneMapFile(), mMap.size(), std::move(blobImpls),
377 std::move(mChangedKeys));
380 mChangedKeys.Clear();
383 void WritableSharedMap::Delete(const nsACString& aName) {
384 if (mEntries.Remove(aName)) {
385 KeyChanged(aName);
389 void WritableSharedMap::Set(JSContext* aCx, const nsACString& aName,
390 JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
391 StructuredCloneData holder;
393 holder.Write(aCx, aValue, aRv);
394 if (aRv.Failed()) {
395 return;
398 if (!holder.InputStreams().IsEmpty()) {
399 aRv.Throw(NS_ERROR_INVALID_ARG);
400 return;
403 Entry* entry = mEntries.GetOrInsertNew(aName, *this, aName);
404 entry->TakeData(std::move(holder));
406 KeyChanged(aName);
409 void WritableSharedMap::Flush() { BroadcastChanges(); }
411 void WritableSharedMap::IdleFlush() {
412 mPendingFlush = false;
413 Flush();
416 nsresult WritableSharedMap::KeyChanged(const nsACString& aName) {
417 if (!mChangedKeys.ContainsSorted(aName)) {
418 mChangedKeys.InsertElementSorted(aName);
420 mEntryArray.reset();
422 if (!mPendingFlush) {
423 MOZ_TRY(NS_DispatchToCurrentThreadQueue(
424 NewRunnableMethod("WritableSharedMap::IdleFlush", this,
425 &WritableSharedMap::IdleFlush),
426 EventQueuePriority::Idle));
427 mPendingFlush = true;
429 return NS_OK;
432 JSObject* SharedMap::WrapObject(JSContext* aCx,
433 JS::Handle<JSObject*> aGivenProto) {
434 return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto);
437 JSObject* WritableSharedMap::WrapObject(JSContext* aCx,
438 JS::Handle<JSObject*> aGivenProto) {
439 return MozWritableSharedMap_Binding::Wrap(aCx, this, aGivenProto);
442 /* static */
443 already_AddRefed<SharedMapChangeEvent> SharedMapChangeEvent::Constructor(
444 EventTarget* aEventTarget, const nsAString& aType,
445 const MozSharedMapChangeEventInit& aInit) {
446 RefPtr<SharedMapChangeEvent> event = new SharedMapChangeEvent(aEventTarget);
448 bool trusted = event->Init(aEventTarget);
449 event->InitEvent(aType, aInit.mBubbles, aInit.mCancelable);
450 event->SetTrusted(trusted);
451 event->SetComposed(aInit.mComposed);
453 event->mChangedKeys = aInit.mChangedKeys;
455 return event.forget();
458 NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableSharedMap, SharedMap, mReadOnly)
460 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableSharedMap)
461 NS_INTERFACE_MAP_END_INHERITING(SharedMap)
463 NS_IMPL_ADDREF_INHERITED(WritableSharedMap, SharedMap)
464 NS_IMPL_RELEASE_INHERITED(WritableSharedMap, SharedMap)
466 } // namespace dom::ipc
467 } // namespace mozilla