diff --git a/src/dom/attribute.zig b/src/dom/attribute.zig index 758a71fb..292d58c9 100644 --- a/src/dom/attribute.zig +++ b/src/dom/attribute.zig @@ -1,5 +1,9 @@ const std = @import("std"); +const jsruntime = @import("jsruntime"); +const Case = jsruntime.test_utils.Case; +const checkCases = jsruntime.test_utils.checkCases; + const parser = @import("../netsurf.zig"); const Node = @import("node.zig").Node; @@ -10,4 +14,73 @@ pub const Attr = struct { pub const Self = parser.Attribute; pub const prototype = *Node; pub const mem_guarantied = true; + + pub fn get_namespaceURI(self: *parser.Attribute) !?[]const u8 { + return try parser.nodeGetNamespace(parser.attributeToNode(self)); + } + + pub fn get_prefix(self: *parser.Attribute) !?[]const u8 { + return try parser.nodeGetPrefix(parser.attributeToNode(self)); + } + + pub fn get_localName(self: *parser.Attribute) ![]const u8 { + return try parser.nodeLocalName(parser.attributeToNode(self)); + } + + pub fn get_name(self: *parser.Attribute) ![]const u8 { + return try parser.attributeGetName(self); + } + + pub fn get_value(self: *parser.Attribute) !?[]const u8 { + return try parser.attributeGetValue(self); + } + + pub fn set_value(self: *parser.Attribute, v: []const u8) !?[]const u8 { + try parser.attributeSetValue(self, v); + return v; + } + + pub fn get_ownerElement(self: *parser.Attribute) !?*parser.Element { + return try parser.attributeGetOwnerElement(self); + } + + pub fn get_specified(_: *parser.Attribute) bool { + return true; + } }; + +// Tests +// ----- + +pub fn testExecFn( + _: std.mem.Allocator, + js_env: *jsruntime.Env, + comptime _: []jsruntime.API, +) !void { + var getters = [_]Case{ + .{ .src = "let a = document.createAttributeNS('foo', 'bar')", .ex = "undefined" }, + .{ .src = "a.namespaceURI", .ex = "foo" }, + .{ .src = "a.prefix", .ex = "null" }, + .{ .src = "a.localName", .ex = "bar" }, + .{ .src = "a.name", .ex = "bar" }, + .{ .src = "a.value", .ex = "" }, + // TODO: libdom has a bug here: the created attr has no parent, it + // causes a panic w/ libdom when setting the value. + //.{ .src = "a.value = 'nok'", .ex = "nok" }, + .{ .src = "a.ownerElement", .ex = "null" }, + }; + try checkCases(js_env, &getters); + + var attr = [_]Case{ + .{ .src = "let b = document.getElementById('link').getAttributeNode('class')", .ex = "undefined" }, + .{ .src = "b.name", .ex = "class" }, + .{ .src = "b.value", .ex = "ok" }, + .{ .src = "b.value = 'nok'", .ex = "nok" }, + .{ .src = "b.value", .ex = "nok" }, + .{ .src = "b.value = null", .ex = "null" }, + .{ .src = "b.value", .ex = "null" }, + .{ .src = "b.value = 'ok'", .ex = "ok" }, + .{ .src = "b.ownerElement.id", .ex = "link" }, + }; + try checkCases(js_env, &attr); +} diff --git a/src/dom/element.zig b/src/dom/element.zig index 5827604a..2c2378f1 100644 --- a/src/dom/element.zig +++ b/src/dom/element.zig @@ -127,6 +127,26 @@ pub const Element = struct { return true; } + pub fn _getAttributeNode(self: *parser.Element, name: []const u8) !?*parser.Attribute { + return try parser.elementGetAttributeNode(self, name); + } + + pub fn _getAttributeNodeNS(self: *parser.Element, ns: []const u8, name: []const u8) !?*parser.Attribute { + return try parser.elementGetAttributeNodeNS(self, ns, name); + } + + pub fn _setAttributeNode(self: *parser.Element, attr: *parser.Attribute) !?*parser.Attribute { + return try parser.elementSetAttributeNode(self, attr); + } + + pub fn _setAttributeNodeNS(self: *parser.Element, attr: *parser.Attribute) !?*parser.Attribute { + return try parser.elementSetAttributeNodeNS(self, attr); + } + + pub fn _removeAttributeNode(self: *parser.Element, attr: *parser.Attribute) !*parser.Attribute { + return try parser.elementRemoveAttributeNode(self, attr); + } + pub fn _getElementsByTagName( self: *parser.Element, alloc: std.mem.Allocator, @@ -366,4 +386,14 @@ pub fn testExecFn( .{ .src = "e.querySelectorAll('*').length", .ex = "4" }, }; try checkCases(js_env, &querySelector); + + var attrNode = [_]Case{ + .{ .src = "let f = document.getElementById('content')", .ex = "undefined" }, + .{ .src = "let ff = document.createAttribute('foo')", .ex = "undefined" }, + .{ .src = "f.setAttributeNode(ff)", .ex = "null" }, + .{ .src = "f.getAttributeNode('foo').name", .ex = "foo" }, + .{ .src = "f.removeAttributeNode(ff).name", .ex = "foo" }, + .{ .src = "f.getAttributeNode('bar')", .ex = "null" }, + }; + try checkCases(js_env, &attrNode); } diff --git a/src/netsurf.zig b/src/netsurf.zig index 84f5c363..e0e05093 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -841,6 +841,46 @@ pub const ProcessingInstruction = c.dom_processing_instruction; // Attribute pub const Attribute = c.dom_attr; +fn attributeVtable(a: *Attribute) c.dom_attr_vtable { + return getVtable(c.dom_attr_vtable, Attribute, a); +} + +pub fn attributeGetName(a: *Attribute) ![]const u8 { + var s: ?*String = undefined; + const err = attributeVtable(a).dom_attr_get_name.?(a, &s); + try DOMErr(err); + + return strToData(s.?); +} + +pub fn attributeGetValue(a: *Attribute) !?[]const u8 { + var s: ?*String = undefined; + const err = attributeVtable(a).dom_attr_get_value.?(a, &s); + try DOMErr(err); + if (s == null) return null; + + return strToData(s.?); +} + +pub fn attributeSetValue(a: *Attribute, v: []const u8) !void { + const err = attributeVtable(a).dom_attr_set_value.?(a, try strFromData(v)); + try DOMErr(err); +} + +pub fn attributeGetOwnerElement(a: *Attribute) !?*Element { + var elt: ?*Element = undefined; + const err = attributeVtable(a).dom_attr_get_owner_element.?(a, &elt); + try DOMErr(err); + if (elt == null) return null; + + return elt.?; +} + +// attributeToNode is an helper to convert an attribute to a node. +pub inline fn attributeToNode(a: *Attribute) *Node { + return @as(*Node, @ptrCast(a)); +} + // Element pub const Element = c.dom_element; @@ -878,6 +918,46 @@ pub fn elementHasAttribute(elem: *Element, qname: []const u8) !bool { return res; } +pub fn elementGetAttributeNode(elem: *Element, name: []const u8) !?*Attribute { + var a: ?*Attribute = undefined; + const err = elementVtable(elem).dom_element_get_attribute_node.?(elem, try strFromData(name), &a); + try DOMErr(err); + return a; +} + +pub fn elementGetAttributeNodeNS(elem: *Element, ns: []const u8, name: []const u8) !?*Attribute { + var a: ?*Attribute = undefined; + const err = elementVtable(elem).dom_element_get_attribute_node_ns.?( + elem, + try strFromData(ns), + try strFromData(name), + &a, + ); + try DOMErr(err); + return a; +} + +pub fn elementSetAttributeNode(elem: *Element, attr: *Attribute) !?*Attribute { + var a: ?*Attribute = undefined; + const err = elementVtable(elem).dom_element_set_attribute_node.?(elem, attr, &a); + try DOMErr(err); + return a; +} + +pub fn elementSetAttributeNodeNS(elem: *Element, attr: *Attribute) !?*Attribute { + var a: ?*Attribute = undefined; + const err = elementVtable(elem).dom_element_set_attribute_node_ns.?(elem, attr, &a); + try DOMErr(err); + return a; +} + +pub fn elementRemoveAttributeNode(elem: *Element, attr: *Attribute) !*Attribute { + var a: ?*Attribute = undefined; + const err = elementVtable(elem).dom_element_remove_attribute_node.?(elem, attr, &a); + try DOMErr(err); + return a.?; +} + 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 3f6b17e8..8be3eff6 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -18,6 +18,7 @@ const DOMImplementationExecFn = @import("dom/implementation.zig").testExecFn; const NamedNodeMapExecFn = @import("dom/namednodemap.zig").testExecFn; const DOMTokenListExecFn = @import("dom/token_list.zig").testExecFn; const NodeListTestExecFn = @import("dom/nodelist.zig").testExecFn; +const AttrTestExecFn = @import("dom/attribute.zig").testExecFn; var doc: *parser.DocumentHTML = undefined; @@ -70,6 +71,7 @@ fn testsAllExecFn( NamedNodeMapExecFn, DOMTokenListExecFn, NodeListTestExecFn, + AttrTestExecFn, }; inline for (testFns) |testFn| { diff --git a/tests/wpt b/tests/wpt index 26ad8ed7..4f7ac437 160000 --- a/tests/wpt +++ b/tests/wpt @@ -1 +1 @@ -Subproject commit 26ad8ed75d77406e1aea6d8ac29c5207efaeaeaa +Subproject commit 4f7ac437ea6fb1d4e3b207078c0fb52f8f2d3b94