Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ inline bool grid_layout_equal(
if (!layout_length_equal(left_track.minimum, right_track.minimum)
|| !layout_length_equal(left_track.maximum, right_track.maximum)
|| left_track.fraction != right_track.fraction
|| left_track.maximum_is_auto != right_track.maximum_is_auto
|| left_track.kind != right_track.kind) {
return false;
}
Expand Down Expand Up @@ -132,6 +133,7 @@ inline bool computed_layout_style_equal(
&& left.direction == right.direction
&& left.align_items == right.align_items
&& left.align_self == right.align_self
&& left.align_content_stretches == right.align_content_stretches
&& left.justify_content == right.justify_content
&& left.overflow_x == right.overflow_x
&& left.overflow_y == right.overflow_y
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ inline void reset_cascaded_style(dom_node& node,
node.style.align_self = align_mode::stretch;
node.style.align_self_specified = false;
}
if ((node.style.inline_property_mask & inline_align_content) == 0U) {
node.style.align_content_stretches = true;
}
if ((node.style.inline_property_mask & inline_justify_content) == 0U) {
node.style.justify_content = justify_mode::start;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ inline std::vector<node_style::grid_data::track> parse_simple_grid_tracks(
if (token == "auto" || token == "max-content") {
grid_track track;
track.kind = grid_track::sizing::automatic;
track.maximum_is_auto = token == "auto";
return track;
}
if (token == "min-content") {
Expand Down Expand Up @@ -106,6 +107,7 @@ inline std::vector<node_style::grid_data::track> parse_simple_grid_tracks(
track.minimum = native_document::parse_length(std::string(minimum));
}
track.fraction = parse_fraction(maximum);
track.maximum_is_auto = maximum == "auto";
if (track.fraction <= 0 && maximum != "auto"
&& maximum != "min-content" && maximum != "max-content") {
track.maximum = native_document::parse_length(std::string(maximum));
Expand Down Expand Up @@ -498,6 +500,8 @@ bool apply_flex_value(dom_node& node,const std::string& name,const std::string&
: value == "flex-end" || value == "end" ? align_mode::end
: value == "baseline" || value == "first baseline" ? align_mode::baseline
: align_mode::stretch;
} else if (name == "align-content" && !is_inline(inline_align_content)) {
node.style.align_content_stretches = value == "normal" || value == "stretch";
} else if (name == "justify-content" && !is_inline(inline_justify_content)) {
node.style.justify_content = value == "center" ? justify_mode::center
: value == "flex-end" || value == "end" ? justify_mode::end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ enum inline_style_property : uint64_t {
inline_scrollbar_width = 1ULL << 58U,
inline_scrollbar_color = 1ULL << 59U,
inline_svg_stroke_width = 1ULL << 60U,
inline_align_content = 1ULL << 61U,
inline_transition = inline_transition_property | inline_transition_duration
| inline_transition_delay | inline_transition_timing
};
Expand Down Expand Up @@ -152,6 +153,7 @@ inline uint64_t property_mask(std::string_view name)
}
if (name == "align-items") return inline_align_items;
if (name == "align-self") return inline_align_self;
if (name == "align-content" || name == "alignContent") return inline_align_content;
if (name == "justify-content") return inline_justify_content;
if (name == "gap" || name == "row-gap" || name == "column-gap"
|| name == "rowGap" || name == "columnGap") return inline_gap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ inline void apply_all_unset(dom_node& node)
reset.align_self = previous.align_self;
reset.align_self_specified = previous.align_self_specified;
}
if (is_inline(inline_align_content)) {
reset.align_content_stretches = previous.align_content_stretches;
}
if (is_inline(inline_justify_content)) {
reset.justify_content = previous.justify_content;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ struct node_style final {
css_length maximum{};
float fraction{0};
sizing kind{sizing::automatic};
bool maximum_is_auto{false};
};

struct named_area final {
Expand Down Expand Up @@ -732,6 +733,7 @@ struct node_style final {
align_mode align_items{align_mode::stretch};
align_mode align_self{align_mode::stretch};
justify_mode justify_content{justify_mode::start};
bool align_content_stretches : 1 {true};
overflow_mode overflow_x{overflow_mode::visible};
overflow_mode overflow_y{overflow_mode::visible};
bool outline_current_color : 1 {false};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2382,7 +2382,8 @@ void native_document::layout_children(dom_node& parent)
}
const auto single_auto_row = row_count == 1U
&& (parent_grid.template_rows.empty()
|| parent_grid.template_rows[0].kind == grid_track::sizing::automatic);
|| (parent_grid.template_rows[0].kind == grid_track::sizing::automatic
&& parent_grid.template_rows[0].maximum_is_auto));
if (single_auto_row && parent.used_height_is_definite) {
// An auto track's max-content growth limit is not its minimum.
// Scrollable items and explicit min-height can allow the row to
Expand All @@ -2395,18 +2396,18 @@ void native_document::layout_children(dom_node& parent)
row_heights.begin(), row_heights.end(), total_row_gap);
if (fractional_row_weight > 0) {
distribute_fractions(row_heights,parent_grid.template_rows,content.height,total_row_gap);
} else if (single_auto_row
} else if (parent.used_height_is_definite
&& parent.style.align_content_stretches
&& committed_height < content.height) {
// The common one-row implicit grid case stretches its auto row to
// the definite container height. Keep this compatibility path
// scoped until multi-row align-content distribution is modeled;
// distributing every multi-row auto grid here can rewrite an
// application's primary layout when it has other alignment rules.
// `align-content: normal` behaves as stretch for grid containers.
// Distribute remaining block space only to tracks whose maximum
// sizing function is auto. In particular, minmax(min-content,
// auto) grows while fixed, percentage, max-content, and flexible
// tracks keep their resolved sizes.
std::vector<size_t> stretchable_rows;
for (size_t row = 0; row < row_count; ++row) {
if (row >= parent_grid.template_rows.size()
|| parent_grid.template_rows[row].kind
== grid_track::sizing::automatic) {
|| parent_grid.template_rows[row].maximum_is_auto) {
stretchable_rows.push_back(row);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ struct v8_dom_runtime::implementation final {
"left", "top", "right", "bottom", "inset", "insetInlineStart", "insetInlineEnd",
"display", "position", "contain", "cssFloat", "flexDirection", "flexFlow",
"flexGrow", "flexShrink", "flexBasis", "flexWrap",
"alignItems", "alignSelf", "justifyContent", "gap", "rowGap", "columnGap",
"alignItems", "alignSelf", "alignContent", "justifyContent", "gap", "rowGap", "columnGap",
"gridGap", "gridRowGap", "gridColumnGap",
"padding", "paddingInline", "paddingBlock",
"paddingLeft", "paddingTop", "paddingRight", "paddingBottom",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@
node.style.align_self = align_mode::stretch;
node.style.align_self_specified = false;
node.style.inline_property_mask &= ~inline_align_self;
} else if (name == "alignContent" || name == "align-content") {
node.style.align_content_stretches = true;
node.style.inline_property_mask &= ~inline_align_content;
} else if (name == "justifyContent" || name == "justify-content") {
node.style.justify_content = justify_mode::start;
node.style.inline_property_mask &= ~inline_justify_content;
Expand Down Expand Up @@ -708,6 +711,9 @@
: value == "baseline" || value == "first baseline" ? align_mode::baseline
: align_mode::stretch;
node->style.inline_property_mask |= inline_align_self;
} else if (name == "align-content") {
node->style.align_content_stretches = value == "normal" || value == "stretch";
node->style.inline_property_mask |= inline_align_content;
} else if (name == "justify-content") {
node->style.justify_content = value == "center" ? justify_mode::center
: value == "flex-end" || value == "end" ? justify_mode::end
Expand Down Expand Up @@ -1553,6 +1559,8 @@
: node->style.align_self == align_mode::start ? "flex-start"
: node->style.align_self == align_mode::end ? "flex-end"
: node->style.align_self == align_mode::baseline ? "baseline" : "stretch";
} else if (name == "alignContent") {
value = node->style.align_content_stretches ? "stretch" : "flex-start";
} else if (name == "justifyContent") {
value = node->style.justify_content == justify_mode::center ? "center"
: node->style.justify_content == justify_mode::end ? "flex-end"
Expand Down Expand Up @@ -2097,6 +2105,9 @@
: value == "baseline" || value == "first baseline" ? align_mode::baseline
: align_mode::stretch;
node->style.inline_property_mask |= inline_align_self;
} else if (name == "alignContent") {
node->style.align_content_stretches = value == "normal" || value == "stretch";
node->style.inline_property_mask |= inline_align_content;
} else if (name == "justifyContent") {
node->style.justify_content = value == "center" ? justify_mode::center
: value == "flex-end" || value == "end" ? justify_mode::end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,63 @@ void test_named_grid_template_areas_layout_cssom_and_mutation(webscene_engine* e
+ result);
}

void test_grid_auto_maximum_tracks_stretch_remaining_space(webscene_engine* engine)
{
resize(engine, 700, 400, 202U);
const auto result = evaluate(engine, R"JS(
(() => {
document.body.innerHTML = `
<style>
html,body { margin:0; }
#grid {
display:grid;width:700px;height:400px;
grid-template-columns:700px;
grid-template-rows:25% minmax(min-content,auto) min-content;
grid-template-areas:"header" "middle" "footer";
}
.nonstretch { align-content:flex-start; }
#header { grid-area:header;height:40px; }
#middle { grid-area:middle;height:200px; }
#footer { grid-area:footer;height:18px; }
</style>
<div id="grid"><div id="header"></div><div id="middle"></div><div id="footer"></div></div>`;
const rect = id => {
const value = document.getElementById(id).getBoundingClientRect();
return [value.x,value.y,value.width,value.height].map(number =>
Math.round(number * 1000) / 1000);
};
const grid = document.getElementById('grid');
const stretched = rect('footer');
grid.style.alignContent = 'flex-start';
const flexStart = rect('footer');
grid.style.removeProperty('align-content');
const restored = rect('footer');
grid.className = 'nonstretch';
const stylesheet = rect('footer');
grid.style.alignContent = 'stretch';
const inlineOverride = rect('footer');
grid.style.removeProperty('align-content');
const cascadeRestored = rect('footer');
grid.className = '';

const started = performance.now();
let publicationChecksum = 0;
for (let index = 0; index < 500; index++) {
grid.style.height = index % 2 ? '400px' : '500px';
publicationChecksum += rect('footer')[1];
}
const elapsed = performance.now() - started;
if (elapsed > 2000) throw new Error(`auto-max publication gate exceeded: ${elapsed}ms`);
grid.style.height = '400px';
return {stretched,flexStart,restored,stylesheet,inlineOverride,cascadeRestored,
final:rect('footer'),publicationChecksumPositive:publicationChecksum>0};
})()
)JS", "native-grid-auto-max-stretch.js");
require(
result == R"JSON({"stretched":[0,382,700,18],"flexStart":[0,300,700,18],"restored":[0,382,700,18],"stylesheet":[0,300,700,18],"inlineOverride":[0,382,700,18],"cascadeRestored":[0,300,700,18],"final":[0,382,700,18],"publicationChecksumPositive":true})JSON",
"auto-maximum grid row stretch or mutation diverged: " + result);
}

void test_media_query_list_tracks_outer_and_frame_viewport_breakpoints(
webscene_engine* engine)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,13 @@ int main()
webscene_engine_destroy(focused_engine);
return 0;
}
if (selected == "grid-auto-max-stretch") {
auto* focused_engine = webscene_engine_create(0);
require(focused_engine != nullptr, "focused grid engine creation failed");
test_grid_auto_maximum_tracks_stretch_remaining_space(focused_engine);
webscene_engine_destroy(focused_engine);
return 0;
}
if (selected == "scrollbar-style-drag") {
auto* focused_engine = webscene_engine_create(64);
require(focused_engine != nullptr, "focused engine creation failed");
Expand Down Expand Up @@ -691,6 +698,7 @@ int main()
test_responsive_positioned_sizing(engine);
test_compact_go_to_fixed_grid_tracks_preserve_trailing_space(engine);
test_named_grid_template_areas_layout_cssom_and_mutation(engine);
test_grid_auto_maximum_tracks_stretch_remaining_space(engine);
test_go_to_tab_lines_and_calendar_scroll_ranges(engine);
test_media_query_list_tracks_outer_and_frame_viewport_breakpoints(engine);
test_responsive_unset_restores_auto_inset(engine);
Expand Down
25 changes: 18 additions & 7 deletions src/WebScene.Css/CssArrangementEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1214,8 +1214,10 @@ private static void ArrangeGrid(
{
if (TryParseFixedPixelTracks(style.GridTemplateColumns, out var fixedTracks))
{
_ = TryParseFixedPixelTracks(style.GridTemplateRows, out var fixedRowTracks);
ArrangeFixedPixelGrid(children, style, content, boxes, fixedTracks, fixedRowTracks);
var parsedRows = CssGridTrackList.TryParseRows(
style.GridTemplateRows, content.Height, out var rowTracks);
ArrangeFixedPixelGrid(children, style, content, boxes, fixedTracks, rowTracks,
parsedRows || string.IsNullOrWhiteSpace(style.GridTemplateRows));
return;
}

Expand Down Expand Up @@ -1250,7 +1252,8 @@ private static void ArrangeFixedPixelGrid(
WebSceneRect content,
Dictionary<long, CssLayoutBox> boxes,
IReadOnlyList<double> tracks,
IReadOnlyList<double> fixedRowTracks)
IReadOnlyList<CssGridTrack> rowTracks,
bool rowTemplateSupportsDistribution)
{
_ = CssGridTemplateAreas.TryParse(style.GridTemplateAreas, out var namedAreas);
var placements = new List<(CssLayoutNode Node, int Row, int Column, int RowSpan, int ColumnSpan, ResolvedMetrics Metrics)>();
Expand Down Expand Up @@ -1289,14 +1292,15 @@ private static void ArrangeFixedPixelGrid(
}

var usedRowCount = placements.Count == 0 ? 0 : placements.Max(item => item.Row + item.RowSpan);
var rowHeights = new double[Math.Max(fixedRowTracks.Count, usedRowCount)];
for (var index = 0; index < fixedRowTracks.Count; index++)
var rowHeights = new double[Math.Max(rowTracks.Count, usedRowCount)];
for (var index = 0; index < rowTracks.Count; index++)
{
rowHeights[index] = fixedRowTracks[index];
rowHeights[index] = rowTracks[index].BaseSize;
}
foreach (var item in placements)
{
if (item.RowSpan == 1 && item.Row >= fixedRowTracks.Count)
if (item.RowSpan == 1 && (item.Row >= rowTracks.Count
|| rowTracks[item.Row].AcceptsIntrinsicContribution))
{
rowHeights[item.Row] = Math.Max(
rowHeights[item.Row],
Expand All @@ -1305,6 +1309,13 @@ private static void ArrangeFixedPixelGrid(
}
var columnGap = Math.Max(0, style.ColumnGap.Resolve(content.Width) ?? 0);
var rowGap = Math.Max(0, style.RowGap.Resolve(content.Height) ?? 0);
CssGridTrackList.DistributeRemainingSpace(
rowHeights,
rowTracks,
content.Height,
rowGap * Math.Max(0, rowHeights.Length - 1),
rowTemplateSupportsDistribution
&& style.AlignContent == CssLayoutAlignContent.Stretch);
var columnOffsets = new double[tracks.Count];
for (var index = 1; index < columnOffsets.Length; index++)
{
Expand Down
Loading
Loading