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/. */
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
;
32 // Align to size of uintptr_t here, to be safe. It's probably not strictly
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
));
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();
60 aRv
.Throw(res
.unwrapErr());
64 Entry
* entry
= mEntries
.Get(aName
);
70 entry
->Read(aCx
, aRetVal
, aRv
);
73 void SharedMap::Entry::Read(JSContext
* aCx
,
74 JS::MutableHandle
<JS::Value
> aRetVal
,
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
);
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
);
94 holder
.BlobImpls().AppendElements(Blobs());
96 holder
.Read(aCx
, aRetVal
, aRv
);
99 FileDescriptor
SharedMap::CloneMapFile() const {
100 if (mMap
.initialized()) {
101 return mMap
.cloneHandle();
106 void SharedMap::Update(const FileDescriptor
& aMapFile
, size_t aMapSize
,
107 nsTArray
<RefPtr
<BlobImpl
>>&& aBlobs
,
108 nsTArray
<nsCString
>&& aChangedKeys
) {
109 MOZ_DIAGNOSTIC_ASSERT(!mWritable
);
113 *mMapFile
= aMapFile
;
115 mMapFile
.reset(new FileDescriptor(aMapFile
));
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");
131 for (auto& key
: aChangedKeys
) {
132 Unused
<< init
.mChangedKeys
.AppendElement(NS_ConvertUTF8toUTF16(key
),
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()) {
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 {
164 EntryArray()[aIndex
]->Read(aCx
, aResult
, rv
);
165 if (rv
.MaybeSetPendingException(aCx
)) {
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
);
187 MOZ_ASSERT(uint32_t(ptr
- aDestPtr
) == mSize
);
189 memcpy(aDestPtr
, Data(), mSize
);
192 mData
= AsVariant(aNewOffset
);
193 mBlobOffset
= aNewBlobOffset
;
196 Result
<Ok
, nsresult
> SharedMap::MaybeRebuild() {
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
));
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
);
219 buffer
.codeUint32(count
);
221 for (uint32_t i
= 0; i
< count
; i
++) {
222 auto entry
= MakeUnique
<Entry
>(*this);
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
));
239 void SharedMap::MaybeRebuild() const {
240 Unused
<< const_cast<SharedMap
*>(this)->MaybeRebuild();
243 WritableSharedMap::WritableSharedMap() {
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() {
253 nsTArray
<RefPtr
<BlobImpl
>> blobs(mBlobImpls
.Clone());
255 new SharedMap(ContentProcessMessageManager::Get()->GetParentObject(),
256 CloneMapFile(), MapSize(), std::move(blobs
));
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
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
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();
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
);
304 header
.codeUint32(count
);
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
);
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
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.
340 MOZ_RELEASE_ASSERT(mem
.Finalize(mMap
).isOk());
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
))) {
355 Unused
<< aParent
->SendUpdateSharedData(CloneMapFile(), mMap
.size(), blobs
,
359 void WritableSharedMap::BroadcastChanges() {
360 if (mChangedKeys
.IsEmpty()) {
364 if (!Serialize().isOk()) {
368 nsTArray
<ContentParent
*> parents
;
369 ContentParent::GetAll(parents
);
370 for (auto& parent
: parents
) {
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
)) {
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
);
398 if (!holder
.InputStreams().IsEmpty()) {
399 aRv
.Throw(NS_ERROR_INVALID_ARG
);
403 Entry
* entry
= mEntries
.GetOrInsertNew(aName
, *this, aName
);
404 entry
->TakeData(std::move(holder
));
409 void WritableSharedMap::Flush() { BroadcastChanges(); }
411 void WritableSharedMap::IdleFlush() {
412 mPendingFlush
= false;
416 nsresult
WritableSharedMap::KeyChanged(const nsACString
& aName
) {
417 if (!mChangedKeys
.ContainsSorted(aName
)) {
418 mChangedKeys
.InsertElementSorted(aName
);
422 if (!mPendingFlush
) {
423 MOZ_TRY(NS_DispatchToCurrentThreadQueue(
424 NewRunnableMethod("WritableSharedMap::IdleFlush", this,
425 &WritableSharedMap::IdleFlush
),
426 EventQueuePriority::Idle
));
427 mPendingFlush
= true;
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
);
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