/* -*- 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 #include #include #include "mozilla/Attributes.h" #include "mozilla/BitSet.h" #include "mozilla/EnumSet.h" #include "nsStringFwd.h" #include "nscore.h" // Referenced via macro definitions #include "mozilla/ErrorResult.h" class PickleIterator; namespace IPC { class Message; class MessageReader; class MessageWriter; } // namespace IPC namespace mozilla { namespace ipc { class IProtocol; class IPCResult; template struct IPDLParamTraits; } // namespace ipc namespace dom { class ContentParent; class ContentChild; template class MaybeDiscarded; namespace syncedcontext { template using Index = typename std::integral_constant; // We're going to use the empty base optimization for synced fields of different // sizes, so we define an empty class for that purpose. template struct Empty {}; // A templated container for a synced field. I is the index and T is the type. template struct Field { T mField{}; }; // SizedField is a Field with a helper to define either an "empty" field, or a // field of a given type. template using SizedField = std::conditional_t<((sizeof(T) > 8) ? 8 : sizeof(T)) == S, Field, Empty>; template class Transaction { public: using IndexSet = EnumSet>; // 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) { mValues.Get(Index{}) = std::forward(aValue); mModified += I; } // 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`, clearing the modified field set. [[nodiscard]] 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); // Apply the changes from this transaction to the specified Context WITHOUT // syncing the changes to other processes. // // Unlike `Commit`, this method will NOT call the corresponding `CanSet` or // `DidSet` methods, and can be performed when the target context is // unattached or discarded. // // NOTE: YOU PROBABLY DO NOT WANT TO USE THIS METHOD void CommitWithoutSyncing(Context* aOwner); private: friend struct mozilla::ipc::IPDLParamTraits>; void Write(IPC::MessageWriter* aWriter, mozilla::ipc::IProtocol* aActor) const; bool Read(IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor); // 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, bool aFromIPC); // Returns the set of fields which failed to validate, or an empty set if // there were no validation errors. // // NOTE: This method mutates `this` if any changes were reverted. IndexSet Validate(Context* aOwner, ContentParent* aSource); template static void EachIndex(F&& aCallback) { Context::FieldValues::EachIndex(aCallback); } template static uint64_t& FieldEpoch(Index, Context* aContext) { return std::get(aContext->mFields.mEpochs); } typename Context::FieldValues mValues; IndexSet mModified; }; template class FieldValues : public Base { public: // The number of fields stored by this type. static constexpr size_t count = Count; // The base type will define a series of `Get` methods for looking up a field // by its field index. using Base::Get; // Calls a generic lambda with an `Index` for each index less than the // field count. template static void EachIndex(F&& aCallback) { EachIndexInner(std::make_index_sequence(), std::forward(aCallback)); } private: friend struct mozilla::ipc::IPDLParamTraits>; void Write(IPC::MessageWriter* aWriter, mozilla::ipc::IProtocol* aActor) const; bool Read(IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor); template static void EachIndexInner(std::index_sequence aIndexes, F&& aCallback) { (aCallback(Index()), ...); } }; // Storage related to synchronized context fields. Contains both a tuple of // individual field values, and epoch information for field synchronization. template class FieldStorage { public: // Unsafely grab a reference directly to the internal values structure which // can be modified 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. Values& RawValues() { return mValues; } const Values& RawValues() const { return mValues; } // Get an individual field by index. template const auto& Get() const { return RawValues().Get(Index{}); } // 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) { GetNonSyncingReference() = 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 RawValues().Get(Index{}); } FieldStorage() = default; explicit FieldStorage(Values&& aInit) : mValues(std::move(aInit)) {} private: template friend class Transaction; // Data Members std::array mEpochs{}; Values mValues; }; // Alternative return type enum for `CanSet` validators which allows specifying // more behaviour. enum class CanSetResult : uint8_t { // The set attempt is denied. This is equivalent to returning `false`. Deny, // The set attempt is allowed. This is equivalent to returning `true`. Allow, // The set attempt is reverted non-fatally. Revert, }; // Helper type traits to use concrete types rather than generic forwarding // references for the `SetXXX` methods defined on the synced context type. // // This helps avoid potential issues where someone accidentally declares an // overload of these methods with slightly different types and different // behaviours. See bug 1659520. template struct GetFieldSetterType { using SetterArg = T; }; template <> struct GetFieldSetterType { using SetterArg = const nsAString&; }; template <> struct GetFieldSetterType { using SetterArg = const nsACString&; }; template using FieldSetterType = typename GetFieldSetterType::SetterArg; #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(); } \ \ [[nodiscard]] nsresult Set##name( \ ::mozilla::dom::syncedcontext::FieldSetterType aValue) { \ Transaction txn; \ txn.template Set(std::move(aValue)); \ return txn.Commit(this); \ } \ void Set##name(::mozilla::dom::syncedcontext::FieldSetterType aValue, \ ErrorResult& aRv) { \ nsresult rv = this->Set##name(std::move(aValue)); \ if (NS_FAILED(rv)) { \ aRv.ThrowInvalidStateError("cannot set synced field '" #name \ "': context is discarded"); \ } \ } #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; #define MOZ_DECL_SYNCED_FIELD_INHERIT(name, type) \ public \ syncedcontext::SizedField, #define MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER(name, type) \ type& Get(FieldIndex) { \ return Field::mField; \ } \ const type& Get(FieldIndex) const { \ return Field::mField; \ } // 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) \ public: \ /* Index constants for referring to each field in generic code */ \ enum FieldIndexes { \ eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX) SYNCED_FIELD_COUNT \ }; \ \ /* Helper for overloading methods like `CanSet` and `DidSet` */ \ template \ using FieldIndex = typename ::mozilla::dom::syncedcontext::Index; \ \ /* Fields contain all synced fields defined by \ * `eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT)`, but only those where the size \ * of the field is equal to size Size will be present. We use SizedField to \ * remove fields of the wrong size. */ \ template \ struct Fields : eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT) \ syncedcontext::Empty {}; \ \ /* Struct containing the data for all synced fields as members. We filter \ * sizes to lay out fields of size 1, then 2, then 4 and last 8 or greater. \ * This way we don't need to consider packing when defining fields, but \ * we'll just reorder them here. \ */ \ struct BaseFieldValues : public Fields<1>, \ public Fields<2>, \ public Fields<4>, \ public Fields<8> { \ template \ auto& Get() { \ return Get(FieldIndex{}); \ } \ template \ const auto& Get() const { \ return Get(FieldIndex{}); \ } \ eachfield(MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER) \ }; \ using FieldValues = \ typename ::mozilla::dom::syncedcontext::FieldValues; \ \ protected: \ friend class ::mozilla::dom::syncedcontext::Transaction; \ ::mozilla::dom::syncedcontext::FieldStorage mFields; \ \ public: \ /* 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::MessageWriter* aWriter, IProtocol* aActor, const paramType& aParam) { aParam.Write(aWriter, aActor); } static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, paramType* aResult) { return aResult->Read(aReader, aActor); } }; template struct IPDLParamTraits> { typedef dom::syncedcontext::FieldValues paramType; static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, const paramType& aParam) { aParam.Write(aWriter, aActor); } static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, paramType* aResult) { return aResult->Read(aReader, aActor); } }; } // namespace ipc } // namespace mozilla #endif // !defined(mozilla_dom_SyncedContext_h)