diff --git a/src/browser/html/Navigation.zig b/src/browser/html/Navigation.zig
index a0818828..7968d821 100644
--- a/src/browser/html/Navigation.zig
+++ b/src/browser/html/Navigation.zig
@@ -20,18 +20,18 @@ const std = @import("std");
const log = @import("../../log.zig");
const URL = @import("../../url.zig").URL;
-const Js = @import("../js/js.zig");
+const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
-// https://developer.mozilla.org/en-US/docs/Web/API/Navigation
-const Navigation = @This();
-
const EventTarget = @import("../dom/event_target.zig").EventTarget;
const EventHandler = @import("../events/event.zig").EventHandler;
const parser = @import("../netsurf.zig");
-const Interfaces = .{
+// https://developer.mozilla.org/en-US/docs/Web/API/Navigation
+const Navigation = @This();
+
+pub const Interfaces = .{
Navigation,
NavigationActivation,
NavigationHistoryEntry,
@@ -51,39 +51,76 @@ const NavigationHistoryEntry = struct {
base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain },
id: []const u8,
- index: usize,
key: []const u8,
url: ?[]const u8,
- same_document: bool,
state: ?[]const u8,
pub fn get_id(self: *const NavigationHistoryEntry) []const u8 {
return self.id;
}
- pub fn get_index(self: *const NavigationHistoryEntry) usize {
- return self.index;
+ 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) bool {
- return self.same_document;
+ 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 {
+ pub fn _getState(self: *const NavigationHistoryEntry, page: *Page) !?js.Value {
if (self.state) |state| {
- return try Js.Value.fromJson(page.main_context, state);
+ return try js.Value.fromJson(page.js, state);
} else {
return null;
}
}
+
+ pub fn navigate(entry: NavigationHistoryEntry, reload: enum { none, force }, page: *Page) !NavigationReturn {
+ const arena = page.session.arena;
+ const url = entry.url orelse return error.MissingURL;
+
+ // https://github.com/WICG/navigation-api/issues/95
+ //
+ // These will only settle on same-origin navigation (mostly intended for SPAs).
+ // It is fine (and expected) for these to not settle on cross-origin requests :)
+ const committed = try page.js.createPromiseResolver(.page);
+ const finished = try page.js.createPromiseResolver(.page);
+
+ const new_url = try URL.parse(url, null);
+ if (try page.url.eqlDocument(&new_url, arena) or reload == .force) {
+ page.url = new_url;
+ try committed.resolve({});
+
+ // todo: Fire navigate event
+
+ try finished.resolve({});
+ } else {
+ // TODO: Change to history
+ try page.navigateFromWebAPI(url, .{ .reason = .history });
+ }
+
+ return .{
+ .committed = committed.promise(),
+ .finished = finished.promise(),
+ };
+ }
};
// https://developer.mozilla.org/en-US/docs/Web/API/NavigationActivation
@@ -124,49 +161,61 @@ pub fn get_canGoForward(self: *const Navigation) bool {
return self.entries.items.len > self.index + 1;
}
-pub fn get_currentEntry(_: *const Navigation) NavigationHistoryEntry {
- // TODO
- unreachable;
+pub fn currentEntry(self: *Navigation) *NavigationHistoryEntry {
+ return &self.entries.items[self.index];
+}
+
+pub fn get_currentEntry(self: *const Navigation) NavigationHistoryEntry {
+ return self.entries.items[self.index];
}
const NavigationReturn = struct {
- comitted: Js.Promise,
- finished: Js.Promise,
+ committed: js.Promise,
+ finished: js.Promise,
};
-pub fn _back(_: *const Navigation) !NavigationReturn {
- unreachable;
+pub fn _back(self: *Navigation, page: *Page) !NavigationReturn {
+ if (!self.get_canGoBack()) {
+ return error.InvalidStateError;
+ }
+
+ const new_index = self.index - 1;
+ const next_entry = self.entries.items[new_index];
+ self.index = new_index;
+
+ return next_entry.navigate(.none, page);
}
pub fn _entries(self: *const Navigation) []NavigationHistoryEntry {
return self.entries.items;
}
-pub fn _forward(_: *const Navigation) !NavigationReturn {
- unreachable;
+pub fn _forward(self: *Navigation, page: *Page) !NavigationReturn {
+ if (!self.get_canGoForward()) {
+ return error.InvalidStateError;
+ }
+
+ const new_index = self.index + 1;
+ const next_entry = self.entries.items[new_index];
+ self.index = new_index;
+
+ return next_entry.navigate(.none, page);
}
-const NavigateOptions = struct {
- const NavigateOptionsHistory = enum {
- auto,
- push,
- replace,
- };
-
- state: ?Js.Object = null,
- info: ?Js.Object = null,
- history: NavigateOptionsHistory = .auto,
-};
-
-pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, page: *Page) !NavigationReturn {
+/// Pushes an entry into the Navigation stack WITHOUT actually navigating to it.
+/// For that, use `navigate`.
+pub fn pushEntry(self: *Navigation, _url: ?[]const u8, _opts: ?NavigateOptions, page: *Page) !NavigationHistoryEntry {
const arena = page.session.arena;
const options = _opts orelse NavigateOptions{};
- const url = try arena.dupe(u8, _url);
+ const url = if (_url) |u| try arena.dupe(u8, u) else null;
- // TODO: handle push history NotSupportedError.
+ // truncates our history here.
+ if (self.entries.items.len > self.index + 1) {
+ self.entries.shrinkRetainingCapacity(self.index + 1);
+ }
+ self.index = self.entries.items.len;
- const index = self.entries.items.len;
const id = self.next_entry_id;
self.next_entry_id += 1;
@@ -174,7 +223,7 @@ pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, p
const state: ?[]const u8 = blk: {
if (options.state) |s| {
- break :blk try s.toJson(arena);
+ break :blk s.toJson(arena) catch return error.DataClone;
} else {
break :blk null;
}
@@ -182,40 +231,70 @@ pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, p
const entry = NavigationHistoryEntry{
.id = id_str,
- .index = index,
- .same_document = false,
- .url = url,
.key = id_str,
+ .url = url,
.state = state,
};
try self.entries.append(arena, entry);
- // https://github.com/WICG/navigation-api/issues/95
- //
- // These will only settle on same-origin navigation (mostly intended for SPAs).
- // It is fine (and expected) for these to not settle on cross-origin requests :)
- const committed = try page.main_context.createPersistentPromiseResolver(.page);
- const finished = try page.main_context.createPersistentPromiseResolver(.page);
-
- if (entry.same_document) {
- page.url = try URL.parse(url, null);
- try committed.resolve(void);
-
- // todo: Fire navigate event
- //
-
- } else {
- page.navigateFromWebAPI(url, .{ .reason = .navigation });
- }
-
- return .{
- .comitted = committed,
- .finished = finished,
- };
+ return entry;
}
-// const testing = @import("../../testing.zig");
-// test "Browser: Navigation" {
-// try testing.htmlRunner("html/navigation.html");
-// }
+const NavigateOptions = struct {
+ const NavigateOptionsHistory = enum {
+ pub const ENUM_JS_USE_TAG = true;
+
+ auto,
+ push,
+ replace,
+ };
+
+ state: ?js.Object = null,
+ info: ?js.Object = null,
+ history: NavigateOptionsHistory = .auto,
+};
+
+pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, page: *Page) !NavigationReturn {
+ const entry = try self.pushEntry(_url, _opts, page);
+ return entry.navigate(.none, page);
+}
+
+pub const ReloadOptions = struct {
+ state: ?js.Object = null,
+ info: ?js.Object = null,
+};
+
+pub fn _reload(self: *Navigation, _opts: ?ReloadOptions, page: *Page) !NavigationReturn {
+ const arena = page.session.arena;
+
+ const opts = _opts orelse ReloadOptions{};
+ const entry = self.currentEntry();
+ if (opts.state) |state| {
+ entry.state = state.toJson(arena) catch return error.DataClone;
+ }
+
+ return entry.navigate(.force, page);
+}
+
+pub fn _transition(_: *const Navigation) !NavigationReturn {
+ unreachable;
+}
+
+pub fn _traverseTo(_: *const Navigation, _: []const u8) !NavigationReturn {
+ unreachable;
+}
+
+pub const UpdateCurrentEntryOptions = struct {
+ state: js.Object,
+};
+
+pub fn _updateCurrentEntry(self: *Navigation, options: UpdateCurrentEntryOptions, page: *Page) !void {
+ const arena = page.session.arena;
+ self.currentEntry().state = options.state.toJson(arena) catch return error.DataClone;
+}
+
+const testing = @import("../../testing.zig");
+test "Browser: Navigation" {
+ try testing.htmlRunner("html/navigation.html");
+}
diff --git a/src/browser/html/html.zig b/src/browser/html/html.zig
index ef8a99f7..d5aa81a1 100644
--- a/src/browser/html/html.zig
+++ b/src/browser/html/html.zig
@@ -34,6 +34,7 @@ pub const Interfaces = .{
Window,
Navigator,
History,
+ @import("Navigation.zig").Interfaces,
Location,
MediaQueryList,
@import("DataSet.zig"),
diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig
index ef015492..e6ea1963 100644
--- a/src/browser/html/window.zig
+++ b/src/browser/html/window.zig
@@ -25,6 +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 Location = @import("location.zig").Location;
const Crypto = @import("../crypto/crypto.zig").Crypto;
const Console = @import("../console/console.zig").Console;
@@ -190,6 +191,10 @@ pub const Window = struct {
return &page.session.history;
}
+ pub fn get_navigation(_: *Window, page: *Page) *Navigation {
+ return &page.session.navigation;
+ }
+
// The interior height of the window in pixels, including the height of the horizontal scroll bar, if present.
pub fn get_innerHeight(_: *Window, page: *Page) u32 {
// We do not have scrollbars or padding so this is the same as Element.clientHeight