mem: Switch Element's _type to a bare tag

Follows https://github.com/lightpanda-io/browser/pull/3104. Also applies to Html
and Svg. A div chain shrinks by 24 bytes.
This commit is contained in:
Karl Seguin committed 2026-08-04 08:05:24 +08:00
1 parent 5f88b33ed6
commit d5807bf8a0
18 files changed
+767 -478

No files matched your search

+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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,
},
+11 -7
View File
@@ -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;
+9 -1
View File
@@ -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));
},
}
}
+1 -1
View File
@@ -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
+274 -245
View File
@@ -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)) {
+1 -1
View File
@@ -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,
@@ -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)",
};
+170 -88
View File
@@ -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)) {
+78 -42
View File
@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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,
};
+1 -1
View File
@@ -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,
};
}
+28 -14
View File
@@ -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 <media> 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 => {},
+1 -1
View File
@@ -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
},
+45 -21
View File
@@ -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),
};
}
@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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;
}
+54 -26
View File
@@ -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);
+31 -9
View File
@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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;
}
@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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;
}