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

Next Dom document implementation #118

Merged
merged 13 commits into from
Dec 8, 2023
Merged
12 changes: 12 additions & 0 deletions src/dom/cdata_section.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const std = @import("std");

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

const Text = @import("text.zig").Text;

// https://dom.spec.whatwg.org/#cdatasection
pub const CDATASection = struct {
pub const Self = parser.CDATASection;
pub const prototype = *Text;
pub const mem_guarantied = true;
};
7 changes: 5 additions & 2 deletions src/dom/character_data.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ const parser = @import("../netsurf.zig");

const Node = @import("node.zig").Node;
const Comment = @import("comment.zig").Comment;
const Text = @import("text.zig").Text;
const Text = @import("text.zig");
const ProcessingInstruction = @import("processing_instruction.zig").ProcessingInstruction;
const HTMLElem = @import("../html/elements.zig");

// CharacterData interfaces
pub const Interfaces = generate.Tuple(.{
Comment,
Text,
Text.Text,
Text.Interfaces,
ProcessingInstruction,
});

// CharacterData implementation
Expand Down
82 changes: 81 additions & 1 deletion src/dom/document.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases;

const Node = @import("node.zig").Node;
const NodeUnion = @import("node.zig").Union;

const collection = @import("html_collection.zig");

Expand Down Expand Up @@ -129,6 +130,40 @@ pub const Document = struct {
return try parser.documentCreateDocumentFragment(self);
}

pub fn _createTextNode(self: *parser.Document, data: []const u8) !*parser.Text {
return try parser.documentCreateTextNode(self, data);
}

pub fn _createCDATASection(self: *parser.Document, data: []const u8) !*parser.CDATASection {
return try parser.documentCreateCDATASection(self, data);
}

pub fn _createComment(self: *parser.Document, data: []const u8) !*parser.Comment {
return try parser.documentCreateComment(self, data);
}

pub fn _createProcessingInstruction(self: *parser.Document, target: []const u8, data: []const u8) !*parser.ProcessingInstruction {
return try parser.documentCreateProcessingInstruction(self, target, data);
}

pub fn _importNode(self: *parser.Document, node: *parser.Node, deep: ?bool) !NodeUnion {
const n = try parser.documentImportNode(self, node, deep orelse false);
return try Node.toInterface(n);
}

pub fn _adoptNode(self: *parser.Document, node: *parser.Node) !NodeUnion {
const n = try parser.documentAdoptNode(self, node);
return try Node.toInterface(n);
}

pub fn _createAttribute(self: *parser.Document, name: []const u8) !*parser.Attribute {
return try parser.documentCreateAttribute(self, name);
}

pub fn _createAttributeNS(self: *parser.Document, ns: []const u8, qname: []const u8) !*parser.Attribute {
return try parser.documentCreateAttributeNS(self, ns, qname);
}

pub fn deinit(_: *parser.Document, _: std.mem.Allocator) void {}
};

Expand Down Expand Up @@ -222,10 +257,55 @@ pub fn testExecFn(
try checkCases(js_env, &new);

var createDocumentFragment = [_]Case{
.{ .src = "document.createDocumentFragment()", .ex = "[object DocumentFragment]" },
.{ .src = "var v = document.createDocumentFragment()", .ex = "undefined" },
.{ .src = "v.nodeName", .ex = "#document-fragment" },
};
try checkCases(js_env, &createDocumentFragment);

var createTextNode = [_]Case{
.{ .src = "var v = document.createTextNode('foo')", .ex = "undefined" },
.{ .src = "v.nodeName", .ex = "#text" },
};
try checkCases(js_env, &createTextNode);

var createCDATASection = [_]Case{
.{ .src = "var v = document.createCDATASection('foo')", .ex = "undefined" },
.{ .src = "v.nodeName", .ex = "#cdata-section" },
};
try checkCases(js_env, &createCDATASection);

var createComment = [_]Case{
.{ .src = "var v = document.createComment('foo')", .ex = "undefined" },
.{ .src = "v.nodeName", .ex = "#comment" },
};
try checkCases(js_env, &createComment);

var createProcessingInstruction = [_]Case{
.{ .src = "let pi = document.createProcessingInstruction('foo', 'bar')", .ex = "undefined" },
.{ .src = "pi.target", .ex = "foo" },
};
try checkCases(js_env, &createProcessingInstruction);

var importNode = [_]Case{
.{ .src = "let nimp = document.getElementById('content')", .ex = "undefined" },
.{ .src = "var v = document.importNode(nimp)", .ex = "undefined" },
.{ .src = "v.nodeName", .ex = "DIV" },
};
try checkCases(js_env, &importNode);

var adoptNode = [_]Case{
.{ .src = "let nadop = document.getElementById('content')", .ex = "undefined" },
.{ .src = "var v = document.adoptNode(nadop)", .ex = "undefined" },
.{ .src = "v.nodeName", .ex = "DIV" },
};
try checkCases(js_env, &adoptNode);

var createAttr = [_]Case{
.{ .src = "var v = document.createAttribute('foo')", .ex = "undefined" },
.{ .src = "v.nodeName", .ex = "foo" },
};
try checkCases(js_env, &createAttr);

const tags = comptime parser.Tag.all();
comptime var createElements: [(tags.len) * 2]Case = undefined;
inline for (tags, 0..) |tag, i| {
Expand Down
2 changes: 2 additions & 0 deletions src/dom/node.zig
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ pub const Node = struct {
),
.comment => .{ .Comment = @as(*parser.Comment, @ptrCast(node)) },
.text => .{ .Text = @as(*parser.Text, @ptrCast(node)) },
.cdata_section => .{ .CDATASection = @as(*parser.CDATASection, @ptrCast(node)) },
.processing_instruction => .{ .ProcessingInstruction = @as(*parser.ProcessingInstruction, @ptrCast(node)) },
francisbouvier marked this conversation as resolved.
Show resolved Hide resolved
.document => .{ .HTMLDocument = @as(*parser.DocumentHTML, @ptrCast(node)) },
.document_type => .{ .DocumentType = @as(*parser.DocumentType, @ptrCast(node)) },
.attribute => .{ .Attr = @as(*parser.Attribute, @ptrCast(node)) },
Expand Down
17 changes: 17 additions & 0 deletions src/dom/processing_instruction.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const std = @import("std");

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

const CharacterData = @import("character_data.zig").CharacterData;

// https://dom.spec.whatwg.org/#processinginstruction
pub const ProcessingInstruction = struct {
pub const Self = parser.ProcessingInstruction;
pub const prototype = *CharacterData;
pub const mem_guarantied = true;

pub fn get_target(self: *parser.ProcessingInstruction) ![]const u8 {
// libdom stores the ProcessingInstruction target in the node's name.
return try parser.nodeName(@as(*parser.Node, @ptrCast(self)));
}
};
7 changes: 7 additions & 0 deletions src/dom/text.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ 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 CharacterData = @import("character_data.zig").CharacterData;
const CDATASection = @import("cdata_section.zig").CDATASection;

// Text interfaces
pub const Interfaces = generate.Tuple(.{
CDATASection,
});

pub const Text = struct {
pub const Self = parser.Text;
Expand Down
74 changes: 74 additions & 0 deletions src/netsurf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,9 @@ pub fn characterDataSubstringData(cdata: *CharacterData, offset: u32, count: u32
return strToData(s.?);
}

// CDATASection
pub const CDATASection = c.dom_cdata_section;

// Text
pub const Text = c.dom_text;

Expand All @@ -815,6 +818,9 @@ pub fn textSplitText(text: *Text, offset: u32) !*Text {
// Comment
pub const Comment = c.dom_comment;

// ProcessingInstruction
pub const ProcessingInstruction = c.dom_processing_instruction;

// Attribute
pub const Attribute = c.dom_attr;

Expand Down Expand Up @@ -1137,6 +1143,74 @@ pub inline fn documentCreateDocumentFragment(doc: *Document) !*DocumentFragment
return df.?;
}

pub inline fn documentCreateTextNode(doc: *Document, s: []const u8) !*Text {
var txt: ?*Text = undefined;
const err = documentVtable(doc).dom_document_create_text_node.?(doc, try strFromData(s), &txt);
try DOMErr(err);
return txt.?;
}

pub inline fn documentCreateCDATASection(doc: *Document, s: []const u8) !*CDATASection {
var cdata: ?*CDATASection = undefined;
const err = documentVtable(doc).dom_document_create_cdata_section.?(doc, try strFromData(s), &cdata);
try DOMErr(err);
return cdata.?;
}

pub inline fn documentCreateComment(doc: *Document, s: []const u8) !*Comment {
var com: ?*Comment = undefined;
const err = documentVtable(doc).dom_document_create_comment.?(doc, try strFromData(s), &com);
try DOMErr(err);
return com.?;
}

pub inline fn documentCreateProcessingInstruction(doc: *Document, target: []const u8, data: []const u8) !*ProcessingInstruction {
var pi: ?*ProcessingInstruction = undefined;
const err = documentVtable(doc).dom_document_create_processing_instruction.?(
doc,
try strFromData(target),
try strFromData(data),
&pi,
);
try DOMErr(err);
return pi.?;
}

pub inline fn documentImportNode(doc: *Document, node: *Node, deep: bool) !*Node {
var res: NodeExternal = undefined;
const nodeext = toNodeExternal(Node, node);
const err = documentVtable(doc).dom_document_import_node.?(doc, nodeext, deep, &res);
try DOMErr(err);
return @as(*Node, @ptrCast(res));
}

pub inline fn documentAdoptNode(doc: *Document, node: *Node) !*Node {
var res: NodeExternal = undefined;
const nodeext = toNodeExternal(Node, node);
const err = documentVtable(doc).dom_document_adopt_node.?(doc, nodeext, &res);
try DOMErr(err);
return @as(*Node, @ptrCast(res));
}

pub inline fn documentCreateAttribute(doc: *Document, name: []const u8) !*Attribute {
var attr: ?*Attribute = undefined;
const err = documentVtable(doc).dom_document_create_attribute.?(doc, try strFromData(name), &attr);
try DOMErr(err);
return attr.?;
}

pub inline fn documentCreateAttributeNS(doc: *Document, ns: []const u8, qname: []const u8) !*Attribute {
var attr: ?*Attribute = undefined;
const err = documentVtable(doc).dom_document_create_attribute_ns.?(
doc,
try strFromData(ns),
try strFromData(qname),
&attr,
);
try DOMErr(err);
return attr.?;
}

// DocumentHTML
pub const DocumentHTML = c.dom_html_document;

Expand Down
50 changes: 50 additions & 0 deletions tests/wpt/dom/nodes/Document-adoptNode.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!doctype html>
<meta charset=utf-8>
<title>Document.adoptNode</title>
<link rel=help href="https://dom.spec.whatwg.org/#dom-document-adoptnode">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<!--creates an element with local name "x<": --><x<>x</x<>
<script>
test(function() {
var y = document.getElementsByTagName("x<")[0]
var child = y.firstChild
assert_equals(y.parentNode, document.body)
assert_equals(y.ownerDocument, document)
assert_equals(document.adoptNode(y), y)
assert_equals(y.parentNode, null)
assert_equals(y.firstChild, child)
assert_equals(y.ownerDocument, document)
assert_equals(child.ownerDocument, document)
var doc = document.implementation.createDocument(null, null, null)
assert_equals(doc.adoptNode(y), y)
assert_equals(y.parentNode, null)
assert_equals(y.firstChild, child)
assert_equals(y.ownerDocument, doc)
assert_equals(child.ownerDocument, doc)
}, "Adopting an Element called 'x<' should work.")

test(function() {
var x = document.createElement(":good:times:")
assert_equals(document.adoptNode(x), x);
var doc = document.implementation.createDocument(null, null, null)
assert_equals(doc.adoptNode(x), x)
assert_equals(x.parentNode, null)
assert_equals(x.ownerDocument, doc)
}, "Adopting an Element called ':good:times:' should work.")

test(function() {
var doctype = document.doctype;
assert_equals(doctype.parentNode, document)
assert_equals(doctype.ownerDocument, document)
assert_equals(document.adoptNode(doctype), doctype)
assert_equals(doctype.parentNode, null)
assert_equals(doctype.ownerDocument, document)
}, "Explicitly adopting a DocumentType should work.")

test(function() {
var doc = document.implementation.createDocument(null, null, null)
assert_throws_dom("NOT_SUPPORTED_ERR", function() { document.adoptNode(doc) })
}, "Adopting a Document should throw.")
</script>
55 changes: 55 additions & 0 deletions tests/wpt/dom/nodes/Document-createAttribute.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!doctype html>
<meta charset=utf-8>
<title>Document.createAttribute</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=attributes.js></script>
<script src=productions.js></script>
<div id=log>
<script>
var xml_document;
setup(function() {
xml_document = document.implementation.createDocument(null, null, null);
});

invalid_names.forEach(function(name) {
test(function() {
assert_throws_dom("INVALID_CHARACTER_ERR", function() {
document.createAttribute(name, "test");
});
}, "HTML document.createAttribute(" + format_value(name) + ") should throw");

test(function() {
assert_throws_dom("INVALID_CHARACTER_ERR", function() {
xml_document.createAttribute(name, "test");
});
}, "XML document.createAttribute(" + format_value(name) + ") should throw");
});

valid_names.forEach(name => {
test(() => {
let attr = document.createAttribute(name);
attr_is(attr, "", name.toLowerCase(), null, null, name.toLowerCase());
}, `HTML document.createAttribute(${format_value(name)})`);

test(() => {
let attr = xml_document.createAttribute(name);
attr_is(attr, "", name, null, null, name);
}, `XML document.createAttribute(${format_value(name)})`);
});

var tests = ["title", "TITLE", null, undefined];
tests.forEach(function(name) {
test(function() {
var attribute = document.createAttribute(name);
attr_is(attribute, "", String(name).toLowerCase(), null, null, String(name).toLowerCase());
assert_equals(attribute.ownerElement, null);
}, "HTML document.createAttribute(" + format_value(name) + ")");

test(function() {
var attribute = xml_document.createAttribute(name);
attr_is(attribute, "", String(name), null, null, String(name));
assert_equals(attribute.ownerElement, null);
}, "XML document.createAttribute(" + format_value(name) + ")");
});
</script>
Loading