mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Add support for normalization anchor-size css value
vibed this. Seems esoteric, but it helps over 1000 WPT cases pass in /css/css-anchor-position/anchor-size-parse-valid.html
This commit is contained in:
@@ -341,3 +341,43 @@
|
|||||||
testing.expectEqual('10px 20px', div.style.gap);
|
testing.expectEqual('10px 20px', div.style.gap);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id="CSSStyleDeclaration_normalize_anchor_size">
|
||||||
|
{
|
||||||
|
// anchor-size() should serialize with dashed ident (anchor name) before size keyword
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
// Already canonical order - should stay the same
|
||||||
|
div.style.width = 'anchor-size(--foo width)';
|
||||||
|
testing.expectEqual('anchor-size(--foo width)', div.style.width);
|
||||||
|
|
||||||
|
// Non-canonical order - should be reordered
|
||||||
|
div.style.width = 'anchor-size(width --foo)';
|
||||||
|
testing.expectEqual('anchor-size(--foo width)', div.style.width);
|
||||||
|
|
||||||
|
// With fallback value
|
||||||
|
div.style.width = 'anchor-size(height --bar, 100px)';
|
||||||
|
testing.expectEqual('anchor-size(--bar height, 100px)', div.style.width);
|
||||||
|
|
||||||
|
// Different size keywords
|
||||||
|
div.style.width = 'anchor-size(block --baz)';
|
||||||
|
testing.expectEqual('anchor-size(--baz block)', div.style.width);
|
||||||
|
|
||||||
|
div.style.width = 'anchor-size(inline --qux)';
|
||||||
|
testing.expectEqual('anchor-size(--qux inline)', div.style.width);
|
||||||
|
|
||||||
|
div.style.width = 'anchor-size(self-block --test)';
|
||||||
|
testing.expectEqual('anchor-size(--test self-block)', div.style.width);
|
||||||
|
|
||||||
|
div.style.width = 'anchor-size(self-inline --test)';
|
||||||
|
testing.expectEqual('anchor-size(--test self-inline)', div.style.width);
|
||||||
|
|
||||||
|
// Without anchor name (implicit default anchor)
|
||||||
|
div.style.width = 'anchor-size(width)';
|
||||||
|
testing.expectEqual('anchor-size(width)', div.style.width);
|
||||||
|
|
||||||
|
// Nested anchor-size in fallback
|
||||||
|
div.style.width = 'anchor-size(width --foo, anchor-size(height --bar))';
|
||||||
|
testing.expectEqual('anchor-size(--foo width, anchor-size(--bar height))', div.style.width);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -258,9 +258,194 @@ fn normalizePropertyValue(arena: Allocator, property_name: []const u8, value: []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Canonicalize anchor-size() function: anchor name (dashed ident) comes before size keyword
|
||||||
|
if (std.mem.indexOf(u8, value, "anchor-size(") != null) {
|
||||||
|
return try canonicalizeAnchorSize(arena, value);
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Canonicalize anchor-size() so that the dashed ident (anchor name) comes before the size keyword.
|
||||||
|
// e.g. "anchor-size(width --foo)" -> "anchor-size(--foo width)"
|
||||||
|
fn canonicalizeAnchorSize(arena: Allocator, value: []const u8) ![]const u8 {
|
||||||
|
var buf = std.Io.Writer.Allocating.init(arena);
|
||||||
|
var i: usize = 0;
|
||||||
|
|
||||||
|
while (i < value.len) {
|
||||||
|
// Look for "anchor-size("
|
||||||
|
if (std.mem.startsWith(u8, value[i..], "anchor-size(")) {
|
||||||
|
try buf.writer.writeAll("anchor-size(");
|
||||||
|
i += "anchor-size(".len;
|
||||||
|
|
||||||
|
// Parse and canonicalize the arguments
|
||||||
|
i = try canonicalizeAnchorSizeArgs(value, i, &buf.writer);
|
||||||
|
} else {
|
||||||
|
try buf.writer.writeByte(value[i]);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.written();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse anchor-size arguments and write them in canonical order
|
||||||
|
fn canonicalizeAnchorSizeArgs(value: []const u8, start: usize, writer: *std.Io.Writer) !usize {
|
||||||
|
var i = start;
|
||||||
|
var depth: usize = 1;
|
||||||
|
|
||||||
|
// Skip leading whitespace
|
||||||
|
while (i < value.len and value[i] == ' ') : (i += 1) {}
|
||||||
|
|
||||||
|
// Collect tokens before the comma or close paren
|
||||||
|
var first_token_start: ?usize = null;
|
||||||
|
var first_token_end: usize = 0;
|
||||||
|
var second_token_start: ?usize = null;
|
||||||
|
var second_token_end: usize = 0;
|
||||||
|
var comma_pos: ?usize = null;
|
||||||
|
var token_count: usize = 0;
|
||||||
|
|
||||||
|
const args_start = i;
|
||||||
|
var in_token = false;
|
||||||
|
|
||||||
|
// First pass: find the structure of arguments before comma/closing paren at depth 1
|
||||||
|
while (i < value.len and depth > 0) {
|
||||||
|
const c = value[i];
|
||||||
|
|
||||||
|
if (c == '(') {
|
||||||
|
depth += 1;
|
||||||
|
in_token = true;
|
||||||
|
i += 1;
|
||||||
|
} else if (c == ')') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth == 0) {
|
||||||
|
if (in_token) {
|
||||||
|
if (token_count == 0) {
|
||||||
|
first_token_end = i;
|
||||||
|
} else if (token_count == 1) {
|
||||||
|
second_token_end = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
} else if (c == ',' and depth == 1) {
|
||||||
|
if (in_token) {
|
||||||
|
if (token_count == 0) {
|
||||||
|
first_token_end = i;
|
||||||
|
} else if (token_count == 1) {
|
||||||
|
second_token_end = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
comma_pos = i;
|
||||||
|
break;
|
||||||
|
} else if (c == ' ') {
|
||||||
|
if (in_token and depth == 1) {
|
||||||
|
if (token_count == 0) {
|
||||||
|
first_token_end = i;
|
||||||
|
token_count = 1;
|
||||||
|
} else if (token_count == 1 and second_token_start != null) {
|
||||||
|
second_token_end = i;
|
||||||
|
token_count = 2;
|
||||||
|
}
|
||||||
|
in_token = false;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
if (!in_token and depth == 1) {
|
||||||
|
if (token_count == 0) {
|
||||||
|
first_token_start = i;
|
||||||
|
} else if (token_count == 1) {
|
||||||
|
second_token_start = i;
|
||||||
|
}
|
||||||
|
in_token = true;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle end of tokens
|
||||||
|
if (in_token and token_count == 1 and second_token_start != null) {
|
||||||
|
second_token_end = i;
|
||||||
|
token_count = 2;
|
||||||
|
} else if (in_token and token_count == 0) {
|
||||||
|
first_token_end = i;
|
||||||
|
token_count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have exactly two tokens that need reordering
|
||||||
|
if (token_count == 2) {
|
||||||
|
const first_start = first_token_start orelse args_start;
|
||||||
|
const second_start = second_token_start orelse first_token_end;
|
||||||
|
|
||||||
|
const first_token = value[first_start..first_token_end];
|
||||||
|
const second_token = value[second_start..second_token_end];
|
||||||
|
|
||||||
|
// If second token is a dashed ident and first is a size keyword, swap them
|
||||||
|
if (std.mem.startsWith(u8, second_token, "--") and isAnchorSizeKeyword(first_token)) {
|
||||||
|
try writer.writeAll(second_token);
|
||||||
|
try writer.writeByte(' ');
|
||||||
|
try writer.writeAll(first_token);
|
||||||
|
} else {
|
||||||
|
// Keep original order
|
||||||
|
try writer.writeAll(first_token);
|
||||||
|
try writer.writeByte(' ');
|
||||||
|
try writer.writeAll(second_token);
|
||||||
|
}
|
||||||
|
} else if (first_token_start) |fts| {
|
||||||
|
// Single token, just copy it
|
||||||
|
try writer.writeAll(value[fts..first_token_end]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle comma and fallback value (may contain nested anchor-size)
|
||||||
|
if (comma_pos) |cp| {
|
||||||
|
try writer.writeAll(", ");
|
||||||
|
i = cp + 1;
|
||||||
|
// Skip whitespace after comma
|
||||||
|
while (i < value.len and value[i] == ' ') : (i += 1) {}
|
||||||
|
|
||||||
|
// Copy the fallback, recursively handling nested anchor-size
|
||||||
|
while (i < value.len and depth > 0) {
|
||||||
|
if (std.mem.startsWith(u8, value[i..], "anchor-size(")) {
|
||||||
|
try writer.writeAll("anchor-size(");
|
||||||
|
i += "anchor-size(".len;
|
||||||
|
depth += 1;
|
||||||
|
i = try canonicalizeAnchorSizeArgs(value, i, writer);
|
||||||
|
depth -= 1;
|
||||||
|
} else if (value[i] == '(') {
|
||||||
|
depth += 1;
|
||||||
|
try writer.writeByte(value[i]);
|
||||||
|
i += 1;
|
||||||
|
} else if (value[i] == ')') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth == 0) break;
|
||||||
|
try writer.writeByte(value[i]);
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
try writer.writeByte(value[i]);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write closing paren
|
||||||
|
try writer.writeByte(')');
|
||||||
|
|
||||||
|
return i + 1; // Skip past the closing paren
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isAnchorSizeKeyword(token: []const u8) bool {
|
||||||
|
const keywords = std.StaticStringMap(void).initComptime(.{
|
||||||
|
.{ "width", {} },
|
||||||
|
.{ "height", {} },
|
||||||
|
.{ "block", {} },
|
||||||
|
.{ "inline", {} },
|
||||||
|
.{ "self-block", {} },
|
||||||
|
.{ "self-inline", {} },
|
||||||
|
});
|
||||||
|
return keywords.has(token);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if a value is "X X" (duplicate) and return just "X"
|
// Check if a value is "X X" (duplicate) and return just "X"
|
||||||
fn collapseDuplicateValue(value: []const u8) ?[]const u8 {
|
fn collapseDuplicateValue(value: []const u8) ?[]const u8 {
|
||||||
const space_idx = std.mem.indexOfScalar(u8, value, ' ') orelse return null;
|
const space_idx = std.mem.indexOfScalar(u8, value, ' ') orelse return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user