gecko-dev/docshell/base/SyncedContext.h

403 строки
16 KiB
C++

/* -*- 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)