mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
css: improve CSSOM rule handling and serialization
Refactors `CSSRule` to a union type for better type safety and updates `CSSStyleRule` to use `CSSStyleProperties`. Adds comprehensive tests for `insertRule`, `deleteRule`, and `replaceSync`.
This commit is contained in:
@@ -419,3 +419,57 @@
|
|||||||
testing.expectEqual('anchor-size(--foo width, anchor-size(--bar height))', div.style.width);
|
testing.expectEqual('anchor-size(--foo width, anchor-size(--bar height))', div.style.width);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id="CSSStyleSheet_insertRule_deleteRule">
|
||||||
|
{
|
||||||
|
const style = document.createElement('style');
|
||||||
|
document.head.appendChild(style);
|
||||||
|
const sheet = style.sheet;
|
||||||
|
|
||||||
|
testing.expectEqual(0, sheet.cssRules.length);
|
||||||
|
|
||||||
|
sheet.insertRule('.test { color: green; }', 0);
|
||||||
|
testing.expectEqual(1, sheet.cssRules.length);
|
||||||
|
console.warn("constructor:", sheet.cssRules[0].constructor.name);
|
||||||
|
testing.expectEqual('.test', sheet.cssRules[0].selectorText);
|
||||||
|
testing.expectEqual('green', sheet.cssRules[0].style.color);
|
||||||
|
|
||||||
|
sheet.deleteRule(0);
|
||||||
|
testing.expectEqual(0, sheet.cssRules.length);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="CSSStyleSheet_replaceSync">
|
||||||
|
{
|
||||||
|
const sheet = new CSSStyleSheet();
|
||||||
|
testing.expectEqual(0, sheet.cssRules.length);
|
||||||
|
|
||||||
|
sheet.replaceSync('.test { color: blue; }');
|
||||||
|
testing.expectEqual(1, sheet.cssRules.length);
|
||||||
|
testing.expectEqual('.test', sheet.cssRules[0].selectorText);
|
||||||
|
testing.expectEqual('blue', sheet.cssRules[0].style.color);
|
||||||
|
|
||||||
|
let replacedAsync = false;
|
||||||
|
testing.async(async () => {
|
||||||
|
await sheet.replace('.async-test { margin: 10px; }');
|
||||||
|
testing.expectEqual(1, sheet.cssRules.length);
|
||||||
|
testing.expectEqual('.async-test', sheet.cssRules[0].selectorText);
|
||||||
|
replacedAsync = true;
|
||||||
|
});
|
||||||
|
testing.eventually(() => testing.expectTrue(replacedAsync));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="CSSStyleRule_cssText">
|
||||||
|
{
|
||||||
|
const sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync('.test { color: red; margin: 10px; }');
|
||||||
|
|
||||||
|
// Check serialization format
|
||||||
|
const cssText = sheet.cssRules[0].cssText;
|
||||||
|
testing.expectTrue(cssText.includes('.test { '));
|
||||||
|
testing.expectTrue(cssText.includes('color: red;'));
|
||||||
|
testing.expectTrue(cssText.includes('margin: 10px;'));
|
||||||
|
testing.expectTrue(cssText.includes('}'));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -131,3 +131,17 @@
|
|||||||
testing.eventually(() => testing.expectEqual(true, result));
|
testing.eventually(() => testing.expectEqual(true, result));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id="style-tag-content-parsing">
|
||||||
|
{
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.textContent = '.content-test { padding: 5px; }';
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
const sheet = style.sheet;
|
||||||
|
testing.expectTrue(sheet instanceof CSSStyleSheet);
|
||||||
|
testing.expectEqual(1, sheet.cssRules.length);
|
||||||
|
testing.expectEqual('.content-test', sheet.cssRules[0].selectorText);
|
||||||
|
testing.expectEqual('5px', sheet.cssRules[0].style.padding);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -2,29 +2,42 @@ const std = @import("std");
|
|||||||
const js = @import("../../js/js.zig");
|
const js = @import("../../js/js.zig");
|
||||||
const Page = @import("../../Page.zig");
|
const Page = @import("../../Page.zig");
|
||||||
|
|
||||||
|
const CSSStyleRule = @import("CSSStyleRule.zig");
|
||||||
|
|
||||||
const CSSRule = @This();
|
const CSSRule = @This();
|
||||||
|
|
||||||
pub const Type = enum(u16) {
|
pub const Type = union(enum) {
|
||||||
style = 1,
|
style: *CSSStyleRule,
|
||||||
charset = 2,
|
charset: void,
|
||||||
import = 3,
|
import: void,
|
||||||
media = 4,
|
media: void,
|
||||||
font_face = 5,
|
font_face: void,
|
||||||
page = 6,
|
page: void,
|
||||||
keyframes = 7,
|
keyframes: void,
|
||||||
keyframe = 8,
|
keyframe: void,
|
||||||
margin = 9,
|
margin: void,
|
||||||
namespace = 10,
|
namespace: void,
|
||||||
counter_style = 11,
|
counter_style: void,
|
||||||
supports = 12,
|
supports: void,
|
||||||
document = 13,
|
document: void,
|
||||||
font_feature_values = 14,
|
font_feature_values: void,
|
||||||
viewport = 15,
|
viewport: void,
|
||||||
region_style = 16,
|
region_style: void,
|
||||||
};
|
};
|
||||||
|
|
||||||
_type: Type,
|
_type: Type,
|
||||||
|
|
||||||
|
pub fn as(self: *CSSRule, comptime T: type) *T {
|
||||||
|
return self.is(T).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is(self: *CSSRule, comptime T: type) ?*T {
|
||||||
|
switch (self._type) {
|
||||||
|
.style => |r| return if (T == CSSStyleRule) r else null,
|
||||||
|
else => return null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(rule_type: Type, page: *Page) !*CSSRule {
|
pub fn init(rule_type: Type, page: *Page) !*CSSRule {
|
||||||
return page._factory.create(CSSRule{
|
return page._factory.create(CSSRule{
|
||||||
._type = rule_type,
|
._type = rule_type,
|
||||||
@@ -32,7 +45,7 @@ pub fn init(rule_type: Type, page: *Page) !*CSSRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getType(self: *const CSSRule) u16 {
|
pub fn getType(self: *const CSSRule) u16 {
|
||||||
return @intFromEnum(self._type);
|
return @as(u16, @intFromEnum(std.meta.activeTag(self._type))) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getCssText(self: *const CSSRule, page: *Page) []const u8 {
|
pub fn getCssText(self: *const CSSRule, page: *Page) []const u8 {
|
||||||
|
|||||||
@@ -180,8 +180,6 @@ pub fn getCssText(self: *const CSSStyleDeclaration, page: *Page) ![]const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setCssText(self: *CSSStyleDeclaration, text: []const u8, page: *Page) !void {
|
pub fn setCssText(self: *CSSStyleDeclaration, text: []const u8, page: *Page) !void {
|
||||||
if (self._element == null) return;
|
|
||||||
|
|
||||||
// Clear existing properties
|
// Clear existing properties
|
||||||
var node = self._properties.first;
|
var node = self._properties.first;
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
@@ -197,6 +195,7 @@ pub fn setCssText(self: *CSSStyleDeclaration, text: []const u8, page: *Page) !vo
|
|||||||
while (it.next()) |declaration| {
|
while (it.next()) |declaration| {
|
||||||
try self.setPropertyImpl(declaration.name, declaration.value, declaration.important, page);
|
try self.setPropertyImpl(declaration.name, declaration.value, declaration.important, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.syncStyleAttribute(page);
|
try self.syncStyleAttribute(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,20 @@ const std = @import("std");
|
|||||||
const js = @import("../../js/js.zig");
|
const js = @import("../../js/js.zig");
|
||||||
const Page = @import("../../Page.zig");
|
const Page = @import("../../Page.zig");
|
||||||
const CSSRule = @import("CSSRule.zig");
|
const CSSRule = @import("CSSRule.zig");
|
||||||
const CSSStyleDeclaration = @import("CSSStyleDeclaration.zig");
|
const CSSStyleProperties = @import("CSSStyleProperties.zig");
|
||||||
|
|
||||||
const CSSStyleRule = @This();
|
const CSSStyleRule = @This();
|
||||||
|
|
||||||
_proto: *CSSRule,
|
_proto: *CSSRule,
|
||||||
_selector_text: []const u8 = "",
|
_selector_text: []const u8 = "",
|
||||||
_style: ?*CSSStyleDeclaration = null,
|
_style: ?*CSSStyleProperties = null,
|
||||||
|
|
||||||
pub fn init(page: *Page) !*CSSStyleRule {
|
pub fn init(page: *Page) !*CSSStyleRule {
|
||||||
const rule = try CSSRule.init(.style, page);
|
const style_rule = try page._factory.create(CSSStyleRule{
|
||||||
return page._factory.create(CSSStyleRule{
|
._proto = undefined,
|
||||||
._proto = rule,
|
|
||||||
});
|
});
|
||||||
|
style_rule._proto = try CSSRule.init(.{ .style = style_rule }, page);
|
||||||
|
return style_rule;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSelectorText(self: *const CSSStyleRule) []const u8 {
|
pub fn getSelectorText(self: *const CSSStyleRule) []const u8 {
|
||||||
@@ -25,17 +26,18 @@ pub fn setSelectorText(self: *CSSStyleRule, text: []const u8, page: *Page) !void
|
|||||||
self._selector_text = try page.dupeString(text);
|
self._selector_text = try page.dupeString(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getStyle(self: *CSSStyleRule, page: *Page) !*CSSStyleDeclaration {
|
pub fn getStyle(self: *CSSStyleRule, page: *Page) !*CSSStyleProperties {
|
||||||
if (self._style) |style| {
|
if (self._style) |style| {
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
const style = try CSSStyleDeclaration.init(null, false, page);
|
const style = try CSSStyleProperties.init(null, false, page);
|
||||||
self._style = style;
|
self._style = style;
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getCssText(self: *CSSStyleRule, page: *Page) ![]const u8 {
|
pub fn getCssText(self: *CSSStyleRule, page: *Page) ![]const u8 {
|
||||||
const style = try self.getStyle(page);
|
const style_props = try self.getStyle(page);
|
||||||
|
const style = style_props.asCSSStyleDeclaration();
|
||||||
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
||||||
try buf.writer.print("{s} {{ ", .{self._selector_text});
|
try buf.writer.print("{s} {{ ", .{self._selector_text});
|
||||||
try style.format(&buf.writer);
|
try style.format(&buf.writer);
|
||||||
@@ -54,7 +56,7 @@ pub const JsApi = struct {
|
|||||||
|
|
||||||
pub const Meta = struct {
|
pub const Meta = struct {
|
||||||
pub const name = "CSSStyleRule";
|
pub const name = "CSSStyleRule";
|
||||||
pub const prototype_chain = bridge.prototypeChain(CSSRule);
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ pub fn insertRule(self: *CSSStyleSheet, rule: []const u8, index: u32, page: *Pag
|
|||||||
const style_rule = try CSSStyleRule.init(page);
|
const style_rule = try CSSStyleRule.init(page);
|
||||||
try style_rule.setSelectorText(parsed_rule.selector, page);
|
try style_rule.setSelectorText(parsed_rule.selector, page);
|
||||||
|
|
||||||
const style = try style_rule.getStyle(page);
|
const style_props = try style_rule.getStyle(page);
|
||||||
|
const style = style_props.asCSSStyleDeclaration();
|
||||||
try style.setCssText(parsed_rule.block, page);
|
try style.setCssText(parsed_rule.block, page);
|
||||||
|
|
||||||
const rules = try self.getCssRules(page);
|
const rules = try self.getCssRules(page);
|
||||||
@@ -90,7 +91,8 @@ pub fn replaceSync(self: *CSSStyleSheet, text: []const u8, page: *Page) !void {
|
|||||||
const style_rule = try CSSStyleRule.init(page);
|
const style_rule = try CSSStyleRule.init(page);
|
||||||
try style_rule.setSelectorText(parsed_rule.selector, page);
|
try style_rule.setSelectorText(parsed_rule.selector, page);
|
||||||
|
|
||||||
const style = try style_rule.getStyle(page);
|
const style_props = try style_rule.getStyle(page);
|
||||||
|
const style = style_props.asCSSStyleDeclaration();
|
||||||
try style.setCssText(parsed_rule.block, page);
|
try style.setCssText(parsed_rule.block, page);
|
||||||
|
|
||||||
try rules.insert(index, style_rule._proto, page);
|
try rules.insert(index, style_rule._proto, page);
|
||||||
|
|||||||
Reference in New Issue
Block a user