From de3f5011bc67bb5159afaf1e8923ed9c1c0ae84d Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 19 Feb 2026 12:26:04 +0100 Subject: [PATCH 1/4] parse style attribute on CSSStyleDeclaration init To reflect the current style attribute, CSSStyleDeclaration now parses it on init. Moreover, this PR synchronizes the element's style attribute with the dynamic changes. --- src/browser/tests/css/stylesheet.html | 51 +++++++++++++++++++ .../webapi/css/CSSStyleDeclaration.zig | 43 ++++++++++++++-- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/browser/tests/css/stylesheet.html b/src/browser/tests/css/stylesheet.html index abc1ed92..ec14f4fc 100644 --- a/src/browser/tests/css/stylesheet.html +++ b/src/browser/tests/css/stylesheet.html @@ -205,3 +205,54 @@ testing.expectEqual('', style.getPropertyPriority('content')); } + + + + diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig index 8380b6f3..23b04170 100644 --- a/src/browser/webapi/css/CSSStyleDeclaration.zig +++ b/src/browser/webapi/css/CSSStyleDeclaration.zig @@ -33,10 +33,27 @@ _properties: std.DoublyLinkedList = .{}, _is_computed: bool = false, pub fn init(element: ?*Element, is_computed: bool, page: *Page) !*CSSStyleDeclaration { - return page._factory.create(CSSStyleDeclaration{ + const self = try page._factory.create(CSSStyleDeclaration{ ._element = element, ._is_computed = is_computed, }); + + // Parse the element's existing style attribute into _properties so that + // subsequent JS reads and writes see all CSS properties, not just newly + // added ones. Computed styles have no inline attribute to parse. + if (!is_computed) { + if (element) |el| { + if (el.getAttributeSafe(String.wrap("style"))) |attr_value| { + var it = CssParser.parseDeclarationsList(attr_value); + while (it.next()) |declaration| { + const priority: ?[]const u8 = if (declaration.important) "important" else null; + try self.setPropertyImpl(declaration.name, declaration.value, priority, page); + } + } + } + } + + return self; } pub fn length(self: *const CSSStyleDeclaration) u32 { @@ -76,8 +93,13 @@ pub fn getPropertyPriority(self: *const CSSStyleDeclaration, property_name: []co } pub fn setProperty(self: *CSSStyleDeclaration, property_name: []const u8, value: []const u8, priority_: ?[]const u8, page: *Page) !void { + try self.setPropertyImpl(property_name, value, priority_, page); + try self.syncStyleAttribute(page); +} + +fn setPropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, value: []const u8, priority_: ?[]const u8, page: *Page) !void { if (value.len == 0) { - _ = try self.removeProperty(property_name, page); + _ = try self.removePropertyImpl(property_name, page); return; } @@ -110,6 +132,12 @@ pub fn setProperty(self: *CSSStyleDeclaration, property_name: []const u8, value: } pub fn removeProperty(self: *CSSStyleDeclaration, property_name: []const u8, page: *Page) ![]const u8 { + const result = try self.removePropertyImpl(property_name, page); + try self.syncStyleAttribute(page); + return result; +} + +fn removePropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, page: *Page) ![]const u8 { const normalized = normalizePropertyName(property_name, &page.buf); const prop = self.findProperty(normalized) orelse return ""; @@ -121,6 +149,14 @@ pub fn removeProperty(self: *CSSStyleDeclaration, property_name: []const u8, pag return old_value; } +// Serialize current properties back to the element's style attribute so that +// DOM serialization (outerHTML, getAttribute) reflects JS-modified styles. +fn syncStyleAttribute(self: *CSSStyleDeclaration, page: *Page) !void { + const element = self._element orelse return; + const css_text = try self.getCssText(page); + try element.setAttributeSafe(String.wrap("style"), String.wrap(css_text), page); +} + pub fn getFloat(self: *const CSSStyleDeclaration, page: *Page) []const u8 { return self.getPropertyValue("float", page); } @@ -154,8 +190,9 @@ pub fn setCssText(self: *CSSStyleDeclaration, text: []const u8, page: *Page) !vo var it = CssParser.parseDeclarationsList(text); while (it.next()) |declaration| { const priority: ?[]const u8 = if (declaration.important) "important" else null; - try self.setProperty(declaration.name, declaration.value, priority, page); + try self.setPropertyImpl(declaration.name, declaration.value, priority, page); } + try self.syncStyleAttribute(page); } pub fn format(self: *const CSSStyleDeclaration, writer: *std.Io.Writer) !void { From 7263d484deca733b496ba9cb52111c880e003913 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 19 Feb 2026 14:36:02 +0100 Subject: [PATCH 2/4] Update src/browser/webapi/css/CSSStyleDeclaration.zig Co-authored-by: Karl Seguin --- src/browser/webapi/css/CSSStyleDeclaration.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig index 23b04170..13c08f76 100644 --- a/src/browser/webapi/css/CSSStyleDeclaration.zig +++ b/src/browser/webapi/css/CSSStyleDeclaration.zig @@ -43,7 +43,7 @@ pub fn init(element: ?*Element, is_computed: bool, page: *Page) !*CSSStyleDeclar // added ones. Computed styles have no inline attribute to parse. if (!is_computed) { if (element) |el| { - if (el.getAttributeSafe(String.wrap("style"))) |attr_value| { + if (el.getAttributeSafe(comptime .wrap("style"))) |attr_value| { var it = CssParser.parseDeclarationsList(attr_value); while (it.next()) |declaration| { const priority: ?[]const u8 = if (declaration.important) "important" else null; From 5be977005e8bf6ab956a28fddf9cbc24db65e0d6 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 19 Feb 2026 14:42:05 +0100 Subject: [PATCH 3/4] avoid useless priority parsing in CSSStyleDeclaration --- .../webapi/css/CSSStyleDeclaration.zig | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig index 13c08f76..3764ac8e 100644 --- a/src/browser/webapi/css/CSSStyleDeclaration.zig +++ b/src/browser/webapi/css/CSSStyleDeclaration.zig @@ -46,8 +46,7 @@ pub fn init(element: ?*Element, is_computed: bool, page: *Page) !*CSSStyleDeclar if (el.getAttributeSafe(comptime .wrap("style"))) |attr_value| { var it = CssParser.parseDeclarationsList(attr_value); while (it.next()) |declaration| { - const priority: ?[]const u8 = if (declaration.important) "important" else null; - try self.setPropertyImpl(declaration.name, declaration.value, priority, page); + try self.setPropertyImpl(declaration.name, declaration.value, declaration.important, page); } } } @@ -93,20 +92,8 @@ pub fn getPropertyPriority(self: *const CSSStyleDeclaration, property_name: []co } pub fn setProperty(self: *CSSStyleDeclaration, property_name: []const u8, value: []const u8, priority_: ?[]const u8, page: *Page) !void { - try self.setPropertyImpl(property_name, value, priority_, page); - try self.syncStyleAttribute(page); -} - -fn setPropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, value: []const u8, priority_: ?[]const u8, page: *Page) !void { - if (value.len == 0) { - _ = try self.removePropertyImpl(property_name, page); - return; - } - - const normalized = normalizePropertyName(property_name, &page.buf); - const priority = priority_ orelse ""; - // Validate priority + const priority = priority_ orelse ""; const important = if (priority.len > 0) blk: { if (!std.ascii.eqlIgnoreCase(priority, "important")) { return; @@ -114,6 +101,19 @@ fn setPropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, value: break :blk true; } else false; + try self.setPropertyImpl(property_name, value, important, page); + + try self.syncStyleAttribute(page); +} + +fn setPropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, value: []const u8, important: bool, page: *Page) !void { + if (value.len == 0) { + _ = try self.removePropertyImpl(property_name, page); + return; + } + + const normalized = normalizePropertyName(property_name, &page.buf); + // Find existing property if (self.findProperty(normalized)) |existing| { existing._value = try String.init(page.arena, value, .{}); @@ -162,7 +162,8 @@ pub fn getFloat(self: *const CSSStyleDeclaration, page: *Page) []const u8 { } pub fn setFloat(self: *CSSStyleDeclaration, value_: ?[]const u8, page: *Page) !void { - return self.setProperty("float", value_ orelse "", null, page); + try self.setPropertyImpl("float", value_ orelse "", false, page); + try self.syncStyleAttribute(page); } pub fn getCssText(self: *const CSSStyleDeclaration, page: *Page) ![]const u8 { @@ -189,8 +190,7 @@ pub fn setCssText(self: *CSSStyleDeclaration, text: []const u8, page: *Page) !vo // Parse and set new properties var it = CssParser.parseDeclarationsList(text); while (it.next()) |declaration| { - const priority: ?[]const u8 = if (declaration.important) "important" else null; - try self.setPropertyImpl(declaration.name, declaration.value, priority, page); + try self.setPropertyImpl(declaration.name, declaration.value, declaration.important, page); } try self.syncStyleAttribute(page); } From 19fd2b12c036a029ae5a0e31bae7ef98439b450c Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 19 Feb 2026 15:29:36 +0100 Subject: [PATCH 4/4] Update src/browser/webapi/css/CSSStyleDeclaration.zig Co-authored-by: Karl Seguin --- src/browser/webapi/css/CSSStyleDeclaration.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig index 3764ac8e..5f61c607 100644 --- a/src/browser/webapi/css/CSSStyleDeclaration.zig +++ b/src/browser/webapi/css/CSSStyleDeclaration.zig @@ -154,7 +154,7 @@ fn removePropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, pag fn syncStyleAttribute(self: *CSSStyleDeclaration, page: *Page) !void { const element = self._element orelse return; const css_text = try self.getCssText(page); - try element.setAttributeSafe(String.wrap("style"), String.wrap(css_text), page); + try element.setAttributeSafe(comptime .wrap("style"), .wrap(css_text), page); } pub fn getFloat(self: *const CSSStyleDeclaration, page: *Page) []const u8 {