From 56e30a9c970287d21481e7c2f288965cd69d42d2 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Wed, 12 Nov 2025 09:11:07 -0800 Subject: [PATCH 1/4] use replaceEntry in History replaceState --- src/browser/html/History.zig | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/browser/html/History.zig b/src/browser/html/History.zig index aa491518d..6d0b0828f 100644 --- a/src/browser/html/History.zig +++ b/src/browser/html/History.zig @@ -66,13 +66,10 @@ pub fn _pushState(_: *const History, state: js.Object, _: ?[]const u8, _url: ?[] pub fn _replaceState(_: *const History, state: js.Object, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void { const arena = page.session.arena; - - const entry = page.session.navigation.currentEntry(); - const json = try state.toJson(arena); const url = if (_url) |u| try arena.dupe(u8, u) else try arena.dupe(u8, page.url.raw); - entry.state = json; - entry.url = url; + const json = try state.toJson(arena); + _ = try page.session.navigation.replaceEntry(url, json, page, true); } pub fn go(_: *const History, delta: i32, page: *Page) !void { From f475f3440e26af178350ec6f4cd477887353d117 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Wed, 12 Nov 2025 09:37:22 -0800 Subject: [PATCH 2/4] seperate Navigation State and History State --- src/browser/html/History.zig | 8 ++++---- src/browser/navigation/Navigation.zig | 27 ++++++++++++++++++++------- src/browser/navigation/root.zig | 21 +++++++++++++++------ 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/browser/html/History.zig b/src/browser/html/History.zig index 6d0b0828f..69739f6a1 100644 --- a/src/browser/html/History.zig +++ b/src/browser/html/History.zig @@ -48,7 +48,7 @@ pub fn set_scrollRestoration(self: *History, mode: ScrollRestorationMode) void { } pub fn get_state(_: *History, page: *Page) !?js.Value { - if (page.session.navigation.currentEntry().state) |state| { + if (page.session.navigation.currentEntry().state.value) |state| { const value = try js.Value.fromJson(page.js, state); return value; } else { @@ -61,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, true); + _ = try page.session.navigation.pushEntry(url, .{ .source = .history, .value = json }, page, true); } pub fn _replaceState(_: *const History, state: js.Object, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void { @@ -69,7 +69,7 @@ pub fn _replaceState(_: *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 = try state.toJson(arena); - _ = try page.session.navigation.replaceEntry(url, json, page, true); + _ = try page.session.navigation.replaceEntry(url, .{ .source = .history, .value = json }, page, true); } pub fn go(_: *const History, delta: i32, page: *Page) !void { @@ -86,7 +86,7 @@ pub fn go(_: *const History, delta: i32, page: *Page) !void { if (entry.url) |url| { if (try page.isSameOrigin(url)) { - PopStateEvent.dispatch(entry.state, page); + PopStateEvent.dispatch(entry.state.value, page); } } diff --git a/src/browser/navigation/Navigation.zig b/src/browser/navigation/Navigation.zig index 3c6bfcfd9..222a51e09 100644 --- a/src/browser/navigation/Navigation.zig +++ b/src/browser/navigation/Navigation.zig @@ -35,6 +35,7 @@ const Navigation = @This(); const NavigationKind = @import("root.zig").NavigationKind; const NavigationHistoryEntry = @import("root.zig").NavigationHistoryEntry; const NavigationTransition = @import("root.zig").NavigationTransition; +const NavigationState = @import("root.zig").NavigationState; const NavigationCurrentEntryChangeEvent = @import("root.zig").NavigationCurrentEntryChangeEvent; const NavigationEventTarget = @import("NavigationEventTarget.zig"); @@ -110,10 +111,10 @@ pub fn _forward(self: *Navigation, page: *Page) !NavigationReturn { pub fn updateEntries(self: *Navigation, url: []const u8, kind: NavigationKind, page: *Page, dispatch: bool) !void { switch (kind) { .replace => { - _ = try self.replaceEntry(url, null, page, dispatch); + _ = try self.replaceEntry(url, .{ .source = .navigation, .value = null }, page, dispatch); }, .push => |state| { - _ = try self.pushEntry(url, state, page, dispatch); + _ = try self.pushEntry(url, .{ .source = .navigation, .value = state }, page, dispatch); }, .traverse => |index| { self.index = index; @@ -132,7 +133,13 @@ pub fn processNavigation(self: *Navigation, page: *Page) !void { /// 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, dispatch: bool) !*NavigationHistoryEntry { +pub fn pushEntry( + self: *Navigation, + _url: []const u8, + state: NavigationState, + page: *Page, + dispatch: bool, +) !*NavigationHistoryEntry { const arena = page.session.arena; const url = try arena.dupe(u8, _url); @@ -171,7 +178,13 @@ pub fn pushEntry(self: *Navigation, _url: []const u8, state: ?[]const u8, page: return entry; } -pub fn replaceEntry(self: *Navigation, _url: []const u8, state: ?[]const u8, page: *Page, dispatch: bool) !*NavigationHistoryEntry { +pub fn replaceEntry( + self: *Navigation, + _url: []const u8, + state: NavigationState, + page: *Page, + dispatch: bool, +) !*NavigationHistoryEntry { const arena = page.session.arena; const url = try arena.dupe(u8, _url); @@ -242,7 +255,7 @@ pub fn navigate( // todo: Fire navigate event try finished.resolve({}); - _ = try self.pushEntry(url, state, page, true); + _ = try self.pushEntry(url, .{ .source = .navigation, .value = state }, page, true); } else { try page.navigateFromWebAPI(url, .{ .reason = .navigation }, kind); } @@ -290,7 +303,7 @@ pub fn _reload(self: *Navigation, _opts: ?ReloadOptions, page: *Page) !Navigatio const entry = self.currentEntry(); if (opts.state) |state| { const previous = entry; - entry.state = state.toJson(arena) catch return error.DataClone; + entry.state = .{ .source = .navigation, .value = state.toJson(arena) catch return error.DataClone }; NavigationCurrentEntryChangeEvent.dispatch(self, previous, .reload); } @@ -323,6 +336,6 @@ pub fn _updateCurrentEntry(self: *Navigation, options: UpdateCurrentEntryOptions const arena = page.session.arena; const previous = self.currentEntry(); - self.currentEntry().state = options.state.toJson(arena) catch return error.DataClone; + self.currentEntry().state = .{ .source = .navigation, .value = options.state.toJson(arena) catch return error.DataClone }; NavigationCurrentEntryChangeEvent.dispatch(self, previous, null); } diff --git a/src/browser/navigation/root.zig b/src/browser/navigation/root.zig index bfe5830f1..14c5898cf 100644 --- a/src/browser/navigation/root.zig +++ b/src/browser/navigation/root.zig @@ -56,6 +56,11 @@ pub const NavigationKind = union(NavigationType) { reload, }; +pub const NavigationState = struct { + source: enum { history, navigation }, + value: ?[]const u8, +}; + // https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry pub const NavigationHistoryEntry = struct { pub const prototype = *EventTarget; @@ -64,7 +69,7 @@ pub const NavigationHistoryEntry = struct { id: []const u8, key: []const u8, url: ?[]const u8, - state: ?[]const u8, + state: NavigationState, pub fn get_id(self: *const NavigationHistoryEntry) []const u8 { return self.id; @@ -95,12 +100,16 @@ pub const NavigationHistoryEntry = struct { 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; + pub const StateReturn = union(enum) { value: ?js.Value, undefined: void }; + + pub fn _getState(self: *const NavigationHistoryEntry, page: *Page) !StateReturn { + if (self.state.source == .navigation) { + if (self.state.value) |value| { + return .{ .value = try js.Value.fromJson(page.js, value) }; + } } + + return .undefined; } }; From 67f979be7717b1a1079aab53df955ca6b746cdb2 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Wed, 12 Nov 2025 09:37:54 -0800 Subject: [PATCH 3/4] update navigation index before currenteventchange --- src/browser/navigation/Navigation.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/navigation/Navigation.zig b/src/browser/navigation/Navigation.zig index 222a51e09..a739eaf72 100644 --- a/src/browser/navigation/Navigation.zig +++ b/src/browser/navigation/Navigation.zig @@ -167,14 +167,14 @@ pub fn pushEntry( // we don't always have a current entry... const previous = if (self.entries.items.len > 0) self.currentEntry() else null; try self.entries.append(arena, entry); + self.index = index; + if (previous) |prev| { if (dispatch) { NavigationCurrentEntryChangeEvent.dispatch(self, prev, .push); } } - self.index = index; - return entry; } From 4fc09eccdf20e9b4872a155beee21578278bcb9f Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Wed, 12 Nov 2025 09:51:49 -0800 Subject: [PATCH 4/4] proper handling of history opt in navigate --- src/browser/html/location.zig | 4 ++-- src/browser/navigation/Navigation.zig | 24 +++++++++++++++++++++--- src/browser/navigation/root.zig | 2 +- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/browser/html/location.zig b/src/browser/html/location.zig index 458178457..dc959e87a 100644 --- a/src/browser/html/location.zig +++ b/src/browser/html/location.zig @@ -56,7 +56,7 @@ pub const Location = struct { break :blk try std.fmt.allocPrint(page.arena, "#{s}", .{hash}); }; - return page.navigateFromWebAPI(normalized_hash, .{ .reason = .script }, .replace); + return page.navigateFromWebAPI(normalized_hash, .{ .reason = .script }, .{ .replace = null }); } pub fn get_protocol(self: *Location) []const u8 { @@ -96,7 +96,7 @@ pub const Location = struct { } pub fn _replace(_: *const Location, url: []const u8, page: *Page) !void { - return page.navigateFromWebAPI(url, .{ .reason = .script }, .replace); + return page.navigateFromWebAPI(url, .{ .reason = .script }, .{ .replace = null }); } pub fn _reload(_: *const Location, page: *Page) !void { diff --git a/src/browser/navigation/Navigation.zig b/src/browser/navigation/Navigation.zig index a739eaf72..9f2da76cc 100644 --- a/src/browser/navigation/Navigation.zig +++ b/src/browser/navigation/Navigation.zig @@ -197,7 +197,7 @@ pub fn replaceEntry( const entry = try arena.create(NavigationHistoryEntry); entry.* = NavigationHistoryEntry{ .id = id_str, - .key = id_str, + .key = previous.key, .url = url, .state = state, }; @@ -260,6 +260,19 @@ pub fn navigate( try page.navigateFromWebAPI(url, .{ .reason = .navigation }, kind); } }, + .replace => |state| { + if (is_same_document) { + page.url = new_url; + + try committed.resolve({}); + // todo: Fire navigate event + try finished.resolve({}); + + _ = try self.replaceEntry(url, .{ .source = .navigation, .value = state }, page, true); + } else { + try page.navigateFromWebAPI(url, .{ .reason = .navigation }, kind); + } + }, .traverse => |index| { self.index = index; @@ -276,7 +289,6 @@ pub fn navigate( .reload => { try page.navigateFromWebAPI(url, .{ .reason = .navigation }, kind); }, - else => unreachable, } return .{ @@ -288,7 +300,13 @@ pub fn navigate( pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, page: *Page) !NavigationReturn { const opts = _opts orelse NavigateOptions{}; const json = if (opts.state) |state| state.toJson(page.session.arena) catch return error.DataClone else null; - return try self.navigate(_url, .{ .push = json }, page); + + const kind: NavigationKind = switch (opts.history) { + .replace => .{ .replace = json }, + .push, .auto => .{ .push = json }, + }; + + return try self.navigate(_url, kind, page); } pub const ReloadOptions = struct { diff --git a/src/browser/navigation/root.zig b/src/browser/navigation/root.zig index 14c5898cf..969a99971 100644 --- a/src/browser/navigation/root.zig +++ b/src/browser/navigation/root.zig @@ -51,7 +51,7 @@ pub const NavigationType = enum { pub const NavigationKind = union(NavigationType) { push: ?[]const u8, - replace, + replace: ?[]const u8, traverse: usize, reload, };