Reland Fix possible invalid measurements when width or height is zero pixels (#1823)

Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1823

X-link: https://github.com/facebook/react-native/pull/52348

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: D76851589

fbshipit-source-id: 6f5a0e6beccc51f591726c9e83e9b90f3350ed0f
This commit is contained in:
Nick Gerleman
2025-06-30 20:46:39 -07:00
committed by Facebook GitHub Bot
parent 30291398f3
commit 4b5ca50117
5 changed files with 268 additions and 9 deletions

View File

@@ -240,3 +240,14 @@
<div style="position:relative; width:50px; height:50px;">
</div>
</div>
<div id="align_items_non_stretch_s526008"
style="flex-direction: column; width: 400px; height: 400px;">
<div style="flex-direction: row;">
<div style="flex-direction: column; align-items: flex-start;">
<div>
<div style="width: 0; height: 10px;"></div>
</div>
</div>
</div>
</div>

View File

@@ -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<<e8d11c0e97041bb2f1487f0d87363fdc>>
* @generated SignedSource<<84597dd8b2c4f3aac1f21abe68f25308>>
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGAlignItemsTest.html
*/
@@ -2293,6 +2293,86 @@ public class YGAlignItemsTest {
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);
}

View File

@@ -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<<fd80af208d8635435f86ffa8f3810b39>>
* 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();
}
});

View File

@@ -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);
}

View File

@@ -415,6 +415,12 @@ static void measureNodeWithoutChildren(
Dimension::Height);
}
inline bool isFixedSize(float dim, SizingMode sizingMode) {
return sizingMode == SizingMode::StretchFit ||
(yoga::isDefined(dim) && 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,