Skip to content

Commit 7897e69

Browse files
committed
Introduce more general notification capabilities
Replaces the existing, very specialized Notification with something more general. Currently, the existing page_navigate and page_navigated have been migrated. Telemetry's page navigation event now also hooks into these events to generate the telemetry record.
1 parent 332508f commit 7897e69

File tree

6 files changed

+316
-47
lines changed

6 files changed

+316
-47
lines changed

src/app.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const js = @import("runtime/js.zig");
55
const Loop = @import("runtime/loop.zig").Loop;
66
const HttpClient = @import("http/client.zig").Client;
77
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
8+
const Notification = @import("notification.zig").Notification;
89

910
const log = std.log.scoped(.app);
1011

@@ -17,6 +18,7 @@ pub const App = struct {
1718
telemetry: Telemetry,
1819
http_client: HttpClient,
1920
app_dir_path: ?[]const u8,
21+
notification: *Notification,
2022

2123
pub const RunMode = enum {
2224
help,
@@ -41,19 +43,24 @@ pub const App = struct {
4143
loop.* = try Loop.init(allocator);
4244
errdefer loop.deinit();
4345

46+
const notification = try Notification.init(allocator, null);
47+
errdefer notification.deinit();
48+
4449
const app_dir_path = getAndMakeAppDir(allocator);
4550

4651
app.* = .{
4752
.loop = loop,
4853
.allocator = allocator,
4954
.telemetry = undefined,
5055
.app_dir_path = app_dir_path,
56+
.notification = notification,
5157
.http_client = try HttpClient.init(allocator, 5, .{
5258
.tls_verify_host = config.tls_verify_host,
5359
}),
5460
.config = config,
5561
};
5662
app.telemetry = Telemetry.init(app, config.run_mode);
63+
try app.telemetry.register(app.notification);
5764

5865
return app;
5966
}
@@ -67,6 +74,7 @@ pub const App = struct {
6774
self.loop.deinit();
6875
allocator.destroy(self.loop);
6976
self.http_client.deinit();
77+
self.notification.deinit();
7078
allocator.destroy(self);
7179
}
7280
};

src/browser/browser.zig

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ pub const Browser = struct {
5757
allocator: Allocator,
5858
http_client: *http.Client,
5959
session_pool: SessionPool,
60+
notification: *Notification,
6061
page_arena: std.heap.ArenaAllocator,
6162
pub const EnvType = Env;
6263

@@ -70,11 +71,15 @@ pub const Browser = struct {
7071
});
7172
errdefer env.deinit();
7273

74+
const notification = try Notification.init(allocator, app.notification);
75+
errdefer notification.deinit();
76+
7377
return .{
7478
.app = app,
7579
.env = env,
7680
.session = null,
7781
.allocator = allocator,
82+
.notification = notification,
7883
.http_client = &app.http_client,
7984
.session_pool = SessionPool.init(allocator),
8085
.page_arena = std.heap.ArenaAllocator.init(allocator),
@@ -86,6 +91,7 @@ pub const Browser = struct {
8691
self.env.deinit();
8792
self.session_pool.deinit();
8893
self.page_arena.deinit();
94+
self.notification.deinit();
8995
}
9096

9197
pub fn newSession(self: *Browser, ctx: anytype) !*Session {
@@ -143,32 +149,14 @@ pub const Session = struct {
143149
page: ?Page = null,
144150
http_client: *http.Client,
145151

146-
// recipient of notification, passed as the first parameter to notify
147-
notify_ctx: *anyopaque,
148-
notify_func: *const fn (ctx: *anyopaque, notification: *const Notification) anyerror!void,
149-
150152
fn init(self: *Session, browser: *Browser, ctx: anytype) !void {
151-
const ContextT = @TypeOf(ctx);
152-
const ContextStruct = switch (@typeInfo(ContextT)) {
153-
.@"struct" => ContextT,
154-
.pointer => |ptr| ptr.child,
155-
.void => NoopContext,
156-
else => @compileError("invalid context type"),
157-
};
158-
159-
// ctx can be void, to be able to store it in our *anyopaque field, we
160-
// need to play a little game.
161-
const any_ctx: *anyopaque = if (@TypeOf(ctx) == void) @constCast(@ptrCast(&{})) else ctx;
162-
163153
const app = browser.app;
164154
const allocator = app.allocator;
165155
self.* = .{
166156
.app = app,
167157
.aux_data = null,
168158
.browser = browser,
169-
.notify_ctx = any_ctx,
170159
.inspector = undefined,
171-
.notify_func = ContextStruct.notify,
172160
.http_client = browser.http_client,
173161
.executor = undefined,
174162
.storage_shed = storage.Shed.init(allocator),
@@ -313,12 +301,6 @@ pub const Session = struct {
313301
log.debug("inspector context created", .{});
314302
self.inspector.contextCreated(self.executor, "", (page.origin() catch "://") orelse "://", self.aux_data, true);
315303
}
316-
317-
fn notify(self: *const Session, notification: *const Notification) void {
318-
self.notify_func(self.notify_ctx, notification) catch |err| {
319-
log.err("notify {}: {}", .{ std.meta.activeTag(notification.*), err });
320-
};
321-
}
322304
};
323305

324306
// Page navigates to an url.
@@ -389,8 +371,6 @@ pub const Page = struct {
389371
}
390372

391373
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
392-
// - aux_data: extra data forwarded to the Inspector
393-
// see Inspector.contextCreated
394374
pub fn navigate(self: *Page, request_url: URL, opts: NavigateOpts) !void {
395375
const arena = self.arena;
396376
const session = self.session;
@@ -408,20 +388,15 @@ pub const Page = struct {
408388
self.url = request_url;
409389
var url = &self.url.?;
410390

411-
session.app.telemetry.record(.{ .navigate = .{
412-
.proxy = false,
413-
.tls = std.ascii.eqlIgnoreCase(url.scheme(), "https"),
414-
} });
415-
416391
// load the data
417392
var request = try self.newHTTPRequest(.GET, url, .{ .navigation = true });
418393
defer request.deinit();
419394

420-
session.notify(&.{ .page_navigate = .{
395+
session.browser.notification.dispatch(.page_navigate, &.{
421396
.url = url,
422397
.reason = opts.reason,
423398
.timestamp = timestamp(),
424-
} });
399+
});
425400

426401
var response = try request.sendSync(.{});
427402

@@ -458,10 +433,10 @@ pub const Page = struct {
458433
self.raw_data = arr.items;
459434
}
460435

461-
session.notify(&.{ .page_navigated = .{
436+
session.browser.notification.dispatch(.page_navigated, &.{
462437
.url = url,
463438
.timestamp = timestamp(),
464-
} });
439+
});
465440
}
466441

467442
// https://html.spec.whatwg.org/#read-html

src/cdp/cdp.zig

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,10 @@ pub fn BrowserContext(comptime CDP_T: type) type {
336336
.isolated_world = null,
337337
};
338338
self.node_search_list = Node.Search.List.init(allocator, &self.node_registry);
339+
errdefer self.deinit();
340+
341+
try cdp.browser.notification.register(.page_navigate, self, onPageNavigate);
342+
try cdp.browser.notification.register(.page_navigated, self, onPageNavigated);
339343
}
340344

341345
pub fn deinit(self: *Self) void {
@@ -346,6 +350,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
346350
}
347351
self.node_registry.deinit();
348352
self.node_search_list.deinit();
353+
self.cdp.browser.notification.unregisterAll(self);
349354
}
350355

351356
pub fn reset(self: *Self) void {
@@ -387,13 +392,14 @@ pub fn BrowserContext(comptime CDP_T: type) type {
387392
return if (page.url) |*url| url.raw else null;
388393
}
389394

390-
pub fn notify(ctx: *anyopaque, notification: *const Notification) !void {
395+
pub fn onPageNavigate(ctx: *anyopaque, data: *const Notification.PageNavigate) !void {
391396
const self: *Self = @alignCast(@ptrCast(ctx));
397+
return @import("domains/page.zig").pageNavigate(self, data);
398+
}
392399

393-
switch (notification.*) {
394-
.page_navigate => |*pn| return @import("domains/page.zig").pageNavigate(self, pn),
395-
.page_navigated => |*pn| return @import("domains/page.zig").pageNavigated(self, pn),
396-
}
400+
pub fn onPageNavigated(ctx: *anyopaque, data: *const Notification.PageNavigated) !void {
401+
const self: *Self = @alignCast(@ptrCast(ctx));
402+
return @import("domains/page.zig").pageNavigated(self, data);
397403
}
398404

399405
pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void {

src/cdp/testing.zig

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const Testing = @This();
2525
const main = @import("cdp.zig");
2626
const URL = @import("../url.zig").URL;
2727
const App = @import("../app.zig").App;
28+
const Notification = @import("../notification.zig").Notification;
29+
2830
const parser = @import("../browser/netsurf.zig");
2931

3032
const base = @import("../testing.zig");
@@ -37,20 +39,26 @@ pub const expectEqualSlices = base.expectEqualSlices;
3739
pub const Document = @import("../testing.zig").Document;
3840

3941
const Browser = struct {
42+
env: Env,
4043
session: ?*Session = null,
4144
arena: std.heap.ArenaAllocator,
42-
env: Env,
45+
notification: *Notification,
4346
pub const EnvType = Env;
4447

4548
pub fn init(app: *App) !Browser {
49+
const notification = try Notification.init(app.allocator, null);
50+
errdefer notification.deinit();
51+
4652
return .{
53+
.env = .{},
54+
.notification = notification,
4755
.arena = std.heap.ArenaAllocator.init(app.allocator),
48-
.env = Env{},
4956
};
5057
}
5158

5259
pub fn deinit(self: *Browser) void {
5360
self.arena.deinit();
61+
self.notification.deinit();
5462
}
5563

5664
pub fn newSession(self: *Browser, ctx: anytype) !*Session {
@@ -273,7 +281,7 @@ const TestContext = struct {
273281
self.client = Client.init(self.arena.allocator());
274282
// Don't use the arena here. We want to detect leaks in CDP.
275283
// The arena is only for test-specific stuff
276-
self.cdp_ = try TestCDP.init(self.app, &self.client.?);
284+
self.cdp_ = TestCDP.init(self.app, &self.client.?) catch unreachable;
277285
}
278286
return &self.cdp_.?;
279287
}

0 commit comments

Comments
 (0)