Remove legacy absolute positioning path (#1725)

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

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

The legacy (wrong) absolute positioning path positions in two places, including work that is definitely always overwritten in the new absolute layout path.

This came up before for position: static, but we didn't clean this up at the time. This code is also now leading display: contents impl being more annoying.

This diff tries to converge to the more spec correct implementation of positioning here, that also only happens in one place.

Previous path would potentially also incorrectly justify when `justify-content` was non-default, but not handled in the previous few cases? We don't have access to the flexLine at this point later, and apart from the existing tests now passing I reused the new correct logic for justification (spec says we should position child as if its the only child in the container https://www.w3.org/TR/css-flexbox-1/#abspos-items).

I added a new, more scoped errata `AbsolutePositionWithoutInsetsExcludesPadding` to preserve some of the legacy behavior that showed as very breaking.

I also did not try removing `AbsolutePercentAgainstInnerSize` which I suspect would be more breaking than this change.

Changelog:
[General][Breaking] - More spec compliant absolute positioning

Reviewed By: joevilches

Differential Revision: D64244949

fbshipit-source-id: ca97570e0de82e8f0424a0912adfd0b05254559e
This commit is contained in:
Nick Gerleman
2024-10-17 22:40:16 -07:00
committed by Facebook GitHub Bot
parent 43be5888c4
commit 568718242d
8 changed files with 212 additions and 355 deletions

View File

@@ -61,9 +61,10 @@ ENUMS = {
# Allows main-axis flex basis to be stretched without flexGrow being
# set (previously referred to as "UseLegacyStretchBehaviour")
("StretchFlexBasis", 1 << 0),
# Positioning of absolute nodes will have various bugs related to
# justification, alignment, and insets
("AbsolutePositioningIncorrect", 1 << 1),
# Absolute position in a given axis will be relative to the padding
# edge of the parent container instead of the content edge when a
# specific inset (top/bottom/left/right) is not set.
("AbsolutePositionWithoutInsetsExcludesPadding", 1 << 1),
# Absolute nodes will resolve percentages against the inner size of
# their containing node, not the padding box
("AbsolutePercentAgainstInnerSize", 1 << 2),

View File

@@ -12,7 +12,7 @@ package com.facebook.yoga;
public enum YogaErrata {
NONE(0),
STRETCH_FLEX_BASIS(1),
ABSOLUTE_POSITIONING_INCORRECT(2),
ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING(2),
ABSOLUTE_PERCENT_AGAINST_INNER_SIZE(4),
ALL(2147483647),
CLASSIC(2147483646);
@@ -31,7 +31,7 @@ public enum YogaErrata {
switch (value) {
case 0: return NONE;
case 1: return STRETCH_FLEX_BASIS;
case 2: return ABSOLUTE_POSITIONING_INCORRECT;
case 2: return ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING;
case 4: return ABSOLUTE_PERCENT_AGAINST_INNER_SIZE;
case 2147483647: return ALL;
case 2147483646: return CLASSIC;

View File

@@ -55,7 +55,7 @@ export enum Edge {
export enum Errata {
None = 0,
StretchFlexBasis = 1,
AbsolutePositioningIncorrect = 2,
AbsolutePositionWithoutInsetsExcludesPadding = 2,
AbsolutePercentAgainstInnerSize = 4,
All = 2147483647,
Classic = 2147483646,
@@ -162,7 +162,7 @@ const constants = {
EDGE_ALL: Edge.All,
ERRATA_NONE: Errata.None,
ERRATA_STRETCH_FLEX_BASIS: Errata.StretchFlexBasis,
ERRATA_ABSOLUTE_POSITIONING_INCORRECT: Errata.AbsolutePositioningIncorrect,
ERRATA_ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING: Errata.AbsolutePositionWithoutInsetsExcludesPadding,
ERRATA_ABSOLUTE_PERCENT_AGAINST_INNER_SIZE: Errata.AbsolutePercentAgainstInnerSize,
ERRATA_ALL: Errata.All,
ERRATA_CLASSIC: Errata.Classic,

View File

@@ -105,8 +105,8 @@ const char* YGErrataToString(const YGErrata value) {
return "none";
case YGErrataStretchFlexBasis:
return "stretch-flex-basis";
case YGErrataAbsolutePositioningIncorrect:
return "absolute-positioning-incorrect";
case YGErrataAbsolutePositionWithoutInsetsExcludesPadding:
return "absolute-position-without-insets-excludes-padding";
case YGErrataAbsolutePercentAgainstInnerSize:
return "absolute-percent-against-inner-size";
case YGErrataAll:

View File

@@ -61,7 +61,7 @@ YG_ENUM_DECL(
YGErrata,
YGErrataNone = 0,
YGErrataStretchFlexBasis = 1,
YGErrataAbsolutePositioningIncorrect = 2,
YGErrataAbsolutePositionWithoutInsetsExcludesPadding = 2,
YGErrataAbsolutePercentAgainstInnerSize = 4,
YGErrataAll = 2147483647,
YGErrataClassic = 2147483646)

View File

@@ -19,12 +19,15 @@ static inline void setFlexStartLayoutPosition(
const Direction direction,
const FlexDirection axis,
const float containingBlockWidth) {
child->setLayoutPosition(
child->style().computeFlexStartMargin(
float position = child->style().computeFlexStartMargin(
axis, direction, containingBlockWidth) +
parent->getLayout().border(flexStartEdge(axis)) +
parent->getLayout().padding(flexStartEdge(axis)),
flexStartEdge(axis));
parent->getLayout().border(flexStartEdge(axis));
if (!child->hasErrata(Errata::AbsolutePositionWithoutInsetsExcludesPadding)) {
position += parent->getLayout().padding(flexStartEdge(axis));
}
child->setLayoutPosition(position, flexStartEdge(axis));
}
static inline void setFlexEndLayoutPosition(
@@ -33,15 +36,16 @@ static inline void setFlexEndLayoutPosition(
const Direction direction,
const FlexDirection axis,
const float containingBlockWidth) {
child->setLayoutPosition(
getPositionOfOppositeEdge(
parent->getLayout().border(flexEndEdge(axis)) +
parent->getLayout().padding(flexEndEdge(axis)) +
float flexEndPosition = parent->getLayout().border(flexEndEdge(axis)) +
child->style().computeFlexEndMargin(
axis, direction, containingBlockWidth),
axis,
parent,
child),
axis, direction, containingBlockWidth);
if (!child->hasErrata(Errata::AbsolutePositionWithoutInsetsExcludesPadding)) {
flexEndPosition += parent->getLayout().padding(flexEndEdge(axis));
}
child->setLayoutPosition(
getPositionOfOppositeEdge(flexEndPosition, axis, parent, child),
flexStartEdge(axis));
}
@@ -51,22 +55,30 @@ static inline void setCenterLayoutPosition(
const Direction direction,
const FlexDirection axis,
const float containingBlockWidth) {
const float parentContentBoxSize =
float parentContentBoxSize =
parent->getLayout().measuredDimension(dimension(axis)) -
parent->getLayout().border(flexStartEdge(axis)) -
parent->getLayout().border(flexEndEdge(axis)) -
parent->getLayout().padding(flexStartEdge(axis)) -
parent->getLayout().padding(flexEndEdge(axis));
parent->getLayout().border(flexEndEdge(axis));
if (!child->hasErrata(Errata::AbsolutePositionWithoutInsetsExcludesPadding)) {
parentContentBoxSize -= parent->getLayout().padding(flexStartEdge(axis));
parentContentBoxSize -= parent->getLayout().padding(flexEndEdge(axis));
}
const float childOuterSize =
child->getLayout().measuredDimension(dimension(axis)) +
child->style().computeMarginForAxis(axis, containingBlockWidth);
child->setLayoutPosition(
(parentContentBoxSize - childOuterSize) / 2.0f +
float position = (parentContentBoxSize - childOuterSize) / 2.0f +
parent->getLayout().border(flexStartEdge(axis)) +
parent->getLayout().padding(flexStartEdge(axis)) +
child->style().computeFlexStartMargin(
axis, direction, containingBlockWidth),
flexStartEdge(axis));
axis, direction, containingBlockWidth);
if (!child->hasErrata(Errata::AbsolutePositionWithoutInsetsExcludesPadding)) {
position += parent->getLayout().padding(flexStartEdge(axis));
}
child->setLayoutPosition(position, flexStartEdge(axis));
}
static void justifyAbsoluteChild(
@@ -133,62 +145,6 @@ static void alignAbsoluteChild(
}
}
// To ensure no breaking changes, we preserve the legacy way of positioning
// absolute children and determine if we should use it using an errata.
static void positionAbsoluteChildLegacy(
const yoga::Node* const containingNode,
const yoga::Node* const parent,
yoga::Node* child,
const Direction direction,
const FlexDirection axis,
const bool isMainAxis,
const float containingBlockWidth,
const float containingBlockHeight) {
const bool isAxisRow = isRow(axis);
const bool shouldCenter = isMainAxis
? parent->style().justifyContent() == Justify::Center
: resolveChildAlignment(parent, child) == Align::Center;
const bool shouldFlexEnd = isMainAxis
? parent->style().justifyContent() == Justify::FlexEnd
: ((resolveChildAlignment(parent, child) == Align::FlexEnd) ^
(parent->style().flexWrap() == Wrap::WrapReverse));
if (child->style().isFlexEndPositionDefined(axis, direction) &&
(!child->style().isFlexStartPositionDefined(axis, direction) ||
child->style().isFlexStartPositionAuto(axis, direction))) {
child->setLayoutPosition(
containingNode->getLayout().measuredDimension(dimension(axis)) -
child->getLayout().measuredDimension(dimension(axis)) -
containingNode->style().computeFlexEndBorder(axis, direction) -
child->style().computeFlexEndMargin(
axis,
direction,
isAxisRow ? containingBlockWidth : containingBlockHeight) -
child->style().computeFlexEndPosition(
axis,
direction,
isAxisRow ? containingBlockWidth : containingBlockHeight),
flexStartEdge(axis));
} else if (
(!child->style().isFlexStartPositionDefined(axis, direction) ||
child->style().isFlexStartPositionAuto(axis, direction)) &&
shouldCenter) {
child->setLayoutPosition(
(parent->getLayout().measuredDimension(dimension(axis)) -
child->getLayout().measuredDimension(dimension(axis))) /
2.0f,
flexStartEdge(axis));
} else if (
(!child->style().isFlexStartPositionDefined(axis, direction) ||
child->style().isFlexStartPositionAuto(axis, direction)) &&
shouldFlexEnd) {
child->setLayoutPosition(
(parent->getLayout().measuredDimension(dimension(axis)) -
child->getLayout().measuredDimension(dimension(axis))),
flexStartEdge(axis));
}
}
/*
* Absolutely positioned nodes do not participate in flex layout and thus their
* positions can be determined independently from the rest of their siblings.
@@ -205,7 +161,7 @@ static void positionAbsoluteChildLegacy(
* This function does that positioning for the given axis. The spec has more
* information on this topic: https://www.w3.org/TR/css-flexbox-1/#abspos-items
*/
static void positionAbsoluteChildImpl(
static void positionAbsoluteChild(
const yoga::Node* const containingNode,
const yoga::Node* const parent,
yoga::Node* child,
@@ -267,36 +223,6 @@ static void positionAbsoluteChildImpl(
}
}
static void positionAbsoluteChild(
const yoga::Node* const containingNode,
const yoga::Node* const parent,
yoga::Node* child,
const Direction direction,
const FlexDirection axis,
const bool isMainAxis,
const float containingBlockWidth,
const float containingBlockHeight) {
child->hasErrata(Errata::AbsolutePositioningIncorrect)
? positionAbsoluteChildLegacy(
containingNode,
parent,
child,
direction,
axis,
isMainAxis,
containingBlockWidth,
containingBlockHeight)
: positionAbsoluteChildImpl(
containingNode,
parent,
child,
direction,
axis,
isMainAxis,
containingBlockWidth,
containingBlockHeight);
}
void layoutAbsoluteChild(
const yoga::Node* const containingNode,
const yoga::Node* const node,

View File

@@ -981,7 +981,6 @@ static void resolveFlexibleLength(
static void justifyMainAxis(
yoga::Node* const node,
FlexLine& flexLine,
const size_t startOfLineIndex,
const FlexDirection mainAxis,
const FlexDirection crossAxis,
const Direction direction,
@@ -1081,33 +1080,8 @@ static void justifyMainAxis(
float maxAscentForCurrentLine = 0;
float maxDescentForCurrentLine = 0;
bool isNodeBaselineLayout = isBaselineLayout(node);
for (size_t i = startOfLineIndex; i < flexLine.endOfLineIndex; i++) {
const auto child = node->getChild(i);
const Style& childStyle = child->style();
for (auto child : flexLine.itemsInFlow) {
const LayoutResults& childLayout = child->getLayout();
if (childStyle.display() == Display::None) {
continue;
}
if (childStyle.positionType() == PositionType::Absolute &&
child->style().isFlexStartPositionDefined(mainAxis, direction) &&
!child->style().isFlexStartPositionAuto(mainAxis, direction)) {
if (performLayout) {
// In case the child is position absolute and has left/top being
// defined, we override the position to whatever the user said (and
// margin/border).
child->setLayoutPosition(
child->style().computeFlexStartPosition(
mainAxis, direction, availableInnerMainDim) +
node->style().computeFlexStartBorder(mainAxis, direction) +
child->style().computeFlexStartMargin(
mainAxis, direction, availableInnerWidth),
flexStartEdge(mainAxis));
}
} else {
// Now that we placed the element, we need to update the variables.
// We need to do that only for relative elements. Absolute elements do not
// take part in that phase.
if (childStyle.positionType() != PositionType::Absolute) {
if (child->style().flexStartMarginIsAuto(mainAxis, direction) &&
flexLine.layout.remainingFreeSpace > 0.0f) {
flexLine.layout.mainDim += flexLine.layout.remainingFreeSpace /
@@ -1136,8 +1110,8 @@ static void justifyMainAxis(
// If we skipped the flex step, then we can't rely on the measuredDims
// because they weren't computed. This means we can't call
// dimensionWithMargin.
flexLine.layout.mainDim += child->style().computeMarginForAxis(
mainAxis, availableInnerWidth) +
flexLine.layout.mainDim +=
child->style().computeMarginForAxis(mainAxis, availableInnerWidth) +
childLayout.computedFlexBasis.unwrap();
flexLine.layout.crossDim = availableInnerCrossDim;
} else {
@@ -1171,14 +1145,6 @@ static void justifyMainAxis(
child->dimensionWithMargin(crossAxis, availableInnerWidth));
}
}
} else if (performLayout) {
child->setLayoutPosition(
childLayout.position(flexStartEdge(mainAxis)) +
node->style().computeFlexStartBorder(mainAxis, direction) +
leadingMainDim,
flexStartEdge(mainAxis));
}
}
}
flexLine.layout.mainDim += trailingPaddingAndBorderMain;
@@ -1616,7 +1582,6 @@ static void calculateLayoutImpl(
justifyMainAxis(
node,
flexLine,
startOfLineIndex,
mainAxis,
crossAxis,
direction,
@@ -1668,39 +1633,7 @@ static void calculateLayoutImpl(
// STEP 7: CROSS-AXIS ALIGNMENT
// We can skip child alignment if we're just measuring the container.
if (performLayout) {
for (size_t i = startOfLineIndex; i < endOfLineIndex; i++) {
const auto child = node->getChild(i);
if (child->style().display() == Display::None) {
continue;
}
if (child->style().positionType() == PositionType::Absolute) {
// If the child is absolutely positioned and has a
// top/left/bottom/right set, override all the previously computed
// positions to set it correctly.
const bool isChildLeadingPosDefined =
child->style().isFlexStartPositionDefined(crossAxis, direction) &&
!child->style().isFlexStartPositionAuto(crossAxis, direction);
if (isChildLeadingPosDefined) {
child->setLayoutPosition(
child->style().computeFlexStartPosition(
crossAxis, direction, availableInnerCrossDim) +
node->style().computeFlexStartBorder(crossAxis, direction) +
child->style().computeFlexStartMargin(
crossAxis, direction, availableInnerWidth),
flexStartEdge(crossAxis));
}
// If leading position is not defined or calculations result in Nan,
// default to border + margin
if (!isChildLeadingPosDefined ||
yoga::isUndefined(
child->getLayout().position(flexStartEdge(crossAxis)))) {
child->setLayoutPosition(
node->style().computeFlexStartBorder(crossAxis, direction) +
child->style().computeFlexStartMargin(
crossAxis, direction, availableInnerWidth),
flexStartEdge(crossAxis));
}
} else {
for (auto child : flexLine.itemsInFlow) {
float leadingCrossDim = leadingPaddingAndBorderCross;
// For a relative children, we're either using alignItems (owner) or
@@ -1791,10 +1724,8 @@ static void calculateLayoutImpl(
if (child->style().flexStartMarginIsAuto(crossAxis, direction) &&
child->style().flexEndMarginIsAuto(crossAxis, direction)) {
leadingCrossDim +=
yoga::maxOrDefined(0.0f, remainingCrossDim / 2);
} else if (child->style().flexEndMarginIsAuto(
crossAxis, direction)) {
leadingCrossDim += yoga::maxOrDefined(0.0f, remainingCrossDim / 2);
} else if (child->style().flexEndMarginIsAuto(crossAxis, direction)) {
// No-Op
} else if (child->style().flexStartMarginIsAuto(
crossAxis, direction)) {
@@ -1814,7 +1745,6 @@ static void calculateLayoutImpl(
flexStartEdge(crossAxis));
}
}
}
const float appliedCrossGap = lineCount != 0 ? crossAxisGap : 0.0f;
totalLineCrossDim += flexLine.layout.crossDim + appliedCrossGap;

View File

@@ -18,7 +18,7 @@ namespace facebook::yoga {
enum class Errata : uint32_t {
None = YGErrataNone,
StretchFlexBasis = YGErrataStretchFlexBasis,
AbsolutePositioningIncorrect = YGErrataAbsolutePositioningIncorrect,
AbsolutePositionWithoutInsetsExcludesPadding = YGErrataAbsolutePositionWithoutInsetsExcludesPadding,
AbsolutePercentAgainstInnerSize = YGErrataAbsolutePercentAgainstInnerSize,
All = YGErrataAll,
Classic = YGErrataClassic,