Merge pull request #1602 from lightpanda-io/css-delcaration

parse style attribute on CSSStyleDeclaration init
This commit is contained in:
Karl Seguin
2026-02-20 06:57:58 +08:00
committed by GitHub
2 changed files with 100 additions and 12 deletions

View File

@@ -205,3 +205,54 @@
testing.expectEqual('', style.getPropertyPriority('content'));
}
</script>
<script id="CSSStyleDeclaration_style_syncs_to_attribute">
{
// JS style modifications must be reflected in getAttribute.
const div = document.createElement('div');
// Named property assignment (element.style.X = ...)
div.style.opacity = '0';
testing.expectEqual('opacity: 0;', div.getAttribute('style'));
// Update existing property
div.style.opacity = '1';
testing.expectEqual('opacity: 1;', div.getAttribute('style'));
// Add a second property
div.style.color = 'red';
testing.expectTrue(div.getAttribute('style').includes('opacity: 1'));
testing.expectTrue(div.getAttribute('style').includes('color: red'));
// removeProperty syncs back
div.style.removeProperty('opacity');
testing.expectTrue(!div.getAttribute('style').includes('opacity'));
testing.expectTrue(div.getAttribute('style').includes('color: red'));
// setCssText syncs back
div.style.cssText = 'filter: blur(0px)';
testing.expectEqual('filter: blur(0px);', div.getAttribute('style'));
// setCssText with empty string clears attribute
div.style.cssText = '';
testing.expectEqual('', div.getAttribute('style'));
}
</script>
<script id="CSSStyleDeclaration_outerHTML_reflects_style_changes">
{
// outerHTML must reflect JS-modified styles (regression test for
// DOM serialization reading stale HTML-parsed attribute values).
const div = document.createElement('div');
div.setAttribute('style', 'filter:blur(10px);opacity:0');
div.style.filter = 'blur(0px)';
div.style.opacity = '1';
const html = div.outerHTML;
testing.expectTrue(html.includes('filter: blur(0px)'));
testing.expectTrue(html.includes('opacity: 1'));
testing.expectTrue(!html.includes('blur(10px)'));
testing.expectTrue(!html.includes('opacity:0'));
}
</script>

View File

@@ -33,10 +33,26 @@ _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(comptime .wrap("style"))) |attr_value| {
var it = CssParser.parseDeclarationsList(attr_value);
while (it.next()) |declaration| {
try self.setPropertyImpl(declaration.name, declaration.value, declaration.important, page);
}
}
}
}
return self;
}
pub fn length(self: *const CSSStyleDeclaration) u32 {
@@ -76,15 +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 {
if (value.len == 0) {
_ = try self.removeProperty(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;
@@ -92,6 +101,19 @@ pub fn setProperty(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, .{});
@@ -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,12 +149,21 @@ 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(comptime .wrap("style"), .wrap(css_text), page);
}
pub fn getFloat(self: *const CSSStyleDeclaration, page: *Page) []const u8 {
return self.getPropertyValue("float", page);
}
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 {
@@ -153,9 +190,9 @@ 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.setProperty(declaration.name, declaration.value, priority, page);
try self.setPropertyImpl(declaration.name, declaration.value, declaration.important, page);
}
try self.syncStyleAttribute(page);
}
pub fn format(self: *const CSSStyleDeclaration, writer: *std.Io.Writer) !void {