Skip to content

Commit 3c473b8

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Wire native CSS parsing for aspectRatio
Summary: 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] Differential Revision: D94052732
1 parent 9d9d637 commit 3c473b8

5 files changed

Lines changed: 121 additions & 31 deletions

File tree

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

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

68+
export const aspectRatioAttribute: AnyAttributeType = nativeCSSParsing
69+
? true
70+
: {process: processAspectRatio};
71+
6872
const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
6973
/**
7074
* Layout
7175
*/
7276
alignContent: true,
7377
alignItems: true,
7478
alignSelf: true,
75-
aspectRatio: {process: processAspectRatio},
79+
aspectRatio: aspectRatioAttribute,
7680
borderBottomWidth: true,
7781
borderEndWidth: true,
7882
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
@@ -440,4 +440,64 @@ TEST(ConversionsTest, unprocessed_transform_origin_rawvalue_array) {
440440
EXPECT_EQ(result.z, 5.0f);
441441
}
442442

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

0 commit comments

Comments
 (0)