Skip to content

Commit 595f1a5

Browse files
committed
implements DOM.attributeModified
1 parent c305858 commit 595f1a5

File tree

4 files changed

+94
-2
lines changed

4 files changed

+94
-2
lines changed

src/browser/netsurf.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,14 @@ pub fn mutationEventPrevValue(evt: *MutationEvent) !?[]const u8 {
579579
return strToData(s.?);
580580
}
581581

582+
pub fn mutationEventNewValue(evt: *MutationEvent) !?[]const u8 {
583+
var s: ?*String = null;
584+
const err = c._dom_mutation_event_get_new_value(evt, &s);
585+
try DOMErr(err);
586+
if (s == null) return null;
587+
return strToData(s.?);
588+
}
589+
582590
pub fn mutationEventRelatedNode(evt: *MutationEvent) !?*Node {
583591
var n: NodeExternal = undefined;
584592
const err = c._dom_mutation_event_get_related_node(evt, &n);

src/browser/page.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ pub const Page = struct {
9292

9393
notified_network_idle: IdleNotification = .init,
9494
notified_network_almost_idle: IdleNotification = .init,
95+
auto_enable_dom_monitoring: bool = false,
9596

9697
const Mode = union(enum) {
9798
pre: void,

src/cdp/domains/page.zig

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const std = @import("std");
2020
const URL = @import("../../url.zig").URL;
2121
const Page = @import("../../browser/page.zig").Page;
2222
const Notification = @import("../../notification.zig").Notification;
23+
const log = @import("../../log.zig");
24+
const parser = @import("../../browser/netsurf.zig");
2325

2426
const Allocator = std.mem.Allocator;
2527

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

356358
// frameStoppedLoading
357-
return cdp.sendEvent("Page.frameStoppedLoading", .{
359+
try cdp.sendEvent("Page.frameStoppedLoading", .{
358360
.frameId = target_id,
359361
}, .{ .session_id = session_id });
362+
363+
// Auto-enable DOM monitoring after page navigation is complete
364+
const page = bc.session.currentPage() orelse return;
365+
if (page.auto_enable_dom_monitoring) {
366+
std.debug.print("dom monitoring enabled\n", .{});
367+
autoEnableDOMMonitoring(bc, page) catch |err| {
368+
log.warn(.cdp, "autoenable DOM monitor fail", .{.err = err});
369+
};
370+
} else {
371+
std.debug.print("nalok\n", .{});
372+
}
360373
}
361374

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

405+
// Auto-enable DOM monitoring when pages are created/navigated
406+
fn autoEnableDOMMonitoring(bc: anytype, page: anytype) !void {
407+
const BC = @TypeOf(bc.*);
408+
const CDP = @TypeOf(bc.cdp.*);
409+
410+
// Check if we have a session (required for sending events)
411+
const session_id = bc.session_id orelse return;
412+
413+
// Create a CDP-specific mutation observer that sends DOM.attributeModified events
414+
const arena = page.arena;
415+
const doc = parser.documentHTMLToDocument(page.window.document);
416+
const Observer = struct {
417+
bc: *BC,
418+
cdp: *CDP,
419+
session_id: []const u8,
420+
event_node: parser.EventNode,
421+
422+
const Self = @This();
423+
424+
fn handle(en: *parser.EventNode, event: *parser.Event) void {
425+
const self: *Self = @fieldParentPtr("event_node", en);
426+
self._handle(event) catch |err| {
427+
log.err(.cdp, "DOM Observer handle error", .{.err = err});
428+
};
429+
}
430+
431+
fn _handle(self: *Self, event: *parser.Event) !void {
432+
const mutation_event = parser.eventToMutationEvent(event);
433+
const attribute_name = parser.mutationEventAttributeName(mutation_event) catch return;
434+
const new_value = parser.mutationEventNewValue(mutation_event) catch null;
435+
436+
// Get the target node and register it to get a CDP node ID
437+
const event_target = parser.eventTarget(event) orelse return;
438+
const target_node = parser.eventTargetToNode(event_target);
439+
const node = try self.bc.node_registry.register(target_node);
440+
441+
// Send CDP DOM.attributeModified event
442+
try self.cdp.sendEvent("DOM.attributeModified", .{
443+
.nodeId = node.id,
444+
.name = attribute_name,
445+
.value = new_value,
446+
}, .{
447+
.session_id = self.session_id,
448+
});
449+
}
450+
};
451+
452+
const cdp_observer = try arena.create(Observer);
453+
cdp_observer.* = .{
454+
.bc = bc,
455+
.cdp = bc.cdp,
456+
.session_id = session_id,
457+
.event_node = .{ .id = @intFromPtr(cdp_observer), .func = Observer.handle },
458+
};
459+
460+
// Add event listener to document element to catch all attribute changes
461+
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return;
462+
463+
_ = try parser.eventTargetAddEventListener(
464+
parser.toEventTarget(parser.Element, document_element),
465+
"DOMAttrModified",
466+
&cdp_observer.event_node,
467+
true, // use capture to catch all events
468+
);
469+
}
470+
392471
const testing = @import("../testing.zig");
393472
test "cdp.page: getFrameTree" {
394473
var ctx = testing.context();

src/lib.zig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const BrowserContext = @import("cdp/cdp.zig").BrowserContext;
1010
export fn lightpanda_app_init() ?*anyopaque {
1111
const allocator = std.heap.c_allocator;
1212

13+
@import("log.zig").opts.level = .warn;
14+
1315
const app = App.init(allocator, .{
1416
// .run_mode = .serve,
1517
// .tls_verify_host = false
@@ -55,6 +57,7 @@ export fn lightpanda_browser_new_session(browser_ptr: *anyopaque) ?*anyopaque {
5557
export fn lightpanda_session_create_page(session_ptr: *anyopaque) ?*anyopaque {
5658
const session: *Session = @ptrCast(@alignCast(session_ptr));
5759
const page = session.createPage() catch return null;
60+
page.auto_enable_dom_monitoring = true;
5861
return page;
5962
}
6063

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

128-
_ = cdp.browser_context.?.session.createPage() catch return null;
131+
const page = cdp.browser_context.?.session.createPage() catch return null;
132+
page.auto_enable_dom_monitoring = true;
129133

130134
const target_id = cdp.target_id_gen.next();
131135
cdp.browser_context.?.target_id = target_id;

0 commit comments

Comments
 (0)