diff --git a/src/dom/document.zig b/src/dom/document.zig index 7e2ac989..a2dfe3c8 100644 --- a/src/dom/document.zig +++ b/src/dom/document.zig @@ -7,6 +7,7 @@ const Case = jsruntime.test_utils.Case; const checkCases = jsruntime.test_utils.checkCases; const Node = @import("node.zig").Node; +const HTMLCollection = @import("html_collection.zig").HTMLCollection; const Element = @import("element.zig").Element; const ElementUnion = @import("element.zig").Union; @@ -34,6 +35,23 @@ pub const Document = struct { const e = parser.documentCreateElement(self, tag_name); return Element.toInterface(e); } + + // We can't simply use libdom dom_document_get_elements_by_tag_name here. + // Indeed, netsurf implemented a previous dom spec when + // getElementsByTagName returned a NodeList. + // But since + // https://github.com/whatwg/dom/commit/190700b7c12ecfd3b5ebdb359ab1d6ea9cbf7749 + // the spec changed to return an HTMLCollection instead. + // That's why we reimplemented getElementsByTagName by using an + // HTMLCollection in zig here. + pub fn _getElementsByTagName(self: *parser.Document, tag_name: []const u8) HTMLCollection { + const root = parser.documentGetDocumentNode(self); + return HTMLCollection{ + .root = root, + // TODO handle case insensitive comparison. + .match = tag_name, + }; + } }; // Tests @@ -58,6 +76,19 @@ pub fn testExecFn( }; try checkCases(js_env, &getElementById); + var getElementsByTagName = [_]Case{ + .{ .src = "let getElementsByTagName = document.getElementsByTagName('P')", .ex = "undefined" }, + .{ .src = "getElementsByTagName.length", .ex = "2" }, + .{ .src = "getElementsByTagName.item(0).localName", .ex = "p" }, + .{ .src = "getElementsByTagName.item(1).localName", .ex = "p" }, + .{ .src = "let getElementsByTagNameAll = document.getElementsByTagName('*')", .ex = "undefined" }, + .{ .src = "getElementsByTagNameAll.length", .ex = "8" }, + .{ .src = "getElementsByTagNameAll.item(0).localName", .ex = "html" }, + .{ .src = "getElementsByTagNameAll.item(1).localName", .ex = "head" }, + .{ .src = "getElementsByTagNameAll.item(2).localName", .ex = "body" }, + }; + try checkCases(js_env, &getElementsByTagName); + const tags = comptime parser.Tag.all(); comptime var createElements: [(tags.len) * 2]Case = undefined; inline for (tags, 0..) |tag, i| { diff --git a/src/dom/html_collection.zig b/src/dom/html_collection.zig index a48f5cbc..9dfb3b94 100644 --- a/src/dom/html_collection.zig +++ b/src/dom/html_collection.zig @@ -7,22 +7,122 @@ const jsruntime = @import("jsruntime"); const Element = @import("element.zig").Element; // WEB IDL https://dom.spec.whatwg.org/#htmlcollection +// HTMLCollection is re implemented in zig here because libdom +// dom_html_collection expects a comparison function callback as arguement. +// But we wanted a dynamically comparison here, according to the match tagname. pub const HTMLCollection = struct { - pub const Self = parser.HTMLCollection; pub const mem_guarantied = true; - // JS funcs - // -------- + root: *parser.Node, + // match is used to select node against their name. + // match comparison is case sensitive. + match: []const u8, - pub fn _get_length(self: *parser.HTMLCollection) u32 { - return parser.HTMLCollectionLength(self); + /// _get_length computes the collection's length dynamically according to + /// the current root structure. + // TODO: nodes retrieved must be de-referenced. + pub fn get_length(self: *HTMLCollection) u32 { + var len: u32 = 0; + var node: ?*parser.Node = self.root; + var ntype: parser.NodeType = undefined; + + var is_wildcard = std.mem.eql(u8, self.match, "*"); + + while (node != null) { + ntype = parser.nodeType(node.?); + if (ntype == .element) { + if (is_wildcard or std.mem.eql(u8, self.match, parser.nodeName(node.?))) { + len += 1; + } + } + + // Iterate hover the DOM tree. + var next = parser.nodeFirstChild(node.?); + if (next != null) { + node = next; + continue; + } + + next = parser.nodeNextSibling(node.?); + if (next != null) { + node = next; + continue; + } + + var parent = parser.nodeParentNode(node.?); + var lastchild = parser.nodeLastChild(parent.?); + while (node.? != self.root and node.? == lastchild) { + node = parent; + parent = parser.nodeParentNode(node.?); + lastchild = parser.nodeLastChild(parent.?); + } + + if (node.? == self.root) { + node = null; + continue; + } + + node = parser.nodeNextSibling(node.?); + } + + return len; } - pub fn _item(self: *parser.HTMLCollection, index: u32) ?*parser.Element { - return parser.HTMLCollectionItem(self, index); + pub fn _item(self: *HTMLCollection, index: u32) ?*parser.Element { + var len: u32 = 0; + var node: ?*parser.Node = self.root; + var ntype: parser.NodeType = undefined; + + var is_wildcard = std.mem.eql(u8, self.match, "*"); + + while (node != null) { + ntype = parser.nodeType(node.?); + if (ntype == .element) { + if (is_wildcard or std.mem.eql(u8, self.match, parser.nodeName(node.?))) { + len += 1; + + // check if we found the searched element. + if (len == index + 1) { + return @as(*parser.Element, @ptrCast(node)); + } + } + } + + // Iterate hover the DOM tree. + var next = parser.nodeFirstChild(node.?); + if (next != null) { + node = next; + continue; + } + + next = parser.nodeNextSibling(node.?); + if (next != null) { + node = next; + continue; + } + + var parent = parser.nodeParentNode(node.?); + var lastchild = parser.nodeLastChild(parent.?); + while (node.? != self.root and node.? == lastchild) { + node = parent; + parent = parser.nodeParentNode(node.?); + lastchild = parser.nodeLastChild(parent.?); + } + + if (node.? == self.root) { + node = null; + continue; + } + + node = parser.nodeNextSibling(node.?); + } + + return null; } - pub fn _namedItem(self: *parser.HTMLCollection, name: []const u8) ?*parser.Element { - return parser.HTMLCollectionNamedItem(self, name); + pub fn _namedItem(self: *HTMLCollection, name: []const u8) ?*parser.Element { + _ = name; + _ = self; + return null; } }; diff --git a/src/netsurf.zig b/src/netsurf.zig index 1f21563a..159db9c1 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -765,6 +765,18 @@ pub inline fn documentGetElementsByTagName(doc: *Document, tagname: []const u8) return nlist.?; } +// documentGetDocumentElement returns the root document element. +pub inline fn documentGetDocumentElement(doc: *Document) *Element { + var elem: ?*Element = undefined; + _ = documentVtable(doc).dom_document_get_document_element.?(doc, &elem); + return elem.?; +} + +pub inline fn documentGetDocumentNode(doc: *Document) *Node { + const res = documentGetDocumentElement(doc); + return @as(*Node, @ptrCast(res)); +} + pub inline fn documentCreateElement(doc: *Document, tag_name: []const u8) *Element { var elem: ?*Element = undefined; _ = documentVtable(doc).dom_document_create_element.?(doc, stringFromData(tag_name), &elem);