From 98c4f506b72d1a0d2553537afc27243f69254d4c Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 1 Dec 2023 15:36:56 +0100 Subject: [PATCH 1/6] dom: implement element.*Attribute --- src/dom/element.zig | 50 + src/netsurf.zig | 28 + src/run_tests.zig | 2 + tests/wpt/dom/nodes/Element-hasAttribute.html | 32 + .../wpt/dom/nodes/Element-hasAttributes.html | 40 + .../dom/nodes/Element-removeAttribute.html | 58 ++ .../dom/nodes/Element-removeAttributeNS.html | 18 + .../Element-setAttribute-crbug-1138487.html | 22 + tests/wpt/dom/nodes/Element-setAttribute.html | 38 + tests/wpt/dom/nodes/attributes.html | 858 ++++++++++++++++++ tests/wpt/dom/nodes/attributes.js | 18 + tests/wpt/dom/nodes/productions.js | 3 + 12 files changed, 1167 insertions(+) create mode 100644 tests/wpt/dom/nodes/Element-hasAttribute.html create mode 100644 tests/wpt/dom/nodes/Element-hasAttributes.html create mode 100644 tests/wpt/dom/nodes/Element-removeAttribute.html create mode 100644 tests/wpt/dom/nodes/Element-removeAttributeNS.html create mode 100644 tests/wpt/dom/nodes/Element-setAttribute-crbug-1138487.html create mode 100644 tests/wpt/dom/nodes/Element-setAttribute.html create mode 100644 tests/wpt/dom/nodes/attributes.html create mode 100644 tests/wpt/dom/nodes/attributes.js create mode 100644 tests/wpt/dom/nodes/productions.js diff --git a/src/dom/element.zig b/src/dom/element.zig index c5f9a645..0410ad1e 100644 --- a/src/dom/element.zig +++ b/src/dom/element.zig @@ -2,6 +2,10 @@ const std = @import("std"); const parser = @import("../netsurf.zig"); +const jsruntime = @import("jsruntime"); +const Case = jsruntime.test_utils.Case; +const checkCases = jsruntime.test_utils.checkCases; + const Node = @import("node.zig").Node; const HTMLElem = @import("../html/elements.zig"); pub const Union = @import("../html/elements.zig").Union; @@ -22,4 +26,50 @@ pub const Element = struct { pub fn get_localName(self: *parser.Element) ![]const u8 { return try parser.elementLocalName(self); } + + pub fn _getAttribute(self: *parser.Element, qname: []const u8) !?[]const u8 { + return try parser.elementGetAttribute(self, qname); + } + + pub fn _setAttribute(self: *parser.Element, qname: []const u8, value: []const u8) !void { + return try parser.elementSetAttribute(self, qname, value); + } + + pub fn _removeAttribute(self: *parser.Element, qname: []const u8) !void { + return try parser.elementRemoveAttribute(self, qname); + } + + pub fn _hasAttribute(self: *parser.Element, qname: []const u8) !bool { + return try parser.elementHasAttribute(self, qname); + } }; + +// Tests +// ----- + +pub fn testExecFn( + _: std.mem.Allocator, + js_env: *jsruntime.Env, + comptime _: []jsruntime.API, +) !void { + var attribute = [_]Case{ + .{ .src = "let div = document.getElementById('content')", .ex = "undefined" }, + .{ .src = "div.getAttribute('id')", .ex = "content" }, + + .{ .src = "div.hasAttribute('foo')", .ex = "false" }, + .{ .src = "div.getAttribute('foo')", .ex = "null" }, + + .{ .src = "div.setAttribute('foo', 'bar')", .ex = "undefined" }, + .{ .src = "div.hasAttribute('foo')", .ex = "true" }, + .{ .src = "div.getAttribute('foo')", .ex = "bar" }, + + .{ .src = "div.setAttribute('foo', 'baz')", .ex = "undefined" }, + .{ .src = "div.hasAttribute('foo')", .ex = "true" }, + .{ .src = "div.getAttribute('foo')", .ex = "baz" }, + + .{ .src = "div.removeAttribute('foo')", .ex = "undefined" }, + .{ .src = "div.hasAttribute('foo')", .ex = "false" }, + .{ .src = "div.getAttribute('foo')", .ex = "null" }, + }; + try checkCases(js_env, &attribute); +} diff --git a/src/netsurf.zig b/src/netsurf.zig index a7d0a22f..1c857ad6 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -749,6 +749,34 @@ pub fn elementGetAttribute(elem: *Element, name: []const u8) !?[]const u8 { return stringToData(s.?); } +pub fn elementSetAttribute(elem: *Element, qname: []const u8, value: []const u8) !void { + const err = elementVtable(elem).dom_element_set_attribute.?( + elem, + try stringFromData(qname), + try stringFromData(value), + ); + try DOMErr(err); +} + +pub fn elementRemoveAttribute(elem: *Element, qname: []const u8) !void { + const err = elementVtable(elem).dom_element_remove_attribute.?( + elem, + try stringFromData(qname), + ); + try DOMErr(err); +} + +pub fn elementHasAttribute(elem: *Element, qname: []const u8) !bool { + var res: bool = undefined; + const err = elementVtable(elem).dom_element_has_attribute.?( + elem, + try stringFromData(qname), + &res, + ); + try DOMErr(err); + return res; +} + pub fn elementHasClass(elem: *Element, class: []const u8) !bool { var res: bool = undefined; const err = elementVtable(elem).dom_element_has_class.?( diff --git a/src/run_tests.zig b/src/run_tests.zig index 9edab848..e478b84f 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -11,6 +11,7 @@ const HTMLDocumentTestExecFn = @import("html/document.zig").testExecFn; const nodeTestExecFn = @import("dom/node.zig").testExecFn; const characterDataTestExecFn = @import("dom/character_data.zig").testExecFn; const textTestExecFn = @import("dom/text.zig").testExecFn; +const elementTestExecFn = @import("dom/element.zig").testExecFn; const HTMLCollectionTestExecFn = @import("dom/html_collection.zig").testExecFn; const DOMExceptionTestExecFn = @import("dom/exceptions.zig").testExecFn; const DOMImplementationExecFn = @import("dom/implementation.zig").testExecFn; @@ -56,6 +57,7 @@ fn testsAllExecFn( nodeTestExecFn, characterDataTestExecFn, textTestExecFn, + elementTestExecFn, HTMLCollectionTestExecFn, DOMExceptionTestExecFn, DOMImplementationExecFn, diff --git a/tests/wpt/dom/nodes/Element-hasAttribute.html b/tests/wpt/dom/nodes/Element-hasAttribute.html new file mode 100644 index 00000000..26528d75 --- /dev/null +++ b/tests/wpt/dom/nodes/Element-hasAttribute.html @@ -0,0 +1,32 @@ + + +Element.prototype.hasAttribute + + + + + + + diff --git a/tests/wpt/dom/nodes/Element-hasAttributes.html b/tests/wpt/dom/nodes/Element-hasAttributes.html new file mode 100644 index 00000000..fbb9c233 --- /dev/null +++ b/tests/wpt/dom/nodes/Element-hasAttributes.html @@ -0,0 +1,40 @@ + + + + + + + + +
+

+ + + diff --git a/tests/wpt/dom/nodes/Element-removeAttribute.html b/tests/wpt/dom/nodes/Element-removeAttribute.html new file mode 100644 index 00000000..df79e62c --- /dev/null +++ b/tests/wpt/dom/nodes/Element-removeAttribute.html @@ -0,0 +1,58 @@ + + +Element.prototype.removeAttribute + + + + + diff --git a/tests/wpt/dom/nodes/Element-removeAttributeNS.html b/tests/wpt/dom/nodes/Element-removeAttributeNS.html new file mode 100644 index 00000000..a2773e6f --- /dev/null +++ b/tests/wpt/dom/nodes/Element-removeAttributeNS.html @@ -0,0 +1,18 @@ + +Element.removeAttributeNS + + + +
+ diff --git a/tests/wpt/dom/nodes/Element-setAttribute-crbug-1138487.html b/tests/wpt/dom/nodes/Element-setAttribute-crbug-1138487.html new file mode 100644 index 00000000..9aa9ed81 --- /dev/null +++ b/tests/wpt/dom/nodes/Element-setAttribute-crbug-1138487.html @@ -0,0 +1,22 @@ + + + + diff --git a/tests/wpt/dom/nodes/Element-setAttribute.html b/tests/wpt/dom/nodes/Element-setAttribute.html new file mode 100644 index 00000000..76094068 --- /dev/null +++ b/tests/wpt/dom/nodes/Element-setAttribute.html @@ -0,0 +1,38 @@ + + +Element.prototype.setAttribute + + + + + diff --git a/tests/wpt/dom/nodes/attributes.html b/tests/wpt/dom/nodes/attributes.html new file mode 100644 index 00000000..c6db7eb8 --- /dev/null +++ b/tests/wpt/dom/nodes/attributes.html @@ -0,0 +1,858 @@ + + +Attributes tests + + + + + + + +
+ + + + + + + + diff --git a/tests/wpt/dom/nodes/attributes.js b/tests/wpt/dom/nodes/attributes.js new file mode 100644 index 00000000..ef32bf6a --- /dev/null +++ b/tests/wpt/dom/nodes/attributes.js @@ -0,0 +1,18 @@ +function attr_is(attr, v, ln, ns, p, n) { + assert_equals(attr.value, v) + assert_equals(attr.nodeValue, v) + assert_equals(attr.textContent, v) + assert_equals(attr.localName, ln) + assert_equals(attr.namespaceURI, ns) + assert_equals(attr.prefix, p) + assert_equals(attr.name, n) + assert_equals(attr.nodeName, n); + assert_equals(attr.specified, true) +} + +function attributes_are(el, l) { + for (var i = 0, il = l.length; i < il; i++) { + attr_is(el.attributes[i], l[i][1], l[i][0], (l[i].length < 3) ? null : l[i][2], null, l[i][0]) + assert_equals(el.attributes[i].ownerElement, el) + } +} diff --git a/tests/wpt/dom/nodes/productions.js b/tests/wpt/dom/nodes/productions.js new file mode 100644 index 00000000..218797fc --- /dev/null +++ b/tests/wpt/dom/nodes/productions.js @@ -0,0 +1,3 @@ +var invalid_names = ["", "invalid^Name", "\\", "'", '"', "0", "0:a"] // XXX +var valid_names = ["x", "X", ":", "a:0"] +var invalid_qnames = [":a", "b:", "x:y:z"] // XXX From acf737152c45330080ad1382806e58b9edfff3cf Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 1 Dec 2023 16:25:52 +0100 Subject: [PATCH 2/6] dom: implement element.toggleAttribute --- src/dom/element.zig | 67 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/src/dom/element.zig b/src/dom/element.zig index 0410ad1e..51f7e80b 100644 --- a/src/dom/element.zig +++ b/src/dom/element.zig @@ -42,6 +42,36 @@ pub const Element = struct { pub fn _hasAttribute(self: *parser.Element, qname: []const u8) !bool { return try parser.elementHasAttribute(self, qname); } + + // https://dom.spec.whatwg.org/#dom-element-toggleattribute + pub fn _toggleAttribute(self: *parser.Element, qname: []const u8, force: ?bool) !bool { + const exists = try parser.elementHasAttribute(self, qname); + + // If attribute is null, then: + if (!exists) { + // If force is not given or is true, create an attribute whose + // local name is qualifiedName, value is the empty string and node + // document is this’s node document, then append this attribute to + // this, and then return true. + if (force == null or force.?) { + try parser.elementSetAttribute(self, qname, ""); + return true; + } + + // Return false. + return false; + } + + // Otherwise, if force is not given or is false, remove an attribute + // given qualifiedName and this, and then return false. + if (force == null or !force.?) { + try parser.elementRemoveAttribute(self, qname); + return false; + } + + // Return true. + return true; + } }; // Tests @@ -53,23 +83,34 @@ pub fn testExecFn( comptime _: []jsruntime.API, ) !void { var attribute = [_]Case{ - .{ .src = "let div = document.getElementById('content')", .ex = "undefined" }, - .{ .src = "div.getAttribute('id')", .ex = "content" }, + .{ .src = "let a = document.getElementById('content')", .ex = "undefined" }, + .{ .src = "a.getAttribute('id')", .ex = "content" }, - .{ .src = "div.hasAttribute('foo')", .ex = "false" }, - .{ .src = "div.getAttribute('foo')", .ex = "null" }, + .{ .src = "a.hasAttribute('foo')", .ex = "false" }, + .{ .src = "a.getAttribute('foo')", .ex = "null" }, - .{ .src = "div.setAttribute('foo', 'bar')", .ex = "undefined" }, - .{ .src = "div.hasAttribute('foo')", .ex = "true" }, - .{ .src = "div.getAttribute('foo')", .ex = "bar" }, + .{ .src = "a.setAttribute('foo', 'bar')", .ex = "undefined" }, + .{ .src = "a.hasAttribute('foo')", .ex = "true" }, + .{ .src = "a.getAttribute('foo')", .ex = "bar" }, - .{ .src = "div.setAttribute('foo', 'baz')", .ex = "undefined" }, - .{ .src = "div.hasAttribute('foo')", .ex = "true" }, - .{ .src = "div.getAttribute('foo')", .ex = "baz" }, + .{ .src = "a.setAttribute('foo', 'baz')", .ex = "undefined" }, + .{ .src = "a.hasAttribute('foo')", .ex = "true" }, + .{ .src = "a.getAttribute('foo')", .ex = "baz" }, - .{ .src = "div.removeAttribute('foo')", .ex = "undefined" }, - .{ .src = "div.hasAttribute('foo')", .ex = "false" }, - .{ .src = "div.getAttribute('foo')", .ex = "null" }, + .{ .src = "a.removeAttribute('foo')", .ex = "undefined" }, + .{ .src = "a.hasAttribute('foo')", .ex = "false" }, + .{ .src = "a.getAttribute('foo')", .ex = "null" }, }; try checkCases(js_env, &attribute); + + var toggleAttr = [_]Case{ + .{ .src = "let b = document.getElementById('content')", .ex = "undefined" }, + .{ .src = "b.toggleAttribute('foo')", .ex = "true" }, + .{ .src = "b.hasAttribute('foo')", .ex = "true" }, + .{ .src = "b.getAttribute('foo')", .ex = "" }, + + .{ .src = "b.toggleAttribute('foo')", .ex = "false" }, + .{ .src = "b.hasAttribute('foo')", .ex = "false" }, + }; + try checkCases(js_env, &toggleAttr); } From dcb095d9df941a7da5bdbe8ca862fce9965ddbde Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 1 Dec 2023 16:51:58 +0100 Subject: [PATCH 3/6] dom: add element.HasAttributes --- src/dom/element.zig | 5 +++++ src/netsurf.zig | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/dom/element.zig b/src/dom/element.zig index 51f7e80b..2e725102 100644 --- a/src/dom/element.zig +++ b/src/dom/element.zig @@ -27,6 +27,10 @@ pub const Element = struct { return try parser.elementLocalName(self); } + pub fn _hasAttributes(self: *parser.Element) !bool { + return try parser.nodeHasAttributes(parser.elementToNode(self)); + } + pub fn _getAttribute(self: *parser.Element, qname: []const u8) !?[]const u8 { return try parser.elementGetAttribute(self, qname); } @@ -84,6 +88,7 @@ pub fn testExecFn( ) !void { var attribute = [_]Case{ .{ .src = "let a = document.getElementById('content')", .ex = "undefined" }, + .{ .src = "a.hasAttributes()", .ex = "true" }, .{ .src = "a.getAttribute('id')", .ex = "content" }, .{ .src = "a.hasAttribute('foo')", .ex = "false" }, diff --git a/src/netsurf.zig b/src/netsurf.zig index 1c857ad6..b0423032 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -630,6 +630,13 @@ pub fn nodeReplaceChild(node: *Node, new_child: *Node, old_child: *Node) !*Node return res.?; } +pub fn nodeHasAttributes(node: *Node) !bool { + var res: bool = undefined; + const err = nodeVtable(node).dom_node_has_attributes.?(node, &res); + try DOMErr(err); + return res; +} + // nodeToElement is an helper to convert a node to an element. pub inline fn nodeToElement(node: *Node) *Element { return @as(*Element, @ptrCast(node)); From ad5c6236a7b78c0a345167c7783f7759981a019d Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Sat, 2 Dec 2023 10:54:12 +0100 Subject: [PATCH 4/6] dom: add NamedNodeMap implementation and create Attr type --- src/dom/attribute.zig | 15 +++++ src/dom/dom.zig | 2 + src/dom/element.zig | 10 +++ src/dom/namednodemap.zig | 80 ++++++++++++++++++++++++ src/dom/node.zig | 3 + src/netsurf.zig | 132 +++++++++++++++++++++++++++++++++++++++ src/run_tests.zig | 2 + 7 files changed, 244 insertions(+) create mode 100644 src/dom/attribute.zig create mode 100644 src/dom/namednodemap.zig diff --git a/src/dom/attribute.zig b/src/dom/attribute.zig new file mode 100644 index 00000000..78563920 --- /dev/null +++ b/src/dom/attribute.zig @@ -0,0 +1,15 @@ +const std = @import("std"); + +const parser = @import("../netsurf.zig"); + +const Node = @import("node.zig").Node; +const DOMException = @import("exceptions.zig").DOMException; + +// WEB IDL https://dom.spec.whatwg.org/#attr +pub const Attr = struct { + pub const Self = parser.Attribute; + pub const prototype = *Node; + pub const mem_guarantied = true; + + pub const Exception = DOMException; +}; diff --git a/src/dom/dom.zig b/src/dom/dom.zig index 6a29a937..564dc7ca 100644 --- a/src/dom/dom.zig +++ b/src/dom/dom.zig @@ -3,12 +3,14 @@ const generate = @import("../generate.zig"); const DOMException = @import("exceptions.zig").DOMException; const EventTarget = @import("event_target.zig").EventTarget; const DOMImplementation = @import("implementation.zig").DOMImplementation; +const NamedNodeMap = @import("namednodemap.zig").NamedNodeMap; const Nod = @import("node.zig"); pub const Interfaces = generate.Tuple(.{ DOMException, EventTarget, DOMImplementation, + NamedNodeMap, Nod.Node, Nod.Interfaces, }); diff --git a/src/dom/element.zig b/src/dom/element.zig index 2e725102..13678947 100644 --- a/src/dom/element.zig +++ b/src/dom/element.zig @@ -10,12 +10,16 @@ const Node = @import("node.zig").Node; const HTMLElem = @import("../html/elements.zig"); pub const Union = @import("../html/elements.zig").Union; +const DOMException = @import("exceptions.zig").DOMException; + // WEB IDL https://dom.spec.whatwg.org/#element pub const Element = struct { pub const Self = parser.Element; pub const prototype = *Node; pub const mem_guarantied = true; + pub const Exception = DOMException; + pub fn toInterface(e: *parser.Element) !Union { return try HTMLElem.toInterface(Union, e); } @@ -27,6 +31,10 @@ pub const Element = struct { return try parser.elementLocalName(self); } + pub fn get_attributes(self: *parser.Element) !*parser.NamedNodeMap { + return try parser.nodeGetAttributes(parser.elementToNode(self)); + } + pub fn _hasAttributes(self: *parser.Element) !bool { return try parser.nodeHasAttributes(parser.elementToNode(self)); } @@ -89,6 +97,8 @@ pub fn testExecFn( var attribute = [_]Case{ .{ .src = "let a = document.getElementById('content')", .ex = "undefined" }, .{ .src = "a.hasAttributes()", .ex = "true" }, + .{ .src = "a.attributes.length", .ex = "1" }, + .{ .src = "a.getAttribute('id')", .ex = "content" }, .{ .src = "a.hasAttribute('foo')", .ex = "false" }, diff --git a/src/dom/namednodemap.zig b/src/dom/namednodemap.zig new file mode 100644 index 00000000..8c62a842 --- /dev/null +++ b/src/dom/namednodemap.zig @@ -0,0 +1,80 @@ +const std = @import("std"); + +const parser = @import("../netsurf.zig"); + +const jsruntime = @import("jsruntime"); +const Case = jsruntime.test_utils.Case; +const checkCases = jsruntime.test_utils.checkCases; + +const DOMException = @import("exceptions.zig").DOMException; + +// WEB IDL https://dom.spec.whatwg.org/#namednodemap +pub const NamedNodeMap = struct { + pub const Self = parser.NamedNodeMap; + pub const mem_guarantied = true; + + pub const Exception = DOMException; + + // TODO implement LegacyUnenumerableNamedProperties. + // https://webidl.spec.whatwg.org/#LegacyUnenumerableNamedProperties + + pub fn get_length(self: *parser.NamedNodeMap) !u32 { + return try parser.namedNodeMapGetLength(self); + } + + pub fn _item(self: *parser.NamedNodeMap, index: u32) !?*parser.Attribute { + return try parser.namedNodeMapItem(self, index); + } + + pub fn _getNamedItem(self: *parser.NamedNodeMap, qname: []const u8) !?*parser.Attribute { + return try parser.namedNodeMapGetNamedItem(self, qname); + } + + pub fn _getNamedItemNS( + self: *parser.NamedNodeMap, + namespace: []const u8, + localname: []const u8, + ) !?*parser.Attribute { + return try parser.namedNodeMapGetNamedItemNS(self, namespace, localname); + } + + pub fn _setNamedItem(self: *parser.NamedNodeMap, attr: *parser.Attribute) !?*parser.Attribute { + return try parser.namedNodeMapSetNamedItem(self, attr); + } + + pub fn _setNamedItemNS(self: *parser.NamedNodeMap, attr: *parser.Attribute) !?*parser.Attribute { + return try parser.namedNodeMapSetNamedItemNS(self, attr); + } + + pub fn _removeNamedItem(self: *parser.NamedNodeMap, qname: []const u8) !*parser.Attribute { + return try parser.namedNodeMapRemoveNamedItem(self, qname); + } + + pub fn _removeNamedItemNS( + self: *parser.NamedNodeMap, + namespace: []const u8, + localname: []const u8, + ) !*parser.Attribute { + return try parser.namedNodeMapRemoveNamedItemNS(self, namespace, localname); + } +}; + +// Tests +// ----- + +pub fn testExecFn( + _: std.mem.Allocator, + js_env: *jsruntime.Env, + comptime _: []jsruntime.API, +) !void { + var setItem = [_]Case{ + .{ .src = "let a = document.getElementById('content').attributes", .ex = "undefined" }, + .{ .src = "a.length", .ex = "1" }, + .{ .src = "a.item(0)", .ex = "[object Attr]" }, + .{ .src = "a.item(1)", .ex = "null" }, + .{ .src = "a.getNamedItem('id')", .ex = "[object Attr]" }, + .{ .src = "a.getNamedItem('foo')", .ex = "null" }, + .{ .src = "a.setNamedItem(a.getNamedItem('id'))", .ex = "[object Attr]" }, + }; + try checkCases(js_env, &setItem); +} diff --git a/src/dom/node.zig b/src/dom/node.zig index 710364f4..4f5c1672 100644 --- a/src/dom/node.zig +++ b/src/dom/node.zig @@ -11,6 +11,7 @@ const parser = @import("../netsurf.zig"); const EventTarget = @import("event_target.zig").EventTarget; // DOM +const Attr = @import("attribute.zig").Attr; const CData = @import("character_data.zig"); const Element = @import("element.zig").Element; const Document = @import("document.zig").Document; @@ -23,6 +24,7 @@ const HTMLElem = @import("../html/elements.zig"); // Node interfaces pub const Interfaces = generate.Tuple(.{ + Attr, CData.CharacterData, CData.Interfaces, Element, @@ -52,6 +54,7 @@ pub const Node = struct { .text => .{ .Text = @as(*parser.Text, @ptrCast(node)) }, .document => .{ .HTMLDocument = @as(*parser.DocumentHTML, @ptrCast(node)) }, .document_type => .{ .DocumentType = @as(*parser.DocumentType, @ptrCast(node)) }, + .attribute => .{ .Attr = @as(*parser.Attribute, @ptrCast(node)) }, else => @panic("node type not handled"), // TODO }; } diff --git a/src/netsurf.zig b/src/netsurf.zig index b0423032..6d9b593c 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -336,6 +336,131 @@ fn DOMErr(except: DOMException) DOMError!void { // EventTarget pub const EventTarget = c.dom_event_target; +// NamedNodeMap +pub const NamedNodeMap = c.dom_namednodemap; + +pub fn namedNodeMapGetLength(nnm: *NamedNodeMap) !u32 { + var ln: u32 = undefined; + const err = c.dom_namednodemap_get_length(nnm, &ln); + try DOMErr(err); + return ln; +} + +pub fn namedNodeMapItem(nnm: *NamedNodeMap, index: u32) !?*Attribute { + var n: [*c]c.dom_node = undefined; + const err = c._dom_namednodemap_item(nnm, index, &n); + try DOMErr(err); + + if (n == null) { + return null; + } + + // cast [*c]c.dom_node into *Attribute + return @as(*Attribute, @ptrCast(n)); +} + +pub fn namedNodeMapGetNamedItem(nnm: *NamedNodeMap, qname: []const u8) !?*Attribute { + var n: [*c]c.dom_node = undefined; + const err = c._dom_namednodemap_get_named_item(nnm, try stringFromData(qname), &n); + try DOMErr(err); + + if (n == null) { + return null; + } + + // cast [*c]c.dom_node into *Attribute + return @as(*Attribute, @ptrCast(n)); +} + +pub fn namedNodeMapGetNamedItemNS( + nnm: *NamedNodeMap, + namespace: []const u8, + localname: []const u8, +) !?*Attribute { + var n: [*c]c.dom_node = undefined; + const err = c._dom_namednodemap_get_named_item_ns( + nnm, + try stringFromData(namespace), + try stringFromData(localname), + &n, + ); + try DOMErr(err); + + if (n == null) { + return null; + } + + // cast [*c]c.dom_node into *Attribute + return @as(*Attribute, @ptrCast(n)); +} + +// Convert a parser pointer to a public dom_node pointer. +fn toDOMNode(comptime T: type, v: *T) [*c]c.dom_node { + const v_aligned: *align(@alignOf([*c]c.dom_node)) T = @alignCast(v); + return @ptrCast(v_aligned); +} + +pub fn namedNodeMapSetNamedItem(nnm: *NamedNodeMap, attr: *Attribute) !?*Attribute { + var n: [*c]c.dom_node = undefined; + const err = c._dom_namednodemap_set_named_item( + nnm, + toDOMNode(Attribute, attr), + &n, + ); + try DOMErr(err); + + if (n == null) { + return null; + } + + // cast [*c]c.dom_node into *Attribute + return @as(*Attribute, @ptrCast(n)); +} + +pub fn namedNodeMapSetNamedItemNS(nnm: *NamedNodeMap, attr: *Attribute) !?*Attribute { + var n: [*c]c.dom_node = undefined; + const err = c._dom_namednodemap_set_named_item_ns( + nnm, + toDOMNode(Attribute, attr), + &n, + ); + try DOMErr(err); + + if (n == null) { + return null; + } + + // cast [*c]c.dom_node into *Attribute + return @as(*Attribute, @ptrCast(n)); +} + +pub fn namedNodeMapRemoveNamedItem(nnm: *NamedNodeMap, qname: []const u8) !*Attribute { + var n: [*c]c.dom_node = undefined; + const err = c._dom_namednodemap_remove_named_item(nnm, try stringFromData(qname), &n); + try DOMErr(err); + + // cast [*c]c.dom_node into *Attribute + return @as(*Attribute, @ptrCast(n)); +} + +pub fn namedNodeMapRemoveNamedItemNS( + nnm: *NamedNodeMap, + namespace: []const u8, + localname: []const u8, +) !*Attribute { + var n: [*c]c.dom_node = undefined; + const err = c._dom_namednodemap_remove_named_item_ns( + nnm, + try stringFromData(namespace), + try stringFromData(localname), + &n, + ); + try DOMErr(err); + + // cast [*c]c.dom_node into *Attribute + return @as(*Attribute, @ptrCast(n)); +} + // NodeType pub const NodeType = enum(u4) { @@ -637,6 +762,13 @@ pub fn nodeHasAttributes(node: *Node) !bool { return res; } +pub fn nodeGetAttributes(node: *Node) !*NamedNodeMap { + var res: ?*NamedNodeMap = undefined; + const err = nodeVtable(node).dom_node_get_attributes.?(node, &res); + try DOMErr(err); + return res.?; +} + // nodeToElement is an helper to convert a node to an element. pub inline fn nodeToElement(node: *Node) *Element { return @as(*Element, @ptrCast(node)); diff --git a/src/run_tests.zig b/src/run_tests.zig index e478b84f..58520055 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -15,6 +15,7 @@ const elementTestExecFn = @import("dom/element.zig").testExecFn; const HTMLCollectionTestExecFn = @import("dom/html_collection.zig").testExecFn; const DOMExceptionTestExecFn = @import("dom/exceptions.zig").testExecFn; const DOMImplementationExecFn = @import("dom/implementation.zig").testExecFn; +const NamedNodeMapExecFn = @import("dom/namednodemap.zig").testExecFn; var doc: *parser.DocumentHTML = undefined; @@ -61,6 +62,7 @@ fn testsAllExecFn( HTMLCollectionTestExecFn, DOMExceptionTestExecFn, DOMImplementationExecFn, + NamedNodeMapExecFn, }; inline for (testFns) |testFn| { From e1e4b13be9aa1253d0f7c4fec1acf87f84fa93eb Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 5 Dec 2023 09:15:54 +0100 Subject: [PATCH 5/6] dom: remove useless Exception decl --- src/dom/attribute.zig | 2 -- src/dom/element.zig | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/dom/attribute.zig b/src/dom/attribute.zig index 78563920..758a71fb 100644 --- a/src/dom/attribute.zig +++ b/src/dom/attribute.zig @@ -10,6 +10,4 @@ pub const Attr = struct { pub const Self = parser.Attribute; pub const prototype = *Node; pub const mem_guarantied = true; - - pub const Exception = DOMException; }; diff --git a/src/dom/element.zig b/src/dom/element.zig index 13678947..e7380e3d 100644 --- a/src/dom/element.zig +++ b/src/dom/element.zig @@ -18,8 +18,6 @@ pub const Element = struct { pub const prototype = *Node; pub const mem_guarantied = true; - pub const Exception = DOMException; - pub fn toInterface(e: *parser.Element) !Union { return try HTMLElem.toInterface(Union, e); } From a3f07c894cf31e289acfe0216d657de578fdce85 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 5 Dec 2023 09:44:47 +0100 Subject: [PATCH 6/6] netsurf: one line null check --- src/netsurf.zig | 69 +++++++++++++++++-------------------------------- 1 file changed, 24 insertions(+), 45 deletions(-) diff --git a/src/netsurf.zig b/src/netsurf.zig index 6d9b593c..d0fee760 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -351,9 +351,7 @@ pub fn namedNodeMapItem(nnm: *NamedNodeMap, index: u32) !?*Attribute { const err = c._dom_namednodemap_item(nnm, index, &n); try DOMErr(err); - if (n == null) { - return null; - } + if (n == null) return null; // cast [*c]c.dom_node into *Attribute return @as(*Attribute, @ptrCast(n)); @@ -364,9 +362,7 @@ pub fn namedNodeMapGetNamedItem(nnm: *NamedNodeMap, qname: []const u8) !?*Attrib const err = c._dom_namednodemap_get_named_item(nnm, try stringFromData(qname), &n); try DOMErr(err); - if (n == null) { - return null; - } + if (n == null) return null; // cast [*c]c.dom_node into *Attribute return @as(*Attribute, @ptrCast(n)); @@ -386,9 +382,7 @@ pub fn namedNodeMapGetNamedItemNS( ); try DOMErr(err); - if (n == null) { - return null; - } + if (n == null) return null; // cast [*c]c.dom_node into *Attribute return @as(*Attribute, @ptrCast(n)); @@ -409,9 +403,7 @@ pub fn namedNodeMapSetNamedItem(nnm: *NamedNodeMap, attr: *Attribute) !?*Attribu ); try DOMErr(err); - if (n == null) { - return null; - } + if (n == null) return null; // cast [*c]c.dom_node into *Attribute return @as(*Attribute, @ptrCast(n)); @@ -426,9 +418,7 @@ pub fn namedNodeMapSetNamedItemNS(nnm: *NamedNodeMap, attr: *Attribute) !?*Attri ); try DOMErr(err); - if (n == null) { - return null; - } + if (n == null) return null; // cast [*c]c.dom_node into *Attribute return @as(*Attribute, @ptrCast(n)); @@ -493,9 +483,7 @@ pub fn nodeListItem(nodeList: *NodeList, index: u32) !?*Node { const err = c._dom_nodelist_item(nodeList, index, &n); try DOMErr(err); - if (n == null) { - return null; - } + if (n == null) return null; // cast [*c]c.dom_node into *Node return @as(*Node, @ptrCast(n)); @@ -550,9 +538,8 @@ pub fn nodeNextElementSibling(node: *Node) !?*Element { var n = node; while (true) { const res = try nodeNextSibling(n); - if (res == null) { - return null; - } + if (res == null) return null; + if (try nodeType(res.?) == .element) { return @as(*Element, @ptrCast(res.?)); } @@ -572,9 +559,8 @@ pub fn nodePreviousElementSibling(node: *Node) !?*Element { var n = node; while (true) { const res = try nodePreviousSibling(n); - if (res == null) { - return null; - } + if (res == null) return null; + if (try nodeType(res.?) == .element) { return @as(*Element, @ptrCast(res.?)); } @@ -618,9 +604,8 @@ pub fn nodeValue(node: *Node) !?[]const u8 { var s: ?*String = undefined; const err = nodeVtable(node).dom_node_get_node_value.?(node, &s); try DOMErr(err); - if (s == null) { - return null; - } + if (s == null) return null; + return stringToData(s.?); } @@ -716,9 +701,8 @@ pub fn nodeLookupPrefix(node: *Node, namespace: []const u8) !?[]const u8 { &s, ); try DOMErr(err); - if (s == null) { - return null; - } + if (s == null) return null; + return stringToData(s.?); } @@ -730,9 +714,8 @@ pub fn nodeLookupNamespaceURI(node: *Node, prefix: ?[]const u8) !?[]const u8 { &s, ); try DOMErr(err); - if (s == null) { - return null; - } + if (s == null) return null; + return stringToData(s.?); } @@ -882,9 +865,8 @@ pub fn elementGetAttribute(elem: *Element, name: []const u8) !?[]const u8 { &s, ); try DOMErr(err); - if (s == null) { - return null; - } + if (s == null) return null; + return stringToData(s.?); } @@ -1218,9 +1200,8 @@ pub fn documentHTMLParseFromFileAlloc(allocator: std.mem.Allocator, filename: [] pub fn documentHTMLParseFromFile(filename: [:0]const u8) !*DocumentHTML { // create a null terminated c string. const doc = c.wr_create_doc_dom_from_file(filename.ptr); - if (doc == null) { - return error.ParserError; - } + if (doc == null) return error.ParserError; + return @as(*DocumentHTML, @ptrCast(doc.?)); } @@ -1240,9 +1221,8 @@ pub fn documentHTMLParseFromStrAlloc(allocator: std.mem.Allocator, str: []const // The caller is responsible for closing the document. pub fn documentHTMLParseFromStr(cstr: [:0]const u8) !*DocumentHTML { const doc = c.wr_create_doc_dom_from_string(cstr.ptr); - if (doc == null) { - return error.ParserError; - } + if (doc == null) return error.ParserError; + return @as(*DocumentHTML, @ptrCast(doc.?)); } @@ -1260,8 +1240,7 @@ pub inline fn documentHTMLBody(doc_html: *DocumentHTML) !?*Body { var body: ?*ElementHTML = undefined; const err = documentHTMLVtable(doc_html).get_body.?(doc_html, &body); try DOMErr(err); - if (body == null) { - return null; - } + if (body == null) return null; + return @as(*Body, @ptrCast(body.?)); }