mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 23:23:28 +00:00
Merge pull request #124 from lightpanda-io/dom-parentnode
Dom parentnode accessors mixin
This commit is contained in:
@@ -34,9 +34,10 @@ pub const Document = struct {
|
||||
return DOMImplementation{};
|
||||
}
|
||||
|
||||
pub fn get_documentElement(self: *parser.Document) !ElementUnion {
|
||||
pub fn get_documentElement(self: *parser.Document) !?ElementUnion {
|
||||
const e = try parser.documentGetDocumentElement(self);
|
||||
return try Element.toInterface(e);
|
||||
if (e == null) return null;
|
||||
return try Element.toInterface(e.?);
|
||||
}
|
||||
|
||||
pub fn get_documentURI(self: *parser.Document) ![]const u8 {
|
||||
@@ -105,12 +106,12 @@ pub const Document = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
tag_name: []const u8,
|
||||
) !collection.HTMLCollection {
|
||||
const root = try parser.documentGetDocumentElement(self);
|
||||
return try collection.HTMLCollectionByTagName(
|
||||
alloc,
|
||||
parser.elementToNode(root),
|
||||
tag_name,
|
||||
);
|
||||
var elt: ?*parser.Node = null;
|
||||
if (try parser.documentGetDocumentElement(self)) |root| {
|
||||
elt = parser.elementToNode(root);
|
||||
}
|
||||
|
||||
return try collection.HTMLCollectionByTagName(alloc, elt, tag_name, true);
|
||||
}
|
||||
|
||||
pub fn _getElementsByClassName(
|
||||
@@ -118,12 +119,12 @@ pub const Document = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
classNames: []const u8,
|
||||
) !collection.HTMLCollection {
|
||||
const root = try parser.documentGetDocumentElement(self);
|
||||
return try collection.HTMLCollectionByClassName(
|
||||
alloc,
|
||||
parser.elementToNode(root),
|
||||
classNames,
|
||||
);
|
||||
var elt: ?*parser.Node = null;
|
||||
if (try parser.documentGetDocumentElement(self)) |root| {
|
||||
elt = parser.elementToNode(root);
|
||||
}
|
||||
|
||||
return try collection.HTMLCollectionByClassName(alloc, elt, classNames, true);
|
||||
}
|
||||
|
||||
pub fn _createDocumentFragment(self: *parser.Document) !*parser.DocumentFragment {
|
||||
@@ -164,6 +165,31 @@ pub const Document = struct {
|
||||
return try parser.documentCreateAttributeNS(self, ns, qname);
|
||||
}
|
||||
|
||||
// ParentNode
|
||||
// https://dom.spec.whatwg.org/#parentnode
|
||||
pub fn get_children(self: *parser.Document) !collection.HTMLCollection {
|
||||
var elt: ?*parser.Node = null;
|
||||
if (try parser.documentGetDocumentElement(self)) |root| {
|
||||
elt = parser.elementToNode(root);
|
||||
}
|
||||
return try collection.HTMLCollectionChildren(elt, true);
|
||||
}
|
||||
|
||||
pub fn get_firstElementChild(self: *parser.Document) !?ElementUnion {
|
||||
const elt = try parser.documentGetDocumentElement(self) orelse return null;
|
||||
return try Element.toInterface(elt);
|
||||
}
|
||||
|
||||
pub fn get_lastElementChild(self: *parser.Document) !?ElementUnion {
|
||||
const elt = try parser.documentGetDocumentElement(self) orelse return null;
|
||||
return try Element.toInterface(elt);
|
||||
}
|
||||
|
||||
pub fn get_childElementCount(self: *parser.Document) !u32 {
|
||||
_ = try parser.documentGetDocumentElement(self) orelse return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
pub fn deinit(_: *parser.Document, _: std.mem.Allocator) void {}
|
||||
};
|
||||
|
||||
@@ -179,6 +205,12 @@ 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 = "let newdoc = new Document()", .ex = "undefined" },
|
||||
.{ .src = "newdoc.documentElement", .ex = "null" },
|
||||
.{ .src = "newdoc.children.length", .ex = "0" },
|
||||
.{ .src = "newdoc.getElementsByTagName('*').length", .ex = "0" },
|
||||
.{ .src = "newdoc.getElementsByTagName('*').item(0)", .ex = "null" },
|
||||
};
|
||||
try checkCases(js_env, &constructor);
|
||||
|
||||
@@ -306,6 +338,22 @@ pub fn testExecFn(
|
||||
};
|
||||
try checkCases(js_env, &createAttr);
|
||||
|
||||
var parentNode = [_]Case{
|
||||
.{ .src = "document.children.length", .ex = "1" },
|
||||
.{ .src = "document.children.item(0).nodeName", .ex = "HTML" },
|
||||
.{ .src = "document.firstElementChild.nodeName", .ex = "HTML" },
|
||||
.{ .src = "document.lastElementChild.nodeName", .ex = "HTML" },
|
||||
.{ .src = "document.childElementCount", .ex = "1" },
|
||||
|
||||
.{ .src = "let nd = new Document()", .ex = "undefined" },
|
||||
.{ .src = "nd.children.length", .ex = "0" },
|
||||
.{ .src = "nd.children.item(0)", .ex = "null" },
|
||||
.{ .src = "nd.firstElementChild", .ex = "null" },
|
||||
.{ .src = "nd.lastElementChild", .ex = "null" },
|
||||
.{ .src = "nd.childElementCount", .ex = "0" },
|
||||
};
|
||||
try checkCases(js_env, &parentNode);
|
||||
|
||||
const tags = comptime parser.Tag.all();
|
||||
comptime var createElements: [(tags.len) * 2]Case = undefined;
|
||||
inline for (tags, 0..) |tag, i| {
|
||||
|
||||
@@ -6,6 +6,8 @@ const jsruntime = @import("jsruntime");
|
||||
const Case = jsruntime.test_utils.Case;
|
||||
const checkCases = jsruntime.test_utils.checkCases;
|
||||
|
||||
const collection = @import("html_collection.zig");
|
||||
|
||||
const Node = @import("node.zig").Node;
|
||||
const HTMLElem = @import("../html/elements.zig");
|
||||
pub const Union = @import("../html/elements.zig").Union;
|
||||
@@ -122,6 +124,60 @@ pub const Element = struct {
|
||||
// Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn _getElementsByTagName(
|
||||
self: *parser.Element,
|
||||
alloc: std.mem.Allocator,
|
||||
tag_name: []const u8,
|
||||
) !collection.HTMLCollection {
|
||||
return try collection.HTMLCollectionByTagName(
|
||||
alloc,
|
||||
parser.elementToNode(self),
|
||||
tag_name,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn _getElementsByClassName(
|
||||
self: *parser.Element,
|
||||
alloc: std.mem.Allocator,
|
||||
classNames: []const u8,
|
||||
) !collection.HTMLCollection {
|
||||
return try collection.HTMLCollectionByClassName(
|
||||
alloc,
|
||||
parser.elementToNode(self),
|
||||
classNames,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
// ParentNode
|
||||
// https://dom.spec.whatwg.org/#parentnode
|
||||
pub fn get_children(self: *parser.Element) !collection.HTMLCollection {
|
||||
return try collection.HTMLCollectionChildren(parser.elementToNode(self), false);
|
||||
}
|
||||
|
||||
pub fn get_firstElementChild(self: *parser.Element) !?Union {
|
||||
var children = try get_children(self);
|
||||
return try children._item(0);
|
||||
}
|
||||
|
||||
pub fn get_lastElementChild(self: *parser.Element) !?Union {
|
||||
// TODO we could check the last child node first, if it's an element,
|
||||
// we can return it directly instead of looping twice over the
|
||||
// children.
|
||||
var children = try get_children(self);
|
||||
const ln = try children.get_length();
|
||||
if (ln == 0) return null;
|
||||
return try children._item(ln - 1);
|
||||
}
|
||||
|
||||
pub fn get_childElementCount(self: *parser.Element) !u32 {
|
||||
var children = try get_children(self);
|
||||
return try children.get_length();
|
||||
}
|
||||
|
||||
pub fn deinit(_: *parser.Element, _: std.mem.Allocator) void {}
|
||||
};
|
||||
|
||||
// Tests
|
||||
@@ -192,4 +248,13 @@ pub fn testExecFn(
|
||||
.{ .src = "b.hasAttribute('foo')", .ex = "false" },
|
||||
};
|
||||
try checkCases(js_env, &toggleAttr);
|
||||
|
||||
var parentNode = [_]Case{
|
||||
.{ .src = "let c = document.getElementById('content')", .ex = "undefined" },
|
||||
.{ .src = "c.children.length", .ex = "3" },
|
||||
.{ .src = "c.firstElementChild.nodeName", .ex = "A" },
|
||||
.{ .src = "c.lastElementChild.nodeName", .ex = "P" },
|
||||
.{ .src = "c.childElementCount", .ex = "3" },
|
||||
};
|
||||
try checkCases(js_env, &parentNode);
|
||||
}
|
||||
|
||||
@@ -14,16 +14,21 @@ const Union = @import("element.zig").Union;
|
||||
const Matcher = union(enum) {
|
||||
matchByTagName: MatchByTagName,
|
||||
matchByClassName: MatchByClassName,
|
||||
matchTrue: struct {},
|
||||
|
||||
pub fn match(self: Matcher, node: *parser.Node) !bool {
|
||||
switch (self) {
|
||||
inline else => |case| return case.match(node),
|
||||
inline .matchTrue => return true,
|
||||
inline .matchByTagName => |case| return case.match(node),
|
||||
inline .matchByClassName => |case| return case.match(node),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: Matcher, alloc: std.mem.Allocator) void {
|
||||
switch (self) {
|
||||
inline else => |case| return case.deinit(alloc),
|
||||
inline .matchTrue => return,
|
||||
inline .matchByTagName => |case| return case.deinit(alloc),
|
||||
inline .matchByClassName => |case| return case.deinit(alloc),
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -54,14 +59,17 @@ pub const MatchByTagName = struct {
|
||||
|
||||
pub fn HTMLCollectionByTagName(
|
||||
alloc: std.mem.Allocator,
|
||||
root: *parser.Node,
|
||||
root: ?*parser.Node,
|
||||
tag_name: []const u8,
|
||||
include_root: bool,
|
||||
) !HTMLCollection {
|
||||
return HTMLCollection{
|
||||
.root = root,
|
||||
.walker = Walker{ .walkerDepthFirst = .{} },
|
||||
.matcher = Matcher{
|
||||
.matchByTagName = try MatchByTagName.init(alloc, tag_name),
|
||||
},
|
||||
.include_root = include_root,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,17 +103,108 @@ pub const MatchByClassName = struct {
|
||||
|
||||
pub fn HTMLCollectionByClassName(
|
||||
alloc: std.mem.Allocator,
|
||||
root: *parser.Node,
|
||||
root: ?*parser.Node,
|
||||
classNames: []const u8,
|
||||
include_root: bool,
|
||||
) !HTMLCollection {
|
||||
return HTMLCollection{
|
||||
.root = root,
|
||||
.walker = Walker{ .walkerDepthFirst = .{} },
|
||||
.matcher = Matcher{
|
||||
.matchByClassName = try MatchByClassName.init(alloc, classNames),
|
||||
},
|
||||
.include_root = include_root,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn HTMLCollectionChildren(
|
||||
root: ?*parser.Node,
|
||||
include_root: bool,
|
||||
) !HTMLCollection {
|
||||
return HTMLCollection{
|
||||
.root = root,
|
||||
.walker = Walker{ .walkerChildren = .{} },
|
||||
.matcher = Matcher{ .matchTrue = .{} },
|
||||
.include_root = include_root,
|
||||
};
|
||||
}
|
||||
|
||||
const Walker = union(enum) {
|
||||
walkerDepthFirst: WalkerDepthFirst,
|
||||
walkerChildren: WalkerChildren,
|
||||
|
||||
pub fn get_next(self: Walker, root: *parser.Node, cur: ?*parser.Node) !?*parser.Node {
|
||||
switch (self) {
|
||||
inline else => |case| return case.get_next(root, cur),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// WalkerDepthFirst iterates over the DOM tree to return the next following
|
||||
// node or null at the end.
|
||||
//
|
||||
// This implementation is a zig version of Netsurf code.
|
||||
// http://source.netsurf-browser.org/libdom.git/tree/src/html/html_collection.c#n177
|
||||
//
|
||||
// The iteration is a depth first as required by the specification.
|
||||
// https://dom.spec.whatwg.org/#htmlcollection
|
||||
// https://dom.spec.whatwg.org/#concept-tree-order
|
||||
pub const WalkerDepthFirst = struct {
|
||||
pub fn get_next(_: WalkerDepthFirst, root: *parser.Node, cur: ?*parser.Node) !?*parser.Node {
|
||||
var n = cur orelse root;
|
||||
|
||||
// TODO deinit next
|
||||
if (try parser.nodeFirstChild(n)) |next| {
|
||||
return next;
|
||||
}
|
||||
|
||||
// TODO deinit next
|
||||
if (try parser.nodeNextSibling(n)) |next| {
|
||||
return next;
|
||||
}
|
||||
|
||||
// TODO deinit parent
|
||||
// Back to the parent of cur.
|
||||
// If cur has no parent, then the iteration is over.
|
||||
var parent = try parser.nodeParentNode(n) orelse return null;
|
||||
|
||||
// TODO deinit lastchild
|
||||
var lastchild = try parser.nodeLastChild(parent);
|
||||
while (n != root and n == lastchild) {
|
||||
n = parent;
|
||||
|
||||
// TODO deinit parent
|
||||
// Back to the prev's parent.
|
||||
// If prev has no parent, then the loop must stop.
|
||||
parent = try parser.nodeParentNode(n) orelse break;
|
||||
|
||||
// TODO deinit lastchild
|
||||
lastchild = try parser.nodeLastChild(parent);
|
||||
}
|
||||
|
||||
if (n == root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return try parser.nodeNextSibling(n);
|
||||
}
|
||||
};
|
||||
|
||||
// WalkerChildren iterates over the root's children only.
|
||||
pub const WalkerChildren = struct {
|
||||
pub fn get_next(_: WalkerChildren, root: *parser.Node, cur: ?*parser.Node) !?*parser.Node {
|
||||
// On walk start, we return the first root's child.
|
||||
if (cur == null) return try parser.nodeFirstChild(root);
|
||||
|
||||
// If cur is root, then return null.
|
||||
// This is a special case, if the root is included in the walk, we
|
||||
// don't want to go further to find children.
|
||||
if (root == cur.?) return null;
|
||||
|
||||
return try parser.nodeNextSibling(cur.?);
|
||||
}
|
||||
};
|
||||
|
||||
// WEB IDL https://dom.spec.whatwg.org/#htmlcollection
|
||||
// HTMLCollection is re implemented in zig here because libdom
|
||||
// dom_html_collection expects a comparison function callback as arguement.
|
||||
@@ -114,96 +213,68 @@ pub const HTMLCollection = struct {
|
||||
pub const mem_guarantied = true;
|
||||
|
||||
matcher: Matcher,
|
||||
walker: Walker,
|
||||
|
||||
root: *parser.Node,
|
||||
root: ?*parser.Node,
|
||||
|
||||
// By default the HTMLCollection walk on the root's descendant only.
|
||||
// But on somes cases, like for dom document, we want to walk over the root
|
||||
// itself.
|
||||
include_root: bool = false,
|
||||
|
||||
// save a state for the collection to improve the _item speed.
|
||||
cur_idx: ?u32 = undefined,
|
||||
cur_node: ?*parser.Node = undefined,
|
||||
|
||||
// get_next iterates over the DOM tree to return the next following node or
|
||||
// null at the end.
|
||||
//
|
||||
// This implementation is a zig version of Netsurf code.
|
||||
// http://source.netsurf-browser.org/libdom.git/tree/src/html/html_collection.c#n177
|
||||
//
|
||||
// The iteration is a depth first as required by the specification.
|
||||
// https://dom.spec.whatwg.org/#htmlcollection
|
||||
// https://dom.spec.whatwg.org/#concept-tree-order
|
||||
fn get_next(root: *parser.Node, cur: *parser.Node) !?*parser.Node {
|
||||
// TODO deinit next
|
||||
if (try parser.nodeFirstChild(cur)) |next| {
|
||||
return next;
|
||||
// start returns the first node to walk on.
|
||||
fn start(self: HTMLCollection) !?*parser.Node {
|
||||
if (self.root == null) return null;
|
||||
|
||||
if (self.include_root) {
|
||||
return self.root.?;
|
||||
}
|
||||
|
||||
// TODO deinit next
|
||||
if (try parser.nodeNextSibling(cur)) |next| {
|
||||
return next;
|
||||
}
|
||||
|
||||
// TODO deinit parent
|
||||
// Back to the parent of cur.
|
||||
// If cur has no parent, then the iteration is over.
|
||||
var parent = try parser.nodeParentNode(cur) orelse return null;
|
||||
|
||||
// TODO deinit lastchild
|
||||
var lastchild = try parser.nodeLastChild(parent);
|
||||
var prev = cur;
|
||||
while (prev != root and prev == lastchild) {
|
||||
prev = parent;
|
||||
|
||||
// TODO deinit parent
|
||||
// Back to the prev's parent.
|
||||
// If prev has no parent, then the loop must stop.
|
||||
parent = try parser.nodeParentNode(prev) orelse break;
|
||||
|
||||
// TODO deinit lastchild
|
||||
lastchild = try parser.nodeLastChild(parent);
|
||||
}
|
||||
|
||||
if (prev == root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return try parser.nodeNextSibling(prev);
|
||||
return try self.walker.get_next(self.root.?, null);
|
||||
}
|
||||
|
||||
/// get_length computes the collection's length dynamically according to
|
||||
/// the current root structure.
|
||||
// TODO: nodes retrieved must be de-referenced.
|
||||
pub fn get_length(self: *HTMLCollection) !u32 {
|
||||
if (self.root == null) return 0;
|
||||
|
||||
var len: u32 = 0;
|
||||
var node: *parser.Node = self.root;
|
||||
var ntype: parser.NodeType = undefined;
|
||||
var node = try self.start() orelse return 0;
|
||||
|
||||
while (true) {
|
||||
ntype = try parser.nodeType(node);
|
||||
if (ntype == .element) {
|
||||
if (try parser.nodeType(node) == .element) {
|
||||
if (try self.matcher.match(node)) {
|
||||
len += 1;
|
||||
}
|
||||
}
|
||||
|
||||
node = try get_next(self.root, node) orelse break;
|
||||
node = try self.walker.get_next(self.root.?, node) orelse break;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
pub fn _item(self: *HTMLCollection, index: u32) !?Union {
|
||||
if (self.root == null) return null;
|
||||
|
||||
var i: u32 = 0;
|
||||
var node: *parser.Node = self.root;
|
||||
var ntype: parser.NodeType = undefined;
|
||||
var node: *parser.Node = undefined;
|
||||
|
||||
// Use the current state to improve speed if possible.
|
||||
if (self.cur_idx != null and index >= self.cur_idx.?) {
|
||||
i = self.cur_idx.?;
|
||||
node = self.cur_node.?;
|
||||
} else {
|
||||
node = try self.start() orelse return null;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
ntype = try parser.nodeType(node);
|
||||
if (ntype == .element) {
|
||||
if (try parser.nodeType(node) == .element) {
|
||||
if (try self.matcher.match(node)) {
|
||||
// check if we found the searched element.
|
||||
if (i == index) {
|
||||
@@ -219,23 +290,20 @@ pub const HTMLCollection = struct {
|
||||
}
|
||||
}
|
||||
|
||||
node = try get_next(self.root, node) orelse break;
|
||||
node = try self.walker.get_next(self.root.?, node) orelse break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn _namedItem(self: *HTMLCollection, name: []const u8) !?Union {
|
||||
if (name.len == 0) {
|
||||
return null;
|
||||
}
|
||||
if (self.root == null) return null;
|
||||
if (name.len == 0) return null;
|
||||
|
||||
var node: *parser.Node = self.root;
|
||||
var ntype: parser.NodeType = undefined;
|
||||
var node = try self.start() orelse return null;
|
||||
|
||||
while (true) {
|
||||
ntype = try parser.nodeType(node);
|
||||
if (ntype == .element) {
|
||||
if (try parser.nodeType(node) == .element) {
|
||||
if (try self.matcher.match(node)) {
|
||||
const elem = @as(*parser.Element, @ptrCast(node));
|
||||
|
||||
@@ -253,7 +321,7 @@ pub const HTMLCollection = struct {
|
||||
}
|
||||
}
|
||||
|
||||
node = try get_next(self.root, node) orelse break;
|
||||
node = try self.walker.get_next(self.root.?, node) orelse break;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -290,6 +358,13 @@ pub fn testExecFn(
|
||||
.{ .src = "getElementsByTagNameAll.item(7).localName", .ex = "p" },
|
||||
.{ .src = "getElementsByTagNameAll.namedItem('para-empty-child').localName", .ex = "span" },
|
||||
|
||||
.{ .src = "document.getElementById('content').getElementsByTagName('*').length", .ex = "4" },
|
||||
.{ .src = "document.getElementById('content').getElementsByTagName('p').length", .ex = "2" },
|
||||
.{ .src = "document.getElementById('content').getElementsByTagName('div').length", .ex = "0" },
|
||||
|
||||
.{ .src = "document.children.length", .ex = "1" },
|
||||
.{ .src = "document.getElementById('content').children.length", .ex = "3" },
|
||||
|
||||
// check liveness
|
||||
.{ .src = "let content = document.getElementById('content')", .ex = "undefined" },
|
||||
.{ .src = "let pe = document.getElementById('para-empty')", .ex = "undefined" },
|
||||
|
||||
@@ -1150,10 +1150,11 @@ pub inline fn documentGetElementsByTagName(doc: *Document, tagname: []const u8)
|
||||
}
|
||||
|
||||
// documentGetDocumentElement returns the root document element.
|
||||
pub inline fn documentGetDocumentElement(doc: *Document) !*Element {
|
||||
pub inline fn documentGetDocumentElement(doc: *Document) !?*Element {
|
||||
var elem: ?*Element = undefined;
|
||||
const err = documentVtable(doc).dom_document_get_document_element.?(doc, &elem);
|
||||
try DOMErr(err);
|
||||
if (elem == null) return null;
|
||||
return elem.?;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user