netsurf: move to public only API

And add some Node APIs:
- getters: firstChild, lastChild, nextSibling, previoussibling,
parentNode, parentElement, nodeName, nodeType, ownerDocument,
isConnected
- getters/setters: nodeValue, textContent
- methods: appendChild

And some test comptime optimizations on Document

Signed-off-by: Francis Bouvier <francis.bouvier@gmail.com>
This commit is contained in:
Francis Bouvier
2023-09-26 11:58:05 +02:00
parent 0bce7a5e37
commit fab03586a3
11 changed files with 536 additions and 137 deletions

View File

@@ -5,6 +5,9 @@ const Console = @import("jsruntime").Console;
// DOM
const EventTarget = @import("dom/event_target.zig").EventTarget;
const N = @import("dom/node.zig");
const CharacterData = @import("dom/character_data.zig").CharacterData;
const Comment = @import("dom/comment.zig").Comment;
const Text = @import("dom/text.zig").Text;
const Element = @import("dom/element.zig").Element;
const Document = @import("dom/document.zig").Document;
@@ -22,6 +25,8 @@ const interfaces = .{
EventTarget,
N.Node,
N.Types,
CharacterData,
Comment,
Element,
Document,

View File

@@ -0,0 +1,20 @@
const generate = @import("../generate.zig");
const parser = @import("../netsurf.zig");
const Node = @import("node.zig").Node;
const Comment = @import("comment.zig").Comment;
const Text = @import("text.zig").Text;
pub const CharacterData = struct {
pub const Self = parser.CharacterData;
pub const prototype = *Node;
pub const mem_guarantied = true;
};
pub const Types = generate.Tuple(.{
Comment,
Text,
});
const Generated = generate.Union.compile(Types);
pub const Union = Generated._union;
pub const Tags = Generated._enum;

9
src/dom/comment.zig Normal file
View File

@@ -0,0 +1,9 @@
const parser = @import("../netsurf.zig");
const CharacterData = @import("character_data.zig").CharacterData;
pub const Comment = struct {
pub const Self = parser.Comment;
pub const prototype = *CharacterData;
pub const mem_guarantied = true;
};

View File

@@ -1,10 +1,14 @@
const std = @import("std");
const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases;
const generate = @import("../generate.zig");
const parser = @import("../netsurf.zig");
const EventTarget = @import("event_target.zig").EventTarget;
const CData = @import("character_data.zig");
const HTMLDocument = @import("../html/document.zig").HTMLDocument;
const HTMLElem = @import("../html/elements.zig");
@@ -12,12 +16,241 @@ pub const Node = struct {
pub const Self = parser.Node;
pub const prototype = *EventTarget;
pub const mem_guarantied = true;
pub fn toInterface(node: *parser.Node) Union {
return switch (parser.nodeType(node)) {
.element => HTMLElem.toInterface(Union, @as(*parser.Element, @ptrCast(node))),
.comment => .{ .Comment = @as(*parser.Comment, @ptrCast(node)) },
.text => .{ .Text = @as(*parser.Text, @ptrCast(node)) },
.document => .{ .HTMLDocument = @as(*parser.DocumentHTML, @ptrCast(node)) },
else => @panic("node type not handled"), // TODO
};
}
// JS funcs
// --------
// Read-only attributes
pub fn get_firstChild(self: *parser.Node) ?Union {
const res = parser.nodeFirstChild(self);
if (res == null) {
return null;
}
return Node.toInterface(res.?);
}
pub fn get_lastChild(self: *parser.Node) ?Union {
const res = parser.nodeLastChild(self);
if (res == null) {
return null;
}
return Node.toInterface(res.?);
}
pub fn get_nextSibling(self: *parser.Node) ?Union {
const res = parser.nodeNextSibling(self);
if (res == null) {
return null;
}
return Node.toInterface(res.?);
}
pub fn get_previousSibling(self: *parser.Node) ?Union {
const res = parser.nodePreviousSibling(self);
if (res == null) {
return null;
}
return Node.toInterface(res.?);
}
pub fn get_parentNode(self: *parser.Node) ?Union {
const res = parser.nodeParentNode(self);
if (res == null) {
return null;
}
return Node.toInterface(res.?);
}
pub fn get_parentElement(self: *parser.Node) ?HTMLElem.Union {
const res = parser.nodeParentElement(self);
if (res == null) {
return null;
}
return HTMLElem.toInterface(HTMLElem.Union, @as(*parser.Element, @ptrCast(res.?)));
}
pub fn get_nodeName(self: *parser.Node) []const u8 {
return parser.nodeName(self);
}
pub fn get_nodeType(self: *parser.Node) u8 {
return @intFromEnum(parser.nodeType(self));
}
pub fn get_ownerDocument(self: *parser.Node) ?*parser.DocumentHTML {
const res = parser.nodeOwnerDocument(self);
if (res == null) {
return null;
}
return @as(*parser.DocumentHTML, @ptrCast(res.?));
}
pub fn get_isConnected(self: *parser.Node) bool {
// TODO: handle Shadow DOM
if (parser.nodeType(self) == .document) {
return true;
}
return Node.get_parentNode(self) != null;
}
// Read/Write attributes
pub fn get_nodeValue(self: *parser.Node) ?[]const u8 {
return parser.nodeValue(self);
}
pub fn set_nodeValue(self: *parser.Node, data: []u8) void {
parser.nodeSetValue(self, data);
}
pub fn get_textContent(self: *parser.Node) ?[]const u8 {
return parser.nodeTextContent(self);
}
pub fn set_textContent(self: *parser.Node, data: []u8) void {
return parser.nodeSetTextContent(self, data);
}
// Methods
pub fn _appendChild(self: *parser.Node, child: *parser.Node) Union {
// TODO: DocumentFragment special case
const res = parser.nodeAppendChild(self, child);
return Node.toInterface(res);
}
};
pub const Types = generate.Tuple(.{
CData.Types,
HTMLElem.Types,
HTMLDocument,
});
const Generated = generate.Union.compile(Types);
pub const Union = Generated._union;
pub const Tags = Generated._enum;
// Tests
// -----
pub fn testExecFn(
_: std.mem.Allocator,
js_env: *jsruntime.Env,
comptime _: []jsruntime.API,
) !void {
var first_child = [_]Case{
// for next test cases
.{ .src = "let content = document.getElementById('content')", .ex = "undefined" },
.{ .src = "let link = document.getElementById('link')", .ex = "undefined" },
.{ .src = "let first_child = document.body.firstChild", .ex = "undefined" },
.{ .src = "first_child.localName", .ex = "div" },
.{ .src = "first_child.__proto__.constructor.name", .ex = "HTMLDivElement" },
.{ .src = "document.getElementById('para-empty').firstChild.firstChild", .ex = "null" },
};
try checkCases(js_env, &first_child);
var last_child = [_]Case{
.{ .src = "let last_child = content.lastChild", .ex = "undefined" },
.{ .src = "last_child.__proto__.constructor.name", .ex = "Comment" },
};
try checkCases(js_env, &last_child);
var next_sibling = [_]Case{
.{ .src = "let next_sibling = link.nextSibling", .ex = "undefined" },
.{ .src = "next_sibling.localName", .ex = "p" },
.{ .src = "next_sibling.__proto__.constructor.name", .ex = "HTMLParagraphElement" },
.{ .src = "content.nextSibling", .ex = "null" },
};
try checkCases(js_env, &next_sibling);
var prev_sibling = [_]Case{
.{ .src = "let prev_sibling = document.getElementById('para-empty').previousSibling", .ex = "undefined" },
.{ .src = "prev_sibling.localName", .ex = "a" },
.{ .src = "prev_sibling.__proto__.constructor.name", .ex = "HTMLAnchorElement" },
.{ .src = "content.previousSibling", .ex = "null" },
};
try checkCases(js_env, &prev_sibling);
var parent = [_]Case{
.{ .src = "let parent = document.getElementById('para').parentElement", .ex = "undefined" },
.{ .src = "parent.localName", .ex = "div" },
.{ .src = "parent.__proto__.constructor.name", .ex = "HTMLDivElement" },
.{ .src = "let h = content.parentElement.parentElement", .ex = "undefined" },
.{ .src = "h.parentElement", .ex = "null" },
.{ .src = "h.parentNode.__proto__.constructor.name", .ex = "HTMLDocument" },
};
try checkCases(js_env, &parent);
var node_name = [_]Case{
.{ .src = "content.firstChild.nodeName === 'A'", .ex = "true" },
.{ .src = "link.firstChild.nodeName === '#text'", .ex = "true" },
.{ .src = "content.lastChild.nodeName === '#comment'", .ex = "true" },
.{ .src = "document.nodeName === '#document'", .ex = "true" },
};
try checkCases(js_env, &node_name);
var node_type = [_]Case{
.{ .src = "content.firstChild.nodeType === 1", .ex = "true" },
.{ .src = "link.firstChild.nodeType === 3", .ex = "true" },
.{ .src = "content.lastChild.nodeType === 8", .ex = "true" },
.{ .src = "document.nodeType === 9", .ex = "true" },
};
try checkCases(js_env, &node_type);
var owner = [_]Case{
.{ .src = "let owner = content.ownerDocument", .ex = "undefined" },
.{ .src = "owner.__proto__.constructor.name", .ex = "HTMLDocument" },
.{ .src = "document.ownerDocument", .ex = "null" },
.{ .src = "let owner2 = document.createElement('div').ownerDocument", .ex = "undefined" },
.{ .src = "owner2.__proto__.constructor.name", .ex = "HTMLDocument" },
};
try checkCases(js_env, &owner);
var connected = [_]Case{
.{ .src = "content.isConnected", .ex = "true" },
.{ .src = "document.isConnected", .ex = "true" },
.{ .src = "document.createElement('div').isConnected", .ex = "false" },
};
try checkCases(js_env, &connected);
var node_value = [_]Case{
.{ .src = "content.lastChild.nodeValue === 'comment'", .ex = "true" },
.{ .src = "link.nodeValue === null", .ex = "true" },
.{ .src = "let text = link.firstChild", .ex = "undefined" },
.{ .src = "text.nodeValue === 'OK'", .ex = "true" },
.{ .src = "text.nodeValue = 'OK modified'", .ex = "OK modified" },
.{ .src = "text.nodeValue === 'OK modified'", .ex = "true" },
.{ .src = "link.nodeValue = 'nothing'", .ex = "nothing" },
};
try checkCases(js_env, &node_value);
var node_text_content = [_]Case{
.{ .src = "text.textContent === 'OK modified'", .ex = "true" },
.{ .src = "content.textContent === 'OK modified And'", .ex = "true" },
.{ .src = "text.textContent = 'OK'", .ex = "OK" },
.{ .src = "text.textContent", .ex = "OK" },
.{ .src = "document.getElementById('para-empty').textContent", .ex = "" },
.{ .src = "document.getElementById('para-empty').textContent = 'OK'", .ex = "OK" },
.{ .src = "document.getElementById('para-empty').firstChild.nodeName === '#text'", .ex = "true" },
};
try checkCases(js_env, &node_text_content);
var node_append_child = [_]Case{
.{ .src = "let append = document.createElement('h1')", .ex = "undefined" },
.{ .src = "content.appendChild(append).toString()", .ex = "[object HTMLHeadingElement]" },
.{ .src = "content.lastChild.__proto__.constructor.name", .ex = "HTMLHeadingElement" },
.{ .src = "content.appendChild(link).toString()", .ex = "[object HTMLAnchorElement]" },
};
try checkCases(js_env, &node_append_child);
}

9
src/dom/text.zig Normal file
View File

@@ -0,0 +1,9 @@
const parser = @import("../netsurf.zig");
const CharacterData = @import("character_data.zig").CharacterData;
pub const Text = struct {
pub const Self = parser.Text;
pub const prototype = *CharacterData;
pub const mem_guarantied = true;
};

View File

@@ -41,7 +41,7 @@ pub const HTMLDocument = struct {
// -----
pub fn testExecFn(
alloc: std.mem.Allocator,
_: std.mem.Allocator,
js_env: *jsruntime.Env,
comptime _: []jsruntime.API,
) !void {
@@ -50,7 +50,7 @@ pub fn testExecFn(
.{ .src = "document.__proto__.__proto__.constructor.name", .ex = "Document" },
.{ .src = "document.__proto__.__proto__.__proto__.constructor.name", .ex = "Node" },
.{ .src = "document.__proto__.__proto__.__proto__.__proto__.constructor.name", .ex = "EventTarget" },
.{ .src = "document.body.localName === 'body'", .ex = "true" },
.{ .src = "document.body.localName == 'body'", .ex = "true" },
};
try checkCases(js_env, &constructor);
@@ -63,31 +63,24 @@ pub fn testExecFn(
const tags = comptime parser.Tag.all();
const elements = comptime parser.Tag.allElements();
var createElements: [(tags.len - 1) * 3]Case = undefined;
inline for (tags, 0..) |tag, i| {
if (tag == .undef) {
continue;
}
comptime var createElements: [(tags.len) * 3]Case = undefined;
inline for (tags, elements, 0..) |tag, element_name, i| {
// if (tag == .undef) {
// continue;
// }
const tag_name = @tagName(tag);
const element_name = elements[i];
createElements[i * 3] = Case{
.src = try std.fmt.allocPrint(alloc, "var {s}Elem = document.createElement('{s}')", .{ tag_name, tag_name }),
.src = "var " ++ tag_name ++ "Elem = document.createElement('" ++ tag_name ++ "')",
.ex = "undefined",
};
createElements[(i * 3) + 1] = Case{
.src = try std.fmt.allocPrint(alloc, "{s}Elem.constructor.name", .{tag_name}),
.ex = try std.fmt.allocPrint(alloc, "HTML{s}Element", .{element_name}),
.src = tag_name ++ "Elem.constructor.name",
.ex = "HTML" ++ element_name ++ "Element",
};
createElements[(i * 3) + 2] = Case{
.src = try std.fmt.allocPrint(alloc, "{s}Elem.localName", .{tag_name}),
.src = tag_name ++ "Elem.localName",
.ex = tag_name,
};
}
try checkCases(js_env, &createElements);
var unknown = [_]Case{
.{ .src = "let unknown = document.createElement('unknown')", .ex = "undefined" },
.{ .src = "unknown.constructor.name", .ex = "HTMLUnknownElement" },
};
try checkCases(js_env, &unknown);
}

View File

@@ -454,7 +454,8 @@ pub const HTMLVideoElement = struct {
pub const mem_guarantied = true;
};
pub fn toInterface(comptime T: type, elem: *parser.Element) T {
pub fn toInterface(comptime T: type, e: *parser.Element) T {
const elem: *align(@alignOf(*parser.Element)) parser.Element = @alignCast(e);
const tag = parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(elem)));
return switch (tag) {
.a => .{ .HTMLAnchorElement = @as(*parser.Anchor, @ptrCast(elem)) },

View File

@@ -4,8 +4,8 @@ const jsruntime = @import("jsruntime");
const parser = @import("netsurf.zig");
const DOM = @import("dom.zig");
const html_test = @import("html_test.zig").html;
const docTestExecFn = @import("html/document.zig").testExecFn;
const nodeTestExecFn = @import("dom/node.zig").testExecFn;
const socket_path = "/tmp/browsercore-server.sock";
@@ -45,6 +45,24 @@ fn execJS(
}
}
fn testsExecFn(
alloc: std.mem.Allocator,
js_env: *jsruntime.Env,
comptime apis: []jsruntime.API,
) !void {
// start JS env
js_env.start(apis);
defer js_env.stop();
// add document object
try js_env.addObject(apis, doc, "document");
// run tests
try docTestExecFn(alloc, js_env, apis);
try nodeTestExecFn(alloc, js_env, apis);
}
pub fn main() !void {
// generate APIs
@@ -55,30 +73,35 @@ pub fn main() !void {
defer vm.deinit();
// document
// remove socket file of internal server
// reuse_address (SO_REUSEADDR flag) does not seems to work on unix socket
// see: https://gavv.net/articles/unix-socket-reuse/
// TODO: use a lock file instead
std.os.unlink(socket_path) catch |err| {
if (err != error.FileNotFound) {
return err;
}
};
var f = "test.html".*;
doc = parser.documentHTMLParse(&f);
// TODO: defer doc?
// // remove socket file of internal server
// // reuse_address (SO_REUSEADDR flag) does not seems to work on unix socket
// // see: https://gavv.net/articles/unix-socket-reuse/
// // TODO: use a lock file instead
// std.os.unlink(socket_path) catch |err| {
// if (err != error.FileNotFound) {
// return err;
// }
// };
// alloc
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
// var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
// defer arena.deinit();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var bench_alloc = jsruntime.bench_allocator(gpa.allocator());
var arena_alloc = std.heap.ArenaAllocator.init(bench_alloc.allocator());
defer arena_alloc.deinit();
// server
var addr = try std.net.Address.initUnix(socket_path);
server = std.net.StreamServer.init(.{});
defer server.deinit();
try server.listen(addr);
std.debug.print("Listening on: {s}...\n", .{socket_path});
// // server
// var addr = try std.net.Address.initUnix(socket_path);
// server = std.net.StreamServer.init(.{});
// defer server.deinit();
// try server.listen(addr);
// std.debug.print("Listening on: {s}...\n", .{socket_path});
try jsruntime.loadEnv(&arena, execJS, apis);
// try jsruntime.loadEnv(&arena, execJS, apis);
try jsruntime.loadEnv(&arena_alloc, testsExecFn, apis);
}

View File

@@ -1,62 +1,17 @@
const std = @import("std");
const cp = @cImport({
const c = @cImport({
@cInclude("wrapper.h");
});
const c = @cImport({
@cInclude("core/node.h");
@cInclude("core/document.h");
@cInclude("core/element.h");
// Internal
@cInclude("html/html_document.h");
@cInclude("html/html_element.h");
@cInclude("html/html_anchor_element.h");
@cInclude("html/html_area_element.h");
@cInclude("html/html_br_element.h");
@cInclude("html/html_base_element.h");
@cInclude("html/html_body_element.h");
@cInclude("html/html_button_element.h");
@cInclude("html/html_canvas_element.h");
@cInclude("html/html_dlist_element.h");
@cInclude("html/html_div_element.h");
@cInclude("html/html_fieldset_element.h");
@cInclude("html/html_form_element.h");
@cInclude("html/html_frameset_element.h");
@cInclude("html/html_hr_element.h");
@cInclude("html/html_head_element.h");
@cInclude("html/html_heading_element.h");
@cInclude("html/html_html_element.h");
@cInclude("html/html_iframe_element.h");
@cInclude("html/html_image_element.h");
@cInclude("html/html_input_element.h");
@cInclude("html/html_li_element.h");
@cInclude("html/html_label_element.h");
@cInclude("html/html_legend_element.h");
@cInclude("html/html_link_element.h");
@cInclude("html/html_map_element.h");
@cInclude("html/html_meta_element.h");
@cInclude("html/html_mod_element.h");
@cInclude("html/html_olist_element.h");
@cInclude("html/html_object_element.h");
@cInclude("html/html_opt_group_element.h");
@cInclude("html/html_option_element.h");
@cInclude("html/html_paragraph_element.h");
@cInclude("html/html_pre_element.h");
@cInclude("html/html_quote_element.h");
@cInclude("html/html_script_element.h");
@cInclude("html/html_select_element.h");
@cInclude("html/html_style_element.h");
@cInclude("html/html_table_element.h");
@cInclude("html/html_tablecaption_element.h");
@cInclude("html/html_tablecell_element.h");
@cInclude("html/html_tablecol_element.h");
@cInclude("html/html_tablerow_element.h");
@cInclude("html/html_tablesection_element.h");
@cInclude("html/html_text_area_element.h");
@cInclude("html/html_title_element.h");
@cInclude("html/html_ulist_element.h");
});
inline fn getVtable(comptime VtableT: type, comptime NodeT: type, node: anytype) VtableT {
const node_algined: *align(@alignOf([*c]c.dom_node)) NodeT = @alignCast(node);
const base = @as([*c]c.dom_node, @ptrCast(node_algined));
const vtable_aligned: *align(@alignOf([*c]VtableT)) const anyopaque = @alignCast(base.*.vtable.?);
return @as([*c]const VtableT, @ptrCast(vtable_aligned)).*;
}
// Utils
const String = c.dom_string;
@@ -67,7 +22,7 @@ inline fn stringToData(s: *String) []const u8 {
}
inline fn stringFromData(data: []const u8) *String {
var s: ?*String = null;
var s: ?*String = undefined;
_ = c.dom_string_create(data.ptr, data.len, &s);
return s.?;
}
@@ -218,49 +173,187 @@ pub const Tag = enum(u8) {
// EventTarget
pub const EventTarget = c.dom_event_target;
// NodeType
pub const NodeType = enum(u4) {
element = c.DOM_ELEMENT_NODE,
attribute = c.DOM_ATTRIBUTE_NODE,
text = c.DOM_TEXT_NODE,
cdata_section = c.DOM_CDATA_SECTION_NODE,
entity_reference = c.DOM_ENTITY_REFERENCE_NODE, // historical
entity = c.DOM_ENTITY_NODE, // historical
processing_instruction = c.DOM_PROCESSING_INSTRUCTION_NODE,
comment = c.DOM_COMMENT_NODE,
document = c.DOM_DOCUMENT_NODE,
document_type = c.DOM_DOCUMENT_TYPE_NODE,
document_fragment = c.DOM_DOCUMENT_FRAGMENT_NODE,
notation = c.DOM_NOTATION_NODE, // historical
};
// Node
pub const Node = c.dom_node_internal;
fn nodeVtable(node: *Node) c.dom_node_vtable {
return getVtable(c.dom_node_vtable, Node, node);
}
pub fn nodeLocalName(node: *Node) []const u8 {
var s: ?*String = undefined;
_ = nodeVtable(node).dom_node_get_local_name.?(node, &s);
var s_lower: ?*String = undefined;
_ = c.dom_string_tolower(s, true, &s_lower);
return stringToData(s_lower.?);
}
pub fn nodeType(node: *Node) NodeType {
var node_type: c.dom_node_type = undefined;
_ = nodeVtable(node).dom_node_get_node_type.?(node, &node_type);
return @as(NodeType, @enumFromInt(node_type));
}
pub fn nodeFirstChild(node: *Node) ?*Node {
var res: ?*Node = undefined;
_ = nodeVtable(node).dom_node_get_first_child.?(node, &res);
return res;
}
pub fn nodeLastChild(node: *Node) ?*Node {
var res: ?*Node = undefined;
_ = nodeVtable(node).dom_node_get_last_child.?(node, &res);
return res;
}
pub fn nodeNextSibling(node: *Node) ?*Node {
var res: ?*Node = undefined;
_ = nodeVtable(node).dom_node_get_next_sibling.?(node, &res);
return res;
}
pub fn nodePreviousSibling(node: *Node) ?*Node {
var res: ?*Node = undefined;
_ = nodeVtable(node).dom_node_get_previous_sibling.?(node, &res);
return res;
}
pub fn nodeParentNode(node: *Node) ?*Node {
var res: ?*Node = undefined;
_ = nodeVtable(node).dom_node_get_parent_node.?(node, &res);
return res;
}
pub fn nodeParentElement(node: *Node) ?*Element {
const res = nodeParentNode(node);
if (res) |value| {
if (nodeType(value) == .element) {
return @as(*Element, @ptrCast(value));
}
}
return null;
}
pub fn nodeName(node: *Node) []const u8 {
var s: ?*String = undefined;
_ = nodeVtable(node).dom_node_get_node_name.?(node, &s);
return stringToData(s.?);
}
pub fn nodeOwnerDocument(node: *Node) ?*Document {
var doc: ?*Document = undefined;
_ = nodeVtable(node).dom_node_get_owner_document.?(node, &doc);
return doc;
}
pub fn nodeValue(node: *Node) ?[]const u8 {
var s: ?*String = undefined;
_ = nodeVtable(node).dom_node_get_node_value.?(node, &s);
if (s == null) {
return null;
}
return stringToData(s.?);
}
pub fn nodeSetValue(node: *Node, value: []const u8) void {
const s = stringFromData(value);
_ = nodeVtable(node).dom_node_set_node_value.?(node, s);
}
pub fn nodeTextContent(node: *Node) ?[]const u8 {
var s: ?*String = undefined;
_ = nodeVtable(node).dom_node_get_text_content.?(node, &s);
if (s == null) {
// NOTE: it seems that there is a bug in netsurf implem
// an empty Element should return an empty string and not null
if (nodeType(node) == .element) {
return "";
}
return null;
}
return stringToData(s.?);
}
pub fn nodeSetTextContent(node: *Node, value: []const u8) void {
const s = stringFromData(value);
_ = nodeVtable(node).dom_node_set_text_content.?(node, s);
}
pub fn nodeAppendChild(node: *Node, child: *Node) *Node {
var res: ?*Node = undefined;
_ = nodeVtable(node).dom_node_append_child.?(node, child, &res);
return res.?;
}
// CharacterData
pub const CharacterData = c.dom_characterdata;
// Text
pub const Text = c.dom_text;
// Comment
pub const Comment = c.dom_comment;
// Element
pub const Element = c.dom_element;
fn elementVtable(elem: *Element) c.dom_element_vtable {
return getVtable(c.dom_element_vtable, Element, elem);
}
pub fn elementLocalName(elem: *Element) []const u8 {
const elem_aligned: *align(8) Element = @alignCast(elem);
const node = @as(*Node, @ptrCast(elem_aligned));
var s: ?*String = null;
_ = c._dom_node_get_local_name(node, &s);
var s_lower: ?*String = null;
_ = c.dom_string_tolower(s, true, &s_lower);
return stringToData(s_lower.?);
const node = @as(*Node, @ptrCast(elem));
return nodeLocalName(node);
}
// ElementHTML
pub const ElementHTML = c.dom_html_element;
fn elementHTMLVtable(elem_html: *ElementHTML) c.dom_html_element_vtable {
return getVtable(c.dom_html_element_vtable, ElementHTML, elem_html);
}
pub fn elementHTMLGetTagType(elem_html: *ElementHTML) Tag {
var tag_type: c.dom_html_element_type = undefined;
_ = c._dom_html_element_get_tag_type(elem_html, &tag_type);
_ = elementHTMLVtable(elem_html).dom_html_element_get_tag_type.?(elem_html, &tag_type);
return @as(Tag, @enumFromInt(tag_type));
}
// ElementsHTML
pub const MediaElement = struct { base: c.dom_html_element };
pub const MediaElement = struct { base: *c.dom_html_element };
pub const Unknown = struct { base: c.dom_html_element };
pub const Unknown = struct { base: *c.dom_html_element };
pub const Anchor = c.dom_html_anchor_element;
pub const Area = c.dom_html_area_element;
pub const Audio = struct { base: c.dom_html_element };
pub const Audio = struct { base: *c.dom_html_element };
pub const BR = c.dom_html_br_element;
pub const Base = c.dom_html_base_element;
pub const Body = c.dom_html_body_element;
pub const Button = c.dom_html_button_element;
pub const Canvas = c.dom_html_canvas_element;
pub const DList = c.dom_html_dlist_element;
pub const Data = struct { base: c.dom_html_element };
pub const Dialog = struct { base: c.dom_html_element };
pub const Data = struct { base: *c.dom_html_element };
pub const Dialog = struct { base: *c.dom_html_element };
pub const Div = c.dom_html_div_element;
pub const Embed = struct { base: c.dom_html_element };
pub const Embed = struct { base: *c.dom_html_element };
pub const FieldSet = c.dom_html_field_set_element;
pub const Form = c.dom_html_form_element;
pub const FrameSet = c.dom_html_frame_set_element;
@@ -277,22 +370,22 @@ pub const Legend = c.dom_html_legend_element;
pub const Link = c.dom_html_link_element;
pub const Map = c.dom_html_map_element;
pub const Meta = c.dom_html_meta_element;
pub const Meter = struct { base: c.dom_html_element };
pub const Meter = struct { base: *c.dom_html_element };
pub const Mod = c.dom_html_mod_element;
pub const OList = c.dom_html_olist_element;
pub const Object = c.dom_html_object_element;
pub const OptGroup = c.dom_html_opt_group_element;
pub const Option = c.dom_html_option_element;
pub const Output = struct { base: c.dom_html_element };
pub const Output = struct { base: *c.dom_html_element };
pub const Paragraph = c.dom_html_paragraph_element;
pub const Picture = struct { base: c.dom_html_element };
pub const Picture = struct { base: *c.dom_html_element };
pub const Pre = c.dom_html_pre_element;
pub const Progress = struct { base: c.dom_html_element };
pub const Progress = struct { base: *c.dom_html_element };
pub const Quote = c.dom_html_quote_element;
pub const Script = c.dom_html_script_element;
pub const Select = c.dom_html_select_element;
pub const Source = struct { base: c.dom_html_element };
pub const Span = struct { base: c.dom_html_element };
pub const Source = struct { base: *c.dom_html_element };
pub const Span = struct { base: *c.dom_html_element };
pub const Style = c.dom_html_style_element;
pub const Table = c.dom_html_table_element;
pub const TableCaption = c.dom_html_table_caption_element;
@@ -300,39 +393,57 @@ pub const TableCell = c.dom_html_table_cell_element;
pub const TableCol = c.dom_html_table_col_element;
pub const TableRow = c.dom_html_table_row_element;
pub const TableSection = c.dom_html_table_section_element;
pub const Template = struct { base: c.dom_html_element };
pub const Template = struct { base: *c.dom_html_element };
pub const TextArea = c.dom_html_text_area_element;
pub const Time = struct { base: c.dom_html_element };
pub const Time = struct { base: *c.dom_html_element };
pub const Title = c.dom_html_title_element;
pub const Track = struct { base: c.dom_html_element };
pub const Track = struct { base: *c.dom_html_element };
pub const UList = c.dom_html_u_list_element;
pub const Video = struct { base: c.dom_html_element };
pub const Video = struct { base: *c.dom_html_element };
// Document Position
pub const DocumentPosition = enum(u2) {
disconnected = c.DOM_DOCUMENT_POSITION_DISCONNECTED,
preceding = c.DOM_DOCUMENT_POSITION_PRECEDING,
following = c.DOM_DOCUMENT_POSITION_FOLLOWING,
contains = c.DOM_DOCUMENT_POSITION_CONTAINS,
contained_by = c.DOM_DOCUMENT_POSITION_CONTAINED_BY,
implementation_specific = c.DOM_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC,
};
// Document
pub const Document = c.dom_document;
fn documentVtable(doc: *Document) c.dom_document_vtable {
return getVtable(c.dom_document_vtable, Document, doc);
}
pub inline fn documentGetElementById(doc: *Document, id: []const u8) ?*Element {
var elem: ?*Element = undefined;
_ = c._dom_document_get_element_by_id(doc, stringFromData(id), &elem);
_ = documentVtable(doc).dom_document_get_element_by_id.?(doc, stringFromData(id), &elem);
return elem;
}
pub inline fn documentCreateElement(doc: *Document, tag_name: []const u8) *Element {
var elem: ?*Element = undefined;
_ = c._dom_html_document_create_element(doc, stringFromData(tag_name), &elem);
_ = documentVtable(doc).dom_document_create_element.?(doc, stringFromData(tag_name), &elem);
return elem.?;
}
// DocumentHTML
pub const DocumentHTML = c.dom_html_document;
fn documentHTMLVtable(doc_html: *DocumentHTML) c.dom_html_document_vtable {
return getVtable(c.dom_html_document_vtable, DocumentHTML, doc_html);
}
pub fn documentHTMLParse(filename: []u8) *DocumentHTML {
const doc = cp.wr_create_doc_dom_from_file(filename.ptr);
const doc = c.wr_create_doc_dom_from_file(filename.ptr);
if (doc == null) {
@panic("error parser");
}
const doc_aligned: *align(@alignOf((DocumentHTML))) cp.dom_document = @alignCast(doc.?);
return @as(*DocumentHTML, @ptrCast(doc_aligned));
return @as(*DocumentHTML, @ptrCast(doc.?));
}
pub inline fn documentHTMLToDocument(doc_html: *DocumentHTML) *Document {
@@ -341,9 +452,9 @@ pub inline fn documentHTMLToDocument(doc_html: *DocumentHTML) *Document {
pub inline fn documentHTMLBody(doc_html: *DocumentHTML) ?*Body {
var body: ?*ElementHTML = undefined;
_ = c._dom_html_document_get_body(doc_html, &body);
if (body) |value| {
return @as(*Body, @ptrCast(value));
_ = documentHTMLVtable(doc_html).get_body.?(doc_html, &body);
if (body == null) {
return null;
}
return null;
return @as(*Body, @ptrCast(body.?));
}

View File

@@ -5,7 +5,8 @@ const generate = @import("generate.zig");
const parser = @import("netsurf.zig");
const DOM = @import("dom.zig");
const testExecFn = @import("html/document.zig").testExecFn;
const docTestExecFn = @import("html/document.zig").testExecFn;
const nodeTestExecFn = @import("html/document.zig").testExecFn;
var doc: *parser.DocumentHTML = undefined;
@@ -23,7 +24,8 @@ fn testsExecFn(
try js_env.addObject(apis, doc, "document");
// run tests
try testExecFn(alloc, js_env, apis);
try docTestExecFn(alloc, js_env, apis);
try nodeTestExecFn(alloc, js_env, apis);
}
test {

View File

@@ -1,8 +1 @@
<div id='content'>
<a id='link' href='foo'>OK</a>
<p id='para-empty'>
<span id='para-empty-child'></span>
</p>
<p id='para'> And</p>
<!--comment-->
</div>
<div id='content'><a id='link' href='foo'>OK</a><p id='para-empty'><span id='para-empty-child'></span></p><p id='para'> And</p><!--comment--></div>