From ea4eebd2d64b9f0c21df5366adeda73291a8c150 Mon Sep 17 00:00:00 2001 From: egrs Date: Wed, 18 Feb 2026 08:11:08 +0100 Subject: [PATCH 1/3] return CSSStyleSheet from HTMLStyleElement.sheet Per spec, connected style elements with type text/css should return a CSSStyleSheet object. Previously always returned null. The sheet is lazily created and cached on first access. --- src/browser/tests/element/html/style.html | 15 +++++++++++++++ src/browser/webapi/element/html/Style.zig | 14 +++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/browser/tests/element/html/style.html b/src/browser/tests/element/html/style.html index 6c406d8a..040dd729 100644 --- a/src/browser/tests/element/html/style.html +++ b/src/browser/tests/element/html/style.html @@ -3,7 +3,22 @@ diff --git a/src/browser/webapi/element/html/Style.zig b/src/browser/webapi/element/html/Style.zig index 483d07f7..c2c532e1 100644 --- a/src/browser/webapi/element/html/Style.zig +++ b/src/browser/webapi/element/html/Style.zig @@ -16,6 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +const std = @import("std"); const js = @import("../../../js/js.zig"); const Page = @import("../../../Page.zig"); @@ -25,6 +26,7 @@ const HtmlElement = @import("../Html.zig"); const Style = @This(); _proto: *HtmlElement, +_sheet: ?*CSSStyleSheet = null, pub fn asElement(self: *Style) *Element { return self._proto._proto; @@ -75,9 +77,15 @@ pub fn setDisabled(self: *Style, disabled: bool, page: *Page) !void { } const CSSStyleSheet = @import("../../css/CSSStyleSheet.zig"); -pub fn getSheet(_: *const Style) ?*CSSStyleSheet { - // TODO? - return null; +pub fn getSheet(self: *Style, page: *Page) !?*CSSStyleSheet { + // Per spec, sheet is null for disconnected elements or non-CSS types. + if (!self.asNode().isConnected()) return null; + if (!std.mem.eql(u8, self.getType(), "text/css")) return null; + + if (self._sheet) |sheet| return sheet; + const sheet = try CSSStyleSheet.init(page); + self._sheet = sheet; + return sheet; } pub const JsApi = struct { From 54d6eed74020d493f563e056e2f23d2b247835d5 Mon Sep 17 00:00:00 2001 From: egrs Date: Wed, 18 Feb 2026 10:40:42 +0100 Subject: [PATCH 2/3] fix type check: case-insensitive, accept empty string, clear on disconnect Per spec, valid type values are absent, empty string, or a case-insensitive match for "text/css". Also clear cached sheet when the element is disconnected or type becomes invalid. --- src/browser/tests/element/html/style.html | 19 +++++++++++++++++++ src/browser/webapi/element/html/Style.zig | 13 +++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/browser/tests/element/html/style.html b/src/browser/tests/element/html/style.html index 040dd729..c5fa9247 100644 --- a/src/browser/tests/element/html/style.html +++ b/src/browser/tests/element/html/style.html @@ -19,6 +19,25 @@ lessStyle.type = 'text/less'; document.head.appendChild(lessStyle); testing.expectEqual(null, lessStyle.sheet); + + // Empty type attribute is valid (defaults to text/css per spec) + const emptyType = document.createElement('style'); + emptyType.setAttribute('type', ''); + document.head.appendChild(emptyType); + testing.expectEqual(true, emptyType.sheet instanceof CSSStyleSheet); + + // Case-insensitive type check + const upperType = document.createElement('style'); + upperType.type = 'TEXT/CSS'; + document.head.appendChild(upperType); + testing.expectEqual(true, upperType.sheet instanceof CSSStyleSheet); + + // Disconnection clears sheet + const tempStyle = document.createElement('style'); + document.head.appendChild(tempStyle); + testing.expectEqual(true, tempStyle.sheet instanceof CSSStyleSheet); + document.head.removeChild(tempStyle); + testing.expectEqual(null, tempStyle.sheet); } diff --git a/src/browser/webapi/element/html/Style.zig b/src/browser/webapi/element/html/Style.zig index c2c532e1..3e489ed8 100644 --- a/src/browser/webapi/element/html/Style.zig +++ b/src/browser/webapi/element/html/Style.zig @@ -79,8 +79,17 @@ pub fn setDisabled(self: *Style, disabled: bool, page: *Page) !void { const CSSStyleSheet = @import("../../css/CSSStyleSheet.zig"); pub fn getSheet(self: *Style, page: *Page) !?*CSSStyleSheet { // Per spec, sheet is null for disconnected elements or non-CSS types. - if (!self.asNode().isConnected()) return null; - if (!std.mem.eql(u8, self.getType(), "text/css")) return null; + // Valid types: absent (defaults to "text/css"), empty string, or + // case-insensitive match for "text/css". + if (!self.asNode().isConnected()) { + self._sheet = null; + return null; + } + const t = self.getType(); + if (t.len != 0 and !std.ascii.eqlIgnoreCase(t, "text/css")) { + self._sheet = null; + return null; + } if (self._sheet) |sheet| return sheet; const sheet = try CSSStyleSheet.init(page); From 488e72ef4e150835324e5c9c0c9552ec110e5542 Mon Sep 17 00:00:00 2001 From: egrs Date: Wed, 18 Feb 2026 12:41:31 +0100 Subject: [PATCH 3/3] wire up CSSStyleSheet.ownerNode to the style element --- src/browser/tests/element/html/style.html | 5 +++++ src/browser/webapi/css/CSSStyleSheet.zig | 11 ++++++++--- src/browser/webapi/element/html/Style.zig | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/browser/tests/element/html/style.html b/src/browser/tests/element/html/style.html index c5fa9247..713d8e2e 100644 --- a/src/browser/tests/element/html/style.html +++ b/src/browser/tests/element/html/style.html @@ -38,6 +38,11 @@ testing.expectEqual(true, tempStyle.sheet instanceof CSSStyleSheet); document.head.removeChild(tempStyle); testing.expectEqual(null, tempStyle.sheet); + + // ownerNode points back to the style element + const ownStyle = document.createElement('style'); + document.head.appendChild(ownStyle); + testing.expectEqual(true, ownStyle.sheet.ownerNode === ownStyle); } diff --git a/src/browser/webapi/css/CSSStyleSheet.zig b/src/browser/webapi/css/CSSStyleSheet.zig index 493e6506..5500f63c 100644 --- a/src/browser/webapi/css/CSSStyleSheet.zig +++ b/src/browser/webapi/css/CSSStyleSheet.zig @@ -1,6 +1,7 @@ const std = @import("std"); const js = @import("../../js/js.zig"); const Page = @import("../../Page.zig"); +const Element = @import("../Element.zig"); const CSSRuleList = @import("CSSRuleList.zig"); const CSSRule = @import("CSSRule.zig"); @@ -11,14 +12,18 @@ _title: []const u8 = "", _disabled: bool = false, _css_rules: ?*CSSRuleList = null, _owner_rule: ?*CSSRule = null, +_owner_node: ?*Element = null, pub fn init(page: *Page) !*CSSStyleSheet { return page._factory.create(CSSStyleSheet{}); } -pub fn getOwnerNode(self: *const CSSStyleSheet) ?*CSSStyleSheet { - _ = self; - return null; +pub fn initWithOwner(owner: *Element, page: *Page) !*CSSStyleSheet { + return page._factory.create(CSSStyleSheet{ ._owner_node = owner }); +} + +pub fn getOwnerNode(self: *const CSSStyleSheet) ?*Element { + return self._owner_node; } pub fn getHref(self: *const CSSStyleSheet) ?[]const u8 { diff --git a/src/browser/webapi/element/html/Style.zig b/src/browser/webapi/element/html/Style.zig index 3e489ed8..a46f1dde 100644 --- a/src/browser/webapi/element/html/Style.zig +++ b/src/browser/webapi/element/html/Style.zig @@ -92,7 +92,7 @@ pub fn getSheet(self: *Style, page: *Page) !?*CSSStyleSheet { } if (self._sheet) |sheet| return sheet; - const sheet = try CSSStyleSheet.init(page); + const sheet = try CSSStyleSheet.initWithOwner(self.asElement(), page); self._sheet = sheet; return sheet; }