Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/browser/browser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ pub const Browser = struct {
self.session.deinit();
try Session.init(&self.session, alloc, loop, uri);
}

pub fn currentPage(self: *Browser) ?*Page {
if (self.session.page == null) return null;

return &self.session.page.?;
}
};

// Session is like a browser's tab.
Expand Down Expand Up @@ -147,7 +153,7 @@ pub const Session = struct {
}

fn deinit(self: *Session) void {
if (self.page) |*p| p.end();
if (self.page) |*p| p.deinit();

if (self.inspector) |inspector| {
inspector.deinit(self.alloc);
Expand Down Expand Up @@ -259,6 +265,7 @@ pub const Page = struct {
self.session.window.replaceLocation(&self.location) catch |e| {
log.err("reset window location: {any}", .{e});
};
self.doc = null;

// clear netsurf memory arena.
parser.deinit();
Expand All @@ -267,6 +274,7 @@ pub const Page = struct {
}

pub fn deinit(self: *Page) void {
self.end();
self.arena.deinit();
self.session.page = null;
}
Expand Down
28 changes: 28 additions & 0 deletions src/cdp/cdp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const IncomingMessage = @import("msg.zig").IncomingMessage;
const Input = @import("msg.zig").Input;
const inspector = @import("inspector.zig").inspector;
const dom = @import("dom.zig").dom;
const cdpdom = @import("dom.zig");
const css = @import("css.zig").css;
const security = @import("security.zig").security;

Expand Down Expand Up @@ -129,6 +130,33 @@ pub const State = struct {
loaderID: []const u8 = LoaderID,

page_life_cycle_events: bool = false, // TODO; Target based value

// DOM
nodelist: cdpdom.NodeList,
nodesearchlist: cdpdom.NodeSearchList,

pub fn init(alloc: std.mem.Allocator) State {
return .{
.nodelist = cdpdom.NodeList.init(alloc),
.nodesearchlist = cdpdom.NodeSearchList.init(alloc),
};
}

pub fn deinit(self: *State) void {
self.nodelist.deinit();

// deinit all node searches.
for (self.nodesearchlist.items) |*s| s.deinit();
self.nodesearchlist.deinit();
}

pub fn reset(self: *State) void {
self.nodelist.reset();

// deinit all node searches.
for (self.nodesearchlist.items) |*s| s.deinit();
self.nodesearchlist.clearAndFree();
}
};

// Utils
Expand Down
283 changes: 283 additions & 0 deletions src/cdp/dom.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,18 @@ const cdp = @import("cdp.zig");
const result = cdp.result;
const IncomingMessage = @import("msg.zig").IncomingMessage;
const Input = @import("msg.zig").Input;
const css = @import("../dom/css.zig");

const parser = @import("netsurf");

const log = std.log.scoped(.cdp);

const Methods = enum {
enable,
getDocument,
performSearch,
getSearchResults,
discardSearchResults,
};

pub fn dom(
Expand All @@ -42,6 +49,10 @@ pub fn dom(

return switch (method) {
.enable => enable(alloc, msg, ctx),
.getDocument => getDocument(alloc, msg, ctx),
.performSearch => performSearch(alloc, msg, ctx),
.getSearchResults => getSearchResults(alloc, msg, ctx),
.discardSearchResults => discardSearchResults(alloc, msg, ctx),
};
}

Expand All @@ -57,3 +68,275 @@ fn enable(

return result(alloc, input.id, null, null, input.sessionId);
}

// NodeList references tree nodes with an array id.
pub const NodeList = struct {
coll: List,

const List = std.ArrayList(*parser.Node);

pub fn init(alloc: std.mem.Allocator) NodeList {
return .{
.coll = List.init(alloc),
};
}

pub fn deinit(self: *NodeList) void {
self.coll.deinit();
}

pub fn reset(self: *NodeList) void {
self.coll.clearAndFree();
}

pub fn set(self: *NodeList, node: *parser.Node) !NodeId {
for (self.coll.items, 0..) |n, i| {
if (n == node) return @intCast(i);
}

try self.coll.append(node);
return @intCast(self.coll.items.len);
}
};

const NodeId = u32;

const Node = struct {
nodeId: NodeId,
parentId: ?NodeId = null,
backendNodeId: NodeId,
nodeType: u32,
nodeName: []const u8 = "",
localName: []const u8 = "",
nodeValue: []const u8 = "",
childNodeCount: ?u32 = null,
children: ?[]const Node = null,
documentURL: ?[]const u8 = null,
baseURL: ?[]const u8 = null,
xmlVersion: []const u8 = "",
compatibilityMode: []const u8 = "NoQuirksMode",
isScrollable: bool = false,

fn init(n: *parser.Node, nlist: *NodeList) !Node {
const id = try nlist.set(n);
return .{
.nodeId = id,
.backendNodeId = id,
.nodeType = @intFromEnum(try parser.nodeType(n)),
.nodeName = try parser.nodeName(n),
.localName = try parser.nodeLocalName(n),
.nodeValue = try parser.nodeValue(n) orelse "",
};
}

fn initChildren(
self: *Node,
alloc: std.mem.Allocator,
n: *parser.Node,
nlist: *NodeList,
) !std.ArrayList(Node) {
const children = try parser.nodeGetChildNodes(n);
const ln = try parser.nodeListLength(children);
self.childNodeCount = ln;

var list = try std.ArrayList(Node).initCapacity(alloc, ln);

var i: u32 = 0;
while (i < ln) {
defer i += 1;
const child = try parser.nodeListItem(children, i) orelse continue;
try list.append(try Node.init(child, nlist));
}

self.children = list.items;

return list;
}
};

// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getDocument
fn getDocument(
alloc: std.mem.Allocator,
msg: *IncomingMessage,
ctx: *Ctx,
) ![]const u8 {
// input
const Params = struct {
depth: ?u32 = null,
pierce: ?bool = null,
};
const input = try Input(Params).get(alloc, msg);
defer input.deinit();
std.debug.assert(input.sessionId != null);
log.debug("Req > id {d}, method {s}", .{ input.id, "DOM.getDocument" });

// retrieve the root node
const page = ctx.browser.currentPage() orelse return error.NoPage;

if (page.doc == null) return error.NoDocument;

const node = parser.documentToNode(page.doc.?);
var n = try Node.init(node, &ctx.state.nodelist);
var list = try n.initChildren(alloc, node, &ctx.state.nodelist);
defer list.deinit();

// output
const Resp = struct {
root: Node,
};
const resp: Resp = .{
.root = n,
};

const res = try result(alloc, input.id, Resp, resp, input.sessionId);
try ctx.send(res);

return "";
}

pub const NodeSearch = struct {
coll: List,
name: []u8,
alloc: std.mem.Allocator,

var count: u8 = 0;

const List = std.ArrayListUnmanaged(NodeId);

pub fn initCapacity(alloc: std.mem.Allocator, ln: usize) !NodeSearch {
count += 1;

return .{
.alloc = alloc,
.coll = try List.initCapacity(alloc, ln),
.name = try std.fmt.allocPrint(alloc, "{d}", .{count}),
};
}

pub fn deinit(self: *NodeSearch) void {
self.coll.deinit(self.alloc);
self.alloc.free(self.name);
}

pub fn append(self: *NodeSearch, id: NodeId) !void {
try self.coll.append(self.alloc, id);
}
};
pub const NodeSearchList = std.ArrayList(NodeSearch);

// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-performSearch
fn performSearch(
alloc: std.mem.Allocator,
msg: *IncomingMessage,
ctx: *Ctx,
) ![]const u8 {
// input
const Params = struct {
query: []const u8,
includeUserAgentShadowDOM: ?bool = null,
};
const input = try Input(Params).get(alloc, msg);
defer input.deinit();
std.debug.assert(input.sessionId != null);
log.debug("Req > id {d}, method {s}", .{ input.id, "DOM.performSearch" });

// retrieve the root node
const page = ctx.browser.currentPage() orelse return error.NoPage;

if (page.doc == null) return error.NoDocument;

const list = try css.querySelectorAll(alloc, parser.documentToNode(page.doc.?), input.params.query);
const ln = list.nodes.items.len;
var ns = try NodeSearch.initCapacity(alloc, ln);

for (list.nodes.items) |n| {
const id = try ctx.state.nodelist.set(n);
try ns.append(id);
}

try ctx.state.nodesearchlist.append(ns);

// output
const Resp = struct {
searchId: []const u8,
resultCount: u32,
};
const resp: Resp = .{
.searchId = ns.name,
.resultCount = @intCast(ln),
};

return result(alloc, input.id, Resp, resp, input.sessionId);
}

// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-discardSearchResults
fn discardSearchResults(
alloc: std.mem.Allocator,
msg: *IncomingMessage,
ctx: *Ctx,
) ![]const u8 {
// input
const Params = struct {
searchId: []const u8,
};
const input = try Input(Params).get(alloc, msg);
defer input.deinit();
std.debug.assert(input.sessionId != null);
log.debug("Req > id {d}, method {s}", .{ input.id, "DOM.discardSearchResults" });

// retrieve the search from context
for (ctx.state.nodesearchlist.items, 0..) |*s, i| {
if (!std.mem.eql(u8, s.name, input.params.searchId)) continue;

s.deinit();
_ = ctx.state.nodesearchlist.swapRemove(i);
break;
}

return result(alloc, input.id, null, null, input.sessionId);
}

// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getSearchResults
fn getSearchResults(
alloc: std.mem.Allocator,
msg: *IncomingMessage,
ctx: *Ctx,
) ![]const u8 {
// input
const Params = struct {
searchId: []const u8,
fromIndex: u32,
toIndex: u32,
};
const input = try Input(Params).get(alloc, msg);
defer input.deinit();
std.debug.assert(input.sessionId != null);
log.debug("Req > id {d}, method {s}", .{ input.id, "DOM.getSearchResults" });

if (input.params.fromIndex >= input.params.toIndex) return error.BadIndices;

// retrieve the search from context
var ns: ?*const NodeSearch = undefined;
for (ctx.state.nodesearchlist.items) |s| {
if (!std.mem.eql(u8, s.name, input.params.searchId)) continue;

ns = &s;
break;
}

if (ns == null) return error.searchResultNotFound;
const items = ns.?.coll.items;

if (input.params.fromIndex >= items.len) return error.BadFromIndex;
if (input.params.toIndex > items.len) return error.BadToIndex;

// output
const Resp = struct {
nodeIds: []NodeId,
};
const resp: Resp = .{
.nodeIds = ns.?.coll.items[input.params.fromIndex..input.params.toIndex],
};

return result(alloc, input.id, Resp, resp, input.sessionId);
}
Loading