Skip to content

Commit df5d42e

Browse files
NickGerlemanmeta-codesync[bot]
authored andcommitted
Wire native CSS parsing for aspectRatio (#55683)
Summary: Pull Request resolved: #55683 Gate `processAspectRatio` behind `enableNativeCSSParsing()`. When the flag is on, CSS ratio strings like `"16/9"` and number strings are parsed natively using the existing CSS ratio parser instead of being preprocessed in JS. The parsing is done in `fromRawValue(... FloatOptional &)` — string values are only sent for aspectRatio; other FloatOptional yoga props never receive strings from JS. Changelog: [Internal] Reviewed By: jorge-cab Differential Revision: D94052732 fbshipit-source-id: 8f6d29c90c69b4da3dc43d007653c3db833cb959
1 parent 77cb2ba commit df5d42e

File tree

5 files changed

+121
-31
lines changed

5 files changed

+121
-31
lines changed

packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,18 @@ export const transformOriginAttribute: AnyAttributeType = nativeCSSParsing
6767
? true
6868
: {process: processTransformOrigin};
6969

70+
export const aspectRatioAttribute: AnyAttributeType = nativeCSSParsing
71+
? true
72+
: {process: processAspectRatio};
73+
7074
const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
7175
/**
7276
* Layout
7377
*/
7478
alignContent: true,
7579
alignItems: true,
7680
alignSelf: true,
77-
aspectRatio: {process: processAspectRatio},
81+
aspectRatio: aspectRatioAttribute,
7882
borderBottomWidth: true,
7983
borderEndWidth: true,
8084
borderLeftWidth: true,

packages/react-native/ReactCommon/react/renderer/components/view/YogaStylableProps.cpp

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -147,35 +147,40 @@ void YogaStylableProps::setProp(
147147
REBUILD_FIELD_SWITCH_CASE_YSP(flexBasis, setFlexBasis);
148148
REBUILD_FIELD_SWITCH_CASE2(positionType, setPositionType, "position");
149149
REBUILD_FIELD_YG_GUTTER(gap, setGap, "rowGap", "columnGap", "gap");
150-
REBUILD_FIELD_SWITCH_CASE_YSP(aspectRatio, setAspectRatio);
151-
REBUILD_FIELD_SWITCH_CASE_YSP(boxSizing, setBoxSizing);
152-
REBUILD_FIELD_YG_DIMENSION(dimension, setDimension, "width", "height");
153-
REBUILD_FIELD_YG_DIMENSION(
154-
minDimension, setMinDimension, "minWidth", "minHeight");
155-
REBUILD_FIELD_YG_DIMENSION(
156-
maxDimension, setMaxDimension, "maxWidth", "maxHeight");
157-
REBUILD_FIELD_YG_EDGES_POSITION();
158-
REBUILD_FIELD_YG_EDGES(margin, setMargin, "margin", "");
159-
REBUILD_FIELD_YG_EDGES(padding, setPadding, "padding", "");
160-
REBUILD_FIELD_YG_EDGES(border, setBorder, "border", "Width");
150+
case CONSTEXPR_RAW_PROPS_KEY_HASH("aspectRatio"): {
151+
yogaStyle.setAspectRatio(
152+
value.hasValue() ? convertAspectRatio(context, value)
153+
: ygDefaults.aspectRatio());
154+
return;
155+
}
156+
REBUILD_FIELD_SWITCH_CASE_YSP(boxSizing, setBoxSizing);
157+
REBUILD_FIELD_YG_DIMENSION(dimension, setDimension, "width", "height");
158+
REBUILD_FIELD_YG_DIMENSION(
159+
minDimension, setMinDimension, "minWidth", "minHeight");
160+
REBUILD_FIELD_YG_DIMENSION(
161+
maxDimension, setMaxDimension, "maxWidth", "maxHeight");
162+
REBUILD_FIELD_YG_EDGES_POSITION();
163+
REBUILD_FIELD_YG_EDGES(margin, setMargin, "margin", "");
164+
REBUILD_FIELD_YG_EDGES(padding, setPadding, "padding", "");
165+
REBUILD_FIELD_YG_EDGES(border, setBorder, "border", "Width");
161166

162-
// Aliases
163-
RAW_SET_PROP_SWITCH_CASE(insetBlockEnd, "insetBlockEnd");
164-
RAW_SET_PROP_SWITCH_CASE(insetBlockStart, "insetBlockStart");
165-
RAW_SET_PROP_SWITCH_CASE(insetInlineEnd, "insetInlineEnd");
166-
RAW_SET_PROP_SWITCH_CASE(insetInlineStart, "insetInlineStart");
167-
RAW_SET_PROP_SWITCH_CASE(marginInline, "marginInline");
168-
RAW_SET_PROP_SWITCH_CASE(marginInlineStart, "marginInlineStart");
169-
RAW_SET_PROP_SWITCH_CASE(marginInlineEnd, "marginInlineEnd");
170-
RAW_SET_PROP_SWITCH_CASE(marginBlock, "marginBlock");
171-
RAW_SET_PROP_SWITCH_CASE(marginBlockStart, "marginBlockStart");
172-
RAW_SET_PROP_SWITCH_CASE(marginBlockEnd, "marginBlockEnd");
173-
RAW_SET_PROP_SWITCH_CASE(paddingInline, "paddingInline");
174-
RAW_SET_PROP_SWITCH_CASE(paddingInlineStart, "paddingInlineStart");
175-
RAW_SET_PROP_SWITCH_CASE(paddingInlineEnd, "paddingInlineEnd");
176-
RAW_SET_PROP_SWITCH_CASE(paddingBlock, "paddingBlock");
177-
RAW_SET_PROP_SWITCH_CASE(paddingBlockStart, "paddingBlockStart");
178-
RAW_SET_PROP_SWITCH_CASE(paddingBlockEnd, "paddingBlockEnd");
167+
// Aliases
168+
RAW_SET_PROP_SWITCH_CASE(insetBlockEnd, "insetBlockEnd");
169+
RAW_SET_PROP_SWITCH_CASE(insetBlockStart, "insetBlockStart");
170+
RAW_SET_PROP_SWITCH_CASE(insetInlineEnd, "insetInlineEnd");
171+
RAW_SET_PROP_SWITCH_CASE(insetInlineStart, "insetInlineStart");
172+
RAW_SET_PROP_SWITCH_CASE(marginInline, "marginInline");
173+
RAW_SET_PROP_SWITCH_CASE(marginInlineStart, "marginInlineStart");
174+
RAW_SET_PROP_SWITCH_CASE(marginInlineEnd, "marginInlineEnd");
175+
RAW_SET_PROP_SWITCH_CASE(marginBlock, "marginBlock");
176+
RAW_SET_PROP_SWITCH_CASE(marginBlockStart, "marginBlockStart");
177+
RAW_SET_PROP_SWITCH_CASE(marginBlockEnd, "marginBlockEnd");
178+
RAW_SET_PROP_SWITCH_CASE(paddingInline, "paddingInline");
179+
RAW_SET_PROP_SWITCH_CASE(paddingInlineStart, "paddingInlineStart");
180+
RAW_SET_PROP_SWITCH_CASE(paddingInlineEnd, "paddingInlineEnd");
181+
RAW_SET_PROP_SWITCH_CASE(paddingBlock, "paddingBlock");
182+
RAW_SET_PROP_SWITCH_CASE(paddingBlockStart, "paddingBlockStart");
183+
RAW_SET_PROP_SWITCH_CASE(paddingBlockEnd, "paddingBlockEnd");
179184
}
180185
}
181186

packages/react-native/ReactCommon/react/renderer/components/view/conversions.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,23 @@ inline void fromRawValue(const PropsParserContext &context, const RawValue &valu
498498
result = value.hasType<float>() ? yoga::FloatOptional((float)value) : yoga::FloatOptional();
499499
}
500500

501+
inline yoga::FloatOptional convertAspectRatio(const PropsParserContext &context, const RawValue &value)
502+
{
503+
if (value.hasType<float>()) {
504+
return yoga::FloatOptional((float)value);
505+
}
506+
if (ReactNativeFeatureFlags::enableNativeCSSParsing() && value.hasType<std::string>()) {
507+
auto ratio = parseCSSProperty<CSSRatio>((std::string)value);
508+
if (std::holds_alternative<CSSRatio>(ratio)) {
509+
auto r = std::get<CSSRatio>(ratio);
510+
if (!r.isDegenerate()) {
511+
return yoga::FloatOptional(r.numerator / r.denominator);
512+
}
513+
}
514+
}
515+
return yoga::FloatOptional();
516+
}
517+
501518
inline std::optional<Float> toRadians(const RawValue &value)
502519
{
503520
if (value.hasType<Float>()) {

packages/react-native/ReactCommon/react/renderer/components/view/propsConversions.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,12 @@ convertRawProp(const PropsParserContext &context, const RawProps &rawProps, cons
356356
yoga::Dimension::Height,
357357
convertRawProp(context, rawProps, "maxHeight", sourceValue.maxDimension(yoga::Dimension::Height), {}));
358358

359-
yogaStyle.setAspectRatio(
360-
convertRawProp(context, rawProps, "aspectRatio", sourceValue.aspectRatio(), yogaStyle.aspectRatio()));
359+
{
360+
const auto *rawValue = rawProps.at("aspectRatio", nullptr, nullptr);
361+
if (rawValue != nullptr) {
362+
yogaStyle.setAspectRatio(rawValue->hasValue() ? convertAspectRatio(context, *rawValue) : yogaStyle.aspectRatio());
363+
}
364+
}
361365

362366
yogaStyle.setBoxSizing(
363367
convertRawProp(context, rawProps, "boxSizing", sourceValue.boxSizing(), yogaStyle.boxSizing()));

packages/react-native/ReactCommon/react/renderer/components/view/tests/ConversionsTest.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,4 +454,64 @@ TEST(ConversionsTest, unprocessed_transform_origin_rawvalue_string_with_z) {
454454
EXPECT_EQ(result.z, 15.0f);
455455
}
456456

457+
TEST(ConversionsTest, convert_aspect_ratio_float) {
458+
RawValue value{folly::dynamic(1.5)};
459+
auto result =
460+
convertAspectRatio(PropsParserContext{-1, ContextContainer{}}, value);
461+
462+
EXPECT_FALSE(result.isUndefined());
463+
EXPECT_EQ(result.unwrap(), 1.5f);
464+
}
465+
466+
TEST(ConversionsTest, convert_aspect_ratio_ratio_string) {
467+
// CSSRatio parses "16/9" as {numerator: 16, denominator: 9}
468+
auto ratio = parseCSSProperty<CSSRatio>("16/9");
469+
ASSERT_TRUE(std::holds_alternative<CSSRatio>(ratio));
470+
auto r = std::get<CSSRatio>(ratio);
471+
EXPECT_FALSE(r.isDegenerate());
472+
EXPECT_NEAR(r.numerator / r.denominator, 16.0f / 9.0f, 0.001f);
473+
}
474+
475+
TEST(ConversionsTest, convert_aspect_ratio_number_string) {
476+
// CSSRatio parses "1.5" as {numerator: 1.5, denominator: 1.0}
477+
auto ratio = parseCSSProperty<CSSRatio>("1.5");
478+
ASSERT_TRUE(std::holds_alternative<CSSRatio>(ratio));
479+
auto r = std::get<CSSRatio>(ratio);
480+
EXPECT_FALSE(r.isDegenerate());
481+
EXPECT_EQ(r.numerator / r.denominator, 1.5f);
482+
}
483+
484+
TEST(ConversionsTest, convert_aspect_ratio_degenerate) {
485+
auto ratio = parseCSSProperty<CSSRatio>("0/0");
486+
ASSERT_TRUE(std::holds_alternative<CSSRatio>(ratio));
487+
EXPECT_TRUE(std::get<CSSRatio>(ratio).isDegenerate());
488+
}
489+
490+
TEST(ConversionsTest, float_optional_from_rawvalue_float) {
491+
RawValue value{folly::dynamic(1.5)};
492+
yoga::FloatOptional result;
493+
fromRawValue(PropsParserContext{-1, ContextContainer{}}, value, result);
494+
495+
EXPECT_FALSE(result.isUndefined());
496+
EXPECT_EQ(result.unwrap(), 1.5f);
497+
}
498+
499+
TEST(ConversionsTest, float_optional_undefined_for_non_float) {
500+
RawValue value{folly::dynamic(nullptr)};
501+
yoga::FloatOptional result;
502+
fromRawValue(PropsParserContext{-1, ContextContainer{}}, value, result);
503+
504+
EXPECT_TRUE(result.isUndefined());
505+
}
506+
507+
TEST(ConversionsTest, float_optional_undefined_for_string) {
508+
// fromRawValue for FloatOptional does not parse strings —
509+
// that is handled by convertAspectRatio specifically.
510+
RawValue value{folly::dynamic("16/9")};
511+
yoga::FloatOptional result;
512+
fromRawValue(PropsParserContext{-1, ContextContainer{}}, value, result);
513+
514+
EXPECT_TRUE(result.isUndefined());
515+
}
516+
457517
} // namespace facebook::react

0 commit comments

Comments
 (0)