From b55b9bba0ad81df643dd4eb26577f88aeecfc2c9 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Tue, 14 Oct 2025 18:48:48 -0700 Subject: [PATCH] functional NavigationCurrentEntryChangeEvent --- src/browser/dom/event_target.zig | 6 + src/browser/events/event.zig | 2 +- src/browser/html/History.zig | 5 +- src/browser/html/html.zig | 1 - src/browser/html/location.zig | 4 + src/browser/html/window.zig | 2 +- src/browser/js/types.zig | 1 + .../{html => navigation}/Navigation.zig | 215 ++--------------- .../navigation/NavigationEventTarget.zig | 56 +++++ src/browser/navigation/navigation.zig | 219 ++++++++++++++++++ src/browser/netsurf.zig | 1 + src/browser/page.zig | 27 ++- src/browser/session.zig | 4 +- src/tests/html/navigation/navigation.html | 2 +- .../navigation_currententrychange.html | 15 ++ 15 files changed, 351 insertions(+), 209 deletions(-) rename src/browser/{html => navigation}/Navigation.zig (57%) create mode 100644 src/browser/navigation/NavigationEventTarget.zig create mode 100644 src/browser/navigation/navigation.zig create mode 100644 src/tests/html/navigation/navigation_currententrychange.html diff --git a/src/browser/dom/event_target.zig b/src/browser/dom/event_target.zig index 5ee77751..32d7a14f 100644 --- a/src/browser/dom/event_target.zig +++ b/src/browser/dom/event_target.zig @@ -34,6 +34,7 @@ pub const Union = union(enum) { screen_orientation: *@import("../html/screen.zig").ScreenOrientation, performance: *@import("performance.zig").Performance, media_query_list: *@import("../html/media_query_list.zig").MediaQueryList, + navigation: *@import("../navigation/Navigation.zig"), }; // EventTarget implementation @@ -82,6 +83,11 @@ pub const EventTarget = struct { .media_query_list => { return .{ .media_query_list = @fieldParentPtr("base", @as(*parser.EventTargetTBase, @ptrCast(et))) }; }, + .navigation => { + const NavigationEventTarget = @import("../navigation/NavigationEventTarget.zig"); + const base: *NavigationEventTarget = @fieldParentPtr("base", @as(*parser.EventTargetTBase, @ptrCast(et))); + return .{ .navigation = @fieldParentPtr("proto", base) }; + }, } } diff --git a/src/browser/events/event.zig b/src/browser/events/event.zig index ba6b367d..4be76295 100644 --- a/src/browser/events/event.zig +++ b/src/browser/events/event.zig @@ -39,7 +39,7 @@ const ErrorEvent = @import("../html/error_event.zig").ErrorEvent; const MessageEvent = @import("../dom/MessageChannel.zig").MessageEvent; const PopStateEvent = @import("../html/History.zig").PopStateEvent; const CompositionEvent = @import("composition_event.zig").CompositionEvent; -const NavigationCurrentEntryChangeEvent = @import("../html/Navigation.zig").NavigationCurrentEntryChangeEvent; +const NavigationCurrentEntryChangeEvent = @import("../navigation/navigation.zig").NavigationCurrentEntryChangeEvent; // Event interfaces pub const Interfaces = .{ diff --git a/src/browser/html/History.zig b/src/browser/html/History.zig index 4412e887..aa491518 100644 --- a/src/browser/html/History.zig +++ b/src/browser/html/History.zig @@ -21,6 +21,7 @@ const log = @import("../../log.zig"); const js = @import("../js/js.zig"); const Page = @import("../page.zig").Page; +const Window = @import("window.zig").Window; // https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-history-interface const History = @This(); @@ -60,7 +61,7 @@ pub fn _pushState(_: *const History, state: js.Object, _: ?[]const u8, _url: ?[] const url = if (_url) |u| try arena.dupe(u8, u) else try arena.dupe(u8, page.url.raw); const json = state.toJson(arena) catch return error.DataClone; - _ = try page.session.navigation.pushEntry(url, json, page); + _ = try page.session.navigation.pushEntry(url, json, page, true); } pub fn _replaceState(_: *const History, state: js.Object, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void { @@ -163,7 +164,7 @@ pub const PopStateEvent = struct { }; _ = parser.eventTargetDispatchEvent( - @as(*parser.EventTarget, @ptrCast(&page.window)), + parser.toEventTarget(Window, &page.window), &evt.proto, ) catch |err| { log.err(.app, "dispatch popstate event error", .{ diff --git a/src/browser/html/html.zig b/src/browser/html/html.zig index d5aa81a1..ef8a99f7 100644 --- a/src/browser/html/html.zig +++ b/src/browser/html/html.zig @@ -34,7 +34,6 @@ pub const Interfaces = .{ Window, Navigator, History, - @import("Navigation.zig").Interfaces, Location, MediaQueryList, @import("DataSet.zig"), diff --git a/src/browser/html/location.zig b/src/browser/html/location.zig index 220b4974..abe097bc 100644 --- a/src/browser/html/location.zig +++ b/src/browser/html/location.zig @@ -73,6 +73,10 @@ pub const Location = struct { return self.url.get_origin(page); } + pub fn set_href(_: *Location, url: []const u8, page: *Page) !void { + return page.navigateFromWebAPI(url, .{ .reason = .script }, .{ .push = null }); + } + pub fn _assign(_: *const Location, url: []const u8, page: *Page) !void { return page.navigateFromWebAPI(url, .{ .reason = .script }, .{ .push = null }); } diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index e89ab203..8bbfe086 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -25,7 +25,7 @@ const Page = @import("../page.zig").Page; const Navigator = @import("navigator.zig").Navigator; const History = @import("History.zig"); -const Navigation = @import("Navigation.zig"); +const Navigation = @import("../navigation/Navigation.zig"); const Location = @import("location.zig").Location; const Crypto = @import("../crypto/crypto.zig").Crypto; const Console = @import("../console/console.zig").Console; diff --git a/src/browser/js/types.zig b/src/browser/js/types.zig index e0c192df..89322984 100644 --- a/src/browser/js/types.zig +++ b/src/browser/js/types.zig @@ -16,6 +16,7 @@ const Interfaces = generate.Tuple(.{ @import("../storage/storage.zig").Interfaces, @import("../url/url.zig").Interfaces, @import("../xhr/xhr.zig").Interfaces, + @import("../navigation/navigation.zig").Interfaces, @import("../xhr/form_data.zig").Interfaces, @import("../xhr/File.zig"), @import("../xmlserializer/xmlserializer.zig").Interfaces, diff --git a/src/browser/html/Navigation.zig b/src/browser/navigation/Navigation.zig similarity index 57% rename from src/browser/html/Navigation.zig rename to src/browser/navigation/Navigation.zig index defa50c2..71fbe9d8 100644 --- a/src/browser/html/Navigation.zig +++ b/src/browser/navigation/Navigation.zig @@ -32,122 +32,21 @@ const parser = @import("../netsurf.zig"); // https://developer.mozilla.org/en-US/docs/Web/API/Navigation const Navigation = @This(); -pub const Interfaces = .{ - Navigation, - NavigationActivation, - NavigationTransition, - NavigationHistoryEntry, -}; +const NavigationKind = @import("navigation.zig").NavigationKind; +const NavigationHistoryEntry = @import("navigation.zig").NavigationHistoryEntry; +const NavigationTransition = @import("navigation.zig").NavigationTransition; +const NavigationEventTarget = @import("NavigationEventTarget.zig"); -pub const NavigationType = enum { - pub const ENUM_JS_USE_TAG = true; +const NavigationCurrentEntryChangeEvent = @import("navigation.zig").NavigationCurrentEntryChangeEvent; - push, - replace, - traverse, - reload, -}; - -pub const NavigationKind = union(NavigationType) { - push: ?[]const u8, - replace, - traverse: usize, - reload, -}; - -pub const prototype = *EventTarget; -base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain }, +pub const prototype = *NavigationEventTarget; +proto: NavigationEventTarget = NavigationEventTarget{}, index: usize = 0, // Need to be stable pointers, because Events can reference entries. entries: std.ArrayListUnmanaged(*NavigationHistoryEntry) = .empty, next_entry_id: usize = 0, -oncurrententrychange_callback: ?js.Function = null, - -// https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry -const NavigationHistoryEntry = struct { - pub const prototype = *EventTarget; - base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain }, - - id: []const u8, - key: []const u8, - url: ?[]const u8, - state: ?[]const u8, - - pub fn get_id(self: *const NavigationHistoryEntry) []const u8 { - return self.id; - } - - pub fn get_index(self: *const NavigationHistoryEntry, page: *Page) i32 { - const navigation = page.session.navigation; - for (navigation.entries.items, 0..) |entry, i| { - if (std.mem.eql(u8, entry.id, self.id)) { - return @intCast(i); - } - } - - return -1; - } - - pub fn get_key(self: *const NavigationHistoryEntry) []const u8 { - return self.key; - } - - pub fn get_sameDocument(self: *const NavigationHistoryEntry, page: *Page) !bool { - const _url = self.url orelse return false; - const url = try URL.parse(_url, null); - return page.url.eqlDocument(&url, page.arena); - } - - pub fn get_url(self: *const NavigationHistoryEntry) ?[]const u8 { - return self.url; - } - - pub fn _getState(self: *const NavigationHistoryEntry, page: *Page) !?js.Value { - if (self.state) |state| { - return try js.Value.fromJson(page.js, state); - } else { - return null; - } - } -}; - -// https://developer.mozilla.org/en-US/docs/Web/API/NavigationActivation -const NavigationActivation = struct { - const NavigationActivationType = enum { - pub const ENUM_JS_USE_TAG = true; - - push, - reload, - replace, - traverse, - }; - - entry: NavigationHistoryEntry, - from: ?NavigationHistoryEntry = null, - type: NavigationActivationType, - - pub fn get_entry(self: *const NavigationActivation) NavigationHistoryEntry { - return self.entry; - } - - pub fn get_from(self: *const NavigationActivation) ?NavigationHistoryEntry { - return self.from; - } - - pub fn get_navigationType(self: *const NavigationActivation) NavigationActivationType { - return self.type; - } -}; - -// https://developer.mozilla.org/en-US/docs/Web/API/NavigationTransition -const NavigationTransition = struct { - finished: js.Promise, - from: NavigationHistoryEntry, - navigation_type: NavigationActivation.NavigationActivationType, -}; - pub fn get_canGoBack(self: *const Navigation) bool { return self.index > 0; } @@ -202,16 +101,6 @@ pub fn _forward(self: *Navigation, page: *Page) !NavigationReturn { return self.navigate(next_entry.url, .{ .traverse = new_index }, page); } -/// Returns `oncurrententrychange_callback`. -pub fn get_oncurrententrychange(self: *const Navigation) ?js.Function { - return self.oncurrententrychange_callback; -} - -/// Sets `oncurrententrychange_callback`. -pub fn set_oncurrententrychange(self: *Navigation, maybe_listener: ?EventHandler.Listener, page: *Page) !void { - try DirectEventHandler(Navigation, self, "currententrychange", maybe_listener, &self.oncurrententrychange_callback, page.arena); -} - // This is for after true navigation processing, where we need to ensure that our entries are up to date. // This is only really safe to run in the `pageDoneCallback` where we can guarantee that the URL and NavigationKind are correct. pub fn processNavigation(self: *Navigation, page: *Page) !void { @@ -227,18 +116,18 @@ pub fn processNavigation(self: *Navigation, page: *Page) !void { entry.state = null; }, .push => |state| { - _ = try self.pushEntry(url, state, page); + _ = try self.pushEntry(url, state, page, false); }, .traverse, .reload => {}, } } else { - _ = try self.pushEntry(url, null, page); + _ = try self.pushEntry(url, null, page, false); } } /// Pushes an entry into the Navigation stack WITHOUT actually navigating to it. /// For that, use `navigate`. -pub fn pushEntry(self: *Navigation, _url: ?[]const u8, state: ?[]const u8, page: *Page) !*NavigationHistoryEntry { +pub fn pushEntry(self: *Navigation, _url: ?[]const u8, state: ?[]const u8, page: *Page, dispatch: bool) !*NavigationHistoryEntry { const arena = page.session.arena; const url = if (_url) |u| try arena.dupe(u8, u) else null; @@ -267,7 +156,9 @@ pub fn pushEntry(self: *Navigation, _url: ?[]const u8, state: ?[]const u8, page: const previous = if (self.entries.items.len > 0) self.currentEntry() else null; try self.entries.append(arena, entry); if (previous) |prev| { - NavigationCurrentEntryChangeEvent.dispatch(prev, .push, page); + if (dispatch) { + NavigationCurrentEntryChangeEvent.dispatch(self, prev, .push); + } } self.index = index; @@ -312,11 +203,12 @@ pub fn navigate( .push => |state| { if (is_same_document) { page.url = new_url; + try committed.resolve({}); // todo: Fire navigate event try finished.resolve({}); - _ = try self.pushEntry(url, state, page); + _ = try self.pushEntry(url, state, page, true); } else { try page.navigateFromWebAPI(url, .{ .reason = .navigation }, kind); } @@ -365,7 +257,7 @@ pub fn _reload(self: *Navigation, _opts: ?ReloadOptions, page: *Page) !Navigatio if (opts.state) |state| { const previous = entry; entry.state = state.toJson(arena) catch return error.DataClone; - NavigationCurrentEntryChangeEvent.dispatch(previous, .reload, page); + NavigationCurrentEntryChangeEvent.dispatch(self, previous, .reload); } return self.navigate(entry.url, .reload, page); @@ -396,78 +288,5 @@ pub fn _updateCurrentEntry(self: *Navigation, options: UpdateCurrentEntryOptions const previous = self.currentEntry(); self.currentEntry().state = options.state.toJson(arena) catch return error.DataClone; - NavigationCurrentEntryChangeEvent.dispatch(previous, null, page); -} - -const Event = @import("../events/event.zig").Event; - -pub const NavigationCurrentEntryChangeEvent = struct { - pub const prototype = *Event; - pub const union_make_copy = true; - - pub const EventInit = struct { - from: *NavigationHistoryEntry, - navigation_type: ?NavigationType = null, - }; - - proto: parser.Event, - from: *NavigationHistoryEntry, - navigation_type: ?NavigationType, - - pub fn constructor(event_type: []const u8, opts: EventInit) !NavigationCurrentEntryChangeEvent { - const event = try parser.eventCreate(); - defer parser.eventDestroy(event); - try parser.eventInit(event, event_type, .{}); - parser.eventSetInternalType(event, .navigation_current_entry_change_event); - - return .{ - .proto = event.*, - .from = opts.from, - .navigation_type = opts.navigation_type, - }; - } - - pub fn get_from(self: *NavigationCurrentEntryChangeEvent) *NavigationHistoryEntry { - return self.from; - } - - pub fn get_navigationType(self: *const NavigationCurrentEntryChangeEvent) ?NavigationType { - return self.navigation_type; - } - - pub fn dispatch(from: *NavigationHistoryEntry, typ: ?NavigationType, page: *Page) void { - log.debug(.script_event, "dispatch event", .{ - .type = "currententrychange", - .source = "navigation", - }); - - var evt = NavigationCurrentEntryChangeEvent.constructor( - "currententrychange", - .{ .from = from, .navigation_type = typ }, - ) catch |err| { - log.err(.app, "event constructor error", .{ - .err = err, - .type = "currententrychange", - .source = "navigation", - }); - - return; - }; - - _ = parser.eventTargetDispatchEvent( - @as(*parser.EventTarget, @ptrCast(&page.session.navigation)), - &evt.proto, - ) catch |err| { - log.err(.app, "dispatch event error", .{ - .err = err, - .type = "currententrychange", - .source = "navigation", - }); - }; - } -}; - -const testing = @import("../../testing.zig"); -test "Browser: Navigation" { - try testing.htmlRunner("html/navigation/navigation.html"); + NavigationCurrentEntryChangeEvent.dispatch(self, previous, null); } diff --git a/src/browser/navigation/NavigationEventTarget.zig b/src/browser/navigation/NavigationEventTarget.zig new file mode 100644 index 00000000..261e60a8 --- /dev/null +++ b/src/browser/navigation/NavigationEventTarget.zig @@ -0,0 +1,56 @@ +const std = @import("std"); + +const js = @import("../js/js.zig"); +const Page = @import("../page.zig").Page; + +const EventTarget = @import("../dom/event_target.zig").EventTarget; +const EventHandler = @import("../events/event.zig").EventHandler; + +const parser = @import("../netsurf.zig"); + +pub const NavigationEventTarget = @This(); + +pub const prototype = *EventTarget; +// Extend libdom event target for pure zig struct. +base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .navigation }, + +oncurrententrychange_cbk: ?js.Function = null, + +fn register( + self: *NavigationEventTarget, + alloc: std.mem.Allocator, + typ: []const u8, + listener: EventHandler.Listener, +) !?js.Function { + const target = @as(*parser.EventTarget, @ptrCast(self)); + + // The only time this can return null if the listener is already + // registered. But before calling `register`, all of our functions + // remove any existing listener, so it should be impossible to get null + // from this function call. + const eh = (try EventHandler.register(alloc, target, typ, listener, null)) orelse unreachable; + return eh.callback; +} + +fn unregister(self: *NavigationEventTarget, typ: []const u8, cbk_id: usize) !void { + const et = @as(*parser.EventTarget, @ptrCast(self)); + // check if event target has already this listener + const lst = try parser.eventTargetHasListener(et, typ, false, cbk_id); + if (lst == null) { + return; + } + + // remove listener + try parser.eventTargetRemoveEventListener(et, typ, lst.?, false); +} + +pub fn get_oncurrententrychange(self: *NavigationEventTarget) ?js.Function { + return self.oncurrententrychange_cbk; +} + +pub fn set_oncurrententrychange(self: *NavigationEventTarget, listener: ?EventHandler.Listener, page: *Page) !void { + if (self.oncurrententrychange_cbk) |cbk| try self.unregister("currententrychange", cbk.id); + if (listener) |listen| { + self.oncurrententrychange_cbk = try self.register(page.arena, "currententrychange", listen); + } +} diff --git a/src/browser/navigation/navigation.zig b/src/browser/navigation/navigation.zig new file mode 100644 index 00000000..9e08bf04 --- /dev/null +++ b/src/browser/navigation/navigation.zig @@ -0,0 +1,219 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const log = @import("../../log.zig"); +const URL = @import("../../url.zig").URL; + +const js = @import("../js/js.zig"); +const Page = @import("../page.zig").Page; + +const DirectEventHandler = @import("../events/event.zig").DirectEventHandler; +const EventTarget = @import("../dom/event_target.zig").EventTarget; +const EventHandler = @import("../events/event.zig").EventHandler; + +const parser = @import("../netsurf.zig"); + +const Navigation = @import("Navigation.zig"); +const NavigationEventTarget = @import("NavigationEventTarget.zig"); + +pub const Interfaces = .{ + Navigation, + NavigationEventTarget, + NavigationActivation, + NavigationTransition, + NavigationHistoryEntry, +}; + +pub const NavigationType = enum { + pub const ENUM_JS_USE_TAG = true; + + push, + replace, + traverse, + reload, +}; + +pub const NavigationKind = union(NavigationType) { + push: ?[]const u8, + replace, + traverse: usize, + reload, +}; + +// https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry +pub const NavigationHistoryEntry = struct { + pub const prototype = *EventTarget; + base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain }, + + id: []const u8, + key: []const u8, + url: ?[]const u8, + state: ?[]const u8, + + pub fn get_id(self: *const NavigationHistoryEntry) []const u8 { + return self.id; + } + + pub fn get_index(self: *const NavigationHistoryEntry, page: *Page) i32 { + const navigation = page.session.navigation; + for (navigation.entries.items, 0..) |entry, i| { + if (std.mem.eql(u8, entry.id, self.id)) { + return @intCast(i); + } + } + + return -1; + } + + pub fn get_key(self: *const NavigationHistoryEntry) []const u8 { + return self.key; + } + + pub fn get_sameDocument(self: *const NavigationHistoryEntry, page: *Page) !bool { + const _url = self.url orelse return false; + const url = try URL.parse(_url, null); + return page.url.eqlDocument(&url, page.arena); + } + + pub fn get_url(self: *const NavigationHistoryEntry) ?[]const u8 { + return self.url; + } + + pub fn _getState(self: *const NavigationHistoryEntry, page: *Page) !?js.Value { + if (self.state) |state| { + return try js.Value.fromJson(page.js, state); + } else { + return null; + } + } +}; + +// https://developer.mozilla.org/en-US/docs/Web/API/NavigationActivation +pub const NavigationActivation = struct { + const NavigationActivationType = enum { + pub const ENUM_JS_USE_TAG = true; + + push, + reload, + replace, + traverse, + }; + + entry: NavigationHistoryEntry, + from: ?NavigationHistoryEntry = null, + type: NavigationActivationType, + + pub fn get_entry(self: *const NavigationActivation) NavigationHistoryEntry { + return self.entry; + } + + pub fn get_from(self: *const NavigationActivation) ?NavigationHistoryEntry { + return self.from; + } + + pub fn get_navigationType(self: *const NavigationActivation) NavigationActivationType { + return self.type; + } +}; + +// https://developer.mozilla.org/en-US/docs/Web/API/NavigationTransition +pub const NavigationTransition = struct { + finished: js.Promise, + from: NavigationHistoryEntry, + navigation_type: NavigationActivation.NavigationActivationType, +}; + +const Event = @import("../events/event.zig").Event; + +pub const NavigationCurrentEntryChangeEvent = struct { + pub const prototype = *Event; + pub const union_make_copy = true; + + pub const EventInit = struct { + from: *NavigationHistoryEntry, + navigation_type: ?NavigationType = null, + }; + + proto: parser.Event, + from: *NavigationHistoryEntry, + navigation_type: ?NavigationType, + + pub fn constructor(event_type: []const u8, opts: EventInit) !NavigationCurrentEntryChangeEvent { + const event = try parser.eventCreate(); + defer parser.eventDestroy(event); + + try parser.eventInit(event, event_type, .{}); + parser.eventSetInternalType(event, .navigation_current_entry_change_event); + + return .{ + .proto = event.*, + .from = opts.from, + .navigation_type = opts.navigation_type, + }; + } + + pub fn get_from(self: *NavigationCurrentEntryChangeEvent) *NavigationHistoryEntry { + return self.from; + } + + pub fn get_navigationType(self: *const NavigationCurrentEntryChangeEvent) ?NavigationType { + return self.navigation_type; + } + + pub fn dispatch(navigation: *Navigation, from: *NavigationHistoryEntry, typ: ?NavigationType) void { + log.debug(.script_event, "dispatch event", .{ + .type = "currententrychange", + .source = "navigation", + }); + + var evt = NavigationCurrentEntryChangeEvent.constructor( + "currententrychange", + .{ .from = from, .navigation_type = typ }, + ) catch |err| { + log.err(.app, "event constructor error", .{ + .err = err, + .type = "currententrychange", + .source = "navigation", + }); + + return; + }; + + _ = parser.eventTargetDispatchEvent( + @as(*parser.EventTarget, @ptrCast(navigation)), + &evt.proto, + ) catch |err| { + log.err(.app, "dispatch event error", .{ + .err = err, + .type = "currententrychange", + .source = "navigation", + }); + }; + } +}; + +const testing = @import("../../testing.zig"); +test "Browser: Navigation" { + try testing.htmlRunner("html/navigation/navigation.html"); + // try testing.htmlRunner("html/navigation/navigation_currententrychange.html"); +} + +test "Browser: NavigationCurrentEntry" { + try testing.htmlRunner("html/navigation/navigation_currententrychange.html"); +} diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index 8263118c..a08a6996 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -832,6 +832,7 @@ pub const EventTargetTBase = extern struct { message_port = 7, screen = 8, screen_orientation = 9, + navigation = 10, }; vtable: ?*const c.struct_dom_event_target_vtable = &c.struct_dom_event_target_vtable{ diff --git a/src/browser/page.zig b/src/browser/page.zig index 631283ef..ff6ecfbe 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -737,6 +737,9 @@ pub const Page = struct { var self: *Page = @ptrCast(@alignCast(ctx)); self.clearTransferArena(); + // We need to handle different navigation types differently. + try self.session.navigation.processNavigation(self); + switch (self.mode) { .pre => { // Received a response without a body like: https://httpbin.io/status/200 @@ -815,9 +818,6 @@ pub const Page = struct { unreachable; }, } - - // We need to handle different navigation types differently. - try self.session.navigation.processNavigation(self); } fn pageErrorCallback(ctx: *anyopaque, err: anyerror) void { @@ -1046,6 +1046,26 @@ pub const Page = struct { // specifically for this type of lifetime. pub fn navigateFromWebAPI(self: *Page, url: []const u8, opts: NavigateOpts, kind: NavigationKind) !void { const session = self.session; + + // Force will force a page load. + // + // TODO: This needs to be reworked but just ensuring events get fired right. + if (!opts.force) { + // If we are navigating within the same document, just change URL. + const new_url = try URL.parse(url, null); + if (try self.url.eqlDocument(&new_url, self.arena)) { + const new_duped_url = try session.arena.dupe(u8, url); + self.url = try URL.parse(new_duped_url, null); + + // TODO: remove this temporary snippet. + const prev = session.navigation.currentEntry(); + const NavigationCurrentEntryChangeEvent = @import("navigation/navigation.zig").NavigationCurrentEntryChangeEvent; + NavigationCurrentEntryChangeEvent.dispatch(&self.session.navigation, prev, kind); + + return; + } + } + if (session.queued_navigation != null) { // It might seem like this should never happen. And it might not, // BUT..consider the case where we have script like: @@ -1190,6 +1210,7 @@ pub const NavigateOpts = struct { method: Http.Method = .GET, body: ?[]const u8 = null, header: ?[:0]const u8 = null, + force: bool = false, }; const IdleNotification = union(enum) { diff --git a/src/browser/session.zig b/src/browser/session.zig index 76fdcad6..a4ed61c9 100644 --- a/src/browser/session.zig +++ b/src/browser/session.zig @@ -22,11 +22,11 @@ const Allocator = std.mem.Allocator; const js = @import("js/js.zig"); const Page = @import("page.zig").Page; -const NavigationKind = @import("html/Navigation.zig").NavigationKind; +const NavigationKind = @import("navigation/navigation.zig").NavigationKind; const Browser = @import("browser.zig").Browser; const NavigateOpts = @import("page.zig").NavigateOpts; const History = @import("html/History.zig"); -const Navigation = @import("html/Navigation.zig"); +const Navigation = @import("navigation/Navigation.zig"); const log = @import("../log.zig"); const parser = @import("netsurf.zig"); diff --git a/src/tests/html/navigation/navigation.html b/src/tests/html/navigation/navigation.html index 287aa507..24efe6c7 100644 --- a/src/tests/html/navigation/navigation.html +++ b/src/tests/html/navigation/navigation.html @@ -12,7 +12,7 @@ const currentIndex = navigation.currentEntry.index; navigation.navigate( - 'http://127.0.0.1:9582/src/tests/html/navigation/navigation2.html', + 'http://localhost:9582/src/tests/html/navigation/navigation2.html', { state: { currentIndex: currentIndex, navTestInProgress: true } } ); diff --git a/src/tests/html/navigation/navigation_currententrychange.html b/src/tests/html/navigation/navigation_currententrychange.html new file mode 100644 index 00000000..c84bcbad --- /dev/null +++ b/src/tests/html/navigation/navigation_currententrychange.html @@ -0,0 +1,15 @@ + + + +