/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_dom_SyncedContext_h #define mozilla_dom_SyncedContext_h #include "mozilla/dom/MaybeDiscarded.h" #include "mozilla/EnumSet.h" #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "mozilla/Tuple.h" #include class PickleIterator; namespace IPC { class Message; } // namespace IPC namespace mozilla { namespace ipc { class IProtocol; class IPCResult; template struct IPDLParamTraits; } // namespace ipc namespace dom { class ContentParent; class ContentChild; namespace syncedcontext { template using Index = typename std::integral_constant; using IndexSet = EnumSet; template class Transaction { public: // Set a field at the given index in this `Transaction`. Creating a // `Transaction` object and setting multiple fields on it allows for // multiple mutations to be performed atomically. template void Set(U&& aValue) { auto& field = mozilla::Get(mMaybeFields); field.emplace(std::forward(aValue)); } // Apply the changes from this transaction to the specified Context in all // processes. This method will call the correct `CanSet` and `DidSet` methods, // as well as move the value. // // If the target has been discarded, changes will be ignored. // // NOTE: This method mutates `this`, resetting all members to `Nothing()` nsresult Commit(Context* aOwner); // Called from `ContentParent` in response to a transaction from content. mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded& aOwner, ContentParent* aSource); // Called from `ContentChild` in response to a transaction from the parent. mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded& aOwner, uint64_t aEpoch, ContentChild* aSource); void Write(IPC::Message* aMsg, mozilla::ipc::IProtocol* aActor) const; bool Read(const IPC::Message* aMsg, PickleIterator* aIter, mozilla::ipc::IProtocol* aActor); private: friend struct mozilla::ipc::IPDLParamTraits>; // You probably don't want to directly call this method - instead call // `Commit`, which will perform the necessary synchronization. // // `Validate` must be called before calling this method. void Apply(Context* aOwner); // Returns the set of fields which failed to validate, or an empty set if // there were no validation errors. IndexSet Validate(Context* aOwner, ContentParent* aSource); using FieldStorage = typename Context::FieldStorage; static FieldStorage& GetFieldStorage(Context* aContext) { return aContext->mFields; } // Call a generic lambda with a `Index` for each index less than the number // of elements in `FieldStorage`. template static void EachIndexInner(Index<0>, F&&) {} template static void EachIndexInner(Index, F&& aCallback) { aCallback(Index()); EachIndexInner(Index(), aCallback); } template static void EachIndex(F&& aCallback) { EachIndexInner(Index(), aCallback); } // Helper for invoking `std::get` or `mozilla::Get` using a // `SyncedFieldIndex` value. template static auto& GetAt(Index, mozilla::Tuple& aTarget) { return mozilla::Get(aTarget); } template static auto& GetAt(Index, std::array& aTarget) { return std::get(aTarget); } typename FieldStorage::MaybeFieldTuple mMaybeFields; }; // Storage related to synchronized context fields. Contains both a tuple of // individual field values, and epoch information for field synchronization. template class FieldStorage { public: using FieldTuple = mozilla::Tuple; static constexpr size_t fieldCount = sizeof...(Ts); static_assert(fieldCount < 64, "At most 64 synced fields are supported. Please file a bug if " "you need to additional fields."); const FieldTuple& Fields() const { return mFields; } // Get an individual field by index. template const auto& Get() const { return mozilla::Get(mFields); } // Set the value of a field without telling other processes about the change. // // This is only sound in specific code which is already messaging other // processes, and doesn't need to worry about epochs or other properties of // field synchronization. template void SetWithoutSyncing(U&& aValue) { mozilla::Get(mFields) = std::move(aValue); } // Get a reference to a field that can modify without telling other // processes about the change. // // This is only sound in specific code which is already messaging other // processes, and doesn't need to worry about epochs or other properties of // field synchronization. template auto& GetNonSyncingReference() { return mozilla::Get(mFields); } FieldStorage() = default; explicit FieldStorage(FieldTuple&& aInit) : mFields(std::move(aInit)) {} private: template friend class Transaction; // Helper type for `Transaction`. using MaybeFieldTuple = mozilla::Tuple...>; // Data Members std::array mEpochs{}; FieldTuple mFields; }; #define MOZ_DECL_SYNCED_CONTEXT_FIELD_TYPE(name, type) , type #define MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX(name, type) IDX_##name, #define MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET(name, type) \ const type& Get##name() const { return mFields.template Get(); } \ \ template \ void Set##name(U&& aValue) { \ Transaction txn; \ txn.template Set(std::forward(aValue)); \ txn.Commit(this); \ } #define MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET(name, type) \ template \ void Set##name(U&& aValue) { \ this->template Set(std::forward(aValue)); \ } #define MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME(name, type) \ case IDX_##name: \ return #name; // Declare a type as a synced context type. // // clazz is the name of the type being declared, and `eachfield` is a macro // which, when called with the name of the macro, will call that macro once for // each field in the synced context. #define MOZ_DECL_SYNCED_CONTEXT(clazz, eachfield) \ protected: \ friend class ::mozilla::dom::syncedcontext::Transaction; \ enum FieldIndexes { eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX) }; \ using FieldStorage = \ typename ::mozilla::dom::syncedcontext::FieldStorage; \ FieldStorage mFields; \ \ public: \ /* Helper for overloading methods like `CanSet` and `DidSet` */ \ template \ using FieldIndex = typename ::mozilla::dom::syncedcontext::Index; \ \ /* Field tuple type for use by initializers */ \ using FieldTuple = typename FieldStorage::FieldTuple; \ \ /* Transaction types for bulk mutations */ \ using BaseTransaction = ::mozilla::dom::syncedcontext::Transaction; \ class Transaction final : public BaseTransaction { \ public: \ eachfield(MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET) \ }; \ \ /* Field name getter by field index */ \ static const char* FieldIndexToName(size_t aIndex) { \ switch (aIndex) { eachfield(MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME) } \ return ""; \ } \ eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET) } // namespace syncedcontext } // namespace dom namespace ipc { template struct IPDLParamTraits> { typedef dom::syncedcontext::Transaction paramType; static void Write(IPC::Message* aMsg, IProtocol* aActor, const paramType& aParam) { aParam.Write(aMsg, aActor); } static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, paramType* aResult) { return aResult->Read(aMsg, aIter, aActor); } }; } // namespace ipc } // namespace mozilla #endif // !defined(mozilla_dom_SyncedContext_h)