Skip to content

Commit 792678d

Browse files
j-piaseckifacebook-github-bot
authored andcommitted
Add support for display: contents style (facebook#47035)
Summary: This PR adds support for `display: contents` style by effectively skipping nodes with `display: contents` set during layout. This required changes in the logic related to children traversal - before this PR a node would be always laid out in the context of its direct parent. After this PR that assumption is no longer true - `display: contents` allows nodes to be skipped, i.e.: ```html <div id="node1"> <div id="node2" style="display: contents;"> <div id="node3" /> </div> </div> ``` `node3` will be laid out as if it were a child of `node1`. Because of this, iterating over direct children of a node is no longer correct to achieve the correct layout. This PR introduces `LayoutableChildren::Iterator` which can traverse the subtree of a given node in a way that nodes with `display: contents` are replaced with their concrete children. A tree like this: ```mermaid flowchart TD A((A)) B((B)) C((C)) D((D)) E((E)) F((F)) G((G)) H((H)) I((I)) J((J)) A --> B A --> C B --> D B --> E C --> F D --> G F --> H G --> I H --> J style B fill:facebook/yoga#50 style C fill:facebook/yoga#50 style D fill:facebook/yoga#50 style H fill:facebook/yoga#50 style I fill:facebook/yoga#50 ``` would be laid out as if the green nodes (ones with `display: contents`) did not exist. It also changes the logic where children were accessed by index to use the iterator instead as random access would be non-trivial to implement and it's not really necessary - the iteration was always sequential and indices were only used as boundaries. There's one place where knowledge of layoutable children is required to calculate the gap. An optimization for this is for a node to keep a counter of how many `display: contents` nodes are its children. If there are none, a short path of just returning the size of the children vector can be taken, otherwise it needs to iterate over layoutable children and count them, since the structure may be complex. One more major change this PR introduces is `cleanupContentsNodesRecursively`. Since nodes with `display: contents` would be entirely skipped during the layout pass, they would keep previous metrics, would be kept as dirty, and, in the case of nested `contents` nodes, would not be cloned, breaking `doesOwn` relation. All of this is handled in the new method which clones `contents` nodes recursively, sets empty layout, and marks them as clean and having a new layout so that it can be used on the React Native side. Relies on facebook/yoga#1725 X-link: facebook/yoga#1726 Test Plan: Added tests for `display: contents` based on existing tests for `display: none` and ensured that all the tests were passing. Differential Revision: D64404340 Pulled By: NickGerleman
1 parent f302779 commit 792678d

File tree

13 files changed

+252
-40
lines changed

13 files changed

+252
-40
lines changed

Diff for: packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaDisplay.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
public enum YogaDisplay {
1313
FLEX(0),
14-
NONE(1);
14+
NONE(1),
15+
CONTENTS(2);
1516

1617
private final int mIntValue;
1718

@@ -27,6 +28,7 @@ public static YogaDisplay fromInt(int value) {
2728
switch (value) {
2829
case 0: return FLEX;
2930
case 1: return NONE;
31+
case 2: return CONTENTS;
3032
default: throw new IllegalArgumentException("Unknown enum value: " + value);
3133
}
3234
}

Diff for: packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ const char* YGDisplayToString(const YGDisplay value) {
7171
return "flex";
7272
case YGDisplayNone:
7373
return "none";
74+
case YGDisplayContents:
75+
return "contents";
7476
}
7577
return "unknown";
7678
}

Diff for: packages/react-native/ReactCommon/yoga/yoga/YGEnums.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ YG_ENUM_DECL(
4343
YG_ENUM_DECL(
4444
YGDisplay,
4545
YGDisplayFlex,
46-
YGDisplayNone)
46+
YGDisplayNone,
47+
YGDisplayContents)
4748

4849
YG_ENUM_DECL(
4950
YGEdge,

Diff for: packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ bool layoutAbsoluteDescendants(
541541
float containingNodeAvailableInnerWidth,
542542
float containingNodeAvailableInnerHeight) {
543543
bool hasNewLayout = false;
544-
for (auto child : currentNode->getChildren()) {
544+
for (auto child : currentNode->getLayoutChildren()) {
545545
if (child->style().display() == Display::None) {
546546
continue;
547547
} else if (child->style().positionType() == PositionType::Absolute) {

Diff for: packages/react-native/ReactCommon/yoga/yoga/algorithm/Baseline.cpp

+2-6
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ float calculateBaseline(const yoga::Node* node) {
3232
}
3333

3434
yoga::Node* baselineChild = nullptr;
35-
const size_t childCount = node->getChildCount();
36-
for (size_t i = 0; i < childCount; i++) {
37-
auto child = node->getChild(i);
35+
for (auto child : node->getLayoutChildren()) {
3836
if (child->getLineIndex() > 0) {
3937
break;
4038
}
@@ -67,9 +65,7 @@ bool isBaselineLayout(const yoga::Node* node) {
6765
if (node->style().alignItems() == Align::Baseline) {
6866
return true;
6967
}
70-
const auto childCount = node->getChildCount();
71-
for (size_t i = 0; i < childCount; i++) {
72-
auto child = node->getChild(i);
68+
for (auto child : node->getLayoutChildren()) {
7369
if (child->style().positionType() != PositionType::Absolute &&
7470
child->style().alignSelf() == Align::Baseline) {
7571
return true;

Diff for: packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp

+36-22
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,21 @@ static void zeroOutLayoutRecursively(yoga::Node* const node) {
476476
}
477477
}
478478

479+
static void cleanupContentsNodesRecursively(yoga::Node* const node) {
480+
for (auto child : node->getChildren()) {
481+
if (child->style().display() == Display::Contents) {
482+
child->getLayout() = {};
483+
child->setLayoutDimension(0, Dimension::Width);
484+
child->setLayoutDimension(0, Dimension::Height);
485+
child->setHasNewLayout(true);
486+
child->setDirty(false);
487+
child->cloneChildrenIfNeeded();
488+
489+
cleanupContentsNodesRecursively(child);
490+
}
491+
}
492+
}
493+
479494
static float calculateAvailableInnerDimension(
480495
const yoga::Node* const node,
481496
const Direction direction,
@@ -525,7 +540,7 @@ static float computeFlexBasisForChildren(
525540
const uint32_t generationCount) {
526541
float totalOuterFlexBasis = 0.0f;
527542
YGNodeRef singleFlexChild = nullptr;
528-
const auto& children = node->getChildren();
543+
const auto& children = node->getLayoutChildren();
529544
SizingMode sizingModeMainDim =
530545
isRow(mainAxis) ? widthSizingMode : heightSizingMode;
531546
// If there is only one child with flexGrow + flexShrink it means we can set
@@ -1316,7 +1331,7 @@ static void calculateLayoutImpl(
13161331
return;
13171332
}
13181333

1319-
const auto childCount = node->getChildCount();
1334+
const auto childCount = node->getLayoutChildCount();
13201335
if (childCount == 0) {
13211336
measureNodeWithoutChildren(
13221337
node,
@@ -1351,6 +1366,9 @@ static void calculateLayoutImpl(
13511366
// Reset layout flags, as they could have changed.
13521367
node->setLayoutHadOverflow(false);
13531368

1369+
// Clean and update all display: contents nodes with a direct path to the
1370+
// current node as they will not be traversed
1371+
cleanupContentsNodesRecursively(node);
13541372
// STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM
13551373
const FlexDirection mainAxis =
13561374
resolveDirection(node->style().flexDirection(), direction);
@@ -1436,9 +1454,9 @@ static void calculateLayoutImpl(
14361454
}
14371455
// STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES
14381456

1439-
// Indexes of children that represent the first and last items in the line.
1440-
size_t startOfLineIndex = 0;
1441-
size_t endOfLineIndex = 0;
1457+
// Iterator representing the beginning of the current line
1458+
Node::LayoutableChildren::Iterator startOfLineIterator =
1459+
node->getLayoutChildren().begin();
14421460

14431461
// Number of lines.
14441462
size_t lineCount = 0;
@@ -1451,20 +1469,17 @@ static void calculateLayoutImpl(
14511469

14521470
// Max main dimension of all the lines.
14531471
float maxLineMainDim = 0;
1454-
for (; endOfLineIndex < childCount;
1455-
lineCount++, startOfLineIndex = endOfLineIndex) {
1472+
for (; startOfLineIterator != node->getLayoutChildren().end(); lineCount++) {
14561473
auto flexLine = calculateFlexLine(
14571474
node,
14581475
ownerDirection,
14591476
ownerWidth,
14601477
mainAxisOwnerSize,
14611478
availableInnerWidth,
14621479
availableInnerMainDim,
1463-
startOfLineIndex,
1480+
startOfLineIterator,
14641481
lineCount);
14651482

1466-
endOfLineIndex = flexLine.endOfLineIndex;
1467-
14681483
// If we don't need to measure the cross axis, we can skip the entire flex
14691484
// step.
14701485
const bool canSkipFlex =
@@ -1816,17 +1831,18 @@ static void calculateLayoutImpl(
18161831
case Align::Baseline:
18171832
break;
18181833
}
1819-
size_t endIndex = 0;
1834+
Node::LayoutableChildren::Iterator endIterator =
1835+
node->getLayoutChildren().begin();
18201836
for (size_t i = 0; i < lineCount; i++) {
1821-
const size_t startIndex = endIndex;
1822-
size_t ii = startIndex;
1837+
const Node::LayoutableChildren::Iterator startIterator = endIterator;
1838+
auto iterator = startIterator;
18231839

18241840
// compute the line's height and find the endIndex
18251841
float lineHeight = 0;
18261842
float maxAscentForCurrentLine = 0;
18271843
float maxDescentForCurrentLine = 0;
1828-
for (; ii < childCount; ii++) {
1829-
const auto child = node->getChild(ii);
1844+
for (; iterator != node->getLayoutChildren().end(); iterator++) {
1845+
const auto child = *iterator;
18301846
if (child->style().display() == Display::None) {
18311847
continue;
18321848
}
@@ -1859,11 +1875,11 @@ static void calculateLayoutImpl(
18591875
}
18601876
}
18611877
}
1862-
endIndex = ii;
1878+
endIterator = iterator;
18631879
currentLead += i != 0 ? crossAxisGap : 0;
18641880

1865-
for (ii = startIndex; ii < endIndex; ii++) {
1866-
const auto child = node->getChild(ii);
1881+
for (iterator = startIterator; iterator != endIterator; iterator++) {
1882+
const auto child = *iterator;
18671883
if (child->style().display() == Display::None) {
18681884
continue;
18691885
}
@@ -2066,8 +2082,7 @@ static void calculateLayoutImpl(
20662082
// As we only wrapped in normal direction yet, we need to reverse the
20672083
// positions on wrap-reverse.
20682084
if (performLayout && node->style().flexWrap() == Wrap::WrapReverse) {
2069-
for (size_t i = 0; i < childCount; i++) {
2070-
const auto child = node->getChild(i);
2085+
for (auto child : node->getLayoutChildren()) {
20712086
if (child->style().positionType() != PositionType::Absolute) {
20722087
child->setLayoutPosition(
20732088
node->getLayout().measuredDimension(dimension(crossAxis)) -
@@ -2084,8 +2099,7 @@ static void calculateLayoutImpl(
20842099
const bool needsCrossTrailingPos = needsTrailingPosition(crossAxis);
20852100

20862101
if (needsMainTrailingPos || needsCrossTrailingPos) {
2087-
for (size_t i = 0; i < childCount; i++) {
2088-
const auto child = node->getChild(i);
2102+
for (auto child : node->getLayoutChildren()) {
20892103
// Absolute children will be handled by their containing block since we
20902104
// cannot guarantee that their positions are set when their parents are
20912105
// done with layout.

Diff for: packages/react-native/ReactCommon/yoga/yoga/algorithm/FlexLine.cpp

+7-6
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ FlexLine calculateFlexLine(
2020
const float mainAxisownerSize,
2121
const float availableInnerWidth,
2222
const float availableInnerMainDim,
23-
const size_t startOfLineIndex,
23+
Node::LayoutableChildren::Iterator& iterator,
2424
const size_t lineCount) {
2525
std::vector<yoga::Node*> itemsInFlow;
26-
itemsInFlow.reserve(node->getChildren().size());
26+
itemsInFlow.reserve(node->getChildCount());
2727

2828
float sizeConsumed = 0.0f;
2929
float totalFlexGrowFactors = 0.0f;
3030
float totalFlexShrinkScaledFactors = 0.0f;
3131
size_t numberOfAutoMargins = 0;
32-
size_t endOfLineIndex = startOfLineIndex;
33-
size_t firstElementInLineIndex = startOfLineIndex;
32+
size_t endOfLineIndex = iterator.index();
33+
size_t firstElementInLineIndex = iterator.index();
3434

3535
float sizeConsumedIncludingMinConstraint = 0;
3636
const Direction direction = node->resolveDirection(ownerDirection);
@@ -41,8 +41,9 @@ FlexLine calculateFlexLine(
4141
node->style().computeGapForAxis(mainAxis, availableInnerMainDim);
4242

4343
// Add items to the current line until it's full or we run out of items.
44-
for (; endOfLineIndex < node->getChildren().size(); endOfLineIndex++) {
45-
auto child = node->getChild(endOfLineIndex);
44+
for (; iterator != node->getLayoutChildren().end();
45+
iterator++, endOfLineIndex = iterator.index()) {
46+
auto child = *iterator;
4647
if (child->style().display() == Display::None ||
4748
child->style().positionType() == PositionType::Absolute) {
4849
if (firstElementInLineIndex == endOfLineIndex) {

Diff for: packages/react-native/ReactCommon/yoga/yoga/algorithm/FlexLine.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ FlexLine calculateFlexLine(
7272
float mainAxisownerSize,
7373
float availableInnerWidth,
7474
float availableInnerMainDim,
75-
size_t startOfLineIndex,
75+
Node::LayoutableChildren::Iterator& iterator,
7676
size_t lineCount);
7777

7878
} // namespace facebook::yoga

Diff for: packages/react-native/ReactCommon/yoga/yoga/algorithm/PixelGrid.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ void roundLayoutResultsToPixelGrid(
124124
Dimension::Height);
125125
}
126126

127-
for (yoga::Node* child : node->getChildren()) {
127+
for (yoga::Node* child : node->getLayoutChildren()) {
128128
roundLayoutResultsToPixelGrid(child, absoluteNodeLeft, absoluteNodeTop);
129129
}
130130
}

Diff for: packages/react-native/ReactCommon/yoga/yoga/enums/Display.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ namespace facebook::yoga {
1818
enum class Display : uint8_t {
1919
Flex = YGDisplayFlex,
2020
None = YGDisplayNone,
21+
Contents = YGDisplayContents,
2122
};
2223

2324
template <>
2425
constexpr int32_t ordinalCount<Display>() {
25-
return 2;
26+
return 3;
2627
}
2728

2829
constexpr Display scopedEnum(YGDisplay unscoped) {

0 commit comments

Comments
 (0)