mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 23:23:28 +00:00
Merge pull request #119 from lightpanda-io/dom-element
dom: implement some element getters
This commit is contained in:
@@ -4,6 +4,7 @@ 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 DOMTokenList = @import("token_list.zig").DOMTokenList;
|
||||
const Nod = @import("node.zig");
|
||||
|
||||
pub const Interfaces = generate.Tuple(.{
|
||||
@@ -11,6 +12,7 @@ pub const Interfaces = generate.Tuple(.{
|
||||
EventTarget,
|
||||
DOMImplementation,
|
||||
NamedNodeMap,
|
||||
DOMTokenList,
|
||||
Nod.Node,
|
||||
Nod.Interfaces,
|
||||
});
|
||||
|
||||
@@ -25,8 +25,48 @@ pub const Element = struct {
|
||||
// JS funcs
|
||||
// --------
|
||||
|
||||
pub fn get_namespaceURI(self: *parser.Element) !?[]const u8 {
|
||||
return try parser.nodeGetNamespace(parser.elementToNode(self));
|
||||
}
|
||||
|
||||
pub fn get_prefix(self: *parser.Element) !?[]const u8 {
|
||||
return try parser.nodeGetPrefix(parser.elementToNode(self));
|
||||
}
|
||||
|
||||
pub fn get_localName(self: *parser.Element) ![]const u8 {
|
||||
return try parser.elementLocalName(self);
|
||||
return try parser.nodeLocalName(parser.elementToNode(self));
|
||||
}
|
||||
|
||||
pub fn get_tagName(self: *parser.Element) ![]const u8 {
|
||||
return try parser.nodeName(parser.elementToNode(self));
|
||||
}
|
||||
|
||||
pub fn get_id(self: *parser.Element) ![]const u8 {
|
||||
return try parser.elementGetAttribute(self, "id") orelse "";
|
||||
}
|
||||
|
||||
pub fn set_id(self: *parser.Element, id: []const u8) !void {
|
||||
return try parser.elementSetAttribute(self, "id", id);
|
||||
}
|
||||
|
||||
pub fn get_className(self: *parser.Element) ![]const u8 {
|
||||
return try parser.elementGetAttribute(self, "class") orelse "";
|
||||
}
|
||||
|
||||
pub fn set_className(self: *parser.Element, class: []const u8) !void {
|
||||
return try parser.elementSetAttribute(self, "class", class);
|
||||
}
|
||||
|
||||
pub fn get_slot(self: *parser.Element) ![]const u8 {
|
||||
return try parser.elementGetAttribute(self, "slot") orelse "";
|
||||
}
|
||||
|
||||
pub fn set_slot(self: *parser.Element, slot: []const u8) !void {
|
||||
return try parser.elementSetAttribute(self, "slot", slot);
|
||||
}
|
||||
|
||||
pub fn get_classList(self: *parser.Element) !*parser.TokenList {
|
||||
return try parser.tokenListCreate(self, "class");
|
||||
}
|
||||
|
||||
pub fn get_attributes(self: *parser.Element) !*parser.NamedNodeMap {
|
||||
@@ -92,6 +132,32 @@ pub fn testExecFn(
|
||||
js_env: *jsruntime.Env,
|
||||
comptime _: []jsruntime.API,
|
||||
) !void {
|
||||
var getters = [_]Case{
|
||||
.{ .src = "let g = document.getElementById('content')", .ex = "undefined" },
|
||||
.{ .src = "g.namespaceURI", .ex = "http://www.w3.org/1999/xhtml" },
|
||||
.{ .src = "g.prefix", .ex = "null" },
|
||||
.{ .src = "g.localName", .ex = "div" },
|
||||
.{ .src = "g.tagName", .ex = "DIV" },
|
||||
};
|
||||
try checkCases(js_env, &getters);
|
||||
|
||||
var gettersetters = [_]Case{
|
||||
.{ .src = "let gs = document.getElementById('content')", .ex = "undefined" },
|
||||
.{ .src = "gs.id", .ex = "content" },
|
||||
.{ .src = "gs.id = 'foo'", .ex = "foo" },
|
||||
.{ .src = "gs.id", .ex = "foo" },
|
||||
.{ .src = "gs.id = 'content'", .ex = "content" },
|
||||
.{ .src = "gs.className", .ex = "" },
|
||||
.{ .src = "let gs2 = document.getElementById('para-empty')", .ex = "undefined" },
|
||||
.{ .src = "gs2.className", .ex = "ok empty" },
|
||||
.{ .src = "gs2.className = 'foo bar baz'", .ex = "foo bar baz" },
|
||||
.{ .src = "gs2.className", .ex = "foo bar baz" },
|
||||
.{ .src = "gs2.className = 'ok empty'", .ex = "ok empty" },
|
||||
.{ .src = "let cl = gs2.classList", .ex = "undefined" },
|
||||
.{ .src = "cl.length", .ex = "2" },
|
||||
};
|
||||
try checkCases(js_env, &gettersetters);
|
||||
|
||||
var attribute = [_]Case{
|
||||
.{ .src = "let a = document.getElementById('content')", .ex = "undefined" },
|
||||
.{ .src = "a.hasAttributes()", .ex = "true" },
|
||||
|
||||
147
src/dom/token_list.zig
Normal file
147
src/dom/token_list.zig
Normal file
@@ -0,0 +1,147 @@
|
||||
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 Variadic = jsruntime.Variadic;
|
||||
|
||||
const DOMException = @import("exceptions.zig").DOMException;
|
||||
|
||||
// https://dom.spec.whatwg.org/#domtokenlist
|
||||
pub const DOMTokenList = struct {
|
||||
pub const Self = parser.TokenList;
|
||||
pub const Exception = DOMException;
|
||||
pub const mem_guarantied = true;
|
||||
|
||||
pub fn get_length(self: *parser.TokenList) !u32 {
|
||||
return parser.tokenListGetLength(self);
|
||||
}
|
||||
|
||||
pub fn _item(self: *parser.TokenList, index: u32) !?[]const u8 {
|
||||
return parser.tokenListItem(self, index);
|
||||
}
|
||||
|
||||
pub fn _contains(self: *parser.TokenList, token: []const u8) !bool {
|
||||
return parser.tokenListContains(self, token);
|
||||
}
|
||||
|
||||
pub fn _add(self: *parser.TokenList, tokens: ?Variadic([]const u8)) !void {
|
||||
if (tokens == null) return;
|
||||
for (tokens.?.slice) |token| {
|
||||
try parser.tokenListAdd(self, token);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn _remove(self: *parser.TokenList, tokens: ?Variadic([]const u8)) !void {
|
||||
if (tokens == null) return;
|
||||
for (tokens.?.slice) |token| {
|
||||
try parser.tokenListRemove(self, token);
|
||||
}
|
||||
}
|
||||
|
||||
/// If token is the empty string, then throw a "SyntaxError" DOMException.
|
||||
/// If token contains any ASCII whitespace, then throw an
|
||||
/// "InvalidCharacterError" DOMException.
|
||||
fn validateToken(token: []const u8) !void {
|
||||
if (token.len == 0) {
|
||||
return parser.DOMError.Syntax;
|
||||
}
|
||||
for (token) |c| {
|
||||
if (std.ascii.isWhitespace(c)) return parser.DOMError.InvalidCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn _toggle(self: *parser.TokenList, token: []const u8, force: ?bool) !bool {
|
||||
try validateToken(token);
|
||||
const exists = try parser.tokenListContains(self, token);
|
||||
if (exists) {
|
||||
if (force == null or force.? == false) {
|
||||
try parser.tokenListRemove(self, token);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (force == null or force.? == true) {
|
||||
try parser.tokenListAdd(self, token);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn _replace(self: *parser.TokenList, token: []const u8, new: []const u8) !bool {
|
||||
try validateToken(token);
|
||||
try validateToken(new);
|
||||
const exists = try parser.tokenListContains(self, token);
|
||||
if (!exists) return false;
|
||||
try parser.tokenListRemove(self, token);
|
||||
try parser.tokenListAdd(self, new);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO to implement.
|
||||
pub fn _supports(_: *parser.TokenList, token: []const u8) !bool {
|
||||
try validateToken(token);
|
||||
return error.TypeError;
|
||||
}
|
||||
|
||||
pub fn get_value(self: *parser.TokenList) !?[]const u8 {
|
||||
return try parser.tokenListGetValue(self);
|
||||
}
|
||||
};
|
||||
|
||||
// Tests
|
||||
// -----
|
||||
|
||||
pub fn testExecFn(
|
||||
_: std.mem.Allocator,
|
||||
js_env: *jsruntime.Env,
|
||||
comptime _: []jsruntime.API,
|
||||
) !void {
|
||||
var dynamiclist = [_]Case{
|
||||
.{ .src = "let gs = document.getElementById('para-empty')", .ex = "undefined" },
|
||||
.{ .src = "let cl = gs.classList", .ex = "undefined" },
|
||||
.{ .src = "gs.className", .ex = "ok empty" },
|
||||
.{ .src = "cl.value", .ex = "ok empty" },
|
||||
.{ .src = "cl.length", .ex = "2" },
|
||||
.{ .src = "gs.className = 'foo bar baz'", .ex = "foo bar baz" },
|
||||
.{ .src = "gs.className", .ex = "foo bar baz" },
|
||||
.{ .src = "cl.length", .ex = "3" },
|
||||
.{ .src = "gs.className = 'ok empty'", .ex = "ok empty" },
|
||||
.{ .src = "cl.length", .ex = "2" },
|
||||
};
|
||||
try checkCases(js_env, &dynamiclist);
|
||||
|
||||
var testcases = [_]Case{
|
||||
.{ .src = "let cl2 = gs.classList", .ex = "undefined" },
|
||||
.{ .src = "cl2.length", .ex = "2" },
|
||||
.{ .src = "cl2.item(0)", .ex = "ok" },
|
||||
.{ .src = "cl2.item(1)", .ex = "empty" },
|
||||
.{ .src = "cl2.contains('ok')", .ex = "true" },
|
||||
.{ .src = "cl2.contains('nok')", .ex = "false" },
|
||||
.{ .src = "cl2.add('foo', 'bar', 'baz')", .ex = "undefined" },
|
||||
.{ .src = "cl2.length", .ex = "5" },
|
||||
.{ .src = "cl2.remove('foo', 'bar', 'baz')", .ex = "undefined" },
|
||||
.{ .src = "cl2.length", .ex = "2" },
|
||||
};
|
||||
try checkCases(js_env, &testcases);
|
||||
|
||||
var toogle = [_]Case{
|
||||
.{ .src = "let cl3 = gs.classList", .ex = "undefined" },
|
||||
.{ .src = "cl3.toggle('ok')", .ex = "false" },
|
||||
.{ .src = "cl3.toggle('ok')", .ex = "true" },
|
||||
.{ .src = "cl3.length", .ex = "2" },
|
||||
};
|
||||
try checkCases(js_env, &toogle);
|
||||
|
||||
var replace = [_]Case{
|
||||
.{ .src = "let cl4 = gs.classList", .ex = "undefined" },
|
||||
.{ .src = "cl4.replace('ok', 'nok')", .ex = "true" },
|
||||
.{ .src = "cl4.value", .ex = "empty nok" },
|
||||
.{ .src = "cl4.replace('nok', 'ok')", .ex = "true" },
|
||||
.{ .src = "cl4.value", .ex = "empty ok" },
|
||||
};
|
||||
try checkCases(js_env, &replace);
|
||||
}
|
||||
@@ -725,6 +725,22 @@ pub fn nodeGetAttributes(node: *Node) !*NamedNodeMap {
|
||||
return res.?;
|
||||
}
|
||||
|
||||
pub fn nodeGetNamespace(node: *Node) !?[]const u8 {
|
||||
var s: ?*String = undefined;
|
||||
const err = nodeVtable(node).dom_node_get_namespace.?(node, &s);
|
||||
try DOMErr(err);
|
||||
if (s == null) return null;
|
||||
return strToData(s.?);
|
||||
}
|
||||
|
||||
pub fn nodeGetPrefix(node: *Node) !?[]const u8 {
|
||||
var s: ?*String = undefined;
|
||||
const err = nodeVtable(node).dom_node_get_prefix.?(node, &s);
|
||||
try DOMErr(err);
|
||||
if (s == null) return null;
|
||||
return strToData(s.?);
|
||||
}
|
||||
|
||||
// nodeToElement is an helper to convert a node to an element.
|
||||
pub inline fn nodeToElement(node: *Node) *Element {
|
||||
return @as(*Element, @ptrCast(node));
|
||||
@@ -831,11 +847,6 @@ fn elementVtable(elem: *Element) c.dom_element_vtable {
|
||||
return getVtable(c.dom_element_vtable, Element, elem);
|
||||
}
|
||||
|
||||
pub fn elementLocalName(elem: *Element) ![]const u8 {
|
||||
const node = @as(*Node, @ptrCast(elem));
|
||||
return try nodeLocalName(node);
|
||||
}
|
||||
|
||||
pub fn elementGetAttribute(elem: *Element, name: []const u8) !?[]const u8 {
|
||||
var s: ?*String = undefined;
|
||||
const err = elementVtable(elem).dom_element_get_attribute.?(elem, try strFromData(name), &s);
|
||||
@@ -882,6 +893,56 @@ pub inline fn elementToNode(e: *Element) *Node {
|
||||
return @as(*Node, @ptrCast(e));
|
||||
}
|
||||
|
||||
// TokenList
|
||||
pub const TokenList = c.dom_tokenlist;
|
||||
|
||||
pub fn tokenListCreate(elt: *Element, attr: []const u8) !*TokenList {
|
||||
var list: ?*TokenList = undefined;
|
||||
const err = c.dom_tokenlist_create(elt, try strFromData(attr), &list);
|
||||
try DOMErr(err);
|
||||
return list.?;
|
||||
}
|
||||
|
||||
pub fn tokenListGetLength(l: *TokenList) !u32 {
|
||||
var res: u32 = undefined;
|
||||
const err = c.dom_tokenlist_get_length(l, &res);
|
||||
try DOMErr(err);
|
||||
return res;
|
||||
}
|
||||
|
||||
pub fn tokenListItem(l: *TokenList, index: u32) !?[]const u8 {
|
||||
var res: ?*String = undefined;
|
||||
const err = c._dom_tokenlist_item(l, index, &res);
|
||||
try DOMErr(err);
|
||||
if (res == null) return null;
|
||||
return strToData(res.?);
|
||||
}
|
||||
|
||||
pub fn tokenListContains(l: *TokenList, token: []const u8) !bool {
|
||||
var res: bool = undefined;
|
||||
const err = c.dom_tokenlist_contains(l, try strFromData(token), &res);
|
||||
try DOMErr(err);
|
||||
return res;
|
||||
}
|
||||
|
||||
pub fn tokenListAdd(l: *TokenList, token: []const u8) !void {
|
||||
const err = c.dom_tokenlist_add(l, try strFromData(token));
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn tokenListRemove(l: *TokenList, token: []const u8) !void {
|
||||
const err = c.dom_tokenlist_remove(l, try strFromData(token));
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn tokenListGetValue(l: *TokenList) !?[]const u8 {
|
||||
var res: ?*String = undefined;
|
||||
const err = c.dom_tokenlist_get_value(l, &res);
|
||||
try DOMErr(err);
|
||||
if (res == null) return null;
|
||||
return strToData(res.?);
|
||||
}
|
||||
|
||||
// ElementHTML
|
||||
pub const ElementHTML = c.dom_html_element;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ 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;
|
||||
const DOMTokenListExecFn = @import("dom/token_list.zig").testExecFn;
|
||||
|
||||
var doc: *parser.DocumentHTML = undefined;
|
||||
|
||||
@@ -63,6 +64,7 @@ fn testsAllExecFn(
|
||||
DOMExceptionTestExecFn,
|
||||
DOMImplementationExecFn,
|
||||
NamedNodeMapExecFn,
|
||||
DOMTokenListExecFn,
|
||||
};
|
||||
|
||||
inline for (testFns) |testFn| {
|
||||
|
||||
Reference in New Issue
Block a user