diff --git a/src/dom/css.zig b/src/dom/css.zig new file mode 100644 index 00000000..4e293c92 --- /dev/null +++ b/src/dom/css.zig @@ -0,0 +1,61 @@ +const std = @import("std"); + +const parser = @import("../netsurf.zig"); + +const css = @import("../css/css.zig"); +const Node = @import("../css/libdom.zig").Node; +const NodeList = @import("nodelist.zig").NodeList; + +const MatchFirst = struct { + n: ?*parser.Node = null, + + pub fn match(m: *MatchFirst, n: Node) !void { + m.n = n.node; + } +}; + +pub fn querySelector(alloc: std.mem.Allocator, n: *parser.Node, selector: []const u8) !?*parser.Node { + const ps = try css.parse(alloc, selector, .{ .accept_pseudo_elts = true }); + defer ps.deinit(alloc); + + var m = MatchFirst{}; + + _ = try css.matchFirst(ps, Node{ .node = n }, &m); + return m.n; +} + +const MatchAll = struct { + alloc: std.mem.Allocator, + nl: NodeList, + + fn init(alloc: std.mem.Allocator) MatchAll { + return .{ + .alloc = alloc, + .nl = NodeList.init(), + }; + } + + fn deinit(m: *MatchAll) void { + m.nl.deinit(m.alloc); + } + + pub fn match(m: *MatchAll, n: Node) !void { + try m.nl.append(m.alloc, n.node); + } + + fn toOwnedList(m: *MatchAll) NodeList { + defer m.nl = NodeList.init(); + return m.nl; + } +}; + +pub fn querySelectorAll(alloc: std.mem.Allocator, n: *parser.Node, selector: []const u8) !NodeList { + const ps = try css.parse(alloc, selector, .{ .accept_pseudo_elts = true }); + defer ps.deinit(alloc); + + var m = MatchAll.init(alloc); + defer m.deinit(); + + try css.matchAll(ps, Node{ .node = n }, &m); + return m.toOwnedList(); +} diff --git a/src/dom/document.zig b/src/dom/document.zig index 7f3af5cf..c99cc5fc 100644 --- a/src/dom/document.zig +++ b/src/dom/document.zig @@ -13,6 +13,7 @@ const NodeUnion = @import("node.zig").Union; const Walker = @import("walker.zig").WalkerDepthFirst; const collection = @import("html_collection.zig"); +const css = @import("css.zig"); const Element = @import("element.zig").Element; const ElementUnion = @import("element.zig").Union; @@ -188,54 +189,18 @@ pub const Document = struct { return 1; } - // TODO netsurf doesn't handle query selectors. We have to implement a - // solution by ourselves. - // For now we handle only * and single id selector like `#foo`. - pub fn _querySelector(self: *parser.Document, selectors: []const u8) !?ElementUnion { - if (selectors.len == 0) return null; + pub fn _querySelector(self: *parser.Document, alloc: std.mem.Allocator, selector: []const u8) !?ElementUnion { + if (selector.len == 0) return null; - // catch-all, return the firstElementChild - if (selectors[0] == '*') return try get_firstElementChild(self); + const n = try css.querySelector(alloc, parser.documentToNode(self), selector); - // support only simple id selector. - if (selectors[0] != '#' or std.mem.indexOf(u8, selectors, " ") != null) return null; + if (n == null) return null; - return try _getElementById(self, selectors[1..]); + return try Element.toInterface(parser.nodeToElement(n.?)); } - // TODO netsurf doesn't handle query selectors. We have to implement a - // solution by ourselves. - // We handle only * and single id selector like `#foo`. - pub fn _querySelectorAll(self: *parser.Document, alloc: std.mem.Allocator, selectors: []const u8) !NodeList { - var list = try NodeList.init(); - errdefer list.deinit(alloc); - - if (selectors.len == 0) return list; - - // catch-all, return all elements - if (selectors[0] == '*') { - // walk over the node tree fo find the node by id. - const root = parser.documentToNode(self); - const walker = Walker{}; - var next: ?*parser.Node = null; - while (true) { - next = try walker.get_next(root, next) orelse return list; - // ignore non-element nodes. - if (try parser.nodeType(next.?) != .element) { - continue; - } - try list.append(alloc, next.?); - } - } - - // support only simple id selector. - if (selectors[0] != '#' or std.mem.indexOf(u8, selectors, " ") != null) return list; - - // walk over the node tree fo find the node by id. - const e = try parser.documentGetElementById(self, selectors[1..]) orelse return list; - try list.append(alloc, parser.elementToNode(e)); - - return list; + pub fn _querySelectorAll(self: *parser.Document, alloc: std.mem.Allocator, selector: []const u8) !NodeList { + return css.querySelectorAll(alloc, parser.documentToNode(self), selector); } // TODO according with https://dom.spec.whatwg.org/#parentnode, the @@ -426,6 +391,12 @@ pub fn testExecFn( .{ .src = "document.querySelector('*').nodeName", .ex = "HTML" }, .{ .src = "document.querySelector('#content').id", .ex = "content" }, .{ .src = "document.querySelector('#para').id", .ex = "para" }, + .{ .src = "document.querySelector('.ok').id", .ex = "link" }, + .{ .src = "document.querySelector('a ~ p').id", .ex = "para-empty" }, + .{ .src = "document.querySelector(':root').nodeName", .ex = "HTML" }, + + .{ .src = "document.querySelectorAll('p').length", .ex = "2" }, + .{ .src = "document.querySelectorAll('.ok').item(0).id", .ex = "link" }, }; try checkCases(js_env, &querySelector); diff --git a/src/dom/element.zig b/src/dom/element.zig index 7f58ea10..80d31992 100644 --- a/src/dom/element.zig +++ b/src/dom/element.zig @@ -9,6 +9,7 @@ const Variadic = jsruntime.Variadic; const collection = @import("html_collection.zig"); const writeNode = @import("../browser/dump.zig").writeNode; +const css = @import("css.zig"); const Node = @import("node.zig").Node; const Walker = @import("walker.zig").WalkerDepthFirst; @@ -263,56 +264,18 @@ pub const Element = struct { } } - // TODO netsurf doesn't handle query selectors. We have to implement a - // solution by ourselves. - // We handle only * and single id selector like `#foo`. - pub fn _querySelector(self: *parser.Element, selectors: []const u8) !?Union { - if (selectors.len == 0) return null; + pub fn _querySelector(self: *parser.Element, alloc: std.mem.Allocator, selector: []const u8) !?Union { + if (selector.len == 0) return null; - // catch-all, return the firstElementChild - if (selectors[0] == '*') return try get_firstElementChild(self); + const n = try css.querySelector(alloc, parser.elementToNode(self), selector); - // support only simple id selector. - if (selectors[0] != '#' or std.mem.indexOf(u8, selectors, " ") != null) return null; + if (n == null) return null; - // walk over the node tree fo find the node by id. - const n = try getElementById(self, selectors[1..]) orelse return null; - return try toInterface(parser.nodeToElement(n)); + return try toInterface(parser.nodeToElement(n.?)); } - // TODO netsurf doesn't handle query selectors. We have to implement a - // solution by ourselves. - // We handle only * and single id selector like `#foo`. - pub fn _querySelectorAll(self: *parser.Element, alloc: std.mem.Allocator, selectors: []const u8) !NodeList { - var list = try NodeList.init(); - errdefer list.deinit(alloc); - - if (selectors.len == 0) return list; - - // catch-all, return all elements - if (selectors[0] == '*') { - // walk over the node tree fo find the node by id. - const root = parser.elementToNode(self); - const walker = Walker{}; - var next: ?*parser.Node = null; - while (true) { - next = try walker.get_next(root, next) orelse return list; - // ignore non-element nodes. - if (try parser.nodeType(next.?) != .element) { - continue; - } - try list.append(alloc, next.?); - } - } - - // support only simple id selector. - if (selectors[0] != '#' or std.mem.indexOf(u8, selectors, " ") != null) return list; - - // walk over the node tree fo find the node by id. - const n = try getElementById(self, selectors[1..]) orelse return list; - try list.append(alloc, n); - - return list; + pub fn _querySelectorAll(self: *parser.Element, alloc: std.mem.Allocator, selector: []const u8) !NodeList { + return css.querySelectorAll(alloc, parser.elementToNode(self), selector); } // TODO according with https://dom.spec.whatwg.org/#parentnode, the @@ -433,6 +396,12 @@ pub fn testExecFn( .{ .src = "e.querySelector('#link').id", .ex = "link" }, .{ .src = "e.querySelector('#para').id", .ex = "para" }, .{ .src = "e.querySelector('*').id", .ex = "link" }, + .{ .src = "e.querySelector('')", .ex = "null" }, + .{ .src = "e.querySelector('*').id", .ex = "link" }, + .{ .src = "e.querySelector('#content')", .ex = "null" }, + .{ .src = "e.querySelector('#para').id", .ex = "para" }, + .{ .src = "e.querySelector('.ok').id", .ex = "link" }, + .{ .src = "e.querySelector('a ~ p').id", .ex = "para-empty" }, .{ .src = "e.querySelectorAll('foo').length", .ex = "0" }, .{ .src = "e.querySelectorAll('#foo').length", .ex = "0" }, @@ -441,6 +410,8 @@ pub fn testExecFn( .{ .src = "e.querySelectorAll('#para').length", .ex = "1" }, .{ .src = "e.querySelectorAll('#para').item(0).id", .ex = "para" }, .{ .src = "e.querySelectorAll('*').length", .ex = "4" }, + .{ .src = "e.querySelectorAll('p').length", .ex = "2" }, + .{ .src = "e.querySelectorAll('.ok').item(0).id", .ex = "link" }, }; try checkCases(js_env, &querySelector); diff --git a/src/dom/node.zig b/src/dom/node.zig index 7e6aa383..920768ec 100644 --- a/src/dom/node.zig +++ b/src/dom/node.zig @@ -199,7 +199,7 @@ pub const Node = struct { } pub fn get_childNodes(self: *parser.Node, alloc: std.mem.Allocator) !NodeList { - var list = try NodeList.init(); + var list = NodeList.init(); errdefer list.deinit(alloc); var n = try parser.nodeFirstChild(self) orelse return list; diff --git a/src/dom/nodelist.zig b/src/dom/nodelist.zig index c685f3b5..19ca644a 100644 --- a/src/dom/nodelist.zig +++ b/src/dom/nodelist.zig @@ -26,7 +26,7 @@ pub const NodeList = struct { nodes: NodesArrayList, - pub fn init() !NodeList { + pub fn init() NodeList { return NodeList{ .nodes = NodesArrayList{}, }; diff --git a/src/html/document.zig b/src/html/document.zig index d463ab29..34943ac2 100644 --- a/src/html/document.zig +++ b/src/html/document.zig @@ -80,7 +80,7 @@ pub const HTMLDocument = struct { } pub fn _getElementsByName(self: *parser.DocumentHTML, alloc: std.mem.Allocator, name: []const u8) !NodeList { - var list = try NodeList.init(); + var list = NodeList.init(); errdefer list.deinit(alloc); if (name.len == 0) return list;