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 #ifndef mozilla_dom_SyncedContext_h
8 #define mozilla_dom_SyncedContext_h
11 #include <type_traits>
13 #include "mozilla/Attributes.h"
14 #include "mozilla/BitSet.h"
15 #include "mozilla/EnumSet.h"
16 #include "nsStringFwd.h"
19 // Referenced via macro definitions
20 #include "mozilla/ErrorResult.h"
35 struct IPDLParamTraits
;
44 namespace syncedcontext
{
47 using Index
= typename
std::integral_constant
<size_t, I
>;
49 // We're going to use the empty base optimization for synced fields of different
50 // sizes, so we define an empty class for that purpose.
51 template <size_t I
, size_t S
>
54 // A templated container for a synced field. I is the index and T is the type.
55 template <size_t I
, typename T
>
60 // SizedField is a Field with a helper to define either an "empty" field, or a
61 // field of a given type.
62 template <size_t I
, typename T
, size_t S
>
63 using SizedField
= std::conditional_t
<((sizeof(T
) > 8) ? 8 : sizeof(T
)) == S
,
64 Field
<I
, T
>, Empty
<I
, S
>>;
66 template <typename Context
>
69 using IndexSet
= EnumSet
<size_t, BitSet
<Context::FieldValues::count
>>;
71 // Set a field at the given index in this `Transaction`. Creating a
72 // `Transaction` object and setting multiple fields on it allows for
73 // multiple mutations to be performed atomically.
74 template <size_t I
, typename U
>
75 void Set(U
&& aValue
) {
76 mValues
.Get(Index
<I
>{}) = std::forward
<U
>(aValue
);
80 // Apply the changes from this transaction to the specified Context in all
81 // processes. This method will call the correct `CanSet` and `DidSet` methods,
82 // as well as move the value.
84 // If the target has been discarded, changes will be ignored.
86 // NOTE: This method mutates `this`, clearing the modified field set.
87 [[nodiscard
]] nsresult
Commit(Context
* aOwner
);
89 // Called from `ContentParent` in response to a transaction from content.
90 mozilla::ipc::IPCResult
CommitFromIPC(const MaybeDiscarded
<Context
>& aOwner
,
91 ContentParent
* aSource
);
93 // Called from `ContentChild` in response to a transaction from the parent.
94 mozilla::ipc::IPCResult
CommitFromIPC(const MaybeDiscarded
<Context
>& aOwner
,
95 uint64_t aEpoch
, ContentChild
* aSource
);
97 // Apply the changes from this transaction to the specified Context WITHOUT
98 // syncing the changes to other processes.
100 // Unlike `Commit`, this method will NOT call the corresponding `CanSet` or
101 // `DidSet` methods, and can be performed when the target context is
102 // unattached or discarded.
104 // NOTE: YOU PROBABLY DO NOT WANT TO USE THIS METHOD
105 void CommitWithoutSyncing(Context
* aOwner
);
108 friend struct mozilla::ipc::IPDLParamTraits
<Transaction
<Context
>>;
110 void Write(IPC::MessageWriter
* aWriter
,
111 mozilla::ipc::IProtocol
* aActor
) const;
112 bool Read(IPC::MessageReader
* aReader
, mozilla::ipc::IProtocol
* aActor
);
114 // You probably don't want to directly call this method - instead call
115 // `Commit`, which will perform the necessary synchronization.
117 // `Validate` must be called before calling this method.
118 void Apply(Context
* aOwner
, bool aFromIPC
);
120 // Returns the set of fields which failed to validate, or an empty set if
121 // there were no validation errors.
123 // NOTE: This method mutates `this` if any changes were reverted.
124 IndexSet
Validate(Context
* aOwner
, ContentParent
* aSource
);
126 template <typename F
>
127 static void EachIndex(F
&& aCallback
) {
128 Context::FieldValues::EachIndex(aCallback
);
132 static uint64_t& FieldEpoch(Index
<I
>, Context
* aContext
) {
133 return std::get
<I
>(aContext
->mFields
.mEpochs
);
136 typename
Context::FieldValues mValues
;
140 template <typename Base
, size_t Count
>
141 class FieldValues
: public Base
{
143 // The number of fields stored by this type.
144 static constexpr size_t count
= Count
;
146 // The base type will define a series of `Get` methods for looking up a field
147 // by its field index.
150 // Calls a generic lambda with an `Index<I>` for each index less than the
152 template <typename F
>
153 static void EachIndex(F
&& aCallback
) {
154 EachIndexInner(std::make_index_sequence
<count
>(),
155 std::forward
<F
>(aCallback
));
159 friend struct mozilla::ipc::IPDLParamTraits
<FieldValues
<Base
, Count
>>;
161 void Write(IPC::MessageWriter
* aWriter
,
162 mozilla::ipc::IProtocol
* aActor
) const;
163 bool Read(IPC::MessageReader
* aReader
, mozilla::ipc::IProtocol
* aActor
);
165 template <typename F
, size_t... Indexes
>
166 static void EachIndexInner(std::index_sequence
<Indexes
...> aIndexes
,
168 (aCallback(Index
<Indexes
>()), ...);
172 // Storage related to synchronized context fields. Contains both a tuple of
173 // individual field values, and epoch information for field synchronization.
174 template <typename Values
>
177 // Unsafely grab a reference directly to the internal values structure which
178 // can be modified without telling other processes about the change.
180 // This is only sound in specific code which is already messaging other
181 // processes, and doesn't need to worry about epochs or other properties of
182 // field synchronization.
183 Values
& RawValues() { return mValues
; }
184 const Values
& RawValues() const { return mValues
; }
186 // Get an individual field by index.
188 const auto& Get() const {
189 return RawValues().Get(Index
<I
>{});
192 // Set the value of a field without telling other processes about the change.
194 // This is only sound in specific code which is already messaging other
195 // processes, and doesn't need to worry about epochs or other properties of
196 // field synchronization.
197 template <size_t I
, typename U
>
198 void SetWithoutSyncing(U
&& aValue
) {
199 GetNonSyncingReference
<I
>() = std::move(aValue
);
202 // Get a reference to a field that can modify without telling other
203 // processes about the change.
205 // This is only sound in specific code which is already messaging other
206 // processes, and doesn't need to worry about epochs or other properties of
207 // field synchronization.
209 auto& GetNonSyncingReference() {
210 return RawValues().Get(Index
<I
>{});
213 FieldStorage() = default;
214 explicit FieldStorage(Values
&& aInit
) : mValues(std::move(aInit
)) {}
217 template <typename Context
>
218 friend class Transaction
;
221 std::array
<uint64_t, Values::count
> mEpochs
{};
225 // Alternative return type enum for `CanSet` validators which allows specifying
227 enum class CanSetResult
: uint8_t {
228 // The set attempt is denied. This is equivalent to returning `false`.
230 // The set attempt is allowed. This is equivalent to returning `true`.
232 // The set attempt is reverted non-fatally.
236 // Helper type traits to use concrete types rather than generic forwarding
237 // references for the `SetXXX` methods defined on the synced context type.
239 // This helps avoid potential issues where someone accidentally declares an
240 // overload of these methods with slightly different types and different
241 // behaviours. See bug 1659520.
242 template <typename T
>
243 struct GetFieldSetterType
{
247 struct GetFieldSetterType
<nsString
> {
248 using SetterArg
= const nsAString
&;
251 struct GetFieldSetterType
<nsCString
> {
252 using SetterArg
= const nsACString
&;
254 template <typename T
>
255 using FieldSetterType
= typename GetFieldSetterType
<T
>::SetterArg
;
257 #define MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX(name, type) IDX_##name,
259 #define MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET(name, type) \
260 const type& Get##name() const { return mFields.template Get<IDX_##name>(); } \
262 [[nodiscard]] nsresult Set##name( \
263 ::mozilla::dom::syncedcontext::FieldSetterType<type> aValue) { \
265 txn.template Set<IDX_##name>(std::move(aValue)); \
266 return txn.Commit(this); \
268 void Set##name(::mozilla::dom::syncedcontext::FieldSetterType<type> aValue, \
269 ErrorResult& aRv) { \
270 nsresult rv = this->Set##name(std::move(aValue)); \
271 if (NS_FAILED(rv)) { \
272 aRv.ThrowInvalidStateError("cannot set synced field '" #name \
273 "': context is discarded"); \
277 #define MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET(name, type) \
278 template <typename U> \
279 void Set##name(U&& aValue) { \
280 this->template Set<IDX_##name>(std::forward<U>(aValue)); \
282 #define MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME(name, type) \
286 #define MOZ_DECL_SYNCED_FIELD_INHERIT(name, type) \
288 syncedcontext::SizedField<IDX_##name, type, Size>,
290 #define MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER(name, type) \
291 type& Get(FieldIndex<IDX_##name>) { \
292 return Field<IDX_##name, type>::mField; \
294 const type& Get(FieldIndex<IDX_##name>) const { \
295 return Field<IDX_##name, type>::mField; \
298 // Declare a type as a synced context type.
300 // clazz is the name of the type being declared, and `eachfield` is a macro
301 // which, when called with the name of the macro, will call that macro once for
302 // each field in the synced context.
303 #define MOZ_DECL_SYNCED_CONTEXT(clazz, eachfield) \
305 /* Index constants for referring to each field in generic code */ \
306 enum FieldIndexes { \
307 eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX) SYNCED_FIELD_COUNT \
310 /* Helper for overloading methods like `CanSet` and `DidSet` */ \
311 template <size_t I> \
312 using FieldIndex = typename ::mozilla::dom::syncedcontext::Index<I>; \
314 /* Fields contain all synced fields defined by \
315 * `eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT)`, but only those where the size \
316 * of the field is equal to size Size will be present. We use SizedField to \
317 * remove fields of the wrong size. */ \
318 template <size_t Size> \
319 struct Fields : eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT) \
320 syncedcontext::Empty<SYNCED_FIELD_COUNT, Size> {}; \
322 /* Struct containing the data for all synced fields as members. We filter \
323 * sizes to lay out fields of size 1, then 2, then 4 and last 8 or greater. \
324 * This way we don't need to consider packing when defining fields, but \
325 * we'll just reorder them here. \
327 struct BaseFieldValues : public Fields<1>, \
331 template <size_t I> \
333 return Get(FieldIndex<I>{}); \
335 template <size_t I> \
336 const auto& Get() const { \
337 return Get(FieldIndex<I>{}); \
339 eachfield(MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER) \
341 using FieldValues = \
342 typename ::mozilla::dom::syncedcontext::FieldValues<BaseFieldValues, \
343 SYNCED_FIELD_COUNT>; \
346 friend class ::mozilla::dom::syncedcontext::Transaction<clazz>; \
347 ::mozilla::dom::syncedcontext::FieldStorage<FieldValues> mFields; \
350 /* Transaction types for bulk mutations */ \
351 using BaseTransaction = ::mozilla::dom::syncedcontext::Transaction<clazz>; \
352 class Transaction final : public BaseTransaction { \
354 eachfield(MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET) \
357 /* Field name getter by field index */ \
358 static const char* FieldIndexToName(size_t aIndex) { \
359 switch (aIndex) { eachfield(MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME) } \
360 return "<unknown>"; \
362 eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET)
364 } // namespace syncedcontext
369 template <typename Context
>
370 struct IPDLParamTraits
<dom::syncedcontext::Transaction
<Context
>> {
371 typedef dom::syncedcontext::Transaction
<Context
> paramType
;
373 static void Write(IPC::MessageWriter
* aWriter
, IProtocol
* aActor
,
374 const paramType
& aParam
) {
375 aParam
.Write(aWriter
, aActor
);
378 static bool Read(IPC::MessageReader
* aReader
, IProtocol
* aActor
,
379 paramType
* aResult
) {
380 return aResult
->Read(aReader
, aActor
);
384 template <typename Base
, size_t Count
>
385 struct IPDLParamTraits
<dom::syncedcontext::FieldValues
<Base
, Count
>> {
386 typedef dom::syncedcontext::FieldValues
<Base
, Count
> paramType
;
388 static void Write(IPC::MessageWriter
* aWriter
, IProtocol
* aActor
,
389 const paramType
& aParam
) {
390 aParam
.Write(aWriter
, aActor
);
393 static bool Read(IPC::MessageReader
* aReader
, IProtocol
* aActor
,
394 paramType
* aResult
) {
395 return aResult
->Read(aReader
, aActor
);
400 } // namespace mozilla
402 #endif // !defined(mozilla_dom_SyncedContext_h)