Skip to content

Commit a5ca7e1

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 Changelog: [Internal] 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 1cc648f commit a5ca7e1

File tree

18 files changed

+283
-53
lines changed

18 files changed

+283
-53
lines changed

packages/react-native/React/Views/RCTLayout.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,8 @@ RCTDisplayType RCTReactDisplayTypeFromYogaDisplayType(YGDisplay displayType)
132132
return RCTDisplayTypeFlex;
133133
case YGDisplayNone:
134134
return RCTDisplayTypeNone;
135+
case YGDisplayContents:
136+
RCTAssert(NO, @"YGDisplayContents cannot be converted to RCTDisplayType value.");
137+
return RCTDisplayTypeNone;
135138
}
136139
}

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

Lines changed: 3 additions & 1 deletion
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
}

packages/react-native/ReactCommon/react/renderer/components/view/conversions.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ static inline PositionType positionTypeFromYogaPositionType(
119119
}
120120
}
121121

122+
inline DisplayType displayTypeFromYGDisplay(YGDisplay display) {
123+
switch (display) {
124+
case YGDisplayNone:
125+
return DisplayType::None;
126+
case YGDisplayContents:
127+
return DisplayType::Contents;
128+
case YGDisplayFlex:
129+
return DisplayType::Flex;
130+
}
131+
}
132+
122133
inline LayoutMetrics layoutMetricsFromYogaNode(yoga::Node& yogaNode) {
123134
auto layoutMetrics = LayoutMetrics{};
124135

@@ -146,9 +157,8 @@ inline LayoutMetrics layoutMetricsFromYogaNode(yoga::Node& yogaNode) {
146157
layoutMetrics.borderWidth.bottom +
147158
floatFromYogaFloat(YGNodeLayoutGetPadding(&yogaNode, YGEdgeBottom))};
148159

149-
layoutMetrics.displayType = yogaNode.style().display() == yoga::Display::None
150-
? DisplayType::None
151-
: DisplayType::Flex;
160+
layoutMetrics.displayType =
161+
displayTypeFromYGDisplay(YGNodeStyleGetDisplay(&yogaNode));
152162

153163
layoutMetrics.positionType =
154164
positionTypeFromYogaPositionType(yogaNode.style().positionType());

packages/react-native/ReactCommon/react/renderer/core/LayoutPrimitives.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace facebook::react {
1919
enum class DisplayType {
2020
None = 0,
2121
Flex = 1,
22-
Inline = 2,
22+
Contents = 2,
2323
};
2424

2525
enum class PositionType {

packages/react-native/ReactCommon/react/renderer/core/conversions.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ inline int toInt(const DisplayType& displayType) {
3939
return 0;
4040
case DisplayType::Flex:
4141
return 1;
42-
case DisplayType::Inline:
42+
case DisplayType::Contents:
4343
return 2;
4444
}
4545
}
@@ -50,8 +50,8 @@ inline std::string toString(const DisplayType& displayType) {
5050
return "none";
5151
case DisplayType::Flex:
5252
return "flex";
53-
case DisplayType::Inline:
54-
return "inline";
53+
case DisplayType::Contents:
54+
return "contents";
5555
}
5656
}
5757

packages/react-native/ReactCommon/react/renderer/dom/DOM.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,7 @@ DOMSizeRounded getScrollSize(
380380
*shadowNodeInCurrentRevision,
381381
{.includeTransform = false});
382382

383-
if (layoutMetrics == EmptyLayoutMetrics ||
384-
layoutMetrics.displayType == DisplayType::Inline) {
383+
if (layoutMetrics == EmptyLayoutMetrics) {
385384
return DOMSizeRounded{};
386385
}
387386

@@ -417,8 +416,7 @@ DOMSizeRounded getInnerSize(
417416
*shadowNodeInCurrentRevision,
418417
{.includeTransform = false});
419418

420-
if (layoutMetrics == EmptyLayoutMetrics ||
421-
layoutMetrics.displayType == DisplayType::Inline) {
419+
if (layoutMetrics == EmptyLayoutMetrics) {
422420
return DOMSizeRounded{};
423421
}
424422

@@ -445,8 +443,7 @@ DOMBorderWidthRounded getBorderWidth(
445443
*shadowNodeInCurrentRevision,
446444
{.includeTransform = false});
447445

448-
if (layoutMetrics == EmptyLayoutMetrics ||
449-
layoutMetrics.displayType == DisplayType::Inline) {
446+
if (layoutMetrics == EmptyLayoutMetrics) {
450447
return DOMBorderWidthRounded{};
451448
}
452449

packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp

Lines changed: 2 additions & 0 deletions
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
}

packages/react-native/ReactCommon/yoga/yoga/YGEnums.h

Lines changed: 2 additions & 1 deletion
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,

packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ bool layoutAbsoluteDescendants(
434434
float containingNodeAvailableInnerWidth,
435435
float containingNodeAvailableInnerHeight) {
436436
bool hasNewLayout = false;
437-
for (auto child : currentNode->getChildren()) {
437+
for (auto child : currentNode->getLayoutChildren()) {
438438
if (child->style().display() == Display::None) {
439439
continue;
440440
} else if (child->style().positionType() == PositionType::Absolute) {

packages/react-native/ReactCommon/yoga/yoga/algorithm/Baseline.cpp

Lines changed: 2 additions & 6 deletions
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;

0 commit comments

Comments
 (0)