diff --git a/src/browser/Factory.zig b/src/browser/Factory.zig
index 151cab9d7..109a03fc4 100644
--- a/src/browser/Factory.zig
+++ b/src/browser/Factory.zig
@@ -474,7 +474,7 @@ pub fn svgElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeO
const svg_ptr = chain.get(i);
svg_ptr.* = .{
._tag_name = try String.init(self._arena, tag_name, .{}),
- ._type = unionInit(Element.Svg.Type, chain.get(i + 1)),
+ ._type = typeInit(Element.Svg, chain.get(i + 1)),
};
setProto(svg_ptr, chain.get(i - 1));
} else {
diff --git a/src/browser/dump.zig b/src/browser/dump.zig
index c2db77472..89c35423b 100644
--- a/src/browser/dump.zig
+++ b/src/browser/dump.zig
@@ -261,7 +261,7 @@ fn dumpSlotContent(slot: *Slot, opts: Opts, writer: *std.Io.Writer, frame: *Fram
fn isVoidElement(el: *const Node.Element) bool {
return switch (el._type) {
- .html => |html| switch (html._type) {
+ .html => switch (el.subtype(Node.Element.Html)._type) {
.br, .hr, .img, .input, .link, .meta => true,
else => false,
},
diff --git a/src/browser/frame/user_input.zig b/src/browser/frame/user_input.zig
index 385919b5b..153d8c0db 100644
--- a/src/browser/frame/user_input.zig
+++ b/src/browser/frame/user_input.zig
@@ -208,7 +208,7 @@ fn hasClickActivationBehavior(node: *Node) bool {
return switch (html_element._type) {
.anchor => element.getAttributeSafe(comptime .wrap("href")) != null,
.input, .button, .select, .textarea, .label => true,
- .generic => |generic| generic._tag == .summary,
+ .generic => html_element.subtype(Element.Html.Generic)._tag == .summary,
else => false,
};
}
@@ -328,7 +328,8 @@ pub fn handleClick(frame: *Frame, target: *Node) !void {
const html_element = element.is(Element.Html) orelse return;
switch (html_element._type) {
- .anchor => |anchor| {
+ .anchor => {
+ const anchor = html_element.subtype(Element.Html.Anchor);
const href = element.getAttributeSafe(comptime .wrap("href")) orelse return;
if (href.len == 0) {
return;
@@ -363,7 +364,8 @@ pub fn handleClick(frame: *Frame, target: *Node) !void {
.kind = .{ .push = null },
}, .{ .anchor = target_frame });
},
- .input => |input| {
+ .input => {
+ const input = html_element.subtype(Element.Html.Input);
try element.focus(frame);
// Per HTML §4.10.18.6.4 "Image Button state (type=image)", clicking an
// image button submits its form. The form-data set already gets the
@@ -373,14 +375,16 @@ pub fn handleClick(frame: *Frame, target: *Node) !void {
return frame.submitForm(element, input.getForm(frame), .{});
}
},
- .button => |button| {
+ .button => {
+ const button = html_element.subtype(Element.Html.Button);
try element.focus(frame);
if (std.mem.eql(u8, button.getType(), "submit")) {
return frame.submitForm(element, button.getForm(frame), .{});
}
},
.select, .textarea => try element.focus(frame),
- .label => |label| {
+ .label => {
+ const label = html_element.subtype(Element.Html.Label);
// Per HTML §4.10.4 "The label element", a label's activation
// behavior is to run the synthetic click activation steps on the
// labeled control. Mirrors Chrome's HTMLLabelElement::DefaultEventHandler.
@@ -388,8 +392,8 @@ pub fn handleClick(frame: *Frame, target: *Node) !void {
const control_html = control.is(Element.Html) orelse return;
try control_html.click(frame);
},
- .generic => |generic| {
- switch (generic._tag) {
+ .generic => {
+ switch (html_element.subtype(Element.Html.Generic)._tag) {
.summary => {
const parent_el = target.parentElement() orelse return;
const details = parent_el.is(Element.Html.Details) orelse return;
diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig
index e84766246..0d65665c2 100644
--- a/src/browser/js/Local.zig
+++ b/src/browser/js/Local.zig
@@ -1267,7 +1267,15 @@ pub fn resolveValue(value: anytype) Resolved {
// (e.g. CData); the type maps the tag to the member's type.
if (comptime @typeInfo(@TypeOf(value._type)) == .@"enum" and @hasDecl(T, "Subtype")) {
switch (value._type) {
- inline else => |tag| return resolveValue(value.subtype(T.Subtype(tag))),
+ inline else => |tag| {
+ const S = T.Subtype(tag);
+ if (S == T) {
+ // A tag can map to the type itself (e.g. Media.generic);
+ // the value is already the most specific type.
+ return resolveT(T, value);
+ }
+ return resolveValue(value.subtype(S));
+ },
}
}
diff --git a/src/browser/parser/Parser.zig b/src/browser/parser/Parser.zig
index 1de032884..c8d9ac364 100644
--- a/src/browser/parser/Parser.zig
+++ b/src/browser/parser/Parser.zig
@@ -600,7 +600,7 @@ fn getTemplateContentsCallback(ctx: *anyopaque, target_ref: *anyopaque) callconv
fn _getTemplateContentsCallback(self: *Parser, node: *Node) !*anyopaque {
const element = node.as(Element);
- const template = element._type.html.is(Element.Html.Template) orelse unreachable;
+ const template = element.subtype(Element.Html).is(Element.Html.Template) orelse unreachable;
const content_node = template.getContent().asNode();
// Create a ParsedNode wrapper for the content DocumentFragment
diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig
index 180addb36..4cdf07cc2 100644
--- a/src/browser/webapi/Element.zig
+++ b/src/browser/webapi/Element.zig
@@ -115,15 +115,35 @@ _attributes: Attribute.List = .{},
// work to resolve the proto).
_proto_canary: if (lp.IS_DEBUG) *Node else void = undefined,
-pub const Type = union(enum) {
- html: *Html,
- svg: *Svg,
+pub const Type = enum(u8) {
+ html,
+ svg,
};
+pub fn Subtype(comptime tag: Type) type {
+ return switch (tag) {
+ .html => Html,
+ .svg => Svg,
+ };
+}
+
+pub fn subtype(self: *const Element, comptime T: type) *T {
+ const offset = comptime Factory.chainOffsetOf(T, T) - Factory.chainOffsetOf(T, Element);
+ const sub: *T = @ptrFromInt(@intFromPtr(self) + offset);
+ if (comptime lp.IS_DEBUG) {
+ // This pointer dance only works because the factory allocates the chain
+ // in a contiguous block of memory. In debug, we assert this holds via
+ // the _proto_canary back pointer.
+ std.debug.assert(Factory.protoOf(sub) == self);
+ }
+ return sub;
+}
+
pub fn is(self: *Element, comptime T: type) ?*T {
const type_name = @typeName(T);
switch (self._type) {
- .html => |el| {
+ .html => {
+ const el = self.subtype(Html);
if (T == Html) {
return el;
}
@@ -131,7 +151,8 @@ pub fn is(self: *Element, comptime T: type) ?*T {
return el.is(T);
}
},
- .svg => |svg| {
+ .svg => {
+ const svg = self.subtype(Svg);
if (T == Svg) {
return svg;
}
@@ -178,177 +199,183 @@ pub fn isEqualNode(self: *Element, other: *Element) bool {
pub fn getTagNameLower(self: *const Element) []const u8 {
switch (self._type) {
- .html => |he| switch (he._type) {
- .custom => |ce| {
- @branchHint(.unlikely);
- return ce._tag_name.str();
- },
- else => return switch (he._type) {
- .anchor => "a",
- .area => "area",
- .base => "base",
- .body => "body",
- .br => "br",
- .button => "button",
- .canvas => "canvas",
- .custom => |e| e._tag_name.str(),
- .data => "data",
- .datalist => "datalist",
- .details => "details",
- .dialog => "dialog",
- .directory => "dir",
- .div => "div",
- .dl => "dl",
- .embed => "embed",
- .fieldset => "fieldset",
- .font => "font",
- .frameset => "frameset",
- .form => "form",
- .generic => |e| e._tag_name.str(),
- .heading => |e| e._tag_name.str(),
- .head => "head",
- .html => "html",
- .hr => "hr",
- .iframe => "iframe",
- .img => "img",
- .input => "input",
- .label => "label",
- .legend => "legend",
- .li => "li",
- .link => "link",
- .map => "map",
- .marquee => "marquee",
- .media => |m| switch (m._type) {
- .audio => "audio",
- .video => "video",
- .generic => "media",
+ .html => {
+ const he = self.subtype(Html);
+ switch (he._type) {
+ .custom => {
+ @branchHint(.unlikely);
+ return he.subtype(Html.Custom)._tag_name.str();
},
- .meta => "meta",
- .meter => "meter",
- .mod => |e| e._tag_name.str(),
- .object => "object",
- .ol => "ol",
- .optgroup => "optgroup",
- .option => "option",
- .output => "output",
- .p => "p",
- .picture => "picture",
- .param => "param",
- .pre => "pre",
- .progress => "progress",
- .quote => |e| e._tag_name.str(),
- .script => "script",
- .select => "select",
- .slot => "slot",
- .source => "source",
- .span => "span",
- .style => "style",
- .table => "table",
- .table_caption => "caption",
- .table_cell => |e| e._tag_name.str(),
- .table_col => |e| e._tag_name.str(),
- .table_row => "tr",
- .table_section => |e| e._tag_name.str(),
- .template => "template",
- .textarea => "textarea",
- .time => "time",
- .title => "title",
- .track => "track",
- .ul => "ul",
- .unknown => |e| e._tag_name.str(),
- },
+ else => return switch (he._type) {
+ .anchor => "a",
+ .area => "area",
+ .base => "base",
+ .body => "body",
+ .br => "br",
+ .button => "button",
+ .canvas => "canvas",
+ .custom => he.subtype(Html.Custom)._tag_name.str(),
+ .data => "data",
+ .datalist => "datalist",
+ .details => "details",
+ .dialog => "dialog",
+ .directory => "dir",
+ .div => "div",
+ .dl => "dl",
+ .embed => "embed",
+ .fieldset => "fieldset",
+ .font => "font",
+ .frameset => "frameset",
+ .form => "form",
+ .generic => he.subtype(Html.Generic)._tag_name.str(),
+ .heading => he.subtype(Html.Heading)._tag_name.str(),
+ .head => "head",
+ .html => "html",
+ .hr => "hr",
+ .iframe => "iframe",
+ .img => "img",
+ .input => "input",
+ .label => "label",
+ .legend => "legend",
+ .li => "li",
+ .link => "link",
+ .map => "map",
+ .marquee => "marquee",
+ .media => switch (he.subtype(Html.Media)._type) {
+ .audio => "audio",
+ .video => "video",
+ .generic => "media",
+ },
+ .meta => "meta",
+ .meter => "meter",
+ .mod => he.subtype(Html.Mod)._tag_name.str(),
+ .object => "object",
+ .ol => "ol",
+ .optgroup => "optgroup",
+ .option => "option",
+ .output => "output",
+ .p => "p",
+ .picture => "picture",
+ .param => "param",
+ .pre => "pre",
+ .progress => "progress",
+ .quote => he.subtype(Html.Quote)._tag_name.str(),
+ .script => "script",
+ .select => "select",
+ .slot => "slot",
+ .source => "source",
+ .span => "span",
+ .style => "style",
+ .table => "table",
+ .table_caption => "caption",
+ .table_cell => he.subtype(Html.TableCell)._tag_name.str(),
+ .table_col => he.subtype(Html.TableCol)._tag_name.str(),
+ .table_row => "tr",
+ .table_section => he.subtype(Html.TableSection)._tag_name.str(),
+ .template => "template",
+ .textarea => "textarea",
+ .time => "time",
+ .title => "title",
+ .track => "track",
+ .ul => "ul",
+ .unknown => he.subtype(Html.Unknown)._tag_name.str(),
+ },
+ }
},
- .svg => |svg| return svg._tag_name.str(),
+ .svg => return self.subtype(Svg)._tag_name.str(),
}
}
pub fn getTagNameSpec(self: *const Element, buf: []u8) []const u8 {
return switch (self._type) {
- .html => |he| switch (he._type) {
- .anchor => "A",
- .area => "AREA",
- .base => "BASE",
- .body => "BODY",
- .br => "BR",
- .button => "BUTTON",
- .canvas => "CANVAS",
- .custom => |e| upperTagName(&e._tag_name, buf),
- .data => "DATA",
- .datalist => "DATALIST",
- .details => "DETAILS",
- .dialog => "DIALOG",
- .directory => "DIR",
- .div => "DIV",
- .dl => "DL",
- .embed => "EMBED",
- .fieldset => "FIELDSET",
- .font => "FONT",
- .frameset => "FRAMESET",
- .form => "FORM",
- .generic => |e| upperTagName(&e._tag_name, buf),
- .heading => |e| upperTagName(&e._tag_name, buf),
- .head => "HEAD",
- .html => "HTML",
- .hr => "HR",
- .iframe => "IFRAME",
- .img => "IMG",
- .input => "INPUT",
- .label => "LABEL",
- .legend => "LEGEND",
- .li => "LI",
- .link => "LINK",
- .map => "MAP",
- .marquee => "MARQUEE",
- .meta => "META",
- .media => |m| switch (m._type) {
- .audio => "AUDIO",
- .video => "VIDEO",
- .generic => "MEDIA",
- },
- .meter => "METER",
- .mod => |e| upperTagName(&e._tag_name, buf),
- .object => "OBJECT",
- .ol => "OL",
- .optgroup => "OPTGROUP",
- .option => "OPTION",
- .output => "OUTPUT",
- .p => "P",
- .picture => "PICTURE",
- .param => "PARAM",
- .pre => "PRE",
- .progress => "PROGRESS",
- .quote => |e| upperTagName(&e._tag_name, buf),
- .script => "SCRIPT",
- .select => "SELECT",
- .slot => "SLOT",
- .source => "SOURCE",
- .span => "SPAN",
- .style => "STYLE",
- .table => "TABLE",
- .table_caption => "CAPTION",
- .table_cell => |e| upperTagName(&e._tag_name, buf),
- .table_col => |e| upperTagName(&e._tag_name, buf),
- .table_row => "TR",
- .table_section => |e| upperTagName(&e._tag_name, buf),
- .template => "TEMPLATE",
- .textarea => "TEXTAREA",
- .time => "TIME",
- .title => "TITLE",
- .track => "TRACK",
- .ul => "UL",
- .unknown => |e| switch (self._namespace) {
- .html => upperTagName(&e._tag_name, buf),
- .svg, .xml, .mathml, .unknown, .null => e._tag_name.str(),
- },
+ .html => blk: {
+ const he = self.subtype(Html);
+ break :blk switch (he._type) {
+ .anchor => "A",
+ .area => "AREA",
+ .base => "BASE",
+ .body => "BODY",
+ .br => "BR",
+ .button => "BUTTON",
+ .canvas => "CANVAS",
+ .custom => upperTagName(&he.subtype(Html.Custom)._tag_name, buf),
+ .data => "DATA",
+ .datalist => "DATALIST",
+ .details => "DETAILS",
+ .dialog => "DIALOG",
+ .directory => "DIR",
+ .div => "DIV",
+ .dl => "DL",
+ .embed => "EMBED",
+ .fieldset => "FIELDSET",
+ .font => "FONT",
+ .frameset => "FRAMESET",
+ .form => "FORM",
+ .generic => upperTagName(&he.subtype(Html.Generic)._tag_name, buf),
+ .heading => upperTagName(&he.subtype(Html.Heading)._tag_name, buf),
+ .head => "HEAD",
+ .html => "HTML",
+ .hr => "HR",
+ .iframe => "IFRAME",
+ .img => "IMG",
+ .input => "INPUT",
+ .label => "LABEL",
+ .legend => "LEGEND",
+ .li => "LI",
+ .link => "LINK",
+ .map => "MAP",
+ .marquee => "MARQUEE",
+ .meta => "META",
+ .media => switch (he.subtype(Html.Media)._type) {
+ .audio => "AUDIO",
+ .video => "VIDEO",
+ .generic => "MEDIA",
+ },
+ .meter => "METER",
+ .mod => upperTagName(&he.subtype(Html.Mod)._tag_name, buf),
+ .object => "OBJECT",
+ .ol => "OL",
+ .optgroup => "OPTGROUP",
+ .option => "OPTION",
+ .output => "OUTPUT",
+ .p => "P",
+ .picture => "PICTURE",
+ .param => "PARAM",
+ .pre => "PRE",
+ .progress => "PROGRESS",
+ .quote => upperTagName(&he.subtype(Html.Quote)._tag_name, buf),
+ .script => "SCRIPT",
+ .select => "SELECT",
+ .slot => "SLOT",
+ .source => "SOURCE",
+ .span => "SPAN",
+ .style => "STYLE",
+ .table => "TABLE",
+ .table_caption => "CAPTION",
+ .table_cell => upperTagName(&he.subtype(Html.TableCell)._tag_name, buf),
+ .table_col => upperTagName(&he.subtype(Html.TableCol)._tag_name, buf),
+ .table_row => "TR",
+ .table_section => upperTagName(&he.subtype(Html.TableSection)._tag_name, buf),
+ .template => "TEMPLATE",
+ .textarea => "TEXTAREA",
+ .time => "TIME",
+ .title => "TITLE",
+ .track => "TRACK",
+ .ul => "UL",
+ .unknown => switch (self._namespace) {
+ .html => upperTagName(&he.subtype(Html.Unknown)._tag_name, buf),
+ .svg, .xml, .mathml, .unknown, .null => he.subtype(Html.Unknown)._tag_name.str(),
+ },
+ };
},
- .svg => |svg| svg._tag_name.str(),
+ .svg => self.subtype(Svg)._tag_name.str(),
};
}
pub fn getTagNameDump(self: *const Element) []const u8 {
switch (self._type) {
.html => return self.getTagNameLower(),
- .svg => |svg| return svg._tag_name.str(),
+ .svg => return self.subtype(Svg)._tag_name.str(),
}
}
@@ -1916,81 +1943,84 @@ fn upperTagName(tag_name: *String, buf: []u8) []const u8 {
pub fn getTag(self: *const Element) Tag {
return switch (self._type) {
- .html => |he| switch (he._type) {
- .anchor => .anchor,
- .area => .area,
- .base => .base,
- .div => .div,
- .dl => .dl,
- .embed => .embed,
- .form => .form,
- .p => .p,
- .custom => .custom,
- .data => .data,
- .datalist => .datalist,
- .details => .details,
- .dialog => .dialog,
- .directory => .directory,
- .iframe => .iframe,
- .img => .img,
- .br => .br,
- .button => .button,
- .canvas => .canvas,
- .fieldset => .fieldset,
- .font => .font,
- .frameset => .frameset,
- .heading => |h| h._tag,
- .label => .label,
- .legend => .legend,
- .li => .li,
- .map => .map,
- .marquee => .marquee,
- .ul => .ul,
- .ol => .ol,
- .object => .object,
- .optgroup => .optgroup,
- .output => .output,
- .picture => .picture,
- .param => .param,
- .pre => .pre,
- .generic => |g| g._tag,
- .media => |m| switch (m._type) {
- .audio => .audio,
- .video => .video,
- .generic => .media,
- },
- .meter => .meter,
- .mod => |m| m._tag,
- .progress => .progress,
- .quote => |q| q._tag,
- .script => .script,
- .select => .select,
- .slot => .slot,
- .source => .source,
- .span => .span,
- .option => .option,
- .table => .table,
- .table_caption => .caption,
- .table_cell => |tc| tc._tag,
- .table_col => |tc| tc._tag,
- .table_row => .tr,
- .table_section => |ts| ts._tag,
- .template => .template,
- .textarea => .textarea,
- .time => .time,
- .track => .track,
- .input => .input,
- .link => .link,
- .meta => .meta,
- .hr => .hr,
- .style => .style,
- .title => .title,
- .body => .body,
- .html => .html,
- .head => .head,
- .unknown => .unknown,
+ .html => blk: {
+ const he = self.subtype(Html);
+ break :blk switch (he._type) {
+ .anchor => .anchor,
+ .area => .area,
+ .base => .base,
+ .div => .div,
+ .dl => .dl,
+ .embed => .embed,
+ .form => .form,
+ .p => .p,
+ .custom => .custom,
+ .data => .data,
+ .datalist => .datalist,
+ .details => .details,
+ .dialog => .dialog,
+ .directory => .directory,
+ .iframe => .iframe,
+ .img => .img,
+ .br => .br,
+ .button => .button,
+ .canvas => .canvas,
+ .fieldset => .fieldset,
+ .font => .font,
+ .frameset => .frameset,
+ .heading => he.subtype(Html.Heading)._tag,
+ .label => .label,
+ .legend => .legend,
+ .li => .li,
+ .map => .map,
+ .marquee => .marquee,
+ .ul => .ul,
+ .ol => .ol,
+ .object => .object,
+ .optgroup => .optgroup,
+ .output => .output,
+ .picture => .picture,
+ .param => .param,
+ .pre => .pre,
+ .generic => he.subtype(Html.Generic)._tag,
+ .media => switch (he.subtype(Html.Media)._type) {
+ .audio => .audio,
+ .video => .video,
+ .generic => .media,
+ },
+ .meter => .meter,
+ .mod => he.subtype(Html.Mod)._tag,
+ .progress => .progress,
+ .quote => he.subtype(Html.Quote)._tag,
+ .script => .script,
+ .select => .select,
+ .slot => .slot,
+ .source => .source,
+ .span => .span,
+ .option => .option,
+ .table => .table,
+ .table_caption => .caption,
+ .table_cell => he.subtype(Html.TableCell)._tag,
+ .table_col => he.subtype(Html.TableCol)._tag,
+ .table_row => .tr,
+ .table_section => he.subtype(Html.TableSection)._tag,
+ .template => .template,
+ .textarea => .textarea,
+ .time => .time,
+ .track => .track,
+ .input => .input,
+ .link => .link,
+ .meta => .meta,
+ .hr => .hr,
+ .style => .style,
+ .title => .title,
+ .body => .body,
+ .html => .html,
+ .head => .head,
+ .unknown => .unknown,
+ };
},
- .svg => |se| se.getTag(),
+ .svg => self.subtype(Svg).getTag(),
};
}
@@ -2353,22 +2383,21 @@ pub const Build = struct {
// Calls `func_name` with `args` on the most specific type where it is
// implement. This could be on the Element itself.
pub fn call(self: *const Element, comptime func_name: []const u8, args: anytype) !bool {
- inline for (@typeInfo(Element.Type).@"union".fields) |f| {
- if (@field(Element.Type, f.name) == self._type) {
- // The inner type implements this function. Call it and we're done.
- const S = reflect.Struct(f.type);
+ switch (self._type) {
+ inline else => |tag| {
+ const S = Subtype(tag);
if (@hasDecl(S, "Build")) {
+ // The inner type has its own "call" method. Defer to it.
if (@hasDecl(S.Build, "call")) {
- const sub = @field(self._type, f.name);
- return S.Build.call(sub, func_name, args);
+ return S.Build.call(self.subtype(S), func_name, args);
}
// The inner type implements this function. Call it and we're done.
- if (@hasDecl(f.type, func_name)) {
- return @call(.auto, @field(f.type, func_name), args);
+ if (@hasDecl(S, func_name)) {
+ return @call(.auto, @field(S, func_name), args);
}
}
- }
+ },
}
if (@hasDecl(Element.Build, func_name)) {
diff --git a/src/browser/webapi/collections/node_live.zig b/src/browser/webapi/collections/node_live.zig
index c96f9b179..884aefaf6 100644
--- a/src/browser/webapi/collections/node_live.zig
+++ b/src/browser/webapi/collections/node_live.zig
@@ -407,7 +407,7 @@ pub fn NodeLive(comptime mode: Mode) type {
fn isFormControl(el: *Element) bool {
if (el._type != .html) return false;
- const html = el._type.html;
+ const html = el.subtype(Element.Html);
return switch (html._type) {
.input, .button, .select, .textarea => true,
else => false,
diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig
index 80121f58d..862c89ff2 100644
--- a/src/browser/webapi/css/CSSStyleDeclaration.zig
+++ b/src/browser/webapi/css/CSSStyleDeclaration.zig
@@ -802,8 +802,8 @@ fn getDefaultPropertyValue(self: *const CSSStyleDeclaration, name: String) []con
fn getDefaultDisplay(element: *const Element) []const u8 {
switch (element._type) {
- .html => |html| {
- return switch (html._type) {
+ .html => {
+ return switch (element.subtype(Element.Html)._type) {
.anchor, .br, .span, .label, .time, .font, .mod, .quote => "inline",
.body, .div, .dl, .p, .heading, .form, .button, .canvas, .details, .dialog, .embed, .head, .html, .hr, .iframe, .img, .input, .li, .link, .meta, .ol, .option, .script, .select, .slot, .style, .template, .textarea, .title, .ul, .media, .area, .base, .datalist, .directory, .fieldset, .frameset, .legend, .map, .marquee, .meter, .object, .optgroup, .output, .param, .picture, .pre, .progress, .source, .table, .table_caption, .table_cell, .table_col, .table_row, .table_section, .track => "block",
.generic, .custom, .unknown, .data => blk: {
@@ -835,8 +835,8 @@ fn isInlineTag(tag_name: []const u8) bool {
fn getDefaultColor(element: *const Element) []const u8 {
switch (element._type) {
- .html => |html| {
- return switch (html._type) {
+ .html => {
+ return switch (element.subtype(Element.Html)._type) {
.anchor => "rgb(0, 0, 238)", // blue
else => "rgb(0, 0, 0)",
};
diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig
index 64e96684d..d8d6d0574 100644
--- a/src/browser/webapi/element/Html.zig
+++ b/src/browser/webapi/element/Html.zig
@@ -130,87 +130,169 @@ pub fn upgradeConstruct(frame: *Frame) !*Element {
return node.is(Element) orelse return error.TypeError;
}
-pub const Type = union(enum) {
- anchor: *Anchor,
- area: *Area,
- base: *Base,
- body: *Body,
- br: *BR,
- button: *Button,
- canvas: *Canvas,
- custom: *Custom,
- data: *Data,
- datalist: *DataList,
- details: *Details,
- dialog: *Dialog,
- directory: *Directory,
- div: *Div,
- dl: *DList,
- embed: *Embed,
- fieldset: *FieldSet,
- font: *Font,
- form: *Form,
- frameset: *FrameSet,
- generic: *Generic,
- heading: *Heading,
- head: *Head,
- html: *Html,
- hr: *HR,
- img: *Image,
- iframe: *IFrame,
- input: *Input,
- label: *Label,
- legend: *Legend,
- li: *LI,
- link: *Link,
- map: *Map,
- marquee: *Marquee,
- media: *Media,
- meta: *Meta,
- meter: *Meter,
- mod: *Mod,
- object: *Object,
- ol: *OL,
- optgroup: *OptGroup,
- option: *Option,
- output: *Output,
- p: *Paragraph,
- picture: *Picture,
- param: *Param,
- pre: *Pre,
- progress: *Progress,
- quote: *Quote,
- script: *Script,
- select: *Select,
- slot: *Slot,
- source: *Source,
- span: *Span,
- style: *Style,
- table: *Table,
- table_caption: *TableCaption,
- table_cell: *TableCell,
- table_col: *TableCol,
- table_row: *TableRow,
- table_section: *TableSection,
- template: *Template,
- textarea: *TextArea,
- time: *Time,
- title: *Title,
- track: *Track,
- ul: *UL,
- unknown: *Unknown,
+pub const Type = enum(u8) {
+ anchor,
+ area,
+ base,
+ body,
+ br,
+ button,
+ canvas,
+ custom,
+ data,
+ datalist,
+ details,
+ dialog,
+ directory,
+ div,
+ dl,
+ embed,
+ fieldset,
+ font,
+ form,
+ frameset,
+ generic,
+ heading,
+ head,
+ html,
+ hr,
+ img,
+ iframe,
+ input,
+ label,
+ legend,
+ li,
+ link,
+ map,
+ marquee,
+ media,
+ meta,
+ meter,
+ mod,
+ object,
+ ol,
+ optgroup,
+ option,
+ output,
+ p,
+ picture,
+ param,
+ pre,
+ progress,
+ quote,
+ script,
+ select,
+ slot,
+ source,
+ span,
+ style,
+ table,
+ table_caption,
+ table_cell,
+ table_col,
+ table_row,
+ table_section,
+ template,
+ textarea,
+ time,
+ title,
+ track,
+ ul,
+ unknown,
};
+pub fn Subtype(comptime tag: Type) type {
+ return switch (tag) {
+ .anchor => Anchor,
+ .area => Area,
+ .base => Base,
+ .body => Body,
+ .br => BR,
+ .button => Button,
+ .canvas => Canvas,
+ .custom => Custom,
+ .data => Data,
+ .datalist => DataList,
+ .details => Details,
+ .dialog => Dialog,
+ .directory => Directory,
+ .div => Div,
+ .dl => DList,
+ .embed => Embed,
+ .fieldset => FieldSet,
+ .font => Font,
+ .form => Form,
+ .frameset => FrameSet,
+ .generic => Generic,
+ .heading => Heading,
+ .head => Head,
+ .html => Html,
+ .hr => HR,
+ .img => Image,
+ .iframe => IFrame,
+ .input => Input,
+ .label => Label,
+ .legend => Legend,
+ .li => LI,
+ .link => Link,
+ .map => Map,
+ .marquee => Marquee,
+ .media => Media,
+ .meta => Meta,
+ .meter => Meter,
+ .mod => Mod,
+ .object => Object,
+ .ol => OL,
+ .optgroup => OptGroup,
+ .option => Option,
+ .output => Output,
+ .p => Paragraph,
+ .picture => Picture,
+ .param => Param,
+ .pre => Pre,
+ .progress => Progress,
+ .quote => Quote,
+ .script => Script,
+ .select => Select,
+ .slot => Slot,
+ .source => Source,
+ .span => Span,
+ .style => Style,
+ .table => Table,
+ .table_caption => TableCaption,
+ .table_cell => TableCell,
+ .table_col => TableCol,
+ .table_row => TableRow,
+ .table_section => TableSection,
+ .template => Template,
+ .textarea => TextArea,
+ .time => Time,
+ .title => Title,
+ .track => Track,
+ .ul => UL,
+ .unknown => Unknown,
+ };
+}
+
+pub fn subtype(self: *const HtmlElement, comptime T: type) *T {
+ const offset = comptime Factory.chainOffsetOf(T, T) - Factory.chainOffsetOf(T, HtmlElement);
+ const sub: *T = @ptrFromInt(@intFromPtr(self) + offset);
+ if (comptime lp.IS_DEBUG) {
+ // This pointer dance only works because the factory allocates the chain
+ // in a contiguous block of memory. In debug, we assert this holds via
+ // the _proto_canary back pointer.
+ std.debug.assert(Factory.protoOf(sub) == self);
+ }
+ return sub;
+}
+
pub fn is(self: *HtmlElement, comptime T: type) ?*T {
- inline for (@typeInfo(Type).@"union".fields) |f| {
- if (@field(Type, f.name) == self._type) {
- if (f.type == T) {
- return &@field(self._type, f.name);
+ switch (self._type) {
+ inline else => |tag| {
+ if (Subtype(tag) == T) {
+ return self.subtype(T);
}
- if (f.type == *T) {
- return @field(self._type, f.name);
- }
- }
+ },
}
return null;
}
@@ -304,8 +386,8 @@ pub fn insertAdjacentHTML(
pub fn click(self: *HtmlElement, frame: *Frame) !void {
switch (self._type) {
- inline .button, .input, .textarea, .select => |i| {
- if (i.getDisabled()) {
+ inline .button, .input, .textarea, .select => |tag| {
+ if (self.subtype(Subtype(tag)).getDisabled()) {
return;
}
},
@@ -330,8 +412,8 @@ pub fn click(self: *HtmlElement, frame: *Frame) !void {
if (event._prevent_default == false) {
// toggle the popover_target
const explicit: ?*Element = switch (self._type) {
- .button => |b| b._popover_target,
- .input => |i| i._popover_target,
+ .button => self.subtype(Button)._popover_target,
+ .input => self.subtype(Input)._popover_target,
else => null,
};
try popover.runInvokerActivation(self, explicit, frame);
@@ -543,7 +625,7 @@ fn setAttributeListener(
) !void {
if (comptime lp.IS_DEBUG) {
log.debug(.event, "Html.setAttributeListener", .{
- .type = std.meta.activeTag(self._type),
+ .type = self._type,
.listener_type = listener_type,
});
}
@@ -1492,14 +1574,14 @@ fn collectInnerText(self: *HtmlElement, state: *InnerTextState) std.Io.Writer.Er
const e = child.subtype(Node.Element);
switch (e._type) {
.svg => {},
- .html => |he| {
+ .html => {
const tag = e.getTag();
switch (child_filter) {
.none => {},
.select => if (tag != .option and tag != .optgroup) continue,
.optgroup => if (tag != .option) continue,
}
- try handleChildElement(he, tag, state, &saw_cell, &saw_row);
+ try handleChildElement(e.subtype(HtmlElement), tag, state, &saw_cell, &saw_row);
},
}
},
@@ -1870,17 +1952,17 @@ pub const Build = struct {
// Calls `func_name` with `args` on the most specific type where it is
// implement. This could be on the HtmlElement itself.
pub fn call(self: *const HtmlElement, comptime func_name: []const u8, args: anytype) !bool {
- inline for (@typeInfo(HtmlElement.Type).@"union".fields) |f| {
- if (@field(HtmlElement.Type, f.name) == self._type) {
+ switch (self._type) {
+ inline else => |tag| {
+ const S = Subtype(tag);
// The inner type implements this function. Call it and we're done.
- const S = reflect.Struct(f.type);
if (@hasDecl(S, "Build")) {
if (@hasDecl(S.Build, func_name)) {
try @call(.auto, @field(S.Build, func_name), args);
return true;
}
}
- }
+ },
}
if (@hasDecl(HtmlElement.Build, func_name)) {
diff --git a/src/browser/webapi/element/Svg.zig b/src/browser/webapi/element/Svg.zig
index a7a39cfe3..39960ec0b 100644
--- a/src/browser/webapi/element/Svg.zig
+++ b/src/browser/webapi/element/Svg.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 lp = @import("lightpanda");
const js = @import("../../js/js.zig");
@@ -47,64 +48,99 @@ _type: Type,
_tag_name: String, // Svg elements are case-preserving
_proto_canary: if (lp.IS_DEBUG) *Element else void = undefined,
-pub const Type = union(enum) {
- graphics: *Graphics,
- view: *View,
- title: *Title,
- desc: *Desc,
- metadata: *Metadata,
- gradient: *GradientElement,
- clip_path: *ClipPath,
- marker: *Marker,
- mask: *Mask,
- pattern: *Pattern,
- stop: *Stop,
- generic: *Generic,
+pub const Type = enum(u8) {
+ graphics,
+ view,
+ title,
+ desc,
+ metadata,
+ gradient,
+ clip_path,
+ marker,
+ mask,
+ pattern,
+ stop,
+ generic,
};
+pub fn Subtype(comptime tag: Type) type {
+ return switch (tag) {
+ .graphics => Graphics,
+ .view => View,
+ .title => Title,
+ .desc => Desc,
+ .metadata => Metadata,
+ .gradient => GradientElement,
+ .clip_path => ClipPath,
+ .marker => Marker,
+ .mask => Mask,
+ .pattern => Pattern,
+ .stop => Stop,
+ .generic => Generic,
+ };
+}
+
+pub fn subtype(self: *const Svg, comptime T: type) *T {
+ const offset = comptime Factory.chainOffsetOf(T, T) - Factory.chainOffsetOf(T, Svg);
+ const sub: *T = @ptrFromInt(@intFromPtr(self) + offset);
+ if (comptime lp.IS_DEBUG) {
+ // This pointer dance only works because the factory allocates the chain
+ // in a contiguous block of memory. In debug, we assert this holds via
+ // the _proto_canary back pointer.
+ std.debug.assert(Factory.protoOf(sub) == self);
+ }
+ return sub;
+}
+
pub fn is(self: *Svg, comptime T: type) ?*T {
- inline for (@typeInfo(Type).@"union".fields) |field| {
- if (@field(Type, field.name) == self._type) {
- if (field.type == *T) {
- return @field(self._type, field.name);
+ switch (self._type) {
+ inline else => |tag| {
+ if (Subtype(tag) == T) {
+ return self.subtype(T);
}
- }
+ },
}
if (self._type == .graphics) {
- return self._type.graphics.is(T);
+ return self.subtype(Graphics).is(T);
}
if (self._type == .gradient) {
- return self._type.gradient.is(T);
+ return self.subtype(GradientElement).is(T);
}
return null;
}
pub fn getTag(self: *const Svg) Element.Tag {
return switch (self._type) {
- .graphics => |g| switch (g._type) {
- .svg => .svg,
- .g => .g,
- // No dedicated Element.Tag values; tag-name matching falls back
- // to _tag_name, like it does for generic SVG elements.
- .a, .use, .image, .defs, .symbol, .switch_element, .foreign_object => .unknown,
- .text_content => |content| switch (content._type) {
- .positioning => |positioning| switch (positioning._type) {
- .text => .text,
- .tspan => .unknown,
+ .graphics => blk: {
+ const g = self.subtype(Graphics);
+ break :blk switch (g._type) {
+ .svg => .svg,
+ .g => .g,
+ // No dedicated Element.Tag values; tag-name matching falls back
+ // to _tag_name, like it does for generic SVG elements.
+ .a, .use, .image, .defs, .symbol, .switch_element, .foreign_object => .unknown,
+ .text_content => tc: {
+ const content = g.subtype(Graphics.TextContent);
+ break :tc switch (content._type) {
+ .positioning => switch (content.subtype(Graphics.TextContent.TextPositioning)._type) {
+ .text => .text,
+ .tspan => .unknown,
+ },
+ .text_path => .unknown,
+ };
},
- .text_path => .unknown,
- },
- .geometry => |geo| switch (geo._type) {
- .rect => .rect,
- .circle => .circle,
- .ellipse => .ellipse,
- .line => .line,
- .path => .path,
- .polygon => .polygon,
- .polyline => .polyline,
- },
+ .geometry => switch (g.subtype(Graphics.Geometry)._type) {
+ .rect => .rect,
+ .circle => .circle,
+ .ellipse => .ellipse,
+ .line => .line,
+ .path => .path,
+ .polygon => .polygon,
+ .polyline => .polyline,
+ },
+ };
},
- .generic => |g| g._tag,
+ .generic => self.subtype(Generic)._tag,
.title => .title,
.view, .desc, .metadata, .gradient, .clip_path, .marker, .mask, .pattern, .stop => .unknown,
};
diff --git a/src/browser/webapi/element/html/Label.zig b/src/browser/webapi/element/html/Label.zig
index 7616c0e2c..fd4da498f 100644
--- a/src/browser/webapi/element/html/Label.zig
+++ b/src/browser/webapi/element/html/Label.zig
@@ -49,7 +49,7 @@ fn isLabelable(el: *Element) bool {
const html = el.is(HtmlElement) orelse return false;
return switch (html._type) {
.button, .meter, .output, .progress, .select, .textarea => true,
- .input => |input| input._input_type != .hidden,
+ .input => html.subtype(HtmlElement.Input)._input_type != .hidden,
else => false,
};
}
diff --git a/src/browser/webapi/element/html/Media.zig b/src/browser/webapi/element/html/Media.zig
index 8e3af790c..823dcee38 100644
--- a/src/browser/webapi/element/html/Media.zig
+++ b/src/browser/webapi/element/html/Media.zig
@@ -49,12 +49,34 @@ pub const NetworkState = enum(u16) {
NETWORK_NO_SOURCE = 3,
};
-pub const Type = union(enum) {
+pub const Type = enum(u8) {
generic,
- audio: *Audio,
- video: *Video,
+ audio,
+ video,
};
+// `.generic` maps to Media itself: a bare chain ends at Media, so the
+// tag has no chain member of its own.
+pub fn Subtype(comptime tag: Type) type {
+ return switch (tag) {
+ .generic => Media,
+ .audio => Audio,
+ .video => Video,
+ };
+}
+
+pub fn subtype(self: *const Media, comptime T: type) *T {
+ const offset = comptime Factory.chainOffsetOf(T, T) - Factory.chainOffsetOf(T, Media);
+ const sub: *T = @ptrFromInt(@intFromPtr(self) + offset);
+ if (comptime lp.IS_DEBUG) {
+ // This pointer dance only works because the factory allocates the chain
+ // in a contiguous block of memory. In debug, we assert this holds via
+ // the _proto_canary back pointer.
+ std.debug.assert(Factory.protoOf(sub) == self);
+ }
+ return sub;
+}
+
_type: Type,
_proto_canary: if (lp.IS_DEBUG) *HtmlElement else void = undefined,
_paused: bool = true,
@@ -77,18 +99,10 @@ pub fn asNode(self: *Media) *Node {
}
pub fn is(self: *Media, comptime T: type) ?*T {
- const type_name = @typeName(T);
switch (self._type) {
- .audio => |a| {
- if (T == *Audio) return a;
- if (comptime std.mem.startsWith(u8, type_name, "browser.webapi.element.html.Audio")) {
- return a;
- }
- },
- .video => |v| {
- if (T == *Video) return v;
- if (comptime std.mem.startsWith(u8, type_name, "browser.webapi.element.html.Video")) {
- return v;
+ inline .audio, .video => |tag| {
+ if (Subtype(tag) == T) {
+ return self.subtype(T);
}
},
.generic => {},
diff --git a/src/browser/webapi/element/popover.zig b/src/browser/webapi/element/popover.zig
index 68c490bc4..4912473de 100644
--- a/src/browser/webapi/element/popover.zig
+++ b/src/browser/webapi/element/popover.zig
@@ -207,7 +207,7 @@ pub fn invokerTarget(invoker: *Node, explicit: ?*Element, frame: *Frame) ?*Eleme
pub fn runInvokerActivation(invoker: *HtmlElement, explicit: ?*Element, frame: *Frame) !void {
switch (invoker._type) {
.button => {},
- .input => |input| switch (input._input_type) {
+ .input => switch (invoker.subtype(HtmlElement.Input)._input_type) {
.button, .submit, .reset, .image => {},
else => return, // not an invoker
},
diff --git a/src/browser/webapi/element/svg/Geometry.zig b/src/browser/webapi/element/svg/Geometry.zig
index 7df09b2ce..5f54155da 100644
--- a/src/browser/webapi/element/svg/Geometry.zig
+++ b/src/browser/webapi/element/svg/Geometry.zig
@@ -45,23 +45,47 @@ pub const Proto = Graphics;
_type: Type,
_proto_canary: if (lp.IS_DEBUG) *Graphics else void = undefined,
-pub const Type = union(enum) {
- rect: *Rect,
- circle: *Circle,
- ellipse: *Ellipse,
- line: *Line,
- path: *Path,
- polygon: *Polygon,
- polyline: *Polyline,
+pub const Type = enum(u8) {
+ rect,
+ circle,
+ ellipse,
+ line,
+ path,
+ polygon,
+ polyline,
};
+pub fn Subtype(comptime tag: Type) type {
+ return switch (tag) {
+ .rect => Rect,
+ .circle => Circle,
+ .ellipse => Ellipse,
+ .line => Line,
+ .path => Path,
+ .polygon => Polygon,
+ .polyline => Polyline,
+ };
+}
+
+pub fn subtype(self: *const Geometry, comptime T: type) *T {
+ const offset = comptime Factory.chainOffsetOf(T, T) - Factory.chainOffsetOf(T, Geometry);
+ const sub: *T = @ptrFromInt(@intFromPtr(self) + offset);
+ if (comptime lp.IS_DEBUG) {
+ // This pointer dance only works because the factory allocates the chain
+ // in a contiguous block of memory. In debug, we assert this holds via
+ // the _proto_canary back pointer.
+ std.debug.assert(Factory.protoOf(sub) == self);
+ }
+ return sub;
+}
+
pub fn is(self: *Geometry, comptime T: type) ?*T {
- inline for (@typeInfo(Type).@"union".fields) |f| {
- if (@field(Type, f.name) == self._type) {
- if (f.type == *T) {
- return @field(self._type, f.name);
+ switch (self._type) {
+ inline else => |tag| {
+ if (Subtype(tag) == T) {
+ return self.subtype(T);
}
- }
+ },
}
return null;
}
@@ -107,16 +131,16 @@ pub fn getPointAtLength(self: *Geometry, distance: f64, frame: *Frame) !*DOMPoin
pub fn buildPath(self: *Geometry, frame: *Frame) !PathData.Path {
return switch (self._type) {
- .rect => |rect| buildRect(rect, frame),
- .circle => |circle| buildCircle(circle, frame),
- .ellipse => |ellipse| buildEllipse(ellipse, frame),
- .line => |line| buildLine(line, frame),
- .path => |path| PathData.parse(
- path.asElement().getAttributeSafe(comptime .wrap("d")) orelse "",
+ .rect => buildRect(self.subtype(Rect), frame),
+ .circle => buildCircle(self.subtype(Circle), frame),
+ .ellipse => buildEllipse(self.subtype(Ellipse), frame),
+ .line => buildLine(self.subtype(Line), frame),
+ .path => PathData.parse(
+ self.subtype(Path).asElement().getAttributeSafe(comptime .wrap("d")) orelse "",
frame.local_arena,
),
- .polygon => |polygon| buildPoints(try polygon.getPoints(frame), true, frame),
- .polyline => |polyline| buildPoints(try polyline.getPoints(frame), false, frame),
+ .polygon => buildPoints(try self.subtype(Polygon).getPoints(frame), true, frame),
+ .polyline => buildPoints(try self.subtype(Polyline).getPoints(frame), false, frame),
};
}
diff --git a/src/browser/webapi/element/svg/GradientElement.zig b/src/browser/webapi/element/svg/GradientElement.zig
index c63d460df..26eabb90f 100644
--- a/src/browser/webapi/element/svg/GradientElement.zig
+++ b/src/browser/webapi/element/svg/GradientElement.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 lp = @import("lightpanda");
const js = @import("../../../js/js.zig");
const Frame = @import("../../../Frame.zig");
@@ -39,18 +40,37 @@ pub const Proto = Svg;
_type: Type,
_proto_canary: if (lp.IS_DEBUG) *Svg else void = undefined,
-pub const Type = union(enum) {
- linear: *LinearGradient,
- radial: *RadialGradient,
+pub const Type = enum(u8) {
+ linear,
+ radial,
};
+pub fn Subtype(comptime tag: Type) type {
+ return switch (tag) {
+ .linear => LinearGradient,
+ .radial => RadialGradient,
+ };
+}
+
+pub fn subtype(self: *const GradientElement, comptime T: type) *T {
+ const offset = comptime Factory.chainOffsetOf(T, T) - Factory.chainOffsetOf(T, GradientElement);
+ const sub: *T = @ptrFromInt(@intFromPtr(self) + offset);
+ if (comptime lp.IS_DEBUG) {
+ // This pointer dance only works because the factory allocates the chain
+ // in a contiguous block of memory. In debug, we assert this holds via
+ // the _proto_canary back pointer.
+ std.debug.assert(Factory.protoOf(sub) == self);
+ }
+ return sub;
+}
+
pub fn is(self: *GradientElement, comptime T: type) ?*T {
- inline for (@typeInfo(Type).@"union".fields) |field| {
- if (@field(Type, field.name) == self._type) {
- if (field.type == *T) {
- return @field(self._type, field.name);
+ switch (self._type) {
+ inline else => |tag| {
+ if (Subtype(tag) == T) {
+ return self.subtype(T);
}
- }
+ },
}
return null;
}
diff --git a/src/browser/webapi/element/svg/Graphics.zig b/src/browser/webapi/element/svg/Graphics.zig
index 8668ba3b1..e4b4f9d05 100644
--- a/src/browser/webapi/element/svg/Graphics.zig
+++ b/src/browser/webapi/element/svg/Graphics.zig
@@ -49,33 +49,61 @@ pub const Proto = SvgElement;
_type: Type,
_proto_canary: if (lp.IS_DEBUG) *SvgElement else void = undefined,
-pub const Type = union(enum) {
- svg: *Svg,
- g: *G,
- a: *A,
- use: *Use,
- image: *Image,
- defs: *Defs,
- symbol: *Symbol,
- switch_element: *Switch,
- foreign_object: *ForeignObject,
- text_content: *TextContent,
- geometry: *Geometry,
+pub const Type = enum(u8) {
+ svg,
+ g,
+ a,
+ use,
+ image,
+ defs,
+ symbol,
+ switch_element,
+ foreign_object,
+ text_content,
+ geometry,
};
+pub fn Subtype(comptime tag: Type) type {
+ return switch (tag) {
+ .svg => Svg,
+ .g => G,
+ .a => A,
+ .use => Use,
+ .image => Image,
+ .defs => Defs,
+ .symbol => Symbol,
+ .switch_element => Switch,
+ .foreign_object => ForeignObject,
+ .text_content => TextContent,
+ .geometry => Geometry,
+ };
+}
+
+pub fn subtype(self: *const Graphics, comptime T: type) *T {
+ const offset = comptime Factory.chainOffsetOf(T, T) - Factory.chainOffsetOf(T, Graphics);
+ const sub: *T = @ptrFromInt(@intFromPtr(self) + offset);
+ if (comptime lp.IS_DEBUG) {
+ // This pointer dance only works because the factory allocates the chain
+ // in a contiguous block of memory. In debug, we assert this holds via
+ // the _proto_canary back pointer.
+ std.debug.assert(Factory.protoOf(sub) == self);
+ }
+ return sub;
+}
+
pub fn is(self: *Graphics, comptime T: type) ?*T {
- inline for (@typeInfo(Type).@"union".fields) |f| {
- if (@field(Type, f.name) == self._type) {
- if (f.type == *T) {
- return @field(self._type, f.name);
+ switch (self._type) {
+ inline else => |tag| {
+ if (Subtype(tag) == T) {
+ return self.subtype(T);
}
- }
+ },
}
if (self._type == .geometry) {
- return self._type.geometry.is(T);
+ return self.subtype(Geometry).is(T);
}
if (self._type == .text_content) {
- return self._type.text_content.is(T);
+ return self.subtype(TextContent).is(T);
}
return null;
}
@@ -108,12 +136,12 @@ pub const JsApi = struct {
pub fn getBBox(self: *Graphics, frame: *Frame) !*DOMRect {
var bounds: PathData.Bounds = .{};
switch (self._type) {
- .geometry => |geometry| {
- var path = try geometry.buildPath(frame);
+ .geometry => {
+ var path = try self.subtype(Geometry).buildPath(frame);
defer path.deinit(frame.local_arena);
bounds = path.bounds(.{});
},
- .foreign_object => |foreign_object| bounds = try foreign_object.getBounds(frame),
+ .foreign_object => bounds = try self.subtype(ForeignObject).getBounds(frame),
.g, .a, .svg => try accumulateChildren(self, .{}, &bounds, frame),
.defs, .symbol, .switch_element, .use, .image, .text_content => {},
}
@@ -156,13 +184,13 @@ fn accumulateChildren(parent: *Graphics, matrix: PathData.Matrix, bounds: *PathD
const child_matrix = parent_matrix.multiply(transformMatrix(element));
switch (graphics._type) {
- .geometry => |geometry| {
- var path = try geometry.buildPath(frame);
+ .geometry => {
+ var path = try graphics.subtype(Geometry).buildPath(frame);
defer path.deinit(frame.local_arena);
bounds.merge(path.bounds(child_matrix));
},
- .foreign_object => |foreign_object| {
- const child_bounds = try foreign_object.getBounds(frame);
+ .foreign_object => {
+ const child_bounds = try graphics.subtype(ForeignObject).getBounds(frame);
if (!child_bounds.isEmpty()) {
var foreign_path: PathData.Path = .{};
defer foreign_path.deinit(frame.local_arena);
diff --git a/src/browser/webapi/element/svg/TextContent.zig b/src/browser/webapi/element/svg/TextContent.zig
index 665453aca..4f8683fda 100644
--- a/src/browser/webapi/element/svg/TextContent.zig
+++ b/src/browser/webapi/element/svg/TextContent.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 lp = @import("lightpanda");
const js = @import("../../../js/js.zig");
const Frame = @import("../../../Frame.zig");
@@ -39,18 +40,39 @@ pub const Proto = Graphics;
_type: Type,
_proto_canary: if (lp.IS_DEBUG) *Graphics else void = undefined,
-pub const Type = union(enum) {
- positioning: *TextPositioning,
- text_path: *TextPath,
+pub const Type = enum(u8) {
+ positioning,
+ text_path,
};
-pub fn is(self: *TextContent, comptime T: type) ?*T {
- inline for (@typeInfo(Type).@"union".fields) |field| {
- if (@field(Type, field.name) == self._type) {
- if (field.type == *T) return @field(self._type, field.name);
- }
+pub fn Subtype(comptime tag: Type) type {
+ return switch (tag) {
+ .positioning => TextPositioning,
+ .text_path => TextPath,
+ };
+}
+
+pub fn subtype(self: *const TextContent, comptime T: type) *T {
+ const offset = comptime Factory.chainOffsetOf(T, T) - Factory.chainOffsetOf(T, TextContent);
+ const sub: *T = @ptrFromInt(@intFromPtr(self) + offset);
+ if (comptime lp.IS_DEBUG) {
+ // This pointer dance only works because the factory allocates the chain
+ // in a contiguous block of memory. In debug, we assert this holds via
+ // the _proto_canary back pointer.
+ std.debug.assert(Factory.protoOf(sub) == self);
}
- if (self._type == .positioning) return self._type.positioning.is(T);
+ return sub;
+}
+
+pub fn is(self: *TextContent, comptime T: type) ?*T {
+ switch (self._type) {
+ inline else => |tag| {
+ if (Subtype(tag) == T) {
+ return self.subtype(T);
+ }
+ },
+ }
+ if (self._type == .positioning) return self.subtype(TextPositioning).is(T);
return null;
}
diff --git a/src/browser/webapi/element/svg/TextPositioning.zig b/src/browser/webapi/element/svg/TextPositioning.zig
index 91052aed8..61567a4ea 100644
--- a/src/browser/webapi/element/svg/TextPositioning.zig
+++ b/src/browser/webapi/element/svg/TextPositioning.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 lp = @import("lightpanda");
const js = @import("../../../js/js.zig");
@@ -34,16 +35,37 @@ pub const Proto = TextContent;
_type: Type,
_proto_canary: if (lp.IS_DEBUG) *TextContent else void = undefined,
-pub const Type = union(enum) {
- text: *Text,
- tspan: *TSpan,
+pub const Type = enum(u8) {
+ text,
+ tspan,
};
+pub fn Subtype(comptime tag: Type) type {
+ return switch (tag) {
+ .text => Text,
+ .tspan => TSpan,
+ };
+}
+
+pub fn subtype(self: *const TextPositioning, comptime T: type) *T {
+ const offset = comptime Factory.chainOffsetOf(T, T) - Factory.chainOffsetOf(T, TextPositioning);
+ const sub: *T = @ptrFromInt(@intFromPtr(self) + offset);
+ if (comptime lp.IS_DEBUG) {
+ // This pointer dance only works because the factory allocates the chain
+ // in a contiguous block of memory. In debug, we assert this holds via
+ // the _proto_canary back pointer.
+ std.debug.assert(Factory.protoOf(sub) == self);
+ }
+ return sub;
+}
+
pub fn is(self: *TextPositioning, comptime T: type) ?*T {
- inline for (@typeInfo(Type).@"union".fields) |field| {
- if (@field(Type, field.name) == self._type) {
- if (field.type == *T) return @field(self._type, field.name);
- }
+ switch (self._type) {
+ inline else => |tag| {
+ if (Subtype(tag) == T) {
+ return self.subtype(T);
+ }
+ },
}
return null;
}