mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
Better CDP node serialization
Include direct descendant, with hooks for other serialization options. Don't include parentId if null.
This commit is contained in:
206
src/cdp/Node.zig
206
src/cdp/Node.zig
@@ -44,50 +44,8 @@ const CompatibilityMode = enum {
|
|||||||
NoQuirksMode,
|
NoQuirksMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn jsonStringify(self: *const Node, writer: anytype) !void {
|
pub fn writer(self: *const Node, opts: Writer.Opts) Writer {
|
||||||
try writer.beginObject();
|
return .{ .node = self, .opts = opts };
|
||||||
try writer.objectField("nodeId");
|
|
||||||
try writer.write(self.id);
|
|
||||||
|
|
||||||
try writer.objectField("parentId");
|
|
||||||
try writer.write(self.parent_id);
|
|
||||||
|
|
||||||
try writer.objectField("backendNodeId");
|
|
||||||
try writer.write(self.backend_node_id);
|
|
||||||
|
|
||||||
try writer.objectField("nodeType");
|
|
||||||
try writer.write(self.node_type);
|
|
||||||
|
|
||||||
try writer.objectField("nodeName");
|
|
||||||
try writer.write(self.node_name);
|
|
||||||
|
|
||||||
try writer.objectField("localName");
|
|
||||||
try writer.write(self.local_name);
|
|
||||||
|
|
||||||
try writer.objectField("nodeValue");
|
|
||||||
try writer.write(self.node_value);
|
|
||||||
|
|
||||||
try writer.objectField("childNodeCount");
|
|
||||||
try writer.write(self.child_node_count);
|
|
||||||
|
|
||||||
try writer.objectField("children");
|
|
||||||
try writer.write(self.children);
|
|
||||||
|
|
||||||
try writer.objectField("documentURL");
|
|
||||||
try writer.write(self.document_url);
|
|
||||||
|
|
||||||
try writer.objectField("baseURL");
|
|
||||||
try writer.write(self.base_url);
|
|
||||||
|
|
||||||
try writer.objectField("xmlVersion");
|
|
||||||
try writer.write(self.xml_version);
|
|
||||||
|
|
||||||
try writer.objectField("compatibilityMode");
|
|
||||||
try writer.write(self.compatibility_mode);
|
|
||||||
|
|
||||||
try writer.objectField("isScrollable");
|
|
||||||
try writer.write(self.is_scrollable);
|
|
||||||
try writer.endObject();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whenever we send a node to the client, we register it here for future lookup.
|
// Whenever we send a node to the client, we register it here for future lookup.
|
||||||
@@ -95,6 +53,7 @@ pub fn jsonStringify(self: *const Node, writer: anytype) !void {
|
|||||||
pub const Registry = struct {
|
pub const Registry = struct {
|
||||||
node_id: u32,
|
node_id: u32,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
node_pool: std.heap.MemoryPool(Node),
|
node_pool: std.heap.MemoryPool(Node),
|
||||||
lookup_by_id: std.AutoHashMapUnmanaged(Id, *Node),
|
lookup_by_id: std.AutoHashMapUnmanaged(Id, *Node),
|
||||||
lookup_by_node: std.HashMapUnmanaged(*parser.Node, *Node, NodeContext, std.hash_map.default_max_load_percentage),
|
lookup_by_node: std.HashMapUnmanaged(*parser.Node, *Node, NodeContext, std.hash_map.default_max_load_percentage),
|
||||||
@@ -102,9 +61,10 @@ pub const Registry = struct {
|
|||||||
pub fn init(allocator: Allocator) Registry {
|
pub fn init(allocator: Allocator) Registry {
|
||||||
return .{
|
return .{
|
||||||
.node_id = 0,
|
.node_id = 0,
|
||||||
.allocator = allocator,
|
|
||||||
.lookup_by_id = .{},
|
.lookup_by_id = .{},
|
||||||
.lookup_by_node = .{},
|
.lookup_by_node = .{},
|
||||||
|
.allocator = allocator,
|
||||||
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.node_pool = std.heap.MemoryPool(Node).init(allocator),
|
.node_pool = std.heap.MemoryPool(Node).init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -114,12 +74,14 @@ pub const Registry = struct {
|
|||||||
self.lookup_by_id.deinit(allocator);
|
self.lookup_by_id.deinit(allocator);
|
||||||
self.lookup_by_node.deinit(allocator);
|
self.lookup_by_node.deinit(allocator);
|
||||||
self.node_pool.deinit();
|
self.node_pool.deinit();
|
||||||
|
self.arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(self: *Registry) void {
|
pub fn reset(self: *Registry) void {
|
||||||
self.lookup_by_id.clearRetainingCapacity();
|
self.lookup_by_id.clearRetainingCapacity();
|
||||||
self.lookup_by_node.clearRetainingCapacity();
|
self.lookup_by_node.clearRetainingCapacity();
|
||||||
_ = self.node_pool.reset(.{ .retain_capacity = {} });
|
_ = self.arena.reset(.{ .retain_with_limit = 1024 });
|
||||||
|
_ = self.node_pool.reset(.{ .retain_with_limit = 1024 });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(self: *Registry, n: *parser.Node) !*Node {
|
pub fn register(self: *Registry, n: *parser.Node) !*Node {
|
||||||
@@ -132,11 +94,10 @@ pub const Registry = struct {
|
|||||||
// but, just in case, let's try to keep things tidy.
|
// but, just in case, let's try to keep things tidy.
|
||||||
errdefer _ = self.lookup_by_node.remove(n);
|
errdefer _ = self.lookup_by_node.remove(n);
|
||||||
|
|
||||||
const children = try parser.nodeGetChildNodes(n);
|
|
||||||
const children_count = try parser.nodeListLength(children);
|
|
||||||
|
|
||||||
const id = self.node_id;
|
const id = self.node_id;
|
||||||
defer self.node_id = id + 1;
|
self.node_id = id + 1;
|
||||||
|
|
||||||
|
const child_nodes = try self.registerChildNodes(n);
|
||||||
|
|
||||||
const node = try self.node_pool.create();
|
const node = try self.node_pool.create();
|
||||||
errdefer self.node_pool.destroy(node);
|
errdefer self.node_pool.destroy(node);
|
||||||
@@ -146,12 +107,12 @@ pub const Registry = struct {
|
|||||||
.id = id,
|
.id = id,
|
||||||
.parent_id = null, // TODO
|
.parent_id = null, // TODO
|
||||||
.backend_node_id = id, // ??
|
.backend_node_id = id, // ??
|
||||||
.node_name = try parser.nodeName(n),
|
.node_name = parser.nodeName(n) catch return error.NodeNameError,
|
||||||
.local_name = try parser.nodeLocalName(n),
|
.local_name = parser.nodeLocalName(n) catch return error.NodeLocalNameError,
|
||||||
.node_value = try parser.nodeValue(n) orelse "",
|
.node_value = (parser.nodeValue(n) catch return error.NameValueError) orelse "",
|
||||||
.node_type = @intFromEnum(try parser.nodeType(n)),
|
.node_type = @intFromEnum(parser.nodeType(n) catch return error.NodeTypeError),
|
||||||
.child_node_count = children_count,
|
.child_node_count = @intCast(child_nodes.len),
|
||||||
.children = &.{}, // TODO
|
.children = child_nodes,
|
||||||
.document_url = null,
|
.document_url = null,
|
||||||
.base_url = null,
|
.base_url = null,
|
||||||
.xml_version = "",
|
.xml_version = "",
|
||||||
@@ -168,6 +129,31 @@ pub const Registry = struct {
|
|||||||
try self.lookup_by_id.putNoClobber(self.allocator, id, node);
|
try self.lookup_by_id.putNoClobber(self.allocator, id, node);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn registerChildNodes(self: *Registry, n: *parser.Node) RegisterError![]*Node {
|
||||||
|
const node_list = parser.nodeGetChildNodes(n) catch return error.GetChildNodeError;
|
||||||
|
const count = parser.nodeListLength(node_list) catch return error.NodeListLengthError;
|
||||||
|
|
||||||
|
var arr = try self.arena.allocator().alloc(*Node, count);
|
||||||
|
var i: usize = 0;
|
||||||
|
for (0..count) |_| {
|
||||||
|
const child = (parser.nodeListItem(node_list, @intCast(i)) catch return error.NodeListItemError) orelse continue;
|
||||||
|
arr[i] = try self.register(child);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
return arr[0..i];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const RegisterError = error{
|
||||||
|
OutOfMemory,
|
||||||
|
GetChildNodeError,
|
||||||
|
NodeListLengthError,
|
||||||
|
NodeListItemError,
|
||||||
|
NodeNameError,
|
||||||
|
NodeLocalNameError,
|
||||||
|
NameValueError,
|
||||||
|
NodeTypeError,
|
||||||
};
|
};
|
||||||
|
|
||||||
const NodeContext = struct {
|
const NodeContext = struct {
|
||||||
@@ -271,8 +257,76 @@ pub const Search = struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Need a custom writer, because we can't just serialize the node as-is.
|
||||||
|
// Sometimes we want to serializ the node without chidren, sometimes with just
|
||||||
|
// its direct children, and sometimes the entire tree.
|
||||||
|
// (For now, we only support direct children)
|
||||||
|
pub const Writer = struct {
|
||||||
|
opts: Opts,
|
||||||
|
node: *const Node,
|
||||||
|
|
||||||
|
pub const Opts = struct {};
|
||||||
|
|
||||||
|
pub fn jsonStringify(self: *const Writer, w: anytype) !void {
|
||||||
|
try w.beginObject();
|
||||||
|
try writeCommon(self.node, w);
|
||||||
|
try w.objectField("children");
|
||||||
|
try w.beginArray();
|
||||||
|
for (self.node.children) |node| {
|
||||||
|
try w.beginObject();
|
||||||
|
try writeCommon(node, w);
|
||||||
|
try w.endObject();
|
||||||
|
}
|
||||||
|
try w.endArray();
|
||||||
|
try w.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writeCommon(node: *const Node, w: anytype) !void {
|
||||||
|
try w.objectField("nodeId");
|
||||||
|
try w.write(node.id);
|
||||||
|
|
||||||
|
if (node.parent_id) |pid| {
|
||||||
|
try w.objectField("parentId");
|
||||||
|
try w.write(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
try w.objectField("backendNodeId");
|
||||||
|
try w.write(node.backend_node_id);
|
||||||
|
|
||||||
|
try w.objectField("nodeType");
|
||||||
|
try w.write(node.node_type);
|
||||||
|
|
||||||
|
try w.objectField("nodeName");
|
||||||
|
try w.write(node.node_name);
|
||||||
|
|
||||||
|
try w.objectField("localName");
|
||||||
|
try w.write(node.local_name);
|
||||||
|
|
||||||
|
try w.objectField("nodeValue");
|
||||||
|
try w.write(node.node_value);
|
||||||
|
|
||||||
|
try w.objectField("childNodeCount");
|
||||||
|
try w.write(node.child_node_count);
|
||||||
|
|
||||||
|
try w.objectField("documentURL");
|
||||||
|
try w.write(node.document_url);
|
||||||
|
|
||||||
|
try w.objectField("baseURL");
|
||||||
|
try w.write(node.base_url);
|
||||||
|
|
||||||
|
try w.objectField("xmlVersion");
|
||||||
|
try w.write(node.xml_version);
|
||||||
|
|
||||||
|
try w.objectField("compatibilityMode");
|
||||||
|
try w.write(node.compatibility_mode);
|
||||||
|
|
||||||
|
try w.objectField("isScrollable");
|
||||||
|
try w.write(node.is_scrollable);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const testing = @import("testing.zig");
|
const testing = @import("testing.zig");
|
||||||
test "CDP Node: Registry register" {
|
test "cdp Node: Registry register" {
|
||||||
var registry = Registry.init(testing.allocator);
|
var registry = Registry.init(testing.allocator);
|
||||||
defer registry.deinit();
|
defer registry.deinit();
|
||||||
|
|
||||||
@@ -298,7 +352,8 @@ test "CDP Node: Registry register" {
|
|||||||
try testing.expectEqual("a", node.local_name);
|
try testing.expectEqual("a", node.local_name);
|
||||||
try testing.expectEqual("", node.node_value);
|
try testing.expectEqual("", node.node_value);
|
||||||
try testing.expectEqual(1, node.child_node_count);
|
try testing.expectEqual(1, node.child_node_count);
|
||||||
try testing.expectEqual(0, node.children.len);
|
try testing.expectEqual(1, node.children.len);
|
||||||
|
try testing.expectEqual(1, node.children[0].id);
|
||||||
try testing.expectEqual(null, node.document_url);
|
try testing.expectEqual(null, node.document_url);
|
||||||
try testing.expectEqual(null, node.base_url);
|
try testing.expectEqual(null, node.base_url);
|
||||||
try testing.expectEqual("", node.xml_version);
|
try testing.expectEqual("", node.xml_version);
|
||||||
@@ -310,20 +365,21 @@ test "CDP Node: Registry register" {
|
|||||||
{
|
{
|
||||||
const n = (try doc.querySelector("p")).?;
|
const n = (try doc.querySelector("p")).?;
|
||||||
const node = try registry.register(n);
|
const node = try registry.register(n);
|
||||||
const n1b = registry.lookup_by_id.get(1).?;
|
const n1b = registry.lookup_by_id.get(2).?;
|
||||||
const n1c = registry.lookup_by_node.get(node._node).?;
|
const n1c = registry.lookup_by_node.get(node._node).?;
|
||||||
try testing.expectEqual(node, n1b);
|
try testing.expectEqual(node, n1b);
|
||||||
try testing.expectEqual(node, n1c);
|
try testing.expectEqual(node, n1c);
|
||||||
|
|
||||||
try testing.expectEqual(1, node.id);
|
try testing.expectEqual(2, node.id);
|
||||||
try testing.expectEqual(null, node.parent_id);
|
try testing.expectEqual(null, node.parent_id);
|
||||||
try testing.expectEqual(1, node.node_type);
|
try testing.expectEqual(1, node.node_type);
|
||||||
try testing.expectEqual(1, node.backend_node_id);
|
try testing.expectEqual(2, node.backend_node_id);
|
||||||
try testing.expectEqual("P", node.node_name);
|
try testing.expectEqual("P", node.node_name);
|
||||||
try testing.expectEqual("p", node.local_name);
|
try testing.expectEqual("p", node.local_name);
|
||||||
try testing.expectEqual("", node.node_value);
|
try testing.expectEqual("", node.node_value);
|
||||||
try testing.expectEqual(1, node.child_node_count);
|
try testing.expectEqual(1, node.child_node_count);
|
||||||
try testing.expectEqual(0, node.children.len);
|
try testing.expectEqual(1, node.children.len);
|
||||||
|
try testing.expectEqual(3, node.children[0].id);
|
||||||
try testing.expectEqual(null, node.document_url);
|
try testing.expectEqual(null, node.document_url);
|
||||||
try testing.expectEqual(null, node.base_url);
|
try testing.expectEqual(null, node.base_url);
|
||||||
try testing.expectEqual("", node.xml_version);
|
try testing.expectEqual("", node.xml_version);
|
||||||
@@ -333,7 +389,7 @@ test "CDP Node: Registry register" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "CDP Node: search list" {
|
test "cdp Node: search list" {
|
||||||
var registry = Registry.init(testing.allocator);
|
var registry = Registry.init(testing.allocator);
|
||||||
defer registry.deinit();
|
defer registry.deinit();
|
||||||
|
|
||||||
@@ -383,3 +439,27 @@ test "CDP Node: search list" {
|
|||||||
try testing.expectEqual(2, registry.lookup_by_node.count());
|
try testing.expectEqual(2, registry.lookup_by_node.count());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "cdp Node: Writer" {
|
||||||
|
var registry = Registry.init(testing.allocator);
|
||||||
|
defer registry.deinit();
|
||||||
|
|
||||||
|
var doc = try testing.Document.init("<a id=a1></a><a id=a2></a>");
|
||||||
|
defer doc.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
|
const node = try registry.register(doc.asNode());
|
||||||
|
const json = try std.json.stringifyAlloc(testing.allocator, node.writer(.{}), .{});
|
||||||
|
defer testing.allocator.free(json);
|
||||||
|
|
||||||
|
try testing.expectJson(.{ .nodeId = 0, .backendNodeId = 0, .nodeType = 9, .nodeName = "#document", .localName = "", .nodeValue = "", .documentURL = null, .baseURL = null, .xmlVersion = "", .isScrollable = false, .compatibilityMode = "NoQuirksMode", .childNodeCount = 1, .children = &.{.{ .nodeId = 1, .backendNodeId = 1, .nodeType = 1, .nodeName = "HTML", .localName = "html", .nodeValue = "", .childNodeCount = 2, .documentURL = null, .baseURL = null, .xmlVersion = "", .compatibilityMode = "NoQuirksMode", .isScrollable = false }} }, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const node = registry.lookup_by_id.get(1).?;
|
||||||
|
const json = try std.json.stringifyAlloc(testing.allocator, node.writer(.{}), .{});
|
||||||
|
defer testing.allocator.free(json);
|
||||||
|
|
||||||
|
try testing.expectJson(.{ .nodeId = 1, .backendNodeId = 1, .nodeType = 1, .nodeName = "HTML", .localName = "html", .nodeValue = "", .childNodeCount = 2, .documentURL = null, .baseURL = null, .xmlVersion = "", .compatibilityMode = "NoQuirksMode", .isScrollable = false, .children = &.{ .{ .nodeId = 2, .backendNodeId = 2, .nodeType = 1, .nodeName = "HEAD", .localName = "head", .nodeValue = "", .childNodeCount = 0, .documentURL = null, .baseURL = null, .xmlVersion = "", .compatibilityMode = "NoQuirksMode", .isScrollable = false }, .{ .nodeId = 3, .backendNodeId = 3, .nodeType = 1, .nodeName = "BODY", .localName = "body", .nodeValue = "", .childNodeCount = 2, .documentURL = null, .baseURL = null, .xmlVersion = "", .compatibilityMode = "NoQuirksMode", .isScrollable = false } } }, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,9 +51,7 @@ fn getDocument(cmd: anytype) !void {
|
|||||||
const doc = page.doc orelse return error.DocumentNotLoaded;
|
const doc = page.doc orelse return error.DocumentNotLoaded;
|
||||||
|
|
||||||
const node = try bc.node_registry.register(parser.documentToNode(doc));
|
const node = try bc.node_registry.register(parser.documentToNode(doc));
|
||||||
return cmd.sendResult(.{
|
return cmd.sendResult(.{ .root = node.writer(.{}) }, .{});
|
||||||
.root = node,
|
|
||||||
}, .{});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-performSearch
|
// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-performSearch
|
||||||
@@ -118,6 +116,7 @@ fn getSearchResults(cmd: anytype) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
|
|
||||||
test "cdp.dom: getSearchResults unknown search id" {
|
test "cdp.dom: getSearchResults unknown search id" {
|
||||||
var ctx = testing.context();
|
var ctx = testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
@@ -149,7 +148,7 @@ test "cdp.dom: search flow" {
|
|||||||
.method = "DOM.getSearchResults",
|
.method = "DOM.getSearchResults",
|
||||||
.params = .{ .searchId = "0", .fromIndex = 0, .toIndex = 2 },
|
.params = .{ .searchId = "0", .fromIndex = 0, .toIndex = 2 },
|
||||||
});
|
});
|
||||||
try ctx.expectSentResult(.{ .nodeIds = &.{ 0, 1 } }, .{ .id = 13 });
|
try ctx.expectSentResult(.{ .nodeIds = &.{ 0, 2 } }, .{ .id = 13 });
|
||||||
|
|
||||||
// different fromIndex
|
// different fromIndex
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
@@ -157,7 +156,7 @@ test "cdp.dom: search flow" {
|
|||||||
.method = "DOM.getSearchResults",
|
.method = "DOM.getSearchResults",
|
||||||
.params = .{ .searchId = "0", .fromIndex = 1, .toIndex = 2 },
|
.params = .{ .searchId = "0", .fromIndex = 1, .toIndex = 2 },
|
||||||
});
|
});
|
||||||
try ctx.expectSentResult(.{ .nodeIds = &.{1} }, .{ .id = 14 });
|
try ctx.expectSentResult(.{ .nodeIds = &.{2} }, .{ .id = 14 });
|
||||||
|
|
||||||
// different toIndex
|
// different toIndex
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ const main = @import("cdp.zig");
|
|||||||
const parser = @import("netsurf");
|
const parser = @import("netsurf");
|
||||||
const App = @import("../app.zig").App;
|
const App = @import("../app.zig").App;
|
||||||
|
|
||||||
pub const allocator = @import("../testing.zig").allocator;
|
const base = @import("../testing.zig");
|
||||||
|
pub const allocator = base.allocator;
|
||||||
pub const expectEqual = @import("../testing.zig").expectEqual;
|
pub const expectJson = base.expectJson;
|
||||||
pub const expectError = @import("../testing.zig").expectError;
|
pub const expectEqual = base.expectEqual;
|
||||||
pub const expectEqualSlices = @import("../testing.zig").expectEqualSlices;
|
pub const expectError = base.expectError;
|
||||||
|
pub const expectEqualSlices = base.expectEqualSlices;
|
||||||
|
|
||||||
pub const Document = @import("../testing.zig").Document;
|
pub const Document = @import("../testing.zig").Document;
|
||||||
|
|
||||||
@@ -310,47 +311,5 @@ pub fn context() TestContext {
|
|||||||
fn compareExpectedToSent(expected: []const u8, actual: json.Value) !bool {
|
fn compareExpectedToSent(expected: []const u8, actual: json.Value) !bool {
|
||||||
const expected_value = try std.json.parseFromSlice(json.Value, std.testing.allocator, expected, .{});
|
const expected_value = try std.json.parseFromSlice(json.Value, std.testing.allocator, expected, .{});
|
||||||
defer expected_value.deinit();
|
defer expected_value.deinit();
|
||||||
return compareJsonValues(expected_value.value, actual);
|
return base.isEqualJson(expected_value.value, actual);
|
||||||
}
|
|
||||||
|
|
||||||
fn compareJsonValues(a: std.json.Value, b: std.json.Value) bool {
|
|
||||||
if (!std.mem.eql(u8, @tagName(a), @tagName(b))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (a) {
|
|
||||||
.null => return true,
|
|
||||||
.bool => return a.bool == b.bool,
|
|
||||||
.integer => return a.integer == b.integer,
|
|
||||||
.float => return a.float == b.float,
|
|
||||||
.number_string => return std.mem.eql(u8, a.number_string, b.number_string),
|
|
||||||
.string => return std.mem.eql(u8, a.string, b.string),
|
|
||||||
.array => {
|
|
||||||
const a_len = a.array.items.len;
|
|
||||||
const b_len = b.array.items.len;
|
|
||||||
if (a_len != b_len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (a.array.items, b.array.items) |a_item, b_item| {
|
|
||||||
if (compareJsonValues(a_item, b_item) == false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.object => {
|
|
||||||
var it = a.object.iterator();
|
|
||||||
while (it.next()) |entry| {
|
|
||||||
const key = entry.key_ptr.*;
|
|
||||||
if (b.object.get(key)) |b_item| {
|
|
||||||
if (compareJsonValues(entry.value_ptr.*, b_item) == false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,15 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const parser = @import("netsurf");
|
|
||||||
|
|
||||||
|
const parser = @import("netsurf");
|
||||||
pub const allocator = std.testing.allocator;
|
pub const allocator = std.testing.allocator;
|
||||||
pub const expectError = std.testing.expectError;
|
pub const expectError = std.testing.expectError;
|
||||||
pub const expectString = std.testing.expectEqualStrings;
|
pub const expectString = std.testing.expectEqualStrings;
|
||||||
pub const expectEqualSlices = std.testing.expectEqualSlices;
|
pub const expectEqualSlices = std.testing.expectEqualSlices;
|
||||||
|
|
||||||
const App = @import("app.zig").App;
|
const App = @import("app.zig").App;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
// Merged std.testing.expectEqual and std.testing.expectString
|
// Merged std.testing.expectEqual and std.testing.expectString
|
||||||
// can be useful when testing fields of an anytype an you don't know
|
// can be useful when testing fields of an anytype an you don't know
|
||||||
@@ -217,12 +218,93 @@ pub const Document = struct {
|
|||||||
|
|
||||||
pub fn querySelectorAll(self: *Document, selector: []const u8) ![]const *parser.Node {
|
pub fn querySelectorAll(self: *Document, selector: []const u8) ![]const *parser.Node {
|
||||||
const css = @import("dom/css.zig");
|
const css = @import("dom/css.zig");
|
||||||
const node_list = try css.querySelectorAll(self.arena.allocator(), parser.documentToNode(self.doc), selector);
|
const node_list = try css.querySelectorAll(self.arena.allocator(), self.asNode(), selector);
|
||||||
return node_list.nodes.items;
|
return node_list.nodes.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn querySelector(self: *Document, selector: []const u8) !?*parser.Node {
|
pub fn querySelector(self: *Document, selector: []const u8) !?*parser.Node {
|
||||||
const css = @import("dom/css.zig");
|
const css = @import("dom/css.zig");
|
||||||
return css.querySelector(self.arena.allocator(), parser.documentToNode(self.doc), selector);
|
return css.querySelector(self.arena.allocator(), self.asNode(), selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn asNode(self: *const Document) *parser.Node {
|
||||||
|
return parser.documentToNode(self.doc);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn expectJson(a: anytype, b: anytype) !void {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
const aa = arena.allocator();
|
||||||
|
|
||||||
|
const a_value = try convertToJson(aa, a);
|
||||||
|
const b_value = try convertToJson(aa, b);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
const a_json = std.json.stringifyAlloc(aa, a_value, .{ .whitespace = .indent_2 }) catch unreachable;
|
||||||
|
const b_json = std.json.stringifyAlloc(aa, b_value, .{ .whitespace = .indent_2 }) catch unreachable;
|
||||||
|
std.debug.print("== Expected ==\n{s}\n\n== Actual ==\n{s}", .{ a_json, b_json });
|
||||||
|
}
|
||||||
|
|
||||||
|
try expectJsonValue(a_value, b_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isEqualJson(a: anytype, b: anytype) !bool {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
const aa = arena.allocator();
|
||||||
|
const a_value = try convertToJson(aa, a);
|
||||||
|
const b_value = try convertToJson(aa, b);
|
||||||
|
expectJsonValue(a_value, b_value) catch return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convertToJson(arena: Allocator, value: anytype) !std.json.Value {
|
||||||
|
const T = @TypeOf(value);
|
||||||
|
if (T == std.json.Value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var str: []const u8 = undefined;
|
||||||
|
if (T == []u8 or T == []const u8 or comptime isStringArray(T)) {
|
||||||
|
str = value;
|
||||||
|
} else {
|
||||||
|
str = try std.json.stringifyAlloc(arena, value, .{});
|
||||||
|
}
|
||||||
|
return std.json.parseFromSliceLeaky(std.json.Value, arena, str, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectJsonValue(a: std.json.Value, b: std.json.Value) !void {
|
||||||
|
try expectEqual(@tagName(a), @tagName(b));
|
||||||
|
|
||||||
|
// at this point, we know that if a is an int, b must also be an int
|
||||||
|
switch (a) {
|
||||||
|
.null => return,
|
||||||
|
.bool => try expectEqual(a.bool, b.bool),
|
||||||
|
.integer => try expectEqual(a.integer, b.integer),
|
||||||
|
.float => try expectEqual(a.float, b.float),
|
||||||
|
.number_string => try expectEqual(a.number_string, b.number_string),
|
||||||
|
.string => try expectEqual(a.string, b.string),
|
||||||
|
.array => {
|
||||||
|
const a_len = a.array.items.len;
|
||||||
|
const b_len = b.array.items.len;
|
||||||
|
try expectEqual(a_len, b_len);
|
||||||
|
for (a.array.items, b.array.items) |a_item, b_item| {
|
||||||
|
try expectJsonValue(a_item, b_item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.object => {
|
||||||
|
var it = a.object.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
const key = entry.key_ptr.*;
|
||||||
|
if (b.object.get(key)) |b_item| {
|
||||||
|
try expectJsonValue(entry.value_ptr.*, b_item);
|
||||||
|
} else {
|
||||||
|
return error.MissingKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user