From 0a7e24fa2f0b2d6eb109a4a6f3ecfe499675a16d Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 10 Jul 2026 21:42:39 +0200 Subject: [PATCH] webapi: DOMTokenList-reflected attributes on area, output, iframe, link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes 4 failing tests in WPT /dom/lists/DOMTokenList-coverage-for-attributes.html (135/140 -> 139/140): area.relList, output.htmlFor, iframe.sandbox and link.sizes must be DOMTokenList attributes reflecting their content attributes. Element gains a generic getTokenList lookup (keyed by element and attribute) alongside the existing class/rel dedicated ones, and the four elements expose the accessors following the Anchor/Link relList pattern (undefined outside the HTML namespace). The remaining failure needs an SVGAElement interface (relList on SVG elements), which doesn't exist yet — SVG elements are all generic. Coverage: /dom/lists/DOMTokenList-coverage-for-attributes.html 135/140 -> 139/140. Co-Authored-By: Claude Fable 5 --- src/browser/Frame.zig | 1 + src/browser/webapi/Element.zig | 17 +++++++++++++++++ src/browser/webapi/element/html/Area.zig | 7 +++---- src/browser/webapi/element/html/IFrame.zig | 9 +++++++++ src/browser/webapi/element/html/Link.zig | 9 +++++++++ src/browser/webapi/element/html/Output.zig | 9 +++++++++ 6 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index e55af9316..3631ffb8f 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -134,6 +134,7 @@ _element_computed_styles: Element.StyleLookup = .empty, _element_datasets: Element.DatasetLookup = .empty, _element_class_lists: Element.ClassListLookup = .empty, _element_rel_lists: Element.RelListLookup = .empty, +_element_token_lists: Element.TokenListLookup = .empty, _element_shadow_roots: Element.ShadowRootLookup = .empty, _node_owner_documents: Node.OwnerDocumentLookup = .empty, _element_scroll_positions: Element.ScrollPositionLookup = .empty, diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 42150597b..9edf06296 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -885,6 +885,23 @@ pub fn getRelList(self: *Element, frame: *Frame) !*collections.DOMTokenList { return gop.value_ptr.*; } +// The other DOMTokenList-reflected attributes (class and rel have dedicated +// lookups above). +pub const TokenListAttribute = enum { sizes, sandbox, @"for" }; +pub const TokenListKey = struct { element: *Element, attribute: TokenListAttribute }; +pub const TokenListLookup = std.AutoHashMapUnmanaged(TokenListKey, *collections.DOMTokenList); + +pub fn getTokenList(self: *Element, comptime attribute: TokenListAttribute, frame: *Frame) !*collections.DOMTokenList { + const gop = try frame._element_token_lists.getOrPut(frame.arena, .{ .element = self, .attribute = attribute }); + if (!gop.found_existing) { + gop.value_ptr.* = try frame._factory.create(collections.DOMTokenList{ + ._element = self, + ._attribute_name = comptime .wrap(@tagName(attribute)), + }); + } + return gop.value_ptr.*; +} + pub fn getDataset(self: *Element, frame: *Frame) !*DOMStringMap { const gop = try frame._element_datasets.getOrPut(frame.arena, self); if (!gop.found_existing) { diff --git a/src/browser/webapi/element/html/Area.zig b/src/browser/webapi/element/html/Area.zig index 0c9c821bc..edf6a96ef 100644 --- a/src/browser/webapi/element/html/Area.zig +++ b/src/browser/webapi/element/html/Area.zig @@ -290,12 +290,11 @@ pub const JsApi = struct { pub const referrerPolicy = bridge.accessor(Area.getReferrerPolicy, Area.setReferrerPolicy, .{ .ce_reactions = true }); pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true }); pub const toString = bridge.function(Area.getHref, .{}); - + pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true }); fn _getRelList(self: *Area, frame: *Frame) !?*@import("../../collections.zig").DOMTokenList { const element = self.asElement(); - // relList is only valid for HTML and SVG elements - const namespace = element._namespace; - if (namespace != .html and namespace != .svg) { + // relList is only valid for HTML elements + if (element._namespace != .html) { return null; } return element.getRelList(frame); diff --git a/src/browser/webapi/element/html/IFrame.zig b/src/browser/webapi/element/html/IFrame.zig index a5163c8e4..5e2153b76 100644 --- a/src/browser/webapi/element/html/IFrame.zig +++ b/src/browser/webapi/element/html/IFrame.zig @@ -93,6 +93,15 @@ pub const JsApi = struct { pub const name = bridge.accessor(IFrame.getName, IFrame.setName, .{ .ce_reactions = true }); pub const contentWindow = bridge.accessor(IFrame.getContentWindow, null, .{}); pub const contentDocument = bridge.accessor(IFrame.getContentDocument, null, .{}); + pub const sandbox = bridge.accessor(_getSandbox, null, .{ .null_as_undefined = true }); + + fn _getSandbox(self: *IFrame, frame: *Frame) !?*@import("../../collections.zig").DOMTokenList { + const element = self.asElement(); + if (element._namespace != .html) { + return null; + } + return element.getTokenList(.sandbox, frame); + } }; pub const Build = struct { diff --git a/src/browser/webapi/element/html/Link.zig b/src/browser/webapi/element/html/Link.zig index 7eb2190f5..06eee926d 100644 --- a/src/browser/webapi/element/html/Link.zig +++ b/src/browser/webapi/element/html/Link.zig @@ -256,6 +256,15 @@ pub const JsApi = struct { pub const rev = bridge.accessor(Link.getRev, Link.setRev, .{ .ce_reactions = true }); pub const target = bridge.accessor(Link.getTarget, Link.setTarget, .{ .ce_reactions = true }); pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true }); + pub const sizes = bridge.accessor(_getSizes, null, .{ .null_as_undefined = true }); + + fn _getSizes(self: *Link, frame: *Frame) !?*@import("../../collections.zig").DOMTokenList { + const element = self.asElement(); + if (element._namespace != .html) { + return null; + } + return element.getTokenList(.sizes, frame); + } fn _getRelList(self: *Link, frame: *Frame) !?*@import("../../collections.zig").DOMTokenList { const element = self.asElement(); diff --git a/src/browser/webapi/element/html/Output.zig b/src/browser/webapi/element/html/Output.zig index 207e03a4b..96240077e 100644 --- a/src/browser/webapi/element/html/Output.zig +++ b/src/browser/webapi/element/html/Output.zig @@ -29,4 +29,13 @@ pub const JsApi = struct { }; pub const labels = bridge.accessor(Output.getLabels, null, .{}); + pub const htmlFor = bridge.accessor(_getHtmlFor, null, .{ .null_as_undefined = true }); + + fn _getHtmlFor(self: *Output, frame: *Frame) !?*@import("../../collections.zig").DOMTokenList { + const element = self.asElement(); + if (element._namespace != .html) { + return null; + } + return element.getTokenList(.@"for", frame); + } };