@@ -40,6 +40,7 @@ const Walker = @import("../dom/walker.zig").WalkerDepthFirst;
4040
4141const URL = @import ("../url.zig" ).URL ;
4242const storage = @import ("../storage/storage.zig" );
43+ const Notification = @import ("../notification.zig" ).Notification ;
4344
4445const http = @import ("../http/client.zig" );
4546const UserContext = @import ("../user_context.zig" ).UserContext ;
@@ -137,14 +138,32 @@ pub const Session = struct {
137138
138139 jstypes : [Types .len ]usize = undefined ,
139140
141+ // recipient of notification, passed as the first parameter to notify
142+ notify_ctx : * anyopaque ,
143+ notify_func : * const fn (ctx : * anyopaque , notification : * const Notification ) anyerror ! void ,
144+
140145 fn init (self : * Session , browser : * Browser , ctx : anytype ) ! void {
146+ const ContextT = @TypeOf (ctx );
147+ const ContextStruct = switch (@typeInfo (ContextT )) {
148+ .@"struct" = > ContextT ,
149+ .pointer = > | ptr | ptr .child ,
150+ .void = > NoopContext ,
151+ else = > @compileError ("invalid context type" ),
152+ };
153+
154+ // ctx can be void, to be able to store it in our *anyopaque field, we
155+ // need to play a little game.
156+ const any_ctx : * anyopaque = if (@TypeOf (ctx ) == void ) @constCast (@ptrCast (&{})) else ctx ;
157+
141158 const app = browser .app ;
142159 const allocator = app .allocator ;
143160 self .* = .{
144161 .app = app ,
145162 .env = undefined ,
146163 .browser = browser ,
164+ .notify_ctx = any_ctx ,
147165 .inspector = undefined ,
166+ .notify_func = ContextStruct .notify ,
148167 .http_client = browser .http_client ,
149168 .storage_shed = storage .Shed .init (allocator ),
150169 .arena = std .heap .ArenaAllocator .init (allocator ),
@@ -157,24 +176,15 @@ pub const Session = struct {
157176 errdefer self .env .deinit ();
158177 try self .env .load (& self .jstypes );
159178
160- const ContextT = @TypeOf (ctx );
161- const InspectorContainer = switch (@typeInfo (ContextT )) {
162- .@"struct" = > ContextT ,
163- .pointer = > | ptr | ptr .child ,
164- .void = > NoopInspector ,
165- else = > @compileError ("invalid context type" ),
166- };
167-
168179 // const ctx_opaque = @as(*anyopaque, @ptrCast(ctx));
169180 self .inspector = try jsruntime .Inspector .init (
170181 arena ,
171182 & self .env ,
172- if ( @TypeOf ( ctx ) == void ) @constCast ( @ptrCast (&{})) else ctx ,
173- InspectorContainer .onInspectorResponse ,
174- InspectorContainer .onInspectorEvent ,
183+ any_ctx ,
184+ ContextStruct .onInspectorResponse ,
185+ ContextStruct .onInspectorEvent ,
175186 );
176187 self .env .setInspector (self .inspector );
177-
178188 try self .env .setModuleLoadFn (self , Session .fetchModule );
179189 }
180190
@@ -269,6 +279,12 @@ pub const Session = struct {
269279 log .debug ("inspector context created" , .{});
270280 self .inspector .contextCreated (& self .env , "" , (page .origin () catch "://" ) orelse "://" , aux_data );
271281 }
282+
283+ fn notify (self : * const Session , notification : * const Notification ) void {
284+ self .notify_func (self .notify_ctx , notification ) catch | err | {
285+ log .err ("notify {}: {}" , .{ std .meta .activeTag (notification .* ), err });
286+ };
287+ }
272288};
273289
274290// Page navigates to an url.
@@ -344,37 +360,41 @@ pub const Page = struct {
344360 // spec reference: https://html.spec.whatwg.org/#document-lifecycle
345361 // - aux_data: extra data forwarded to the Inspector
346362 // see Inspector.contextCreated
347- pub fn navigate (self : * Page , url_string : [] const u8 , aux_data : ? []const u8 ) ! void {
363+ pub fn navigate (self : * Page , request_url : URL , aux_data : ? []const u8 ) ! void {
348364 const arena = self .arena ;
365+ const session = self .session ;
349366
350- log .debug ("starting GET {s}" , .{url_string });
367+ log .debug ("starting GET {s}" , .{request_url });
351368
352369 // if the url is about:blank, nothing to do.
353- if (std .mem .eql (u8 , "about:blank" , url_string )) {
370+ if (std .mem .eql (u8 , "about:blank" , request_url . raw )) {
354371 return ;
355372 }
356373
357- // we don't clone url_string , because we're going to replace self.url
374+ // we don't clone url , because we're going to replace self.url
358375 // later in this function, with the final request url (since we might
359376 // redirect)
360- self .url = try URL .parse (url_string , "https" );
361- self .session .app .telemetry .record (.{ .navigate = .{
377+ self .url = request_url ;
378+ var url = & self .url .? ;
379+
380+ session .app .telemetry .record (.{ .navigate = .{
362381 .proxy = false ,
363- .tls = std .ascii .eqlIgnoreCase (self . url .? .scheme (), "https" ),
382+ .tls = std .ascii .eqlIgnoreCase (url .scheme (), "https" ),
364383 } });
365384
366385 // load the data
367- var request = try self .newHTTPRequest (.GET , & self . url .? , .{ .navigation = true });
386+ var request = try self .newHTTPRequest (.GET , url , .{ .navigation = true });
368387 defer request .deinit ();
369388
389+ session .notify (&.{ .page_navigate = .{ .url = url , .timestamp = timestamp () } });
370390 var response = try request .sendSync (.{});
371391
372392 // would be different than self.url in the case of a redirect
373393 self .url = try URL .fromURI (arena , request .uri );
394+ url = & self .url .? ;
374395
375- const url = & self .url .? ;
376396 const header = response .header ;
377- try self . session .cookie_jar .populateFromResponse (& url .uri , & header );
397+ try session .cookie_jar .populateFromResponse (& url .uri , & header );
378398
379399 // TODO handle fragment in url.
380400 try self .session .window .replaceLocation (.{ .url = try url .toWebApi (arena ) });
@@ -407,6 +427,8 @@ pub const Page = struct {
407427 // save the body into the page.
408428 self .raw_data = arr .items ;
409429 }
430+
431+ session .notify (&.{ .page_navigated = .{ .url = url , .timestamp = timestamp () } });
410432 }
411433
412434 pub const ClickResult = union (enum ) {
@@ -835,7 +857,13 @@ const FlatRenderer = struct {
835857 }
836858};
837859
838- const NoopInspector = struct {
860+ const NoopContext = struct {
839861 pub fn onInspectorResponse (_ : * anyopaque , _ : u32 , _ : []const u8 ) void {}
840862 pub fn onInspectorEvent (_ : * anyopaque , _ : []const u8 ) void {}
863+ pub fn notify (_ : * anyopaque , _ : * const Notification ) ! void {}
841864};
865+
866+ fn timestamp () u32 {
867+ const ts = std .posix .clock_gettime (std .posix .CLOCK .MONOTONIC ) catch unreachable ;
868+ return @intCast (ts .sec );
869+ }
0 commit comments