From 6a7f7fdf15e4923bf065afe38fac960a642807db Mon Sep 17 00:00:00 2001 From: egrs Date: Tue, 10 Mar 2026 13:53:27 +0100 Subject: [PATCH] extend CSS value normalization to cover more properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add missing properties to isLengthProperty (0→0px) and isTwoValueShorthand (duplicate value collapse) maps based on WPT test failures in css/css-sizing, css/css-align, css/css-scroll-snap, css/css-logical, and others. New length properties: scroll-margin/padding-*, column-width, column-rule-width, grid-column/row-gap, outline, shape-margin, offset-distance, translate, animation-range-*, block-step-size, text-decoration-inset, and *-rule-*-inset (CSS Gaps). New two-value shorthands: scroll-padding-block/inline, scroll-snap-align, background-size, border-image-repeat, mask-repeat/size, contain-intrinsic-size, scale, text-box-edge, animation-range, grid-gap. --- src/browser/tests/css/stylesheet.html | 38 +++++++ .../webapi/css/CSSStyleDeclaration.zig | 102 ++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/src/browser/tests/css/stylesheet.html b/src/browser/tests/css/stylesheet.html index e717e806..ec7928af 100644 --- a/src/browser/tests/css/stylesheet.html +++ b/src/browser/tests/css/stylesheet.html @@ -293,6 +293,28 @@ div.style.top = '0'; testing.expectEqual('0px', div.style.top); + // Scroll properties + div.style.scrollMarginTop = '0'; + testing.expectEqual('0px', div.style.scrollMarginTop); + + div.style.scrollPaddingBottom = '0'; + testing.expectEqual('0px', div.style.scrollPaddingBottom); + + // Multi-column + div.style.columnWidth = '0'; + testing.expectEqual('0px', div.style.columnWidth); + + div.style.columnRuleWidth = '0'; + testing.expectEqual('0px', div.style.columnRuleWidth); + + // Outline shorthand + div.style.outline = '0'; + testing.expectEqual('0px', div.style.outline); + + // Shapes + div.style.shapeMargin = '0'; + testing.expectEqual('0px', div.style.shapeMargin); + // Non-length properties should not be affected div.style.opacity = '0'; testing.expectEqual('0', div.style.opacity); @@ -313,6 +335,12 @@ div.style.alignContent = 'first baseline'; testing.expectEqual('baseline', div.style.alignContent); + div.style.alignSelf = 'first baseline'; + testing.expectEqual('baseline', div.style.alignSelf); + + div.style.justifySelf = 'first baseline'; + testing.expectEqual('baseline', div.style.justifySelf); + // "last baseline" should remain unchanged div.style.alignItems = 'last baseline'; testing.expectEqual('last baseline', div.style.alignItems); @@ -339,6 +367,16 @@ div.style.gap = '10px 20px'; testing.expectEqual('10px 20px', div.style.gap); + + // New shorthands + div.style.overflow = 'hidden hidden'; + testing.expectEqual('hidden', div.style.overflow); + + div.style.scrollSnapAlign = 'start start'; + testing.expectEqual('start', div.style.scrollSnapAlign); + + div.style.overscrollBehavior = 'auto auto'; + testing.expectEqual('auto', div.style.overscrollBehavior); } diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig index adc8982a..2430f0c4 100644 --- a/src/browser/webapi/css/CSSStyleDeclaration.zig +++ b/src/browser/webapi/css/CSSStyleDeclaration.zig @@ -483,6 +483,21 @@ fn isTwoValueShorthand(name: []const u8) bool { .{ "overflow", {} }, .{ "overscroll-behavior", {} }, .{ "gap", {} }, + .{ "grid-gap", {} }, + // Scroll + .{ "scroll-padding-block", {} }, + .{ "scroll-padding-inline", {} }, + .{ "scroll-snap-align", {} }, + // Background/Mask + .{ "background-size", {} }, + .{ "border-image-repeat", {} }, + .{ "mask-repeat", {} }, + .{ "mask-size", {} }, + // Other + .{ "contain-intrinsic-size", {} }, + .{ "scale", {} }, + .{ "text-box-edge", {} }, + .{ "animation-range", {} }, }); return shorthands.has(name); } @@ -561,17 +576,52 @@ fn isLengthProperty(name: []const u8) bool { .{ "row-gap", {} }, .{ "column-gap", {} }, .{ "flex-basis", {} }, + // Legacy grid aliases + .{ "grid-column-gap", {} }, + .{ "grid-row-gap", {} }, // Outline + .{ "outline", {} }, .{ "outline-width", {} }, .{ "outline-offset", {} }, + // Multi-column + .{ "column-rule-width", {} }, + .{ "column-width", {} }, + // Scroll + .{ "scroll-margin", {} }, + .{ "scroll-margin-top", {} }, + .{ "scroll-margin-right", {} }, + .{ "scroll-margin-bottom", {} }, + .{ "scroll-margin-left", {} }, + .{ "scroll-padding", {} }, + .{ "scroll-padding-top", {} }, + .{ "scroll-padding-right", {} }, + .{ "scroll-padding-bottom", {} }, + .{ "scroll-padding-left", {} }, + // Shapes + .{ "shape-margin", {} }, + // Motion path + .{ "offset-distance", {} }, + // Transforms + .{ "translate", {} }, + // Animations + .{ "animation-range-end", {} }, + .{ "animation-range-start", {} }, // Other .{ "border-spacing", {} }, .{ "text-shadow", {} }, .{ "box-shadow", {} }, .{ "baseline-shift", {} }, .{ "vertical-align", {} }, + .{ "text-decoration-inset", {} }, + .{ "block-step-size", {} }, // Grid lanes .{ "flow-tolerance", {} }, + .{ "column-rule-edge-inset", {} }, + .{ "column-rule-interior-inset", {} }, + .{ "row-rule-edge-inset", {} }, + .{ "row-rule-interior-inset", {} }, + .{ "rule-edge-inset", {} }, + .{ "rule-interior-inset", {} }, }); return length_properties.has(name); @@ -693,3 +743,55 @@ pub const JsApi = struct { pub const removeProperty = bridge.function(CSSStyleDeclaration.removeProperty, .{}); pub const cssFloat = bridge.accessor(CSSStyleDeclaration.getFloat, CSSStyleDeclaration.setFloat, .{}); }; + +const testing = @import("std").testing; + +test "normalizePropertyValue: unitless zero to 0px" { + const cases = .{ + .{ "width", "0", "0px" }, + .{ "height", "0", "0px" }, + .{ "scroll-margin-top", "0", "0px" }, + .{ "scroll-padding-bottom", "0", "0px" }, + .{ "column-width", "0", "0px" }, + .{ "column-rule-width", "0", "0px" }, + .{ "outline", "0", "0px" }, + .{ "shape-margin", "0", "0px" }, + .{ "offset-distance", "0", "0px" }, + .{ "translate", "0", "0px" }, + .{ "grid-column-gap", "0", "0px" }, + .{ "grid-row-gap", "0", "0px" }, + // Non-length properties should NOT normalize + .{ "opacity", "0", "0" }, + .{ "z-index", "0", "0" }, + }; + inline for (cases) |case| { + const result = try normalizePropertyValue(testing.allocator, case[0], case[1]); + try testing.expectEqualStrings(case[2], result); + } +} + +test "normalizePropertyValue: first baseline to baseline" { + const result = try normalizePropertyValue(testing.allocator, "align-items", "first baseline"); + try testing.expectEqualStrings("baseline", result); + + const result2 = try normalizePropertyValue(testing.allocator, "align-self", "last baseline"); + try testing.expectEqualStrings("last baseline", result2); +} + +test "normalizePropertyValue: collapse duplicate two-value shorthands" { + const cases = .{ + .{ "overflow", "hidden hidden", "hidden" }, + .{ "gap", "10px 10px", "10px" }, + .{ "scroll-snap-align", "start start", "start" }, + .{ "scroll-padding-block", "5px 5px", "5px" }, + .{ "background-size", "cover cover", "cover" }, + .{ "overscroll-behavior", "auto auto", "auto" }, + // Different values should NOT collapse + .{ "overflow", "hidden scroll", "hidden scroll" }, + .{ "gap", "10px 20px", "10px 20px" }, + }; + inline for (cases) |case| { + const result = try normalizePropertyValue(testing.allocator, case[0], case[1]); + try testing.expectEqualStrings(case[2], result); + } +}