Merge pull request #3012 from mh0pe/codex/svg-01-factory

webapi: derive SVG prototype chains
This commit is contained in:
Karl Seguin
2026-07-23 10:31:38 +08:00
committed by GitHub
3 changed files with 59 additions and 63 deletions

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);
}
</script>
@@ -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);