mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-24 09:23:04 -04:00
Anchor(...) css property normalization
Expands on https://github.com/lightpanda-io/browser/pull/1754 to do for anchor(...) what we did for anchor-size(...) fixes a number of WPT tests in: /css/css-anchor-position/anchor-parse-valid.html
This commit is contained in:
@@ -256,8 +256,16 @@ 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);
|
||||
if (std.mem.indexOf(u8, value, "anchor-size(")) |idx| {
|
||||
return canonicalizeAnchorSize(arena, value, idx);
|
||||
}
|
||||
|
||||
// Canonicalize anchor() function: anchor name (dashed ident) comes before position keyword
|
||||
// Note: indexOf finds first occurrence, so we check it's not part of "anchor-size("
|
||||
if (std.mem.indexOf(u8, value, "anchor(")) |idx| {
|
||||
if (idx == 0 or value[idx - 1] != '-') {
|
||||
return canonicalizeAnchor(arena, value, idx);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
@@ -265,9 +273,13 @@ fn normalizePropertyValue(arena: Allocator, property_name: []const u8, 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 {
|
||||
fn canonicalizeAnchorSize(arena: Allocator, value: []const u8, start_index: usize) ![]const u8 {
|
||||
var buf = std.Io.Writer.Allocating.init(arena);
|
||||
var i: usize = 0;
|
||||
|
||||
// Copy everything before the first anchor-size(
|
||||
try buf.writer.writeAll(value[0..start_index]);
|
||||
|
||||
var i: usize = start_index;
|
||||
|
||||
while (i < value.len) {
|
||||
// Look for "anchor-size("
|
||||
@@ -276,7 +288,7 @@ fn canonicalizeAnchorSize(arena: Allocator, value: []const u8) ![]const u8 {
|
||||
i += "anchor-size(".len;
|
||||
|
||||
// Parse and canonicalize the arguments
|
||||
i = try canonicalizeAnchorSizeArgs(value, i, &buf.writer);
|
||||
i = try canonicalizeAnchorFnArgs(value, i, &buf.writer, .anchor_size);
|
||||
} else {
|
||||
try buf.writer.writeByte(value[i]);
|
||||
i += 1;
|
||||
@@ -286,21 +298,24 @@ fn canonicalizeAnchorSize(arena: Allocator, value: []const u8) ![]const u8 {
|
||||
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 {
|
||||
const AnchorFnKind = enum { anchor, anchor_size };
|
||||
|
||||
// Parse anchor/anchor-size arguments and write them in canonical order
|
||||
fn canonicalizeAnchorFnArgs(value: []const u8, start: usize, writer: *std.Io.Writer, kind: AnchorFnKind) !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;
|
||||
var comma_pos: ?usize = null;
|
||||
|
||||
var first_token_end: usize = 0;
|
||||
var first_token_start: ?usize = null;
|
||||
|
||||
var second_token_end: usize = 0;
|
||||
var second_token_start: ?usize = null;
|
||||
|
||||
const args_start = i;
|
||||
var in_token = false;
|
||||
@@ -378,13 +393,16 @@ fn canonicalizeAnchorSizeArgs(value: []const u8, start: usize, writer: *std.Io.W
|
||||
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)) {
|
||||
// If second token is a dashed ident, it should come first
|
||||
// For anchor-size, also check that first token is a size keyword
|
||||
const should_swap = std.mem.startsWith(u8, second_token, "--") and
|
||||
(kind == .anchor or isAnchorSizeKeyword(first_token));
|
||||
|
||||
if (should_swap) {
|
||||
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);
|
||||
@@ -394,20 +412,26 @@ fn canonicalizeAnchorSizeArgs(value: []const u8, start: usize, writer: *std.Io.W
|
||||
try writer.writeAll(value[fts..first_token_end]);
|
||||
}
|
||||
|
||||
// Handle comma and fallback value (may contain nested anchor-size)
|
||||
// Handle comma and fallback value (may contain nested functions)
|
||||
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
|
||||
// Copy the fallback, recursively handling nested anchor/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);
|
||||
i = try canonicalizeAnchorFnArgs(value, i, writer, .anchor_size);
|
||||
depth -= 1;
|
||||
} else if (std.mem.startsWith(u8, value[i..], "anchor(")) {
|
||||
try writer.writeAll("anchor(");
|
||||
i += "anchor(".len;
|
||||
depth += 1;
|
||||
i = try canonicalizeAnchorFnArgs(value, i, writer, .anchor);
|
||||
depth -= 1;
|
||||
} else if (value[i] == '(') {
|
||||
depth += 1;
|
||||
@@ -443,6 +467,33 @@ fn isAnchorSizeKeyword(token: []const u8) bool {
|
||||
return keywords.has(token);
|
||||
}
|
||||
|
||||
// Canonicalize anchor() so that the dashed ident (anchor name) comes before the position keyword.
|
||||
// e.g. "anchor(left --foo)" -> "anchor(--foo left)"
|
||||
fn canonicalizeAnchor(arena: Allocator, value: []const u8, start_index: usize) ![]const u8 {
|
||||
var buf = std.Io.Writer.Allocating.init(arena);
|
||||
|
||||
// Copy everything before the first anchor(
|
||||
try buf.writer.writeAll(value[0..start_index]);
|
||||
|
||||
var i: usize = start_index;
|
||||
|
||||
while (i < value.len) {
|
||||
// Look for "anchor(" but not "anchor-size("
|
||||
if (std.mem.startsWith(u8, value[i..], "anchor(") and (i == 0 or value[i - 1] != '-')) {
|
||||
try buf.writer.writeAll("anchor(");
|
||||
i += "anchor(".len;
|
||||
|
||||
// Parse and canonicalize the arguments
|
||||
i = try canonicalizeAnchorFnArgs(value, i, &buf.writer, .anchor);
|
||||
} else {
|
||||
try buf.writer.writeByte(value[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return buf.written();
|
||||
}
|
||||
|
||||
// Check if a value is "X X" (duplicate) and return just "X"
|
||||
fn collapseDuplicateValue(value: []const u8) ?[]const u8 {
|
||||
const space_idx = std.mem.indexOfScalar(u8, value, ' ') orelse return null;
|
||||
@@ -745,8 +796,7 @@ pub const JsApi = struct {
|
||||
pub const cssFloat = bridge.accessor(CSSStyleDeclaration.getFloat, CSSStyleDeclaration.setFloat, .{});
|
||||
};
|
||||
|
||||
const testing = @import("std").testing;
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "normalizePropertyValue: unitless zero to 0px" {
|
||||
const cases = .{
|
||||
.{ "width", "0", "0px" },
|
||||
@@ -767,16 +817,16 @@ test "normalizePropertyValue: unitless zero to 0px" {
|
||||
};
|
||||
inline for (cases) |case| {
|
||||
const result = try normalizePropertyValue(testing.allocator, case[0], case[1]);
|
||||
try testing.expectEqualStrings(case[2], result);
|
||||
try testing.expectEqual(case[2], result);
|
||||
}
|
||||
}
|
||||
|
||||
test "normalizePropertyValue: first baseline to baseline" {
|
||||
const result = try normalizePropertyValue(testing.allocator, "align-items", "first baseline");
|
||||
try testing.expectEqualStrings("baseline", result);
|
||||
try testing.expectEqual("baseline", result);
|
||||
|
||||
const result2 = try normalizePropertyValue(testing.allocator, "align-self", "last baseline");
|
||||
try testing.expectEqualStrings("last baseline", result2);
|
||||
try testing.expectEqual("last baseline", result2);
|
||||
}
|
||||
|
||||
test "normalizePropertyValue: collapse duplicate two-value shorthands" {
|
||||
@@ -793,6 +843,27 @@ test "normalizePropertyValue: collapse duplicate two-value shorthands" {
|
||||
};
|
||||
inline for (cases) |case| {
|
||||
const result = try normalizePropertyValue(testing.allocator, case[0], case[1]);
|
||||
try testing.expectEqualStrings(case[2], result);
|
||||
try testing.expectEqual(case[2], result);
|
||||
}
|
||||
}
|
||||
|
||||
test "normalizePropertyValue: anchor() canonical order" {
|
||||
defer testing.reset();
|
||||
const cases = .{
|
||||
// Dashed ident should come before keyword
|
||||
.{ "left", "anchor(left --foo)", "anchor(--foo left)" },
|
||||
.{ "left", "anchor(inside --foo)", "anchor(--foo inside)" },
|
||||
.{ "left", "anchor(50% --foo)", "anchor(--foo 50%)" },
|
||||
// Already canonical order - keep as-is
|
||||
.{ "left", "anchor(--foo left)", "anchor(--foo left)" },
|
||||
.{ "left", "anchor(left)", "anchor(left)" },
|
||||
// With fallback
|
||||
.{ "left", "anchor(left --foo, 1px)", "anchor(--foo left, 1px)" },
|
||||
// Nested anchor in fallback
|
||||
.{ "left", "anchor(left --foo, anchor(right --bar))", "anchor(--foo left, anchor(--bar right))" },
|
||||
};
|
||||
inline for (cases) |case| {
|
||||
const result = try normalizePropertyValue(testing.arena_allocator, case[0], case[1]);
|
||||
try testing.expectEqual(case[2], result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user