mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
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:
@@ -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,
|
||||
|
||||
|
||||
20
src/dom/character_data.zig
Normal file
20
src/dom/character_data.zig
Normal 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
9
src/dom/comment.zig
Normal 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;
|
||||
};
|
||||
233
src/dom/node.zig
233
src/dom/node.zig
@@ -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
9
src/dom/text.zig
Normal 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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)) },
|
||||
|
||||
65
src/main.zig
65
src/main.zig
@@ -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);
|
||||
}
|
||||
|
||||
285
src/netsurf.zig
285
src/netsurf.zig
@@ -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.?));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user