Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ void BaseScrollViewProps::setProp(
RAW_SET_PROP_SWITCH_CASE_BASIC(centerContent);
RAW_SET_PROP_SWITCH_CASE_BASIC(automaticallyAdjustContentInsets);
RAW_SET_PROP_SWITCH_CASE_BASIC(automaticallyAdjustsScrollIndicatorInsets);
RAW_SET_PROP_SWITCH_CASE_BASIC(automaticallyAdjustKeyboardInsets);
RAW_SET_PROP_SWITCH_CASE_BASIC(decelerationRate);
RAW_SET_PROP_SWITCH_CASE_BASIC(directionalLockEnabled);
RAW_SET_PROP_SWITCH_CASE_BASIC(indicatorStyle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ void BaseTextProps::setProp(
textAttributes,
maxFontSizeMultiplier,
"maxFontSizeMultiplier");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, dynamicTypeRamp, "dynamicTypeRamp");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, letterSpacing, "letterSpacing");
REBUILD_FIELD_SWITCH_CASE(
Expand All @@ -286,7 +288,7 @@ void BaseTextProps::setProp(
value,
textAttributes,
baseWritingDirection,
"baseWritingDirection");
"writingDirection");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ void AccessibilityProps::setProp(
RAW_SET_PROP_SWITCH_CASE_BASIC(accessible);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityState);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabel);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityOrder);
RAW_SET_PROP_SWITCH_CASE(
accessibilityOrder, "experimental_accessibilityOrder");
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabelledBy);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLiveRegion);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityHint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ void BaseViewProps::setProp(
RAW_SET_PROP_SWITCH_CASE_BASIC(shadowOpacity);
RAW_SET_PROP_SWITCH_CASE_BASIC(shadowRadius);
RAW_SET_PROP_SWITCH_CASE_BASIC(transform);
RAW_SET_PROP_SWITCH_CASE_BASIC(transformOrigin);
RAW_SET_PROP_SWITCH_CASE_BASIC(backfaceVisibility);
RAW_SET_PROP_SWITCH_CASE_BASIC(shouldRasterize);
RAW_SET_PROP_SWITCH_CASE_BASIC(zIndex);
Expand Down Expand Up @@ -522,6 +523,7 @@ void BaseViewProps::setProp(
// BorderRadii
SET_CASCADED_RECTANGLE_CORNERS(borderRadii, "border", "Radius", value);
SET_CASCADED_RECTANGLE_EDGES(borderColors, "border", "Color", value);
SET_CASCADED_RECTANGLE_CORNERS(borderCurves, "border", "Curve", value);
SET_CASCADED_RECTANGLE_EDGES(borderStyles, "border", "Style", value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,6 @@ HostPlatformViewProps::HostPlatformViewProps(
sourceProps.nextFocusUp,
{})) {}

#define VIEW_EVENT_CASE(eventType) \
case CONSTEXPR_RAW_PROPS_KEY_HASH("on" #eventType): { \
const auto offset = ViewEvents::Offset::eventType; \
ViewEvents defaultViewEvents{}; \
bool res = defaultViewEvents[offset]; \
if (value.hasValue()) { \
fromRawValue(context, value, res); \
} \
events[offset] = res; \
return; \
}

void HostPlatformViewProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,18 @@ static inline ViewEvents convertRawProp(
"onPointerUpCapture",
sourceValue[Offset::PointerUpCapture],
defaultValue[Offset::PointerUpCapture]);
// TODO: gotPointerCapture & lostPointerCapture
result[Offset::GotPointerCapture] = convertRawProp(
context,
rawProps,
"onGotPointerCapture",
sourceValue[Offset::GotPointerCapture],
defaultValue[Offset::GotPointerCapture]);
result[Offset::LostPointerCapture] = convertRawProp(
context,
rawProps,
"onLostPointerCapture",
sourceValue[Offset::LostPointerCapture],
defaultValue[Offset::LostPointerCapture]);

// PanResponder callbacks
result[Offset::MoveShouldSetResponder] = convertRawProp(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,18 @@ class ConcreteComponentDescriptor : public ComponentDescriptor {
RawPropsParser &&rawPropsParser = {})
: ComponentDescriptor(parameters, std::move(rawPropsParser))
{
rawPropsParser_.prepare<ConcreteProps>();
// The parser's `keys_` / `nameToIndex_` are only consumed by
// `RawProps::at()`, which is reached exclusively through the classic
// per-field `convertRawProp` path. When `ConcreteProps` opts into the
// iterator-setter path and the runtime flag is on, `parse()` is never
// called, so the O(n²) preparation here is wasted. Skip it.
if constexpr (HasIteratorSetterCtor<ConcreteProps>) {
if (!ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) {
rawPropsParser_.prepare<ConcreteProps>();
}
} else {
rawPropsParser_.prepare<ConcreteProps>();
}
}

ComponentHandle getComponentHandle() const override
Expand Down Expand Up @@ -112,9 +123,30 @@ class ConcreteComponentDescriptor : public ComponentDescriptor {
ShadowNodeT::filterRawProps(rawProps);
}

rawProps.parse(rawPropsParser_);
// Two construction paths:
// - Iterator-setter (only available when `ConcreteProps` satisfies
// `HasIteratorSetterCtor` AND the runtime flag is on): copy-construct
// from sourceProps, then walk rawProps in-place via `forEachItem` and
// route each entry through `setProp`. Skips both
// `RawProps::parse(parser)` and the `folly::dynamic` materialization
// that the legacy path needed.
// - Classic (the fallback for any `ConcreteProps` that doesn't opt in,
// and the only path when the flag is off): parse + per-field
// `convertRawProp` via the 3-arg ctor.
constexpr bool kSupportsIteratorSetter = HasIteratorSetterCtor<ConcreteProps>;
const bool useIteratorSetter = kSupportsIteratorSetter && ReactNativeFeatureFlags::enableCppPropsIteratorSetter();

std::shared_ptr<ConcreteProps> shadowNodeProps;
if constexpr (kSupportsIteratorSetter) {
if (useIteratorSetter) {
shadowNodeProps = ShadowNodeT::Props(props);
}
}
if (!useIteratorSetter) {
rawProps.parse(rawPropsParser_);
shadowNodeProps = ShadowNodeT::Props(context, rawProps, props);
}

auto shadowNodeProps = ShadowNodeT::Props(context, rawProps, props);
#ifdef RN_SERIALIZABLE_STATE
bool fallbackToDynamicRawPropsAccumulation = true;
if (ReactNativeFeatureFlags::enableExclusivePropsUpdateAndroid() &&
Expand All @@ -134,19 +166,12 @@ class ConcreteComponentDescriptor : public ComponentDescriptor {
ShadowNodeT::initializeDynamicProps(shadowNodeProps, rawProps, props);
}
#endif
// Use the new-style iterator
// Note that we just check if `Props` has this flag set, no matter
// the type of ShadowNode; it acts as the single global flag.
if (ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) {
#ifdef RN_SERIALIZABLE_STATE
const auto &dynamic =
fallbackToDynamicRawPropsAccumulation ? shadowNodeProps->rawProps : static_cast<folly::dynamic>(rawProps);
#else
const auto &dynamic = static_cast<folly::dynamic>(rawProps);
#endif
for (const auto &pair : dynamic.items()) {
const auto &name = pair.first.getString();
shadowNodeProps->setProp(context, RAW_PROPS_KEY_HASH(name), name.c_str(), RawValue(pair.second));

if constexpr (kSupportsIteratorSetter) {
if (useIteratorSetter) {
rawProps.forEachItem([&](std::string_view name, const RawValue &value) {
shadowNodeProps->setProp(context, RAW_PROPS_KEY_HASH(name), name.data(), value);
});
}
}
return shadowNodeProps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,32 @@ class ConcreteShadowNode : public BaseShadowNodeT {
return BaseShadowNodeT::BaseTraits();
}

/*
* Classic / parse path: construct `PropsT` by parsing `rawProps` field-by-
* field via the 3-arg `(context, sourceProps, rawProps)` constructor.
* `ConcreteComponentDescriptor::cloneProps` calls this when the
* iterator-setter path is disabled (per-class via the
* `HasIteratorSetterCtor` concept, or globally via the runtime flag).
*/
static UnsharedConcreteProps
Props(const PropsParserContext &context, const RawProps &rawProps, const Props::Shared &baseProps = nullptr)
{
return std::make_shared<PropsT>(
context, baseProps ? static_cast<const PropsT &>(*baseProps) : *defaultSharedProps(), rawProps);
}

/*
* Iterator-setter path: copy-construct `PropsT` from `baseProps` only.
* `ConcreteComponentDescriptor::cloneProps` then walks `rawProps` via
* `RawProps::forEachItem` and overwrites individual fields through
* `PropsT::setProp`. Available when `PropsT` satisfies
* `HasIteratorSetterCtor`.
*/
static UnsharedConcreteProps Props(const Props::Shared &baseProps)
{
return std::make_shared<PropsT>(baseProps ? static_cast<const PropsT &>(*baseProps) : *defaultSharedProps());
}

#ifdef RN_SERIALIZABLE_STATE
static void initializeDynamicProps(
UnsharedConcreteProps props,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,7 @@ Props::Props(
rawProps,
"nativeID",
sourceProps.nativeId,
{})) {
#ifdef RN_SERIALIZABLE_STATE
if (!ReactNativeFeatureFlags::enableExclusivePropsUpdateAndroid()) {
initializeDynamicProps(sourceProps, rawProps, filterObjectKeys);
}
#endif
}
{})) {}

void Props::setProp(
const PropsParserContext& context,
Expand Down
64 changes: 63 additions & 1 deletion packages/react-native/ReactCommon/react/renderer/core/Props.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Props : public virtual Sealable, public virtual DebugStringConvertible {
virtual ~Props() = default;
#endif

Props(const Props &other) = delete;
Props(const Props &other) = default;
Props &operator=(const Props &other) = delete;

/**
Expand Down Expand Up @@ -84,4 +84,66 @@ class Props : public virtual Sealable, public virtual DebugStringConvertible {
#endif
};

namespace detail {

/*
* Extracts the class type from a pointer-to-member-function expression.
* Used in unevaluated context only.
*/
template <typename T, typename C>
auto memberFunctionClass(T C::*) -> C;

} // namespace detail

/*
* Internal: `T` declares its OWN `setProp` (not just inherits one from a
* base class). `&T::setProp` resolves to a pointer-to-member-function whose
* class part is the level where `setProp` was actually declared — if `T`
* inherits without overriding, that class is some base, not `T`.
*
* Distinguishing own-declaration from inherited-declaration matters because
* `setProp` is non-virtual. A subclass that adds fields but forgets to
* override `setProp` would silently inherit its parent's switch — and the new
* fields would never be reached by the iterator-setter dispatch.
*/
template <typename T>
concept DeclaresOwnSetProp = std::is_same_v<decltype(detail::memberFunctionClass(&T::setProp)), T>;

/*
* Internal: `T` exposes a `setProp(ctx, hash, name, value) -> void` callable
* with the canonical Props signature, declared on `T` itself.
*/
template <typename T>
concept HasSetProp = DeclaresOwnSetProp<T> &&
requires(T &t, const PropsParserContext &ctx, RawPropsPropNameHash hash, const char *name, const RawValue &value) {
{ t.setProp(ctx, hash, name, value) } -> std::same_as<void>;
};

/*
* Marks a Props type as supporting the iterator-setter construction path used
* by `ConcreteComponentDescriptor::cloneProps` when
* `enableCppPropsIteratorSetter` is on. The contract is:
*
* 1. The type is copy-constructible from a source Props (so `cloneProps`
* can build the new Props by copy and then overwrite individual fields).
* 2. The type descends from `Props`, anchoring the `setProp` chain in the
* `Props::setProp` base case.
* 3. The type declares its OWN `setProp` with the canonical signature —
* not inherited — so the iterator dispatch reaches every field that the
* type adds beyond its base.
*
* `setProp` is non-virtual; subclasses chain explicitly via
* `Parent::setProp(...)`. The chain integrity beyond `T` is enforced by the
* compiler at each level's `setProp` body — if a subclass calls
* `Parent::setProp(...)` and `Parent` does not define one, the build fails
* at that call site. This concept guards the entry point (`T` itself) and
* relies on those per-level calls to keep the chain whole.
*
* When the concept is NOT satisfied for some `ConcreteProps`,
* `cloneProps` falls through to the classic per-field `convertRawProp` path
* for that component regardless of the runtime flag.
*/
template <typename T>
concept HasIteratorSetterCtor = std::copy_constructible<T> && std::derived_from<T, Props> && HasSetProp<T>;

} // namespace facebook::react
39 changes: 39 additions & 0 deletions packages/react-native/ReactCommon/react/renderer/core/RawProps.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,45 @@ class RawProps final {
*/
const RawValue *at(const char *name) const noexcept;

/*
* Iterates the underlying source object and invokes `fn(name, value)` for
* each entry, in source order. Skips parsing — does NOT require a prior
* `parse(parser)` call. For `Mode::JSI` this walks the JSI object in-place
* (no `folly::dynamic` materialization). For `Mode::Dynamic` it walks
* `dynamic_.items()`. For `Mode::Empty` it is a no-op.
*
* The callback signature is `void(std::string_view name, const RawValue &value)`.
* The view points into storage owned by `forEachItem` for the duration of
* the call and is null-terminated (i.e. `name.data()` is a valid C string).
*/
template <typename Fn>
void forEachItem(Fn fn) const
{
switch (mode_) {
case Mode::Empty:
return;
case Mode::JSI: {
auto object = value_.asObject(*runtime_);
auto names = object.getPropertyNames(*runtime_);
auto count = names.size(*runtime_);
for (size_t i = 0; i < count; ++i) {
auto name = names.getValueAtIndex(*runtime_, i).asString(*runtime_);
auto propValue = object.getProperty(*runtime_, name);
auto nameUtf8 = name.utf8(*runtime_);
fn(std::string_view{nameUtf8}, RawValue{*runtime_, std::move(propValue)});
}
return;
}
case Mode::Dynamic:
for (const auto &pair : dynamic_.items()) {
fn(std::string_view{pair.first.getString()}, RawValue{pair.second});
}
return;
default:
return;
}
}

private:
friend class RawPropsParser;

Expand Down
16 changes: 15 additions & 1 deletion scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,15 @@ concept facebook::react::CSSSyntaxVisitorReturn = std::is_default_constructible_
template <typename T>
concept facebook::react::CSSValidCompoundDataType = facebook::react::detail::is_variant_of_data_types<T>::value;
template <typename T>
concept facebook::react::DeclaresOwnSetProp = std::is_same_v<decltype(facebook::react::detail::memberFunctionClass(&T::setProp)), T>;
template <typename T>
concept facebook::react::HasIteratorSetterCtor = std::copy_constructible<T> && std::derived_from<T, facebook::react::Props> && HasSetProp<T>;
template <typename T>
concept facebook::react::HasSetProp = DeclaresOwnSetProp<T> &&
requires(T& t, const facebook::react::PropsParserContext& ctx, facebook::react::RawPropsPropNameHash hash, const char* name, const facebook::react::RawValue& value) {
{ t.setProp(ctx, hash, name, value) } -> std::same_as<void>;
};
template <typename T>
concept facebook::react::Hashable = !std::is_same_v<T, const char*>&& (requires(T a) {
{ std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
});
Expand Down Expand Up @@ -4126,7 +4135,7 @@ class facebook::react::PreparedTextCacheKey {

class facebook::react::Props : public virtual facebook::react::Sealable, public virtual facebook::react::DebugStringConvertible {
public Props() = default;
public Props(const facebook::react::Props& other) = delete;
public Props(const facebook::react::Props& other) = default;
public Props(const facebook::react::PropsParserContext& context, const facebook::react::Props& sourceProps, const facebook::react::RawProps& rawProps, const std::function<bool(const std::string&)>& filterObjectKeys = nullptr);
public facebook::react::Props& operator=(const facebook::react::Props& other) = delete;
public folly::dynamic rawProps;
Expand Down Expand Up @@ -4195,6 +4204,8 @@ class facebook::react::RawProps {
public folly::dynamic toDynamic(const std::function<bool(const std::string&)>& filterObjectKeys = nullptr) const;
public operator folly::dynamic() const;
public void parse(const facebook::react::RawPropsParser& parser) noexcept;
template <typename Fn>
public void forEachItem(Fn fn) const;
}

enum facebook::react::RawProps::Mode {
Expand Down Expand Up @@ -8253,6 +8264,7 @@ class facebook::react::ConcreteShadowNode : public BaseShadowNodeT {
public static facebook::react::ComponentHandle Handle();
public static facebook::react::ComponentName Name();
public static facebook::react::ConcreteShadowNode::ConcreteStateData initialStateData(const facebook::react::Props::Shared&, const facebook::react::ShadowNodeFamily::Shared&, const facebook::react::ComponentDescriptor&);
public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::Props::Shared& baseProps);
public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::PropsParserContext& context, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr);
public static facebook::react::ShadowNodeTraits BaseTraits();
public static void initializeDynamicProps(facebook::react::ConcreteShadowNode::UnsharedConcreteProps props, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr);
Expand Down Expand Up @@ -9937,6 +9949,8 @@ template <typename CSSColor>
std::optional<facebook::react::CSSColor> facebook::react::detail::parseLegacyHslFunction(facebook::react::CSSValueParser& parser);
template <typename CSSColor>
std::optional<facebook::react::CSSColor> facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser);
template <typename T, typename C>
C facebook::react::detail::memberFunctionClass(T C::*);
template <typename... ComponentT>
constexpr std::optional<float> facebook::react::detail::normalizeComponent(const std::variant<std::monostate, ComponentT...>& component, float baseValue);

Expand Down
Loading
Loading