@@ -133,6 +133,10 @@ pub const Session = struct {
133133 storage_shed : storage.Shed ,
134134 cookie_jar : storage.CookieJar ,
135135
136+ // arbitrary that we pass to the inspector, which the inspector will include
137+ // in any response/event that it emits.
138+ aux_data : ? []const u8 = null ,
139+
136140 page : ? Page = null ,
137141 http_client : * http.Client ,
138142
@@ -160,6 +164,7 @@ pub const Session = struct {
160164 self .* = .{
161165 .app = app ,
162166 .env = undefined ,
167+ .aux_data = null ,
163168 .browser = browser ,
164169 .notify_ctx = any_ctx ,
165170 .inspector = undefined ,
@@ -247,8 +252,12 @@ pub const Session = struct {
247252 // TODO: change to 'env' when https://github.com/lightpanda-io/zig-js-runtime/pull/285 lands
248253 try polyfill .load (self .arena .allocator (), & self .env );
249254
255+ if (aux_data ) | ad | {
256+ self .aux_data = try self .arena .allocator ().dupe (u8 , ad );
257+ }
258+
250259 // inspector
251- self .contextCreated (page , aux_data );
260+ self .contextCreated (page );
252261
253262 return page ;
254263 }
@@ -276,9 +285,28 @@ pub const Session = struct {
276285 return &(self .page orelse return null );
277286 }
278287
279- fn contextCreated (self : * Session , page : * Page , aux_data : ? []const u8 ) void {
288+ fn pageNavigate (self : * Session , url_string : []const u8 ) ! void {
289+ // currently, this is only called from the page, so let's hope
290+ // it isn't null!
291+ std .debug .assert (self .page != null );
292+
293+ // can't use the page arena, because we're about to reset it
294+ // and don't want to use the session's arena, because that'll start to
295+ // look like a leak if we navigate from page to page a lot.
296+ var buf : [1024 ]u8 = undefined ;
297+ var fba = std .heap .FixedBufferAllocator .init (& buf );
298+ const url = try self .page .? .url .? .resolve (fba .allocator (), url_string );
299+
300+ self .removePage ();
301+ var page = try self .createPage (null );
302+ return page .navigate (url , .{
303+ .reason = .anchor ,
304+ });
305+ }
306+
307+ fn contextCreated (self : * Session , page : * Page ) void {
280308 log .debug ("inspector context created" , .{});
281- self .inspector .contextCreated (& self .env , "" , (page .origin () catch "://" ) orelse "://" , aux_data );
309+ self .inspector .contextCreated (& self .env , "" , (page .origin () catch "://" ) orelse "://" , self . aux_data );
282310 }
283311
284312 fn notify (self : * const Session , notification : * const Notification ) void {
@@ -361,7 +389,7 @@ pub const Page = struct {
361389 // spec reference: https://html.spec.whatwg.org/#document-lifecycle
362390 // - aux_data: extra data forwarded to the Inspector
363391 // see Inspector.contextCreated
364- pub fn navigate (self : * Page , request_url : URL , aux_data : ? [] const u8 ) ! void {
392+ pub fn navigate (self : * Page , request_url : URL , opts : NavigateOpts ) ! void {
365393 const arena = self .arena ;
366394 const session = self .session ;
367395
@@ -387,7 +415,12 @@ pub const Page = struct {
387415 var request = try self .newHTTPRequest (.GET , url , .{ .navigation = true });
388416 defer request .deinit ();
389417
390- session .notify (&.{ .page_navigate = .{ .url = url , .timestamp = timestamp () } });
418+ session .notify (&.{ .page_navigate = .{
419+ .url = url ,
420+ .reason = opts .reason ,
421+ .timestamp = timestamp (),
422+ } });
423+
391424 var response = try request .sendSync (.{});
392425
393426 // would be different than self.url in the case of a redirect
@@ -418,7 +451,7 @@ pub const Page = struct {
418451 defer mime .deinit ();
419452
420453 if (mime .isHTML ()) {
421- try self .loadHTMLDoc (& response , mime .charset orelse "utf-8" , aux_data );
454+ try self .loadHTMLDoc (& response , mime .charset orelse "utf-8" );
422455 } else {
423456 log .info ("non-HTML document: {s}" , .{ct });
424457 var arr : std .ArrayListUnmanaged (u8 ) = .{};
@@ -429,44 +462,14 @@ pub const Page = struct {
429462 self .raw_data = arr .items ;
430463 }
431464
432- session .notify (&.{ .page_navigated = .{ .url = url , .timestamp = timestamp () } });
433- }
434-
435- pub const ClickResult = union (enum ) {
436- navigate : std.Uri ,
437- };
438-
439- pub const MouseEvent = struct {
440- x : i32 ,
441- y : i32 ,
442- type : Type ,
443-
444- const Type = enum {
445- pressed ,
446- released ,
447- };
448- };
449-
450- pub fn mouseEvent (self : * Page , me : MouseEvent ) ! void {
451- if (me .type != .pressed ) {
452- return ;
453- }
454-
455- const element = self .renderer .getElementAtPosition (me .x , me .y ) orelse return ;
456-
457- const event = try parser .mouseEventCreate ();
458- defer parser .mouseEventDestroy (event );
459- try parser .mouseEventInit (event , "click" , .{
460- .bubbles = true ,
461- .cancelable = true ,
462- .x = me .x ,
463- .y = me .y ,
464- });
465- _ = try parser .elementDispatchEvent (element , @ptrCast (event ));
465+ session .notify (&.{ .page_navigated = .{
466+ .url = url ,
467+ .timestamp = timestamp (),
468+ } });
466469 }
467470
468471 // https://html.spec.whatwg.org/#read-html
469- fn loadHTMLDoc (self : * Page , reader : anytype , charset : []const u8 , aux_data : ? [] const u8 ) ! void {
472+ fn loadHTMLDoc (self : * Page , reader : anytype , charset : []const u8 ) ! void {
470473 const arena = self .arena ;
471474
472475 // start netsurf memory arena.
@@ -508,7 +511,7 @@ pub const Page = struct {
508511 // https://html.spec.whatwg.org/#read-html
509512
510513 // inspector
511- session .contextCreated (self , aux_data );
514+ session .contextCreated (self );
512515
513516 // replace the user context document with the new one.
514517 try session .env .setUserContext (.{
@@ -740,6 +743,35 @@ pub const Page = struct {
740743 return request ;
741744 }
742745
746+ pub const MouseEvent = struct {
747+ x : i32 ,
748+ y : i32 ,
749+ type : Type ,
750+
751+ const Type = enum {
752+ pressed ,
753+ released ,
754+ };
755+ };
756+
757+ pub fn mouseEvent (self : * Page , me : MouseEvent ) ! void {
758+ if (me .type != .pressed ) {
759+ return ;
760+ }
761+
762+ const element = self .renderer .getElementAtPosition (me .x , me .y ) orelse return ;
763+
764+ const event = try parser .mouseEventCreate ();
765+ defer parser .mouseEventDestroy (event );
766+ try parser .mouseEventInit (event , "click" , .{
767+ .bubbles = true ,
768+ .cancelable = true ,
769+ .x = me .x ,
770+ .y = me .y ,
771+ });
772+ _ = try parser .elementDispatchEvent (element , @ptrCast (event ));
773+ }
774+
743775 fn windowClicked (ctx : * anyopaque , event : * parser.Event ) void {
744776 const self : * Page = @alignCast (@ptrCast (ctx ));
745777 self ._windowClicked (event ) catch | err | {
@@ -748,19 +780,22 @@ pub const Page = struct {
748780 }
749781
750782 fn _windowClicked (self : * Page , event : * parser.Event ) ! void {
751- _ = self ;
752-
753783 const target = (try parser .eventTarget (event )) orelse return ;
754784
755785 const node = parser .eventTargetToNode (target );
756786 if (try parser .nodeType (node ) != .element ) {
757787 return ;
758788 }
759789
760- const element : * parser.ElementHTML = @ptrCast (node );
761- const tag_name = try parser .elementHTMLGetTagType (element );
762- // TODO https://github.com/lightpanda-io/browser/pull/501
763- _ = tag_name ;
790+ const html_element : * parser.ElementHTML = @ptrCast (node );
791+ switch (try parser .elementHTMLGetTagType (html_element )) {
792+ .a = > {
793+ const element : * parser.Element = @ptrCast (node );
794+ const href = (try parser .elementGetAttribute (element , "href" )) orelse return ;
795+ return self .session .pageNavigate (href );
796+ },
797+ else = > {},
798+ }
764799 }
765800
766801 const Script = struct {
@@ -827,8 +862,17 @@ pub const Page = struct {
827862 };
828863};
829864
865+ pub const NavigateReason = enum {
866+ anchor ,
867+ address_bar ,
868+ };
869+
870+ const NavigateOpts = struct {
871+ reason : NavigateReason = .address_bar ,
872+ };
873+
830874// provide very poor abstration to the rest of the code. In theory, we can change
831- // the FlatRendere to a different implementation, and it'll all just work.
875+ // the FlatRenderer to a different implementation, and it'll all just work.
832876pub const Renderer = FlatRenderer ;
833877
834878// This "renderer" positions elements in a single row in an unspecified order.
0 commit comments