From 0d8dd84df5bb1083fb007d38403107625832ac13 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 9 Dec 2025 10:07:32 +0100 Subject: [PATCH 1/2] support url on createTarget and send lifecycle events Support url parameter on createTarget. we now navigate on createTarget to dispatch events correctly, even in case of about:blank --- src/Notification.zig | 4 ++-- src/browser/Page.zig | 4 ++-- src/cdp/domains/page.zig | 34 ++++++++++++++++++++++++++++++---- src/cdp/domains/target.zig | 12 ++++++++---- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/Notification.zig b/src/Notification.zig index f535abd92..dea1d5499 100644 --- a/src/Notification.zig +++ b/src/Notification.zig @@ -109,13 +109,13 @@ pub const PageRemove = struct {}; pub const PageNavigate = struct { timestamp: u64, - url: []const u8, + url: [:0]const u8, opts: Page.NavigateOpts, }; pub const PageNavigated = struct { timestamp: u64, - url: []const u8, + url: [:0]const u8, }; pub const PageNetworkIdle = struct { diff --git a/src/browser/Page.zig b/src/browser/Page.zig index af235a5bd..96b9f406f 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -63,7 +63,7 @@ const timestamp = @import("../datetime.zig").timestamp; const milliTimestamp = @import("../datetime.zig").milliTimestamp; pub threadlocal var current: *Page = undefined; -var default_url = URL{ ._raw = "about/blank" }; +var default_url = URL{ ._raw = "about:blank" }; pub var default_location: Location = Location{ ._url = &default_url }; pub const BUF_SIZE = 1024; @@ -201,7 +201,7 @@ fn reset(self: *Page, comptime initializing: bool) !void { self._factory = Factory.init(self); self.version = 0; - self.url = "about/blank"; + self.url = "about:blank"; self.document = (try self._factory.document(Node.Document.HTMLDocument{ ._proto = undefined })).asDocument(); diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index 06b9a8610..1be6f3c91 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -19,6 +19,7 @@ const std = @import("std"); const Page = @import("../../browser/Page.zig"); const Notification = @import("../../Notification.zig"); +const timestampF = @import("../../datetime.zig").timestamp; const Allocator = std.mem.Allocator; @@ -47,7 +48,7 @@ pub fn processMessage(cmd: anytype) !void { const Frame = struct { id: []const u8, loaderId: []const u8, - url: []const u8, + url: [:0]const u8, domainAndRegistry: []const u8 = "", securityOrigin: []const u8, mimeType: []const u8 = "text/html", @@ -82,11 +83,36 @@ fn setLifecycleEventsEnabled(cmd: anytype) !void { })) orelse return error.InvalidParams; const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; - if (params.enabled) { - try bc.lifecycleEventsEnable(); - } else { + + if (params.enabled == false) { bc.lifecycleEventsDisable(); + return cmd.sendResult(null, .{}); } + + // Enable lifecycle events. + try bc.lifecycleEventsEnable(); + + // When we enable lifecycle events, we must dispatch events for all + // attached targets. + const page = bc.session.currentPage() orelse return error.PageNotLoaded; + + if (page._load_state == .complete) { + const now = timestampF(.monotonic); + const http_client = page._session.browser.http_client; + + try sendPageLifecycle(bc, "DOMContentLoaded", now); + try sendPageLifecycle(bc, "load", now); + + const http_active = http_client.active; + const total_network_activity = http_active + http_client.intercepted; + if (page._notified_network_almost_idle.check(total_network_activity <= 2)) { + try sendPageLifecycle(bc, "networkAlmostIdle", now); + } + if (page._notified_network_idle.check(total_network_activity == 0)) { + try sendPageLifecycle(bc, "networkIdle", now); + } + } + return cmd.sendResult(null, .{}); } diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index 3ea78b718..b59285afb 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -109,7 +109,7 @@ fn disposeBrowserContext(cmd: anytype) !void { fn createTarget(cmd: anytype) !void { const params = (try cmd.params(struct { - // url: []const u8, + url: [:0]const u8 = "about:blank", // width: ?u64 = null, // height: ?u64 = null, browserContextId: ?[]const u8 = null, @@ -168,7 +168,7 @@ fn createTarget(cmd: anytype) !void { .targetInfo = TargetInfo{ .attached = false, .targetId = target_id, - .title = "about:blank", + .title = params.url, .browserContextId = bc.id, .url = "about:blank", }, @@ -179,6 +179,10 @@ fn createTarget(cmd: anytype) !void { try doAttachtoTarget(cmd, target_id); } + try page.navigate(params.url, .{ + .reason = .address_bar, + }); + try cmd.sendResult(.{ .targetId = target_id, }, .{}); @@ -518,7 +522,7 @@ test "cdp.target: createTarget" { { var ctx = testing.context(); defer ctx.deinit(); - try ctx.processMessage(.{ .id = 10, .method = "Target.createTarget", .params = .{ .url = "about/blank" } }); + try ctx.processMessage(.{ .id = 10, .method = "Target.createTarget", .params = .{ .url = "about:blank" } }); // should create a browser context const bc = ctx.cdp().browser_context.?; @@ -530,7 +534,7 @@ test "cdp.target: createTarget" { defer ctx.deinit(); // active auto attach to get the Target.attachedToTarget event. try ctx.processMessage(.{ .id = 9, .method = "Target.setAutoAttach", .params = .{ .autoAttach = true, .waitForDebuggerOnStart = false } }); - try ctx.processMessage(.{ .id = 10, .method = "Target.createTarget", .params = .{ .url = "about/blank" } }); + try ctx.processMessage(.{ .id = 10, .method = "Target.createTarget", .params = .{ .url = "about:blank" } }); // should create a browser context const bc = ctx.cdp().browser_context.?; From 37697155820f30d2ee9047dc723c8adc7c9d3b37 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 9 Dec 2025 12:24:01 +0100 Subject: [PATCH 2/2] Page.reset msut create context and window once --- src/browser/Page.zig | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 96b9f406f..48daeeb54 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -205,15 +205,21 @@ fn reset(self: *Page, comptime initializing: bool) !void { self.document = (try self._factory.document(Node.Document.HTMLDocument{ ._proto = undefined })).asDocument(); - const storage_bucket = try self._factory.create(storage.Bucket{}); - self.window = try self._factory.eventTarget(Window{ - ._document = self.document, - ._storage_bucket = storage_bucket, - ._history = History.init(self), - ._performance = Performance.init(), - ._proto = undefined, - ._location = &default_location, - }); + if (comptime initializing == true) { + const storage_bucket = try self._factory.create(storage.Bucket{}); + self.window = try self._factory.eventTarget(Window{ + ._document = self.document, + ._storage_bucket = storage_bucket, + ._history = History.init(self), + ._performance = Performance.init(), + ._proto = undefined, + ._location = &default_location, + }); + } else { + self.window._document = self.document; + self.window._location = &default_location; + // TODO reset _custom_elements? + } self._parse_state = .pre; self._load_state = .parsing; @@ -224,8 +230,10 @@ fn reset(self: *Page, comptime initializing: bool) !void { self._script_manager = ScriptManager.init(self); errdefer self._script_manager.deinit(); - self.js = try self._session.executor.createContext(self, true, JS.GlobalMissingCallback.init(&self._polyfill_loader)); - errdefer self.js.deinit(); + if (comptime initializing == true) { + self.js = try self._session.executor.createContext(self, true, JS.GlobalMissingCallback.init(&self._polyfill_loader)); + errdefer self.js.deinit(); + } self._element_styles = .{}; self._element_datasets = .{};