SyncedContext.h (16540B)
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 mozilla_dom_SyncedContext_h 8 #define mozilla_dom_SyncedContext_h 9 10 #include <array> 11 #include <type_traits> 12 #include <utility> 13 #include "mozilla/BitSet.h" 14 #include "mozilla/EnumSet.h" 15 #include "nsStringFwd.h" 16 #include "nscore.h" 17 18 // Referenced via macro definitions 19 #include "mozilla/ErrorResult.h" 20 21 class PickleIterator; 22 23 namespace IPC { 24 class Message; 25 class MessageReader; 26 class MessageWriter; 27 template <typename T> 28 struct ParamTraits; 29 } // namespace IPC 30 31 namespace mozilla { 32 namespace ipc { 33 class IProtocol; 34 class IPCResult; 35 } // namespace ipc 36 37 namespace dom { 38 class ContentParent; 39 class ContentChild; 40 template <typename T> 41 class MaybeDiscarded; 42 43 namespace syncedcontext { 44 45 template <size_t I> 46 using Index = typename std::integral_constant<size_t, I>; 47 48 // We're going to use the empty base optimization for synced fields of different 49 // sizes, so we define an empty class for that purpose. 50 template <size_t I, size_t S> 51 struct Empty {}; 52 53 // A templated container for a synced field. I is the index and T is the type. 54 template <size_t I, typename T> 55 struct Field { 56 T mField{}; 57 }; 58 59 // SizedField is a Field with a helper to define either an "empty" field, or a 60 // field of a given type. 61 template <size_t I, typename T, size_t S> 62 using SizedField = std::conditional_t<((sizeof(T) > 8) ? 8 : sizeof(T)) == S, 63 Field<I, T>, Empty<I, S>>; 64 65 template <typename Context> 66 class Transaction { 67 public: 68 using IndexSet = EnumSet<size_t, BitSet<Context::FieldValues::count>>; 69 70 // Set a field at the given index in this `Transaction`. Creating a 71 // `Transaction` object and setting multiple fields on it allows for 72 // multiple mutations to be performed atomically. 73 template <size_t I, typename U> 74 void Set(U&& aValue) { 75 mValues.Get(Index<I>{}) = std::forward<U>(aValue); 76 mModified += I; 77 } 78 79 // Apply the changes from this transaction to the specified Context in all 80 // processes. This method will call the correct `CanSet` and `DidSet` methods, 81 // as well as move the value. 82 // 83 // If the target has been discarded, changes will be ignored. 84 // 85 // NOTE: This method mutates `this`, clearing the modified field set. 86 [[nodiscard]] nsresult Commit(Context* aOwner); 87 88 // Called from `ContentParent` in response to a transaction from content. 89 mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded<Context>& aOwner, 90 ContentParent* aSource); 91 92 // Called from `ContentChild` in response to a transaction from the parent. 93 mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded<Context>& aOwner, 94 uint64_t aEpoch, ContentChild* aSource); 95 96 // Apply the changes from this transaction to the specified Context WITHOUT 97 // syncing the changes to other processes. 98 // 99 // Unlike `Commit`, this method will NOT call the corresponding `CanSet` or 100 // `DidSet` methods, and can be performed when the target context is 101 // unattached or discarded. 102 // 103 // NOTE: YOU PROBABLY DO NOT WANT TO USE THIS METHOD 104 void CommitWithoutSyncing(Context* aOwner); 105 106 private: 107 friend struct IPC::ParamTraits<Transaction<Context>>; 108 109 void Write(IPC::MessageWriter* aWriter) const; 110 bool Read(IPC::MessageReader* aReader); 111 112 // You probably don't want to directly call this method - instead call 113 // `Commit`, which will perform the necessary synchronization. 114 // 115 // `Validate` must be called before calling this method. 116 void Apply(Context* aOwner, bool aFromIPC); 117 118 // Returns the set of fields which failed to validate, or an empty set if 119 // there were no validation errors. 120 // 121 // NOTE: This method mutates `this` if any changes were reverted. 122 IndexSet Validate(Context* aOwner, ContentParent* aSource); 123 124 template <typename F> 125 static void EachIndex(F&& aCallback) { 126 Context::FieldValues::EachIndex(aCallback); 127 } 128 129 template <size_t I> 130 static uint64_t& FieldEpoch(Index<I>, Context* aContext) { 131 return std::get<I>(aContext->mFields.mEpochs); 132 } 133 134 typename Context::FieldValues mValues; 135 IndexSet mModified; 136 }; 137 138 template <typename Base, size_t Count> 139 class FieldValues : public Base { 140 public: 141 // The number of fields stored by this type. 142 static constexpr size_t count = Count; 143 144 // The base type will define a series of `Get` methods for looking up a field 145 // by its field index. 146 using Base::Get; 147 148 // Calls a generic lambda with an `Index<I>` for each index less than the 149 // field count. 150 template <typename F> 151 static void EachIndex(F&& aCallback) { 152 EachIndexInner(std::make_index_sequence<count>(), 153 std::forward<F>(aCallback)); 154 } 155 156 private: 157 friend struct IPC::ParamTraits<FieldValues<Base, Count>>; 158 159 void Write(IPC::MessageWriter* aWriter) const; 160 bool Read(IPC::MessageReader* aReader); 161 162 template <typename F, size_t... Indexes> 163 static void EachIndexInner(std::index_sequence<Indexes...> aIndexes, 164 F&& aCallback) { 165 (aCallback(Index<Indexes>()), ...); 166 } 167 }; 168 169 // Storage related to synchronized context fields. Contains both a tuple of 170 // individual field values, and epoch information for field synchronization. 171 template <typename Values> 172 class FieldStorage { 173 public: 174 // Unsafely grab a reference directly to the internal values structure which 175 // can be modified without telling other processes about the change. 176 // 177 // This is only sound in specific code which is already messaging other 178 // processes, and doesn't need to worry about epochs or other properties of 179 // field synchronization. 180 Values& RawValues() { return mValues; } 181 const Values& RawValues() const { return mValues; } 182 183 // Get an individual field by index. 184 template <size_t I> 185 const auto& Get() const { 186 return RawValues().Get(Index<I>{}); 187 } 188 189 // Set the value of a field without telling other processes about the change. 190 // 191 // This is only sound in specific code which is already messaging other 192 // processes, and doesn't need to worry about epochs or other properties of 193 // field synchronization. 194 template <size_t I, typename U> 195 void SetWithoutSyncing(U&& aValue) { 196 GetNonSyncingReference<I>() = std::move(aValue); 197 } 198 199 // Get a reference to a field that can modify without telling other 200 // processes about the change. 201 // 202 // This is only sound in specific code which is already messaging other 203 // processes, and doesn't need to worry about epochs or other properties of 204 // field synchronization. 205 template <size_t I> 206 auto& GetNonSyncingReference() { 207 return RawValues().Get(Index<I>{}); 208 } 209 210 FieldStorage() = default; 211 explicit FieldStorage(Values&& aInit) : mValues(std::move(aInit)) {} 212 213 private: 214 template <typename Context> 215 friend class Transaction; 216 217 // Data Members 218 std::array<uint64_t, Values::count> mEpochs{}; 219 Values mValues; 220 }; 221 222 // Alternative return type enum for `CanSet` validators which allows specifying 223 // more behaviour. 224 enum class CanSetResult : uint8_t { 225 // The set attempt is denied. This is equivalent to returning `false`. 226 Deny, 227 // The set attempt is allowed. This is equivalent to returning `true`. 228 Allow, 229 // The set attempt is reverted non-fatally. 230 Revert, 231 }; 232 233 // Helper type traits to use concrete types rather than generic forwarding 234 // references for the `SetXXX` methods defined on the synced context type. 235 // 236 // This helps avoid potential issues where someone accidentally declares an 237 // overload of these methods with slightly different types and different 238 // behaviours. See bug 1659520. 239 template <typename T> 240 struct GetFieldSetterType { 241 using SetterArg = T; 242 }; 243 template <> 244 struct GetFieldSetterType<nsString> { 245 using SetterArg = const nsAString&; 246 }; 247 template <> 248 struct GetFieldSetterType<nsCString> { 249 using SetterArg = const nsACString&; 250 }; 251 template <typename T> 252 using FieldSetterType = typename GetFieldSetterType<T>::SetterArg; 253 254 #define MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX(name, type) IDX_##name, 255 256 #define MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET(name, type) \ 257 const type& Get##name() const { return mFields.template Get<IDX_##name>(); } \ 258 \ 259 [[nodiscard]] nsresult Set##name( \ 260 ::mozilla::dom::syncedcontext::FieldSetterType<type> aValue) { \ 261 Transaction txn; \ 262 txn.template Set<IDX_##name>(std::move(aValue)); \ 263 return txn.Commit(this); \ 264 } \ 265 void Set##name(::mozilla::dom::syncedcontext::FieldSetterType<type> aValue, \ 266 ErrorResult& aRv) { \ 267 nsresult rv = this->Set##name(std::move(aValue)); \ 268 if (NS_FAILED(rv)) { \ 269 aRv.ThrowInvalidStateError("cannot set synced field '" #name \ 270 "': context is discarded"); \ 271 } \ 272 } 273 274 #define MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET(name, type) \ 275 template <typename U> \ 276 void Set##name(U&& aValue) { \ 277 this->template Set<IDX_##name>(std::forward<U>(aValue)); \ 278 } 279 #define MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME(name, type) \ 280 case IDX_##name: \ 281 return #name; 282 283 #define MOZ_DECL_SYNCED_FIELD_INHERIT(name, type) \ 284 public \ 285 syncedcontext::SizedField<IDX_##name, type, Size>, 286 287 #define MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER(name, type) \ 288 type& Get(FieldIndex<IDX_##name>) { \ 289 return Field<IDX_##name, type>::mField; \ 290 } \ 291 const type& Get(FieldIndex<IDX_##name>) const { \ 292 return Field<IDX_##name, type>::mField; \ 293 } 294 295 // Declare a type as a synced context type. 296 // 297 // clazz is the name of the type being declared, and `eachfield` is a macro 298 // which, when called with the name of the macro, will call that macro once for 299 // each field in the synced context. 300 #define MOZ_DECL_SYNCED_CONTEXT(clazz, eachfield) \ 301 public: \ 302 /* Index constants for referring to each field in generic code */ \ 303 enum FieldIndexes { \ 304 eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX) SYNCED_FIELD_COUNT \ 305 }; \ 306 \ 307 /* Helper for overloading methods like `CanSet` and `DidSet` */ \ 308 template <size_t I> \ 309 using FieldIndex = typename ::mozilla::dom::syncedcontext::Index<I>; \ 310 \ 311 /* Fields contain all synced fields defined by \ 312 * `eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT)`, but only those where the size \ 313 * of the field is equal to size Size will be present. We use SizedField to \ 314 * remove fields of the wrong size. */ \ 315 template <size_t Size> \ 316 struct Fields : eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT) \ 317 syncedcontext::Empty<SYNCED_FIELD_COUNT, Size>{}; \ 318 \ 319 /* Struct containing the data for all synced fields as members. We filter \ 320 * sizes to lay out fields of size 1, then 2, then 4 and last 8 or greater. \ 321 * This way we don't need to consider packing when defining fields, but \ 322 * we'll just reorder them here. \ 323 */ \ 324 struct BaseFieldValues : public Fields<1>, \ 325 public Fields<2>, \ 326 public Fields<4>, \ 327 public Fields<8> { \ 328 template <size_t I> \ 329 auto& Get() { \ 330 return Get(FieldIndex<I>{}); \ 331 } \ 332 template <size_t I> \ 333 const auto& Get() const { \ 334 return Get(FieldIndex<I>{}); \ 335 } \ 336 eachfield(MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER) \ 337 }; \ 338 using FieldValues = \ 339 typename ::mozilla::dom::syncedcontext::FieldValues<BaseFieldValues, \ 340 SYNCED_FIELD_COUNT>; \ 341 \ 342 protected: \ 343 friend class ::mozilla::dom::syncedcontext::Transaction<clazz>; \ 344 ::mozilla::dom::syncedcontext::FieldStorage<FieldValues> mFields; \ 345 \ 346 public: \ 347 /* Transaction types for bulk mutations */ \ 348 using BaseTransaction = ::mozilla::dom::syncedcontext::Transaction<clazz>; \ 349 class Transaction final : public BaseTransaction { \ 350 public: \ 351 eachfield(MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET) \ 352 }; \ 353 \ 354 /* Field name getter by field index */ \ 355 static const char* FieldIndexToName(size_t aIndex) { \ 356 switch (aIndex) { eachfield(MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME) } \ 357 return "<unknown>"; \ 358 } \ 359 eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET) 360 361 } // namespace syncedcontext 362 } // namespace dom 363 } // namespace mozilla 364 365 namespace IPC { 366 367 template <typename Context> 368 struct ParamTraits<mozilla::dom::syncedcontext::Transaction<Context>> { 369 using paramType = mozilla::dom::syncedcontext::Transaction<Context>; 370 371 static void Write(MessageWriter* aWriter, const paramType& aParam) { 372 aParam.Write(aWriter); 373 } 374 375 static bool Read(MessageReader* aReader, paramType* aResult) { 376 return aResult->Read(aReader); 377 } 378 }; 379 380 template <typename Base, size_t Count> 381 struct ParamTraits<mozilla::dom::syncedcontext::FieldValues<Base, Count>> { 382 using paramType = mozilla::dom::syncedcontext::FieldValues<Base, Count>; 383 384 static void Write(MessageWriter* aWriter, const paramType& aParam) { 385 aParam.Write(aWriter); 386 } 387 388 static bool Read(MessageReader* aReader, paramType* aResult) { 389 return aResult->Read(aReader); 390 } 391 }; 392 393 } // namespace IPC 394 395 #endif // !defined(mozilla_dom_SyncedContext_h)