Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CharacterData properties and methods #27

Merged
merged 10 commits into from
Oct 9, 2023
141 changes: 141 additions & 0 deletions src/dom/character_data.zig
Original file line number Diff line number Diff line change
@@ -1,14 +1,79 @@
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 Node = @import("node.zig").Node;
const Comment = @import("comment.zig").Comment;
const Text = @import("text.zig").Text;
const HTMLElem = @import("../html/elements.zig");

pub const CharacterData = struct {
pub const Self = parser.CharacterData;
pub const prototype = *Node;
pub const mem_guarantied = true;

// JS funcs
// --------

// Read attributes

pub fn get_length(self: *parser.CharacterData) u32 {
return parser.characterDataLength(self);
}

pub fn get_nextElementSibling(self: *parser.CharacterData) ?HTMLElem.Union {
const res = parser.nodeNextElementSibling(parser.characterDataToNode(self));
if (res == null) {
return null;
}
return HTMLElem.toInterface(HTMLElem.Union, res.?);
}

pub fn get_previousElementSibling(self: *parser.CharacterData) ?HTMLElem.Union {
const res = parser.nodePreviousElementSibling(parser.characterDataToNode(self));
if (res == null) {
return null;
}
return HTMLElem.toInterface(HTMLElem.Union, res.?);
}

// Read/Write attributes

pub fn get_data(self: *parser.CharacterData) []const u8 {
return parser.characterDataData(self);
}

pub fn set_data(self: *parser.CharacterData, data: []const u8) void {
return parser.characterDataSetData(self, data);
}

// JS methods
// ----------

pub fn _appendData(self: *parser.CharacterData, data: []const u8) void {
return parser.characterDataAppendData(self, data);
}

pub fn _deleteData(self: *parser.CharacterData, offset: u32, count: u32) void {
return parser.characterDataDeleteData(self, offset, count);
}

pub fn _insertData(self: *parser.CharacterData, offset: u32, data: []const u8) void {
return parser.characterDataInsertData(self, offset, data);
}

pub fn _replaceData(self: *parser.CharacterData, offset: u32, count: u32, data: []const u8) void {
return parser.characterDataReplaceData(self, offset, count, data);
}

pub fn _substringData(self: *parser.CharacterData, offset: u32, count: u32) []const u8 {
return parser.characterDataSubstringData(self, offset, count);
}
};

pub const Types = generate.Tuple(.{
Expand All @@ -18,3 +83,79 @@ pub const Types = generate.Tuple(.{
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 get_data = [_]Case{
.{ .src = "let link = document.getElementById('link')", .ex = "undefined" },
.{ .src = "let cdata = link.firstChild", .ex = "undefined" },
.{ .src = "cdata.data", .ex = "OK" },
};
try checkCases(js_env, &get_data);

var set_data = [_]Case{
.{ .src = "cdata.data = 'OK modified'", .ex = "OK modified" },
.{ .src = "cdata.data === 'OK modified'", .ex = "true" },
.{ .src = "cdata.data = 'OK'", .ex = "OK" },
};
try checkCases(js_env, &set_data);

var get_length = [_]Case{
.{ .src = "cdata.length === 2", .ex = "true" },
};
try checkCases(js_env, &get_length);

var get_next_elem_sibling = [_]Case{
.{ .src = "cdata.nextElementSibling === null", .ex = "true" },
// create a next element
.{ .src = "let next = document.createElement('a')", .ex = "undefined" },
.{ .src = "link.appendChild(next, cdata) !== undefined", .ex = "true" },
.{ .src = "cdata.nextElementSibling.localName === 'a' ", .ex = "true" },
};
try checkCases(js_env, &get_next_elem_sibling);

var get_prev_elem_sibling = [_]Case{
.{ .src = "cdata.previousElementSibling === null", .ex = "true" },
// create a prev element
.{ .src = "let prev = document.createElement('div')", .ex = "undefined" },
.{ .src = "link.insertBefore(prev, cdata) !== undefined", .ex = "true" },
.{ .src = "cdata.previousElementSibling.localName === 'div' ", .ex = "true" },
};
try checkCases(js_env, &get_prev_elem_sibling);

var append_data = [_]Case{
.{ .src = "cdata.appendData(' modified')", .ex = "undefined" },
.{ .src = "cdata.data === 'OK modified' ", .ex = "true" },
};
try checkCases(js_env, &append_data);

var delete_data = [_]Case{
.{ .src = "cdata.deleteData('OK'.length, ' modified'.length)", .ex = "undefined" },
.{ .src = "cdata.data == 'OK'", .ex = "true" },
};
try checkCases(js_env, &delete_data);

var insert_data = [_]Case{
.{ .src = "cdata.insertData('OK'.length-1, 'modified')", .ex = "undefined" },
.{ .src = "cdata.data == 'OmodifiedK'", .ex = "true" },
};
try checkCases(js_env, &insert_data);

var replace_data = [_]Case{
.{ .src = "cdata.replaceData('OK'.length-1, 'modified'.length, 'replaced')", .ex = "undefined" },
.{ .src = "cdata.data == 'OreplacedK'", .ex = "true" },
};
try checkCases(js_env, &replace_data);

var substring_data = [_]Case{
.{ .src = "cdata.substringData('OK'.length-1, 'replaced'.length) == 'replaced'", .ex = "true" },
.{ .src = "cdata.substringData('OK'.length-1, 0) == ''", .ex = "true" },
};
try checkCases(js_env, &substring_data);
}
80 changes: 80 additions & 0 deletions src/netsurf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,42 @@ pub fn nodeNextSibling(node: *Node) ?*Node {
return res;
}

pub fn nodeNextElementSibling(node: *Node) ?*Element {
var n = node;
while (true) {
const res = nodeNextSibling(n);
if (res == null) {
return null;
}
if (nodeType(res.?) == .element) {
return @as(*Element, @ptrCast(res.?));
}
n = res.?;
}
return null;
}

pub fn nodePreviousSibling(node: *Node) ?*Node {
var res: ?*Node = undefined;
_ = nodeVtable(node).dom_node_get_previous_sibling.?(node, &res);
return res;
}

pub fn nodePreviousElementSibling(node: *Node) ?*Element {
var n = node;
while (true) {
const res = nodePreviousSibling(n);
if (res == null) {
return null;
}
if (nodeType(res.?) == .element) {
return @as(*Element, @ptrCast(res.?));
}
n = res.?;
}
return null;
}

pub fn nodeParentNode(node: *Node) ?*Node {
var res: ?*Node = undefined;
_ = nodeVtable(node).dom_node_get_parent_node.?(node, &res);
Expand Down Expand Up @@ -420,6 +450,56 @@ pub fn nodeReplaceChild(node: *Node, new_child: *Node, old_child: *Node) *Node {
// CharacterData
pub const CharacterData = c.dom_characterdata;

fn characterDataVtable(data: *CharacterData) c.dom_characterdata_vtable {
return getVtable(c.dom_characterdata_vtable, CharacterData, data);
}

pub inline fn characterDataToNode(cdata: *CharacterData) *Node {
return @as(*Node, @ptrCast(cdata));
}

pub fn characterDataData(cdata: *CharacterData) []const u8 {
var s: ?*String = undefined;
_ = characterDataVtable(cdata).dom_characterdata_get_data.?(cdata, &s);
return stringToData(s.?);
}

pub fn characterDataSetData(cdata: *CharacterData, data: []const u8) void {
const s = stringFromData(data);
_ = characterDataVtable(cdata).dom_characterdata_set_data.?(cdata, s);
}

pub fn characterDataLength(cdata: *CharacterData) u32 {
var n: u32 = undefined;
_ = characterDataVtable(cdata).dom_characterdata_get_length.?(cdata, &n);
return n;
}

pub fn characterDataAppendData(cdata: *CharacterData, data: []const u8) void {
const s = stringFromData(data);
_ = characterDataVtable(cdata).dom_characterdata_append_data.?(cdata, s);
}

pub fn characterDataDeleteData(cdata: *CharacterData, offset: u32, count: u32) void {
_ = characterDataVtable(cdata).dom_characterdata_delete_data.?(cdata, offset, count);
}

pub fn characterDataInsertData(cdata: *CharacterData, offset: u32, data: []const u8) void {
const s = stringFromData(data);
_ = characterDataVtable(cdata).dom_characterdata_insert_data.?(cdata, offset, s);
}

pub fn characterDataReplaceData(cdata: *CharacterData, offset: u32, count: u32, data: []const u8) void {
const s = stringFromData(data);
_ = characterDataVtable(cdata).dom_characterdata_replace_data.?(cdata, offset, count, s);
}

pub fn characterDataSubstringData(cdata: *CharacterData, offset: u32, count: u32) []const u8 {
var s: ?*String = undefined;
_ = characterDataVtable(cdata).dom_characterdata_substring_data.?(cdata, offset, count, &s);
return stringToData(s.?);
}

// Text
pub const Text = c.dom_text;

Expand Down
36 changes: 27 additions & 9 deletions src/run_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,49 @@ const generate = @import("generate.zig");

const parser = @import("netsurf.zig");
const DOM = @import("dom.zig");

const docTestExecFn = @import("html/document.zig").testExecFn;
const nodeTestExecFn = @import("dom/node.zig").testExecFn;
const characterDataTestExecFn = @import("dom/character_data.zig").testExecFn;

var doc: *parser.DocumentHTML = undefined;

fn testsExecFn(
fn testExecFn(
alloc: std.mem.Allocator,
js_env: *jsruntime.Env,
comptime apis: []jsruntime.API,
comptime execFn: jsruntime.ContextExecFn,
) !void {

// start JS env
js_env.start(apis);
defer js_env.stop();

// document
doc = try parser.documentHTMLParseFromFileAlloc(std.testing.allocator, "test.html");
defer parser.documentHTMLClose(doc);

// add document object
try js_env.addObject(apis, doc, "document");

// run tests
try docTestExecFn(alloc, js_env, apis);
try nodeTestExecFn(alloc, js_env, apis);
// run test
try execFn(alloc, js_env, apis);
}

fn testsAllExecFn(
alloc: std.mem.Allocator,
js_env: *jsruntime.Env,
comptime apis: []jsruntime.API,
) !void {
const testFns = [_]jsruntime.ContextExecFn{
docTestExecFn,
nodeTestExecFn,
characterDataTestExecFn,
};

inline for (testFns) |testFn| {
try testExecFn(alloc, js_env, apis, testFn);
}
}

test {
Expand All @@ -37,10 +59,6 @@ test {
// generate APIs
const apis = jsruntime.compile(DOM.Interfaces);

// document
doc = try parser.documentHTMLParseFromFileAlloc(std.testing.allocator, "test.html");
defer parser.documentHTMLClose(doc);

// create JS vm
const vm = jsruntime.VM.init();
defer vm.deinit();
Expand All @@ -49,5 +67,5 @@ test {
var arena_alloc = std.heap.ArenaAllocator.init(bench_alloc.allocator());
defer arena_alloc.deinit();

try jsruntime.loadEnv(&arena_alloc, testsExecFn, apis);
try jsruntime.loadEnv(&arena_alloc, testsAllExecFn, apis);
}