From 7140cfab5ae1e7719b8880bb48fa08f53e1db7f7 Mon Sep 17 00:00:00 2001 From: Madison Steiner <8176115+mh0pe@users.noreply.github.com> Date: Tue, 21 Jul 2026 19:27:55 -0700 Subject: [PATCH] webapi: derive SVG prototype chains --- src/browser/Factory.zig | 75 +++++++++----------- src/browser/frame/node_factory.zig | 36 ++++------ src/browser/tests/element/svg/hierarchy.html | 11 +++ 3 files changed, 59 insertions(+), 63 deletions(-) diff --git a/src/browser/Factory.zig b/src/browser/Factory.zig index 5065b7141..77b94708e 100644 --- a/src/browser/Factory.zig +++ b/src/browser/Factory.zig @@ -354,49 +354,44 @@ pub fn htmlMediaElement(self: *Factory, child: anytype) !*@TypeOf(child) { } pub fn svgElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeOf(child) { - const chain = try PrototypeChain( - &.{ EventTarget, Node, Element, Element.Svg, @TypeOf(child) }, - ).allocate(self._slab.allocator()); + const types = comptime svgPrototypeTypes(@TypeOf(child)); + const chain = try PrototypeChain(types).allocate(self._slab.allocator()); - try self.setSvgChainBase(chain, tag_name); - chain.setLeaf(4, child); - return chain.get(4); -} - -pub fn svgGraphicsElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeOf(child) { - const chain = try PrototypeChain( - &.{ EventTarget, Node, Element, Element.Svg, Element.Svg.Graphics, @TypeOf(child) }, - ).allocate(self._slab.allocator()); - - try self.setSvgChainBase(chain, tag_name); - chain.setMiddle(4, Element.Svg.Graphics.Type); - chain.setLeaf(5, child); - return chain.get(5); -} - -pub fn svgGeometryElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeOf(child) { - const chain = try PrototypeChain( - &.{ EventTarget, Node, Element, Element.Svg, Element.Svg.Graphics, Element.Svg.Graphics.Geometry, @TypeOf(child) }, - ).allocate(self._slab.allocator()); - - try self.setSvgChainBase(chain, tag_name); - chain.setMiddle(4, Element.Svg.Graphics.Type); - chain.setMiddle(5, Element.Svg.Graphics.Geometry.Type); - chain.setLeaf(6, child); - return chain.get(6); -} - -fn setSvgChainBase(self: *Factory, chain: anytype, tag_name: []const u8) !void { chain.setRoot(EventTarget.Type); - chain.setMiddle(1, Node.Type); - chain.setMiddle(2, Element.Type); + inline for (1..types.len - 1) |i| { + const T = types[i]; + if (T == Element.Svg) { + chain.set(i, .{ + ._proto = chain.get(i - 1), + ._tag_name = try String.init(self._arena, tag_name, .{}), + ._type = unionInit(Element.Svg.Type, chain.get(i + 1)), + }); + } else { + chain.setMiddle(i, T.Type); + } + } + chain.setLeaf(types.len - 1, child); + return chain.get(types.len - 1); +} - // Manually set Element.Svg with the tag_name - chain.set(3, .{ - ._proto = chain.get(2), - ._tag_name = try String.init(self._arena, tag_name, .{}), - ._type = unionInit(Element.Svg.Type, chain.get(4)), - }); +fn svgPrototypeTypes(comptime Child: type) []const type { + comptime { + var types: []const type = &[_]type{Child}; + var T = Child; + + while (T != EventTarget) { + if (!@hasField(T, "_proto")) { + @compileError(@typeName(T) ++ " does not lead to EventTarget through _proto"); + } + T = reflect.Struct(@FieldType(T, "_proto")); + types = &[_]type{T} ++ types; + } + + if (types.len < 5 or types[3] != Element.Svg) { + @compileError(@typeName(Child) ++ " is not an SVG prototype chain"); + } + return types; + } } pub fn xhrEventTarget(_: *const Factory, allocator: Allocator, child: anytype) !*@TypeOf(child) { diff --git a/src/browser/frame/node_factory.zig b/src/browser/frame/node_factory.zig index b93d4a36a..8b7f6d74b 100644 --- a/src/browser/frame/node_factory.zig +++ b/src/browser/frame/node_factory.zig @@ -890,37 +890,37 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons // SVG tag names are case-sensitive; no lowering before matching. switch (name.len) { 1 => switch (name[0]) { - 'g' => return createSvgGraphicsElementT(frame, Graphics.G, name, attribute_iterator), - 'a' => return createSvgGraphicsElementT(frame, Graphics.A, name, attribute_iterator), + 'g' => return createSvgElementT(frame, Graphics.G, name, attribute_iterator, .{ ._proto = undefined }), + 'a' => return createSvgElementT(frame, Graphics.A, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, 3 => switch (@as(u24, @bitCast(name[0..3].*))) { - asUint("svg") => return createSvgGraphicsElementT(frame, Graphics.Svg, name, attribute_iterator), - asUint("use") => return createSvgGraphicsElementT(frame, Graphics.Use, name, attribute_iterator), + asUint("svg") => return createSvgElementT(frame, Graphics.Svg, name, attribute_iterator, .{ ._proto = undefined }), + asUint("use") => return createSvgElementT(frame, Graphics.Use, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, 4 => switch (@as(u32, @bitCast(name[0..4].*))) { - asUint("defs") => return createSvgGraphicsElementT(frame, Graphics.Defs, name, attribute_iterator), - asUint("rect") => return createSvgGeometryElementT(frame, Geometry.Rect, name, attribute_iterator), - asUint("line") => return createSvgGeometryElementT(frame, Geometry.Line, name, attribute_iterator), - asUint("path") => return createSvgGeometryElementT(frame, Geometry.Path, name, attribute_iterator), + asUint("defs") => return createSvgElementT(frame, Graphics.Defs, name, attribute_iterator, .{ ._proto = undefined }), + asUint("rect") => return createSvgElementT(frame, Geometry.Rect, name, attribute_iterator, .{ ._proto = undefined }), + asUint("line") => return createSvgElementT(frame, Geometry.Line, name, attribute_iterator, .{ ._proto = undefined }), + asUint("path") => return createSvgElementT(frame, Geometry.Path, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, 5 => switch (@as(u40, @bitCast(name[0..5].*))) { - asUint("image") => return createSvgGraphicsElementT(frame, Graphics.Image, name, attribute_iterator), + asUint("image") => return createSvgElementT(frame, Graphics.Image, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, 6 => switch (@as(u48, @bitCast(name[0..6].*))) { - asUint("circle") => return createSvgGeometryElementT(frame, Geometry.Circle, name, attribute_iterator), + asUint("circle") => return createSvgElementT(frame, Geometry.Circle, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, 7 => switch (@as(u56, @bitCast(name[0..7].*))) { - asUint("ellipse") => return createSvgGeometryElementT(frame, Geometry.Ellipse, name, attribute_iterator), - asUint("polygon") => return createSvgGeometryElementT(frame, Geometry.Polygon, name, attribute_iterator), + asUint("ellipse") => return createSvgElementT(frame, Geometry.Ellipse, name, attribute_iterator, .{ ._proto = undefined }), + asUint("polygon") => return createSvgElementT(frame, Geometry.Polygon, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, 8 => switch (@as(u64, @bitCast(name[0..8].*))) { - asUint("polyline") => return createSvgGeometryElementT(frame, Geometry.Polyline, name, attribute_iterator), + asUint("polyline") => return createSvgElementT(frame, Geometry.Polyline, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, else => {}, @@ -970,16 +970,6 @@ fn createSvgElementT(frame: *Frame, comptime E: type, tag_name: []const u8, attr return initSvgElement(frame, svg_element_ptr.asElement(), attribute_iterator); } -fn createSvgGraphicsElementT(frame: *Frame, comptime E: type, tag_name: []const u8, attribute_iterator: anytype) !*Node { - const svg_element_ptr = try frame._factory.svgGraphicsElement(tag_name, E{ ._proto = undefined }); - return initSvgElement(frame, svg_element_ptr.asElement(), attribute_iterator); -} - -fn createSvgGeometryElementT(frame: *Frame, comptime E: type, tag_name: []const u8, attribute_iterator: anytype) !*Node { - const svg_element_ptr = try frame._factory.svgGeometryElement(tag_name, E{ ._proto = undefined }); - return initSvgElement(frame, svg_element_ptr.asElement(), attribute_iterator); -} - fn initSvgElement(frame: *Frame, element: *Element, attribute_iterator: anytype) !*Node { element._namespace = .svg; element._attributes.normalize = false; diff --git a/src/browser/tests/element/svg/hierarchy.html b/src/browser/tests/element/svg/hierarchy.html index 17dd71194..dac451853 100644 --- a/src/browser/tests/element/svg/hierarchy.html +++ b/src/browser/tests/element/svg/hierarchy.html @@ -48,8 +48,10 @@ testing.expectEqual(true, root instanceof SVGGraphicsElement); testing.expectEqual(true, root instanceof SVGElement); testing.expectEqual(true, root instanceof Element); + testing.expectEqual(true, root instanceof Node); testing.expectEqual(false, root instanceof SVGGeometryElement); testing.expectEqual('svg', root.tagName); + testing.expectEqual('http://www.w3.org/2000/svg', root.namespaceURI); const rect = $('#rect1'); testing.expectEqual(true, rect instanceof SVGRectElement); @@ -75,8 +77,10 @@ // Elements without a dedicated type stay generic, but are still SVGElement. const filter = $('#filter1'); testing.expectEqual(true, filter instanceof SVGElement); + testing.expectEqual(true, filter instanceof Element); testing.expectEqual(false, filter instanceof SVGGraphicsElement); testing.expectEqual('filter', filter.tagName); + testing.expectEqual('http://www.w3.org/2000/svg', filter.namespaceURI); } @@ -85,13 +89,20 @@ const NS = 'http://www.w3.org/2000/svg'; const rect = document.createElementNS(NS, 'rect'); testing.expectEqual(true, rect instanceof SVGRectElement); + testing.expectEqual(true, rect instanceof SVGGeometryElement); + testing.expectEqual(true, rect instanceof SVGGraphicsElement); + testing.expectEqual(true, rect instanceof SVGElement); + testing.expectEqual(true, rect instanceof Element); testing.expectEqual('rect', rect.tagName); + testing.expectEqual(NS, rect.namespaceURI); // SVG tag names are case-sensitive: 'RECT' isn't a rect element. const notRect = document.createElementNS(NS, 'RECT'); testing.expectEqual(false, notRect instanceof SVGRectElement); testing.expectEqual(true, notRect instanceof SVGElement); + testing.expectEqual(true, notRect instanceof Element); testing.expectEqual('RECT', notRect.tagName); + testing.expectEqual(NS, notRect.namespaceURI); const svg = document.createElementNS(NS, 'svg'); testing.expectEqual(true, svg instanceof SVGSVGElement);