[css-flex] Collect UseCounters for potentially breaking change
It would be nice to let developers apply align-content to single-line
flex containers, as discussed in
https://github.com/w3c/csswg-drafts/issues/3052, but there might be
compat problems.
Change-Id: I98bf1a8e647f2fcb7b45040501c9e8c361282b86
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3159204
Commit-Queue: David Grogan <dgrogan@chromium.org>
Reviewed-by: Christian Biesinger <cbiesinger@chromium.org>
Cr-Commit-Position: refs/heads/main@{#928536}
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index 21c105ec..3282ca6 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -3364,6 +3364,7 @@
kV8Navigator_FinalizeAd_Method = 4054,
kRegionCapture = 4055,
kAppHistory = 4056,
+ kFlexboxAlignSingleLineDifference = 4057,
// Add new features immediately above this line. Don't change assigned
// numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
index a42b31b..f2070aec 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
@@ -332,6 +332,23 @@
void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() {
NGFlexChildIterator iterator(Node());
+
+ // This block sets up data collection for
+ // https://github.com/w3c/csswg-drafts/issues/3052
+ bool all_items_have_non_auto_cross_sizes = true;
+ bool all_items_match_container_alignment = true;
+ const StyleContentAlignmentData align_content =
+ algorithm_.ResolvedAlignContent(Style());
+ bool is_alignment_behavior_change_possible =
+ !algorithm_.IsMultiline() &&
+ align_content.Distribution() != ContentDistributionType::kStretch &&
+ align_content.GetPosition() != ContentPosition::kBaseline;
+ const LayoutUnit kAvailableFreeSpace(100);
+ LayoutUnit line_offset = FlexLayoutAlgorithm::InitialContentPositionOffset(
+ Style(), kAvailableFreeSpace, align_content,
+ /* number_of_items */ 1,
+ /* is_reversed */ false);
+
for (NGBlockNode child = iterator.NextChild(); child;
child = iterator.NextChild()) {
if (child.IsOutOfFlowPositioned()) {
@@ -340,6 +357,16 @@
}
const ComputedStyle& child_style = child.Style();
+ if (is_alignment_behavior_change_possible &&
+ all_items_match_container_alignment) {
+ LayoutUnit item_offset = FlexItem::AlignmentOffset(
+ kAvailableFreeSpace,
+ FlexLayoutAlgorithm::AlignmentForChild(Style(), child_style),
+ LayoutUnit(), LayoutUnit(), /* is_wrap_reverse */ false,
+ Style().IsDeprecatedWebkitBox());
+ all_items_match_container_alignment = (item_offset == line_offset);
+ }
+
NGConstraintSpace flex_basis_space = BuildSpaceForFlexBasis(child);
NGPhysicalBoxStrut physical_child_margins =
@@ -362,6 +389,7 @@
const Length& cross_axis_length =
is_horizontal_flow_ ? child.Style().Height() : child.Style().Width();
+ all_items_have_non_auto_cross_sizes &= !cross_axis_length.IsAuto();
absl::optional<MinMaxSizesResult> min_max_sizes;
auto MinMaxSizesFunc = [&](MinMaxSizesType type) -> MinMaxSizesResult {
@@ -619,6 +647,20 @@
algorithm_.all_items_.back().layout_result_ = layout_result;
}
}
+
+ if (is_alignment_behavior_change_possible &&
+ algorithm_.all_items_.size() > 0 &&
+ !all_items_match_container_alignment) {
+ if (algorithm_.IsColumnFlow()) {
+ if (all_items_have_non_auto_cross_sizes) {
+ UseCounter::Count(node_.GetDocument(),
+ WebFeature::kFlexboxAlignSingleLineDifference);
+ }
+ } else if (is_cross_size_definite_) {
+ UseCounter::Count(node_.GetDocument(),
+ WebFeature::kFlexboxAlignSingleLineDifference);
+ }
+ }
}
LayoutUnit
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm_test.cc b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm_test.cc
index 5907b03..0314b8f 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm_test.cc
@@ -212,5 +212,337 @@
EXPECT_EQ(devtools.lines.size(), 1u);
}
+// Current: item is at top of container.
+// Proposed: item is at bottom of container.
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter1) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: flex-end; height: 50px">
+ <div style="height:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter1b) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: flex-end; height: 50px; flex-wrap: wrap;">
+ <div style="height:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter2) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: baseline; height: 50px">
+ <div style="height:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter2b) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; height: 50px; align-content: end;">
+ <div style="height:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter2c) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; height: 50px; align-content: end;">
+ <div style="height:20px; align-self: baseline;">other stuff</div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter2d) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; height: 50px; align-content: end;">
+ <div style="align-self: baseline;">other stuff</div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter2e) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; height: 50px; align-content: start;">
+ <div style="align-self: baseline;">other stuff</div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter2f) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; height: 50px; align-content: center;">
+ <div style="align-self: baseline;">other stuff</div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter2g) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; height: 50px; align-content: end;">
+ <div style="align-self: baseline;">blah<br>blah</div>
+ <div style="align-self: baseline;">other stuff</div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter3) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: initial; height: 50px">
+ <div style="height:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter4) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: stretch; height: 50px">
+ <div style="height:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter5) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: flex-start; height: 50px">
+ <div style="height:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter6) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; height: 50px">
+ <div style="height:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter7) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: flex-end;">
+ <div style="height:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+// Current: item gets 50px height.
+// Proposed: item gets 0px height and abuts bottom edge of container.
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter9) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: flex-end; height: 50px;">
+ <div></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+// Current: item abuts left edge of container.
+// Proposed: item abuts right edge of container.
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter10) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; flex-flow: column; align-content: flex-end;">
+ <div style="width:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter11) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; flex-flow: column; align-content: flex-end;">
+ <div style="width:20px;"></div>
+ <div></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+// Current: items abut left edge of container.
+// Proposed: items abut right edge of container.
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter12) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; flex-flow: column; align-content: flex-end;">
+ <div style="width:20px;"></div>
+ <div style="width:20px;"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter14) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; flex-flow: column; align-content: flex-end; width: 200px">
+ <div style="align-self: flex-end"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter15) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; flex-flow: column; align-content: flex-end; width: 200px">
+ <div style="align-self: flex-end; width: 100px;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+// Current: item at top
+// Proposed: item at bottom
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter15b) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: end; height: 200px">
+ <div style="align-self: flex-start; height: 100px;"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter15c) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: end; height: 200px;">
+ <div style="height: 100px; align-self: self-end;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+// Current: item at top
+// Proposed: item in center
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter15d) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: space-around; height: 200px;">
+ <div style="height: 100px;"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter15e) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: space-around; height: 200px;">
+ <div style="height: 100px; align-self: center;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter15f) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: space-between; height: 200px;">
+ <div style="height: 100px;"></div>
+ </div>
+ )HTML");
+ EXPECT_FALSE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+// Current: first item is on the top
+// Proposed: first item is on the bottom
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter15g) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: end; height: 200px;">
+ <div style="height: 100px; align-self: start"></div>
+ <div style="height: 100px; align-self: end"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+// Current: item is on the right.
+// Proposed: item is on the left.
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter16) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; flex-flow: column; align-content: flex-start; width: 200px">
+ <div style="align-self: flex-end; width: 100px;"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+// Current: first item's right edge abuts container's right edge
+// second item is horizontally centered
+// Proposal: both abut container's right edge
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter17) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; flex-flow: column; align-content: flex-end; width: 200px">
+ <div style="align-self: flex-end; width: 100px;"></div>
+ <div style="align-self: center; width: 100px;"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+// Current: first item's bottom edge abuts container's bottom edge
+// second item is vertically centered
+// Proposal: both abut container's bottom edge
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter18) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; align-content: flex-end; height: 200px">
+ <div style="align-self: flex-end; height: 100px;"></div>
+ <div style="align-self: center; height: 100px;"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
+// This case has no behavior change but checking the used width of each item
+// against the flex container's width is too difficult without fully
+// implementing the new behavior.
+TEST_F(NGFlexLayoutAlgorithmTest, UseCounter19) {
+ SetBodyInnerHTML(R"HTML(
+ <div style="display: flex; flex-flow: column; align-content: flex-end; width: 20px">
+ <div style="width:20px;"></div>
+ <div style="width:10px;"></div>
+ </div>
+ )HTML");
+ EXPECT_TRUE(GetDocument().IsUseCounted(
+ WebFeature::kFlexboxAlignSingleLineDifference));
+}
+
} // namespace
} // namespace blink
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 50da24d5..7384e276 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -34753,6 +34753,7 @@
<int value="4054" label="V8Navigator_FinalizeAd_Method"/>
<int value="4055" label="RegionCapture"/>
<int value="4056" label="AppHistory"/>
+ <int value="4057" label="FlexboxAlignSingleLineDifference"/>
</enum>
<enum name="FeaturePolicyAllowlistType">