diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 77e2d971..9b4fe704 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -1335,625 +1335,624 @@ pub fn adoptNodeTree(self: *Page, node: *Node, new_owner: *Document) !void { } } -pub fn createElement(self: *Page, ns_: ?[]const u8, name: []const u8, attribute_iterator: anytype) !*Node { - const namespace: Element.Namespace = blk: { - const ns = ns_ orelse break :blk .html; - if (std.mem.eql(u8, ns, "http://www.w3.org/2000/svg")) break :blk .svg; - if (std.mem.eql(u8, ns, "http://www.w3.org/1998/Math/MathML")) break :blk .mathml; - if (std.mem.eql(u8, ns, "http://www.w3.org/XML/1998/namespace")) break :blk .xml; - break :blk .html; - }; +pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const u8, attribute_iterator: anytype) !*Node { + switch (namespace) { + .html => { + switch (name.len) { + 1 => switch (name[0]) { + 'p' => return self.createHtmlElementT( + Element.Html.Paragraph, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + 'a' => return self.createHtmlElementT( + Element.Html.Anchor, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + 'b' => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "b", .{}) catch unreachable, ._tag = .b }, + ), + 'i' => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "i", .{}) catch unreachable, ._tag = .i }, + ), + 's' => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "s", .{}) catch unreachable, ._tag = .s }, + ), + else => {}, + }, + 2 => switch (@as(u16, @bitCast(name[0..2].*))) { + asUint("br") => return self.createHtmlElementT( + Element.Html.BR, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("ol") => return self.createHtmlElementT( + Element.Html.OL, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("ul") => return self.createHtmlElementT( + Element.Html.UL, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("li") => return self.createHtmlElementT( + Element.Html.LI, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("h1") => return self.createHtmlElementT( + Element.Html.Heading, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "h1", .{}) catch unreachable, ._tag = .h1 }, + ), + asUint("h2") => return self.createHtmlElementT( + Element.Html.Heading, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "h2", .{}) catch unreachable, ._tag = .h2 }, + ), + asUint("h3") => return self.createHtmlElementT( + Element.Html.Heading, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "h3", .{}) catch unreachable, ._tag = .h3 }, + ), + asUint("h4") => return self.createHtmlElementT( + Element.Html.Heading, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "h4", .{}) catch unreachable, ._tag = .h4 }, + ), + asUint("h5") => return self.createHtmlElementT( + Element.Html.Heading, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "h5", .{}) catch unreachable, ._tag = .h5 }, + ), + asUint("h6") => return self.createHtmlElementT( + Element.Html.Heading, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "h6", .{}) catch unreachable, ._tag = .h6 }, + ), + asUint("hr") => return self.createHtmlElementT( + Element.Html.HR, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("em") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "em", .{}) catch unreachable, ._tag = .em }, + ), + asUint("dd") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "dd", .{}) catch unreachable, ._tag = .dd }, + ), + asUint("dl") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "dl", .{}) catch unreachable, ._tag = .dl }, + ), + asUint("dt") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "dt", .{}) catch unreachable, ._tag = .dt }, + ), + asUint("td") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "td", .{}) catch unreachable, ._tag = .td }, + ), + asUint("th") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "th", .{}) catch unreachable, ._tag = .th }, + ), + asUint("tr") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "tr", .{}) catch unreachable, ._tag = .tr }, + ), + else => {}, + }, + 3 => switch (@as(u24, @bitCast(name[0..3].*))) { + asUint("div") => return self.createHtmlElementT( + Element.Html.Div, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("img") => return self.createHtmlElementT( + Element.Html.Image, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("nav") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "nav", .{}) catch unreachable, ._tag = .nav }, + ), + asUint("del") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "del", .{}) catch unreachable, ._tag = .del }, + ), + asUint("ins") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "ins", .{}) catch unreachable, ._tag = .ins }, + ), + asUint("sub") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "sub", .{}) catch unreachable, ._tag = .sub }, + ), + asUint("sup") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "sup", .{}) catch unreachable, ._tag = .sup }, + ), + asUint("dfn") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "dfn", .{}) catch unreachable, ._tag = .dfn }, + ), + else => {}, + }, + 4 => switch (@as(u32, @bitCast(name[0..4].*))) { + asUint("span") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "span", .{}) catch unreachable, ._tag = .span }, + ), + asUint("meta") => return self.createHtmlElementT( + Element.Html.Meta, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("link") => return self.createHtmlElementT( + Element.Html.Link, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("slot") => return self.createHtmlElementT( + Element.Html.Slot, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("html") => return self.createHtmlElementT( + Element.Html.Html, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("head") => return self.createHtmlElementT( + Element.Html.Head, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("body") => return self.createHtmlElementT( + Element.Html.Body, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("form") => return self.createHtmlElementT( + Element.Html.Form, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("main") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "main", .{}) catch unreachable, ._tag = .main }, + ), + asUint("data") => return self.createHtmlElementT( + Element.Html.Data, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("base") => { + const n = try self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "base", .{}) catch unreachable, ._tag = .base }, + ); - switch (name.len) { - 1 => switch (name[0]) { - 'p' => return self.createHtmlElementT( - Element.Html.Paragraph, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - 'a' => return self.createHtmlElementT( - Element.Html.Anchor, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - 'b' => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "b", .{}) catch unreachable, ._tag = .b }, - ), - 'i' => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "i", .{}) catch unreachable, ._tag = .i }, - ), - 's' => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "s", .{}) catch unreachable, ._tag = .s }, - ), - else => {}, - }, - 2 => switch (@as(u16, @bitCast(name[0..2].*))) { - asUint("br") => return self.createHtmlElementT( - Element.Html.BR, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("ol") => return self.createHtmlElementT( - Element.Html.OL, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("ul") => return self.createHtmlElementT( - Element.Html.UL, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("li") => return self.createHtmlElementT( - Element.Html.LI, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("h1") => return self.createHtmlElementT( - Element.Html.Heading, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "h1", .{}) catch unreachable, ._tag = .h1 }, - ), - asUint("h2") => return self.createHtmlElementT( - Element.Html.Heading, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "h2", .{}) catch unreachable, ._tag = .h2 }, - ), - asUint("h3") => return self.createHtmlElementT( - Element.Html.Heading, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "h3", .{}) catch unreachable, ._tag = .h3 }, - ), - asUint("h4") => return self.createHtmlElementT( - Element.Html.Heading, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "h4", .{}) catch unreachable, ._tag = .h4 }, - ), - asUint("h5") => return self.createHtmlElementT( - Element.Html.Heading, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "h5", .{}) catch unreachable, ._tag = .h5 }, - ), - asUint("h6") => return self.createHtmlElementT( - Element.Html.Heading, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "h6", .{}) catch unreachable, ._tag = .h6 }, - ), - asUint("hr") => return self.createHtmlElementT( - Element.Html.HR, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("em") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "em", .{}) catch unreachable, ._tag = .em }, - ), - asUint("dd") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "dd", .{}) catch unreachable, ._tag = .dd }, - ), - asUint("dl") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "dl", .{}) catch unreachable, ._tag = .dl }, - ), - asUint("dt") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "dt", .{}) catch unreachable, ._tag = .dt }, - ), - asUint("td") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "td", .{}) catch unreachable, ._tag = .td }, - ), - asUint("th") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "th", .{}) catch unreachable, ._tag = .th }, - ), - asUint("tr") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "tr", .{}) catch unreachable, ._tag = .tr }, - ), - else => {}, - }, - 3 => switch (@as(u24, @bitCast(name[0..3].*))) { - asUint("div") => return self.createHtmlElementT( - Element.Html.Div, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("img") => return self.createHtmlElementT( - Element.Html.Image, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("nav") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "nav", .{}) catch unreachable, ._tag = .nav }, - ), - asUint("del") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "del", .{}) catch unreachable, ._tag = .del }, - ), - asUint("ins") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "ins", .{}) catch unreachable, ._tag = .ins }, - ), - asUint("sub") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "sub", .{}) catch unreachable, ._tag = .sub }, - ), - asUint("sup") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "sup", .{}) catch unreachable, ._tag = .sup }, - ), - asUint("dfn") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "dfn", .{}) catch unreachable, ._tag = .dfn }, - ), - else => {}, - }, - 4 => switch (@as(u32, @bitCast(name[0..4].*))) { - asUint("span") => return self.createHtmlElementT( - Element.Html.Span, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("meta") => return self.createHtmlElementT( - Element.Html.Meta, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("link") => return self.createHtmlElementT( - Element.Html.Link, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("slot") => return self.createHtmlElementT( - Element.Html.Slot, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("html") => return self.createHtmlElementT( - Element.Html.Html, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("head") => return self.createHtmlElementT( - Element.Html.Head, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("body") => return self.createHtmlElementT( - Element.Html.Body, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("form") => return self.createHtmlElementT( - Element.Html.Form, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("main") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "main", .{}) catch unreachable, ._tag = .main }, - ), - asUint("data") => return self.createHtmlElementT( - Element.Html.Data, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("base") => { - const n = try self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "base", .{}) catch unreachable, ._tag = .base }, - ); + // If page's base url is not already set, fill it with the base + // tag. + if (self.base_url == null) { + if (n.as(Element).getAttributeSafe("href")) |href| { + self.base_url = try URL.resolve(self.arena, self.url, href, .{}); + } + } - // If page's base url is not already set, fill it with the base - // tag. - if (self.base_url == null) { - if (n.as(Element).getAttributeSafe("href")) |href| { - self.base_url = try URL.resolve(self.arena, self.url, href, .{}); + return n; + }, + asUint("menu") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "menu", .{}) catch unreachable, ._tag = .menu }, + ), + asUint("area") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "area", .{}) catch unreachable, ._tag = .area }, + ), + asUint("code") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "code", .{}) catch unreachable, ._tag = .code }, + ), + asUint("time") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "time", .{}) catch unreachable, ._tag = .time }, + ), + else => {}, + }, + 5 => switch (@as(u40, @bitCast(name[0..5].*))) { + asUint("input") => return self.createHtmlElementT( + Element.Html.Input, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("style") => return self.createHtmlElementT( + Element.Html.Style, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("title") => return self.createHtmlElementT( + Element.Html.Title, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("embed") => return self.createHtmlElementT( + Element.Html.Embed, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("audio") => return self.createHtmlMediaElementT( + Element.Html.Media.Audio, + namespace, + attribute_iterator, + ), + asUint("video") => return self.createHtmlMediaElementT( + Element.Html.Media.Video, + namespace, + attribute_iterator, + ), + asUint("aside") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "aside", .{}) catch unreachable, ._tag = .aside }, + ), + asUint("meter") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "meter", .{}) catch unreachable, ._tag = .meter }, + ), + asUint("table") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "table", .{}) catch unreachable, ._tag = .table }, + ), + asUint("thead") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "thead", .{}) catch unreachable, ._tag = .thead }, + ), + asUint("tbody") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "tbody", .{}) catch unreachable, ._tag = .tbody }, + ), + asUint("tfoot") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "tfoot", .{}) catch unreachable, ._tag = .tfoot }, + ), + else => {}, + }, + 6 => switch (@as(u48, @bitCast(name[0..6].*))) { + asUint("script") => return self.createHtmlElementT( + Element.Html.Script, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("button") => return self.createHtmlElementT( + Element.Html.Button, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("canvas") => return self.createHtmlElementT( + Element.Html.Canvas, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("dialog") => return self.createHtmlElementT( + Element.Html.Dialog, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("strong") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "strong", .{}) catch unreachable, ._tag = .strong }, + ), + asUint("header") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "header", .{}) catch unreachable, ._tag = .header }, + ), + asUint("footer") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "footer", .{}) catch unreachable, ._tag = .footer }, + ), + asUint("select") => return self.createHtmlElementT( + Element.Html.Select, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("option") => return self.createHtmlElementT( + Element.Html.Option, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("iframe") => return self.createHtmlElementT( + Element.Html.IFrame, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("figure") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "figure", .{}) catch unreachable, ._tag = .figure }, + ), + asUint("hgroup") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "hgroup", .{}) catch unreachable, ._tag = .hgroup }, + ), + else => {}, + }, + 7 => switch (@as(u56, @bitCast(name[0..7].*))) { + asUint("section") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "section", .{}) catch unreachable, ._tag = .section }, + ), + asUint("article") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "article", .{}) catch unreachable, ._tag = .article }, + ), + asUint("details") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "details", .{}) catch unreachable, ._tag = .details }, + ), + asUint("summary") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "summary", .{}) catch unreachable, ._tag = .summary }, + ), + asUint("caption") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "caption", .{}) catch unreachable, ._tag = .caption }, + ), + asUint("marquee") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "marquee", .{}) catch unreachable, ._tag = .marquee }, + ), + asUint("address") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "address", .{}) catch unreachable, ._tag = .address }, + ), + else => {}, + }, + 8 => switch (@as(u64, @bitCast(name[0..8].*))) { + asUint("textarea") => return self.createHtmlElementT( + Element.Html.TextArea, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), + asUint("template") => return self.createHtmlElementT( + Element.Html.Template, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._content = undefined }, + ), + asUint("fieldset") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "fieldset", .{}) catch unreachable, ._tag = .fieldset }, + ), + asUint("optgroup") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "optgroup", .{}) catch unreachable, ._tag = .optgroup }, + ), + asUint("progress") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "progress", .{}) catch unreachable, ._tag = .progress }, + ), + asUint("datalist") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "datalist", .{}) catch unreachable, ._tag = .datalist }, + ), + else => {}, + }, + 10 => switch (@as(u80, @bitCast(name[0..10].*))) { + asUint("blockquote") => return self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "blockquote", .{}) catch unreachable, ._tag = .blockquote }, + ), + else => {}, + }, + else => {}, + } + + const tag_name = try String.init(self.arena, name, .{}); + + // Check if this is a custom element (must have hyphen for HTML namespace) + const has_hyphen = std.mem.indexOfScalar(u8, name, '-') != null; + if (has_hyphen and namespace == .html) { + const definition = self.window._custom_elements._definitions.get(name); + const node = try self.createHtmlElementT(Element.Html.Custom, namespace, attribute_iterator, .{ + ._proto = undefined, + ._tag_name = tag_name, + ._definition = definition, + }); + + const def = definition orelse { + const element = node.as(Element); + const custom = element.is(Element.Html.Custom).?; + try self._undefined_custom_elements.append(self.arena, custom); + return node; + }; + + // Save and restore upgrading element to allow nested createElement calls + const prev_upgrading = self._upgrading_element; + self._upgrading_element = node; + defer self._upgrading_element = prev_upgrading; + + var result: JS.Function.Result = undefined; + _ = def.constructor.newInstance(&result) catch |err| { + log.warn(.js, "custom element constructor", .{ .name = name, .err = err }); + return node; + }; + + // After constructor runs, invoke attributeChangedCallback for initial attributes + const element = node.as(Element); + if (element._attributes) |attributes| { + var it = attributes.iterator(); + while (it.next()) |attr| { + Element.Html.Custom.invokeAttributeChangedCallbackOnElement( + element, + attr._name.str(), + null, // old_value is null for initial attributes + attr._value.str(), + self, + ); } } - return n; - }, - asUint("menu") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "menu", .{}) catch unreachable, ._tag = .menu }, - ), - asUint("area") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "area", .{}) catch unreachable, ._tag = .area }, - ), - asUint("code") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "code", .{}) catch unreachable, ._tag = .code }, - ), - asUint("time") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "time", .{}) catch unreachable, ._tag = .time }, - ), - else => {}, - }, - 5 => switch (@as(u40, @bitCast(name[0..5].*))) { - asUint("input") => return self.createHtmlElementT( - Element.Html.Input, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("style") => return self.createHtmlElementT( - Element.Html.Style, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("title") => return self.createHtmlElementT( - Element.Html.Title, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("embed") => return self.createHtmlElementT( - Element.Html.Embed, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("audio") => return self.createHtmlMediaElementT( - Element.Html.Media.Audio, - namespace, - attribute_iterator, - ), - asUint("video") => return self.createHtmlMediaElementT( - Element.Html.Media.Video, - namespace, - attribute_iterator, - ), - asUint("aside") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "aside", .{}) catch unreachable, ._tag = .aside }, - ), - asUint("meter") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "meter", .{}) catch unreachable, ._tag = .meter }, - ), - asUint("table") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "table", .{}) catch unreachable, ._tag = .table }, - ), - asUint("thead") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "thead", .{}) catch unreachable, ._tag = .thead }, - ), - asUint("tbody") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "tbody", .{}) catch unreachable, ._tag = .tbody }, - ), - asUint("tfoot") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "tfoot", .{}) catch unreachable, ._tag = .tfoot }, - ), - else => {}, - }, - 6 => switch (@as(u48, @bitCast(name[0..6].*))) { - asUint("script") => return self.createHtmlElementT( - Element.Html.Script, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("button") => return self.createHtmlElementT( - Element.Html.Button, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("canvas") => return self.createHtmlElementT( - Element.Html.Canvas, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("dialog") => return self.createHtmlElementT( - Element.Html.Dialog, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("strong") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "strong", .{}) catch unreachable, ._tag = .strong }, - ), - asUint("header") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "header", .{}) catch unreachable, ._tag = .header }, - ), - asUint("footer") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "footer", .{}) catch unreachable, ._tag = .footer }, - ), - asUint("select") => return self.createHtmlElementT( - Element.Html.Select, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("option") => return self.createHtmlElementT( - Element.Html.Option, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("iframe") => return self.createHtmlElementT( - Element.Html.IFrame, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("figure") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "figure", .{}) catch unreachable, ._tag = .figure }, - ), - asUint("hgroup") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "hgroup", .{}) catch unreachable, ._tag = .hgroup }, - ), - else => {}, - }, - 7 => switch (@as(u56, @bitCast(name[0..7].*))) { - asUint("section") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "section", .{}) catch unreachable, ._tag = .section }, - ), - asUint("article") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "article", .{}) catch unreachable, ._tag = .article }, - ), - asUint("details") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "details", .{}) catch unreachable, ._tag = .details }, - ), - asUint("summary") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "summary", .{}) catch unreachable, ._tag = .summary }, - ), - asUint("caption") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "caption", .{}) catch unreachable, ._tag = .caption }, - ), - asUint("marquee") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "marquee", .{}) catch unreachable, ._tag = .marquee }, - ), - asUint("address") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "address", .{}) catch unreachable, ._tag = .address }, - ), - else => {}, - }, - 8 => switch (@as(u64, @bitCast(name[0..8].*))) { - asUint("textarea") => return self.createHtmlElementT( - Element.Html.TextArea, - namespace, - attribute_iterator, - .{ ._proto = undefined }, - ), - asUint("template") => return self.createHtmlElementT( - Element.Html.Template, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._content = undefined }, - ), - asUint("fieldset") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "fieldset", .{}) catch unreachable, ._tag = .fieldset }, - ), - asUint("optgroup") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "optgroup", .{}) catch unreachable, ._tag = .optgroup }, - ), - asUint("progress") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "progress", .{}) catch unreachable, ._tag = .progress }, - ), - asUint("datalist") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "datalist", .{}) catch unreachable, ._tag = .datalist }, - ), - else => {}, - }, - 10 => switch (@as(u80, @bitCast(name[0..10].*))) { - asUint("blockquote") => return self.createHtmlElementT( - Element.Html.Generic, - namespace, - attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "blockquote", .{}) catch unreachable, ._tag = .blockquote }, - ), - else => {}, - }, - else => {}, - } - - if (namespace == .svg) { - const tag_name = try String.init(self.arena, name, .{}); - if (std.ascii.eqlIgnoreCase(name, "svg")) { - return self.createSvgElementT(Element.Svg, name, attribute_iterator, .{ - ._proto = undefined, - ._type = .svg, - ._tag_name = tag_name, - }); - } - - // Other SVG elements (rect, circle, text, g, etc.) - const lower = std.ascii.lowerString(&self.buf, name); - const tag = std.meta.stringToEnum(Element.Tag, lower) orelse .unknown; - return self.createSvgElementT(Element.Svg.Generic, name, attribute_iterator, .{ ._proto = undefined, ._tag = tag }); - } - - const tag_name = try String.init(self.arena, name, .{}); - - // Check if this is a custom element (must have hyphen for HTML namespace) - const has_hyphen = std.mem.indexOfScalar(u8, name, '-') != null; - if (has_hyphen and namespace == .html) { - const definition = self.window._custom_elements._definitions.get(name); - const node = try self.createHtmlElementT(Element.Html.Custom, namespace, attribute_iterator, .{ - ._proto = undefined, - ._tag_name = tag_name, - ._definition = definition, - }); - - const def = definition orelse { - const element = node.as(Element); - const custom = element.is(Element.Html.Custom).?; - try self._undefined_custom_elements.append(self.arena, custom); - return node; - }; - - // Save and restore upgrading element to allow nested createElement calls - const prev_upgrading = self._upgrading_element; - self._upgrading_element = node; - defer self._upgrading_element = prev_upgrading; - - var result: JS.Function.Result = undefined; - _ = def.constructor.newInstance(&result) catch |err| { - log.warn(.js, "custom element constructor", .{ .name = name, .err = err }); - return node; - }; - - // After constructor runs, invoke attributeChangedCallback for initial attributes - const element = node.as(Element); - if (element._attributes) |attributes| { - var it = attributes.iterator(); - while (it.next()) |attr| { - Element.Html.Custom.invokeAttributeChangedCallbackOnElement( - element, - attr._name.str(), - null, // old_value is null for initial attributes - attr._value.str(), - self, - ); + return node; } - } - return node; + return self.createHtmlElementT(Element.Html.Unknown, namespace, attribute_iterator, .{ ._proto = undefined, ._tag_name = tag_name }); + }, + .svg => { + const tag_name = try String.init(self.arena, name, .{}); + if (std.ascii.eqlIgnoreCase(name, "svg")) { + return self.createSvgElementT(Element.Svg, name, attribute_iterator, .{ + ._proto = undefined, + ._type = .svg, + ._tag_name = tag_name, + }); + } + + // Other SVG elements (rect, circle, text, g, etc.) + const lower = std.ascii.lowerString(&self.buf, name); + const tag = std.meta.stringToEnum(Element.Tag, lower) orelse .unknown; + return self.createSvgElementT(Element.Svg.Generic, name, attribute_iterator, .{ ._proto = undefined, ._tag = tag }); + }, + else => { + const tag_name = try String.init(self.arena, name, .{}); + return self.createHtmlElementT(Element.Html.Unknown, namespace, attribute_iterator, .{ ._proto = undefined, ._tag_name = tag_name }); + }, } - - return self.createHtmlElementT(Element.Html.Unknown, namespace, attribute_iterator, .{ ._proto = undefined, ._tag_name = tag_name }); } fn createHtmlElementT(self: *Page, comptime E: type, namespace: Element.Namespace, attribute_iterator: anytype, html_element: E) !*Node { diff --git a/src/browser/parser/Parser.zig b/src/browser/parser/Parser.zig index 7e41ecb5..019c3d14 100644 --- a/src/browser/parser/Parser.zig +++ b/src/browser/parser/Parser.zig @@ -104,7 +104,7 @@ pub fn parseXML(self: *Parser, xml: []const u8) void { xml.len, &self.container, self, - createElementCallback, + createXMLElementCallback, getDataCallback, appendCallback, parseErrorCallback, @@ -225,17 +225,26 @@ fn _popCallback(self: *Parser, node: *Node) !void { } fn createElementCallback(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) callconv(.c) ?*anyopaque { + return _createElementCallbackWithDefaultnamespace(ctx, data, qname, attributes, .unknown); +} + +fn createXMLElementCallback(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) callconv(.c) ?*anyopaque { + return _createElementCallbackWithDefaultnamespace(ctx, data, qname, attributes, .xml); +} + +fn _createElementCallbackWithDefaultnamespace(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator, default_namespace: Element.Namespace) ?*anyopaque { const self: *Parser = @ptrCast(@alignCast(ctx)); - return self._createElementCallback(data, qname, attributes) catch |err| { + return self._createElementCallback(data, qname, attributes, default_namespace) catch |err| { self.err = .{ .err = err, .source = .create_element }; return null; }; } -fn _createElementCallback(self: *Parser, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) !*anyopaque { +fn _createElementCallback(self: *Parser, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator, default_namespace: Element.Namespace) !*anyopaque { const page = self.page; const name = qname.local.slice(); - const namespace = qname.ns.slice(); - const node = try page.createElement(namespace, name, attributes); + const namespace_string = qname.ns.slice(); + const namespace = if (namespace_string.len == 0) default_namespace else Element.Namespace.parse(namespace_string); + const node = try page.createElementNS(namespace, name, attributes); const pn = try self.arena.create(ParsedNode); pn.* = .{ diff --git a/src/browser/tests/document/create_element_ns.html b/src/browser/tests/document/create_element_ns.html index 46773ebc..123ccaac 100644 --- a/src/browser/tests/document/create_element_ns.html +++ b/src/browser/tests/document/create_element_ns.html @@ -19,12 +19,13 @@ testing.expectEqual('http://www.w3.org/XML/1998/namespace', xmlElement.namespaceURI); const nullNsElement = document.createElementNS(null, 'span'); - testing.expectEqual('SPAN', nullNsElement.tagName); - testing.expectEqual('http://www.w3.org/1999/xhtml', nullNsElement.namespaceURI); + testing.expectEqual('span', nullNsElement.tagName); + testing.expectEqual(null, nullNsElement.namespaceURI); const unknownNsElement = document.createElementNS('http://example.com/unknown', 'custom'); - testing.expectEqual('CUSTOM', unknownNsElement.tagName); - testing.expectEqual('http://www.w3.org/1999/xhtml', unknownNsElement.namespaceURI); + testing.expectEqual('custom', unknownNsElement.tagName); + // Should be http://example.com/unknown + testing.expectEqual('http://lightpanda.io/unsupported/namespace', unknownNsElement.namespaceURI); const regularDiv = document.createElement('div'); testing.expectEqual('DIV', regularDiv.tagName); @@ -36,5 +37,5 @@ testing.expectEqual('te:ST', custom.tagName); testing.expectEqual('te', custom.prefix); testing.expectEqual('ST', custom.localName); - testing.expectEqual('http://www.w3.org/1999/xhtml', custom.namespaceURI); // Should be test + testing.expectEqual('http://lightpanda.io/unsupported/namespace', custom.namespaceURI); // Should be test diff --git a/src/browser/tests/domimplementation.html b/src/browser/tests/domimplementation.html index d8980a8f..1d524e0d 100644 --- a/src/browser/tests/domimplementation.html +++ b/src/browser/tests/domimplementation.html @@ -168,7 +168,7 @@ const root = doc.documentElement; testing.expectEqual(true, root !== null); // TODO: XML documents should preserve case, but we currently uppercase - testing.expectEqual('ROOT', root.tagName); + testing.expectEqual('root', root.tagName); } @@ -206,10 +206,9 @@ const doc = impl.createDocument('http://example.com', 'prefix:localName', null); const root = doc.documentElement; - // TODO: XML documents should preserve case, but we currently uppercase - testing.expectEqual('prefix:LOCALNAME', root.tagName); - // TODO: Custom namespaces are being overridden to XHTML namespace - testing.expectEqual('http://www.w3.org/1999/xhtml', root.namespaceURI); + testing.expectEqual('prefix:localName', root.tagName); + // TODO: Custom namespaces are being replaced with an empty value + testing.expectEqual('http://lightpanda.io/unsupported/namespace', root.namespaceURI); } @@ -224,8 +223,7 @@ doc.documentElement.appendChild(child); testing.expectEqual(1, doc.documentElement.childNodes.length); - // TODO: XML documents should preserve case, but we currently uppercase - testing.expectEqual('CHILD', doc.documentElement.firstChild.tagName); + testing.expectEqual('child', doc.documentElement.firstChild.tagName); testing.expectEqual('Test', doc.documentElement.firstChild.textContent); } diff --git a/src/browser/tests/domparser.html b/src/browser/tests/domparser.html index 7f08b1ef..70fd6ff9 100644 --- a/src/browser/tests/domparser.html +++ b/src/browser/tests/domparser.html @@ -364,14 +364,14 @@ ]; for (const mime of mimes) { - const doc = parser.parseFromString(sampleXML, "text/xml"); + const doc = parser.parseFromString(sampleXML, mime); const { firstChild: { childNodes, children: collection, tagName }, children } = doc; // doc. testing.expectEqual(true, doc instanceof XMLDocument); testing.expectEqual(1, children.length); // firstChild. // TODO: Modern browsers expect this in lowercase. - testing.expectEqual("CATALOG", tagName); + testing.expectEqual("catalog", tagName); testing.expectEqual(25, childNodes.length); testing.expectEqual(12, collection.length); // Check children of first child. @@ -379,12 +379,12 @@ const {children: elements, id} = collection.item(i); testing.expectEqual("bk" + (100 + i + 1), id); // TODO: Modern browsers expect these in lowercase. - testing.expectEqual("AUTHOR", elements.item(0).tagName); - testing.expectEqual("TITLE", elements.item(1).tagName); - testing.expectEqual("GENRE", elements.item(2).tagName); - testing.expectEqual("PRICE", elements.item(3).tagName); - testing.expectEqual("PUBLISH_DATE", elements.item(4).tagName); - testing.expectEqual("DESCRIPTION", elements.item(5).tagName); + testing.expectEqual("author", elements.item(0).tagName); + testing.expectEqual("title", elements.item(1).tagName); + testing.expectEqual("genre", elements.item(2).tagName); + testing.expectEqual("price", elements.item(3).tagName); + testing.expectEqual("publish_date", elements.item(4).tagName); + testing.expectEqual("description", elements.item(5).tagName); } } } diff --git a/src/browser/webapi/DOMImplementation.zig b/src/browser/webapi/DOMImplementation.zig index 7144b11a..743cdd95 100644 --- a/src/browser/webapi/DOMImplementation.zig +++ b/src/browser/webapi/DOMImplementation.zig @@ -57,26 +57,26 @@ pub fn createHTMLDocument(_: *const DOMImplementation, title: ?[]const u8, page: _ = try document.asNode().appendChild(doctype.asNode(), page); } - const html_node = try page.createElement(null, "html", null); + const html_node = try page.createElementNS(.html, "html", null); _ = try document.asNode().appendChild(html_node, page); - const head_node = try page.createElement(null, "head", null); + const head_node = try page.createElementNS(.html, "head", null); _ = try html_node.appendChild(head_node, page); if (title) |t| { - const title_node = try page.createElement(null, "title", null); + const title_node = try page.createElementNS(.html, "title", null); _ = try head_node.appendChild(title_node, page); const text_node = try page.createTextNode(t); _ = try title_node.appendChild(text_node, page); } - const body_node = try page.createElement(null, "body", null); + const body_node = try page.createElementNS(.html, "body", null); _ = try html_node.appendChild(body_node, page); return document; } -pub fn createDocument(_: *const DOMImplementation, namespace: ?[]const u8, qualified_name: ?[]const u8, doctype: ?*DocumentType, page: *Page) !*Document { +pub fn createDocument(_: *const DOMImplementation, namespace_: ?[]const u8, qualified_name: ?[]const u8, doctype: ?*DocumentType, page: *Page) !*Document { // Create XML Document const document = (try page._factory.document(Node.Document.XMLDocument{ ._proto = undefined })).asDocument(); @@ -88,7 +88,8 @@ pub fn createDocument(_: *const DOMImplementation, namespace: ?[]const u8, quali // Create and append root element if qualified_name provided if (qualified_name) |qname| { if (qname.len > 0) { - const root = try page.createElement(namespace, qname, null); + const namespace = if (namespace_) |ns| Node.Element.Namespace.parse(ns) else .xml; + const root = try page.createElementNS(namespace, qname, null); _ = try document.asNode().appendChild(root, page); } } diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 66027c65..3af6a59b 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -124,7 +124,14 @@ const CreateElementOptions = struct { }; pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElementOptions, page: *Page) !*Element { - const node = try page.createElement(null, name, null); + const namespace: Element.Namespace = blk: { + if (self._type == .xml) { + @branchHint(.unlikely); + break :blk .xml; + } + break :blk .html; + }; + const node = try page.createElementNS(namespace, name, null); const element = node.as(Element); // Track owner document if it's not the main document @@ -142,7 +149,7 @@ pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElement } pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8, page: *Page) !*Element { - const node = try page.createElement(namespace, name, null); + const node = try page.createElementNS(Element.Namespace.parse(namespace), name, null); // Track owner document if it's not the main document if (self != page.document) { diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index b7bf18b0..d7464661 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -53,15 +53,41 @@ pub const Namespace = enum(u8) { svg, mathml, xml, + // We should keep the original value, but don't. If this becomes important + // consider storing it in a page lookup, like `_element_class_lists`, rather + // that adding a slice directly here (directly in every element). + unknown, + null, - pub fn toUri(self: Namespace) []const u8 { + pub fn toUri(self: Namespace) ?[]const u8 { return switch (self) { .html => "http://www.w3.org/1999/xhtml", .svg => "http://www.w3.org/2000/svg", .mathml => "http://www.w3.org/1998/Math/MathML", .xml => "http://www.w3.org/XML/1998/namespace", + .unknown => "http://lightpanda.io/unsupported/namespace", + .null => null, }; } + + pub fn parse(namespace_: ?[]const u8) Namespace { + const namespace = namespace_ orelse return .null; + if (namespace.len == "http://www.w3.org/1999/xhtml".len) { + // Common case, avoid the string comparion. Recklessly + @branchHint(.likely); + return .html; + } + if (std.mem.eql(u8, namespace, "http://www.w3.org/XML/1998/namespace")) { + return .xml; + } + if (std.mem.eql(u8, namespace, "http://www.w3.org/2000/svg")) { + return .svg; + } + if (std.mem.eql(u8, namespace, "http://www.w3.org/1998/Math/MathML")) { + return .mathml; + } + return .unknown; + } }; _type: Type, @@ -211,60 +237,54 @@ pub fn getTagNameLower(self: *const Element) []const u8 { } pub fn getTagNameSpec(self: *const Element, buf: []u8) []const u8 { - switch (self._type) { + return switch (self._type) { .html => |he| switch (he._type) { - .custom => |e| { - @branchHint(.unlikely); - return upperTagName(&e._tag_name, buf); + .anchor => "A", + .body => "BODY", + .br => "BR", + .button => "BUTTON", + .canvas => "CANVAS", + .custom => |e| upperTagName(&e._tag_name, buf), + .data => "DATA", + .dialog => "DIALOG", + .div => "DIV", + .embed => "EMBED", + .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", + .li => "LI", + .link => "LINK", + .meta => "META", + .media => |m| switch (m._type) { + .audio => "AUDIO", + .video => "VIDEO", + .generic => "MEDIA", }, - else => return switch (he._type) { - .anchor => "A", - .body => "BODY", - .br => "BR", - .button => "BUTTON", - .canvas => "CANVAS", - .custom => |e| upperTagName(&e._tag_name, buf), - .data => "DATA", - .dialog => "DIALOG", - .div => "DIV", - .embed => "EMBED", - .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", - .li => "LI", - .link => "LINK", - .meta => "META", - .media => |m| switch (m._type) { - .audio => "AUDIO", - .video => "VIDEO", - .generic => "MEDIA", - }, - .ol => "OL", - .option => "OPTION", - .p => "P", - .script => "SCRIPT", - .select => "SELECT", - .slot => "SLOT", - .span => "SPAN", - .style => "STYLE", - .template => "TEMPLATE", - .textarea => "TEXTAREA", - .title => "TITLE", - .ul => "UL", - .unknown => |e| switch (self._namespace) { - .html => upperTagName(&e._tag_name, buf), - .svg, .xml, .mathml => return e._tag_name.str(), - }, + .ol => "OL", + .option => "OPTION", + .p => "P", + .script => "SCRIPT", + .select => "SELECT", + .slot => "SLOT", + .span => "SPAN", + .style => "STYLE", + .template => "TEMPLATE", + .textarea => "TEXTAREA", + .title => "TITLE", + .ul => "UL", + .unknown => |e| switch (self._namespace) { + .html => upperTagName(&e._tag_name, buf), + .svg, .xml, .mathml, .unknown, .null => e._tag_name.str(), }, }, - .svg => |svg| return svg._tag_name.str(), - } + .svg => |svg| svg._tag_name.str(), + }; } pub fn getTagNameDump(self: *const Element) []const u8 { @@ -274,7 +294,7 @@ pub fn getTagNameDump(self: *const Element) []const u8 { } } -pub fn getNamespaceURI(self: *const Element) []const u8 { +pub fn getNamespaceURI(self: *const Element) ?[]const u8 { return self._namespace.toUri(); } @@ -996,9 +1016,7 @@ pub fn getElementsByClassName(self: *Element, class_name: []const u8, page: *Pag pub fn cloneElement(self: *Element, deep: bool, page: *Page) !*Node { const tag_name = self.getTagNameDump(); - const namespace_uri = self.getNamespaceURI(); - - const node = try page.createElement(namespace_uri, tag_name, self._attributes); + const node = try page.createElementNS(self._namespace, tag_name, self._attributes); // Allow element-specific types to copy their runtime state _ = Element.Build.call(node.as(Element), "cloned", .{ self, node.as(Element), page }) catch |err| { diff --git a/src/browser/webapi/HTMLDocument.zig b/src/browser/webapi/HTMLDocument.zig index 13278423..18aa9f56 100644 --- a/src/browser/webapi/HTMLDocument.zig +++ b/src/browser/webapi/HTMLDocument.zig @@ -132,7 +132,7 @@ pub fn setTitle(self: *HTMLDocument, title: []const u8, page: *Page) !void { } // No title element found, create one - const title_node = try page.createElement(null, "title", null); + const title_node = try page.createElementNS(.html, "title", null); const title_element = title_node.as(Element); // Only add text if non-empty diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig index ce9b0d6d..54f4f5a4 100644 --- a/src/browser/webapi/Range.zig +++ b/src/browser/webapi/Range.zig @@ -443,9 +443,9 @@ pub fn createContextualFragment(self: *const Range, html: []const u8, page: *Pag // Create a temporary element of the same type as the context for parsing // This preserves the parsing context without modifying the original node const temp_node = if (context_node.is(Node.Element)) |el| - try page.createElement(el._namespace.toUri(), el.getTagNameLower(), null) + try page.createElementNS(el._namespace, el.getTagNameLower(), null) else - try page.createElement(null, "div", null); + try page.createElementNS(.html, "div", null); try page.parseHtmlAsChildren(temp_node, html); diff --git a/src/browser/webapi/element/html/Image.zig b/src/browser/webapi/element/html/Image.zig index aa6cdd04..8846d0c6 100644 --- a/src/browser/webapi/element/html/Image.zig +++ b/src/browser/webapi/element/html/Image.zig @@ -10,7 +10,7 @@ const Image = @This(); _proto: *HtmlElement, pub fn constructor(w_: ?u32, h_: ?u32, page: *Page) !*Image { - const node = try page.createElement(null, "img", null); + const node = try page.createElementNS(.html, "img", null); const el = node.as(Element); if (w_) |w| blk: {