Skip to content
Open
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
8 changes: 8 additions & 0 deletions src/browser/netsurf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,14 @@ pub fn mutationEventPrevValue(evt: *MutationEvent) !?[]const u8 {
return strToData(s.?);
}

pub fn mutationEventNewValue(evt: *MutationEvent) !?[]const u8 {
var s: ?*String = null;
const err = c._dom_mutation_event_get_new_value(evt, &s);
try DOMErr(err);
if (s == null) return null;
return strToData(s.?);
}

pub fn mutationEventRelatedNode(evt: *MutationEvent) !?*Node {
var n: NodeExternal = undefined;
const err = c._dom_mutation_event_get_related_node(evt, &n);
Expand Down
1 change: 1 addition & 0 deletions src/browser/page.zig
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ pub const Page = struct {

notified_network_idle: IdleNotification = .init,
notified_network_almost_idle: IdleNotification = .init,
auto_enable_dom_monitoring: bool = false,

const Mode = union(enum) {
pre: void,
Expand Down
81 changes: 80 additions & 1 deletion src/cdp/domains/page.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const std = @import("std");
const URL = @import("../../url.zig").URL;
const Page = @import("../../browser/page.zig").Page;
const Notification = @import("../../notification.zig").Notification;
const log = @import("../../log.zig");
const parser = @import("../../browser/netsurf.zig");

const Allocator = std.mem.Allocator;

Expand Down Expand Up @@ -354,9 +356,20 @@ pub fn pageNavigated(bc: anytype, event: *const Notification.PageNavigated) !voi
}

// frameStoppedLoading
return cdp.sendEvent("Page.frameStoppedLoading", .{
try cdp.sendEvent("Page.frameStoppedLoading", .{
.frameId = target_id,
}, .{ .session_id = session_id });

// Auto-enable DOM monitoring after page navigation is complete
const page = bc.session.currentPage() orelse return;
if (page.auto_enable_dom_monitoring) {
std.debug.print("dom monitoring enabled\n", .{});
autoEnableDOMMonitoring(bc, page) catch |err| {
log.warn(.cdp, "autoenable DOM monitor fail", .{.err = err});
};
} else {
std.debug.print("nalok\n", .{});
}
}

pub fn pageNetworkIdle(bc: anytype, event: *const Notification.PageNetworkIdle) !void {
Expand Down Expand Up @@ -389,6 +402,72 @@ const LifecycleEvent = struct {
timestamp: u32,
};

// Auto-enable DOM monitoring when pages are created/navigated
fn autoEnableDOMMonitoring(bc: anytype, page: anytype) !void {
const BC = @TypeOf(bc.*);
const CDP = @TypeOf(bc.cdp.*);

// Check if we have a session (required for sending events)
const session_id = bc.session_id orelse return;

// Create a CDP-specific mutation observer that sends DOM.attributeModified events
const arena = page.arena;
const doc = parser.documentHTMLToDocument(page.window.document);
const Observer = struct {
bc: *BC,
cdp: *CDP,
session_id: []const u8,
event_node: parser.EventNode,

const Self = @This();

fn handle(en: *parser.EventNode, event: *parser.Event) void {
const self: *Self = @fieldParentPtr("event_node", en);
self._handle(event) catch |err| {
log.err(.cdp, "DOM Observer handle error", .{.err = err});
};
}

fn _handle(self: *Self, event: *parser.Event) !void {
const mutation_event = parser.eventToMutationEvent(event);
const attribute_name = parser.mutationEventAttributeName(mutation_event) catch return;
const new_value = parser.mutationEventNewValue(mutation_event) catch null;

// Get the target node and register it to get a CDP node ID
const event_target = parser.eventTarget(event) orelse return;
const target_node = parser.eventTargetToNode(event_target);
const node = try self.bc.node_registry.register(target_node);

// Send CDP DOM.attributeModified event
try self.cdp.sendEvent("DOM.attributeModified", .{
.nodeId = node.id,
.name = attribute_name,
.value = new_value,
}, .{
.session_id = self.session_id,
});
}
};

const cdp_observer = try arena.create(Observer);
cdp_observer.* = .{
.bc = bc,
.cdp = bc.cdp,
.session_id = session_id,
.event_node = .{ .id = @intFromPtr(cdp_observer), .func = Observer.handle },
};

// Add event listener to document element to catch all attribute changes
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return;

_ = try parser.eventTargetAddEventListener(
parser.toEventTarget(parser.Element, document_element),
"DOMAttrModified",
&cdp_observer.event_node,
true, // use capture to catch all events
);
}

const testing = @import("../testing.zig");
test "cdp.page: getFrameTree" {
var ctx = testing.context();
Expand Down
6 changes: 5 additions & 1 deletion src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const BrowserContext = @import("cdp/cdp.zig").BrowserContext;
export fn lightpanda_app_init() ?*anyopaque {
const allocator = std.heap.c_allocator;

@import("log.zig").opts.level = .warn;

const app = App.init(allocator, .{
// .run_mode = .serve,
// .tls_verify_host = false
Expand Down Expand Up @@ -55,6 +57,7 @@ export fn lightpanda_browser_new_session(browser_ptr: *anyopaque) ?*anyopaque {
export fn lightpanda_session_create_page(session_ptr: *anyopaque) ?*anyopaque {
const session: *Session = @ptrCast(@alignCast(session_ptr));
const page = session.createPage() catch return null;
page.auto_enable_dom_monitoring = true;
return page;
}

Expand Down Expand Up @@ -125,7 +128,8 @@ export fn lightpanda_cdp_create_browser_context(cdp_ptr: *anyopaque) ?[*:0]const
const cdp: *CDP = @ptrCast(@alignCast(cdp_ptr));
const id = cdp.createBrowserContext() catch return null;

_ = cdp.browser_context.?.session.createPage() catch return null;
const page = cdp.browser_context.?.session.createPage() catch return null;
page.auto_enable_dom_monitoring = true;

const target_id = cdp.target_id_gen.next();
cdp.browser_context.?.target_id = target_id;
Expand Down
Loading