diff options
Diffstat (limited to 'docshell/base/SyncedContext.h')
-rw-r--r-- | docshell/base/SyncedContext.h | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/docshell/base/SyncedContext.h b/docshell/base/SyncedContext.h new file mode 100644 index 0000000000..679e07edc2 --- /dev/null +++ b/docshell/base/SyncedContext.h @@ -0,0 +1,402 @@ +/* -*- 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 <array> +#include <type_traits> +#include <utility> +#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 <typename T> +struct IPDLParamTraits; +} // namespace ipc + +namespace dom { +class ContentParent; +class ContentChild; +template <typename T> +class MaybeDiscarded; + +namespace syncedcontext { + +template <size_t I> +using Index = typename std::integral_constant<size_t, I>; + +// 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 <size_t I, size_t S> +struct Empty {}; + +// A templated container for a synced field. I is the index and T is the type. +template <size_t I, typename T> +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 <size_t I, typename T, size_t S> +using SizedField = std::conditional_t<((sizeof(T) > 8) ? 8 : sizeof(T)) == S, + Field<I, T>, Empty<I, S>>; + +template <typename Context> +class Transaction { + public: + using IndexSet = EnumSet<size_t, BitSet<Context::FieldValues::count>>; + + // 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 <size_t I, typename U> + void Set(U&& aValue) { + mValues.Get(Index<I>{}) = std::forward<U>(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<Context>& aOwner, + ContentParent* aSource); + + // Called from `ContentChild` in response to a transaction from the parent. + mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded<Context>& 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<Transaction<Context>>; + + 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 <typename F> + static void EachIndex(F&& aCallback) { + Context::FieldValues::EachIndex(aCallback); + } + + template <size_t I> + static uint64_t& FieldEpoch(Index<I>, Context* aContext) { + return std::get<I>(aContext->mFields.mEpochs); + } + + typename Context::FieldValues mValues; + IndexSet mModified; +}; + +template <typename Base, size_t Count> +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<I>` for each index less than the + // field count. + template <typename F> + static void EachIndex(F&& aCallback) { + EachIndexInner(std::make_index_sequence<count>(), + std::forward<F>(aCallback)); + } + + private: + friend struct mozilla::ipc::IPDLParamTraits<FieldValues<Base, Count>>; + + void Write(IPC::MessageWriter* aWriter, + mozilla::ipc::IProtocol* aActor) const; + bool Read(IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor); + + template <typename F, size_t... Indexes> + static void EachIndexInner(std::index_sequence<Indexes...> aIndexes, + F&& aCallback) { + (aCallback(Index<Indexes>()), ...); + } +}; + +// Storage related to synchronized context fields. Contains both a tuple of +// individual field values, and epoch information for field synchronization. +template <typename Values> +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 <size_t I> + const auto& Get() const { + return RawValues().Get(Index<I>{}); + } + + // 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 <size_t I, typename U> + void SetWithoutSyncing(U&& aValue) { + GetNonSyncingReference<I>() = 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 <size_t I> + auto& GetNonSyncingReference() { + return RawValues().Get(Index<I>{}); + } + + FieldStorage() = default; + explicit FieldStorage(Values&& aInit) : mValues(std::move(aInit)) {} + + private: + template <typename Context> + friend class Transaction; + + // Data Members + std::array<uint64_t, Values::count> 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 <typename T> +struct GetFieldSetterType { + using SetterArg = T; +}; +template <> +struct GetFieldSetterType<nsString> { + using SetterArg = const nsAString&; +}; +template <> +struct GetFieldSetterType<nsCString> { + using SetterArg = const nsACString&; +}; +template <typename T> +using FieldSetterType = typename GetFieldSetterType<T>::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<IDX_##name>(); } \ + \ + [[nodiscard]] nsresult Set##name( \ + ::mozilla::dom::syncedcontext::FieldSetterType<type> aValue) { \ + Transaction txn; \ + txn.template Set<IDX_##name>(std::move(aValue)); \ + return txn.Commit(this); \ + } \ + void Set##name(::mozilla::dom::syncedcontext::FieldSetterType<type> 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 <typename U> \ + void Set##name(U&& aValue) { \ + this->template Set<IDX_##name>(std::forward<U>(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<IDX_##name, type, Size>, + +#define MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER(name, type) \ + type& Get(FieldIndex<IDX_##name>) { \ + return Field<IDX_##name, type>::mField; \ + } \ + const type& Get(FieldIndex<IDX_##name>) const { \ + return Field<IDX_##name, type>::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 <size_t I> \ + using FieldIndex = typename ::mozilla::dom::syncedcontext::Index<I>; \ + \ + /* 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 <size_t Size> \ + struct Fields : eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT) \ + syncedcontext::Empty<SYNCED_FIELD_COUNT, Size> {}; \ + \ + /* 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 <size_t I> \ + auto& Get() { \ + return Get(FieldIndex<I>{}); \ + } \ + template <size_t I> \ + const auto& Get() const { \ + return Get(FieldIndex<I>{}); \ + } \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER) \ + }; \ + using FieldValues = \ + typename ::mozilla::dom::syncedcontext::FieldValues<BaseFieldValues, \ + SYNCED_FIELD_COUNT>; \ + \ + protected: \ + friend class ::mozilla::dom::syncedcontext::Transaction<clazz>; \ + ::mozilla::dom::syncedcontext::FieldStorage<FieldValues> mFields; \ + \ + public: \ + /* Transaction types for bulk mutations */ \ + using BaseTransaction = ::mozilla::dom::syncedcontext::Transaction<clazz>; \ + 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 "<unknown>"; \ + } \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET) + +} // namespace syncedcontext +} // namespace dom + +namespace ipc { + +template <typename Context> +struct IPDLParamTraits<dom::syncedcontext::Transaction<Context>> { + typedef dom::syncedcontext::Transaction<Context> 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 <typename Base, size_t Count> +struct IPDLParamTraits<dom::syncedcontext::FieldValues<Base, Count>> { + typedef dom::syncedcontext::FieldValues<Base, Count> 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) |