From 08c546d8b190486e5cbd17de0c6f1ea529f75841 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Tue, 17 Jun 2025 10:22:02 -0700 Subject: [PATCH] Fix possible invalid measurements when width or height is zero pixels (#1820) Summary: X-link: https://github.com/facebook/react-native/pull/52073 Pull Request resolved: https://github.com/facebook/yoga/pull/1820 Fixes https://github.com/facebook/yoga/issues/1819 Yoga has a fast path when measuring a node, if it thinks it knows its dimensions ahead of time. This path has some eroneous logic, to set both axis to owner size, if *either* will evaluate to zero, while having an `YGMeasureModeAtMost`/`FitContent` constraint. This means that if a node is given a zero width, and Yoga later measures with with `FitContent`, its height will become the maximum allowable height, even if it shouldn't be that large. We can fix this, by only allowing if both axis are this fixed case, instead of just one. This bug has existed for about a decade (going back to at least D3312496). Changelog: [General][Fixed] - Fix possible invalid measurements with width or height is zero pixels Reviewed By: yungsters Differential Revision: D76793705 --- gentest/fixtures/YGAlignItemsTest.html | 11 +++ .../com/facebook/yoga/YGAlignItemsTest.java | 82 ++++++++++++++++- .../tests/generated/YGAlignItemsTest.test.ts | 87 ++++++++++++++++++- tests/generated/YGAlignItemsTest.cpp | 83 +++++++++++++++++- yoga/algorithm/CalculateLayout.cpp | 14 +-- 5 files changed, 268 insertions(+), 9 deletions(-) diff --git a/gentest/fixtures/YGAlignItemsTest.html b/gentest/fixtures/YGAlignItemsTest.html index c3e6af411d..6b50772a80 100644 --- a/gentest/fixtures/YGAlignItemsTest.html +++ b/gentest/fixtures/YGAlignItemsTest.html @@ -240,3 +240,14 @@
+ +
+
+
+
+
+
+
+
+
diff --git a/java/tests/generated/com/facebook/yoga/YGAlignItemsTest.java b/java/tests/generated/com/facebook/yoga/YGAlignItemsTest.java index a5be942b69..8a32201d10 100644 --- a/java/tests/generated/com/facebook/yoga/YGAlignItemsTest.java +++ b/java/tests/generated/com/facebook/yoga/YGAlignItemsTest.java @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<84597dd8b2c4f3aac1f21abe68f25308>> * generated by gentest/gentest-driver.ts from gentest/fixtures/YGAlignItemsTest.html */ @@ -2293,6 +2293,86 @@ public void test_align_stretch_with_row_reverse() { assertEquals(50f, root_child1.getLayoutHeight(), 0.0f); } + @Test + public void test_align_items_non_stretch_s526008() { + YogaConfig config = YogaConfigFactory.create(); + + final YogaNode root = createNode(config); + root.setPositionType(YogaPositionType.ABSOLUTE); + root.setWidth(400f); + root.setHeight(400f); + + final YogaNode root_child0 = createNode(config); + root_child0.setFlexDirection(YogaFlexDirection.ROW); + root.addChildAt(root_child0, 0); + + final YogaNode root_child0_child0 = createNode(config); + root_child0_child0.setAlignItems(YogaAlign.FLEX_START); + root_child0.addChildAt(root_child0_child0, 0); + + final YogaNode root_child0_child0_child0 = createNode(config); + root_child0_child0.addChildAt(root_child0_child0_child0, 0); + + final YogaNode root_child0_child0_child0_child0 = createNode(config); + root_child0_child0_child0_child0.setHeight(10f); + root_child0_child0_child0.addChildAt(root_child0_child0_child0_child0, 0); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(400f, root.getLayoutWidth(), 0.0f); + assertEquals(400f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(400f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child0_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0_child0.getLayoutY(), 0.0f); + assertEquals(0f, root_child0_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child0_child0_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0_child0_child0.getLayoutY(), 0.0f); + assertEquals(0f, root_child0_child0_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child0_child0_child0_child0.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(400f, root.getLayoutWidth(), 0.0f); + assertEquals(400f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(400f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(400f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child0_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0_child0.getLayoutY(), 0.0f); + assertEquals(0f, root_child0_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child0_child0_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0_child0_child0.getLayoutY(), 0.0f); + assertEquals(0f, root_child0_child0_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child0_child0_child0_child0.getLayoutHeight(), 0.0f); + } + private YogaNode createNode(YogaConfig config) { return mNodeFactory.create(config); } diff --git a/javascript/tests/generated/YGAlignItemsTest.test.ts b/javascript/tests/generated/YGAlignItemsTest.test.ts index 39c013a3e5..ae4dc63585 100644 --- a/javascript/tests/generated/YGAlignItemsTest.test.ts +++ b/javascript/tests/generated/YGAlignItemsTest.test.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<075a0b67003e5c4a1f51bd99da71c700>> + * @generated SignedSource<> * generated by gentest/gentest-driver.ts from gentest/fixtures/YGAlignItemsTest.html */ @@ -2442,3 +2442,88 @@ test('align_stretch_with_row_reverse', () => { config.free(); } }); +test('align_items_non_stretch_s526008', () => { + const config = Yoga.Config.create(); + let root; + + try { + root = Yoga.Node.create(config); + root.setPositionType(PositionType.Absolute); + root.setWidth(400); + root.setHeight(400); + + const root_child0 = Yoga.Node.create(config); + root_child0.setFlexDirection(FlexDirection.Row); + root.insertChild(root_child0, 0); + + const root_child0_child0 = Yoga.Node.create(config); + root_child0_child0.setAlignItems(Align.FlexStart); + root_child0.insertChild(root_child0_child0, 0); + + const root_child0_child0_child0 = Yoga.Node.create(config); + root_child0_child0.insertChild(root_child0_child0_child0, 0); + + const root_child0_child0_child0_child0 = Yoga.Node.create(config); + root_child0_child0_child0_child0.setHeight(10); + root_child0_child0_child0.insertChild(root_child0_child0_child0_child0, 0); + root.calculateLayout(undefined, undefined, Direction.LTR); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(400); + expect(root.getComputedHeight()).toBe(400); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(400); + expect(root_child0.getComputedHeight()).toBe(10); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(0); + expect(root_child0_child0.getComputedHeight()).toBe(10); + + expect(root_child0_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0_child0.getComputedWidth()).toBe(0); + expect(root_child0_child0_child0.getComputedHeight()).toBe(10); + + expect(root_child0_child0_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0_child0_child0.getComputedWidth()).toBe(0); + expect(root_child0_child0_child0_child0.getComputedHeight()).toBe(10); + + root.calculateLayout(undefined, undefined, Direction.RTL); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(400); + expect(root.getComputedHeight()).toBe(400); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(400); + expect(root_child0.getComputedHeight()).toBe(10); + + expect(root_child0_child0.getComputedLeft()).toBe(400); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(0); + expect(root_child0_child0.getComputedHeight()).toBe(10); + + expect(root_child0_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0_child0.getComputedWidth()).toBe(0); + expect(root_child0_child0_child0.getComputedHeight()).toBe(10); + + expect(root_child0_child0_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0_child0_child0.getComputedWidth()).toBe(0); + expect(root_child0_child0_child0_child0.getComputedHeight()).toBe(10); + } finally { + if (typeof root !== 'undefined') { + root.freeRecursive(); + } + + config.free(); + } +}); diff --git a/tests/generated/YGAlignItemsTest.cpp b/tests/generated/YGAlignItemsTest.cpp index 4a4f48ecde..e64d68e4f2 100644 --- a/tests/generated/YGAlignItemsTest.cpp +++ b/tests/generated/YGAlignItemsTest.cpp @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * clang-format off - * @generated SignedSource<<71295a398c89ea424490884a05e2c77c>> + * @generated SignedSource<<1db57b05babb408c08efcec7dbdf16fb>> * generated by gentest/gentest-driver.ts from gentest/fixtures/YGAlignItemsTest.html */ @@ -2310,3 +2310,84 @@ TEST(YogaTest, align_stretch_with_row_reverse) { YGConfigFree(config); } + +TEST(YogaTest, align_items_non_stretch_s526008) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); + YGNodeStyleSetWidth(root, 400); + YGNodeStyleSetHeight(root, 400); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexDirection(root_child0, YGFlexDirectionRow); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetAlignItems(root_child0_child0, YGAlignFlexStart); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + + YGNodeRef root_child0_child0_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root_child0_child0, root_child0_child0_child0, 0); + + YGNodeRef root_child0_child0_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root_child0_child0_child0_child0, 10); + YGNodeInsertChild(root_child0_child0_child0, root_child0_child0_child0_child0, 0); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child0_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0_child0_child0_child0)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child0_child0_child0_child0)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child0_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0_child0_child0_child0)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child0_child0_child0_child0)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} diff --git a/yoga/algorithm/CalculateLayout.cpp b/yoga/algorithm/CalculateLayout.cpp index 36184745e2..bdc75fa865 100644 --- a/yoga/algorithm/CalculateLayout.cpp +++ b/yoga/algorithm/CalculateLayout.cpp @@ -415,6 +415,12 @@ static void measureNodeWithoutChildren( Dimension::Height); } +inline bool isFixedSize(float dim, SizingMode sizingMode) { + return yoga::isDefined(dim) && + (sizingMode == SizingMode::StretchFit || + (sizingMode == SizingMode::FitContent && dim <= 0.0)); +} + static bool measureNodeWithFixedSize( yoga::Node* const node, const Direction direction, @@ -424,12 +430,8 @@ static bool measureNodeWithFixedSize( const SizingMode heightSizingMode, const float ownerWidth, const float ownerHeight) { - if ((yoga::isDefined(availableWidth) && - widthSizingMode == SizingMode::FitContent && availableWidth <= 0.0f) || - (yoga::isDefined(availableHeight) && - heightSizingMode == SizingMode::FitContent && availableHeight <= 0.0f) || - (widthSizingMode == SizingMode::StretchFit && - heightSizingMode == SizingMode::StretchFit)) { + if (isFixedSize(availableWidth, widthSizingMode) && + isFixedSize(availableHeight, heightSizingMode)) { node->setLayoutMeasuredDimension( boundAxis( node,