diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index aa20be606..8ecad1a02 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -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); diff --git a/src/browser/page.zig b/src/browser/page.zig index fbd19a3e3..3d4902d6d 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -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, diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index 1e5fde5b7..57e1b6600 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -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; @@ -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 { @@ -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(); diff --git a/src/lib.zig b/src/lib.zig index c6a5de0ba..83f23ad38 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -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 @@ -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; } @@ -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;