Skip to content

Commit ce8674b

Browse files
committed
add functional Navigation
1 parent deefc6a commit ce8674b

File tree

3 files changed

+147
-62
lines changed

3 files changed

+147
-62
lines changed

src/browser/html/Navigation.zig

Lines changed: 141 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,18 @@ const std = @import("std");
2020
const log = @import("../../log.zig");
2121
const URL = @import("../../url.zig").URL;
2222

23-
const Js = @import("../js/js.zig");
23+
const js = @import("../js/js.zig");
2424
const Page = @import("../page.zig").Page;
2525

26-
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation
27-
const Navigation = @This();
28-
2926
const EventTarget = @import("../dom/event_target.zig").EventTarget;
3027
const EventHandler = @import("../events/event.zig").EventHandler;
3128

3229
const parser = @import("../netsurf.zig");
3330

34-
const Interfaces = .{
31+
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation
32+
const Navigation = @This();
33+
34+
pub const Interfaces = .{
3535
Navigation,
3636
NavigationActivation,
3737
NavigationHistoryEntry,
@@ -51,39 +51,76 @@ const NavigationHistoryEntry = struct {
5151
base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain },
5252

5353
id: []const u8,
54-
index: usize,
5554
key: []const u8,
5655
url: ?[]const u8,
57-
same_document: bool,
5856
state: ?[]const u8,
5957

6058
pub fn get_id(self: *const NavigationHistoryEntry) []const u8 {
6159
return self.id;
6260
}
6361

64-
pub fn get_index(self: *const NavigationHistoryEntry) usize {
65-
return self.index;
62+
pub fn get_index(self: *const NavigationHistoryEntry, page: *Page) i32 {
63+
const navigation = page.session.navigation;
64+
for (navigation.entries.items, 0..) |*entry, i| {
65+
if (std.mem.eql(u8, entry.id, self.id)) {
66+
return @intCast(i);
67+
}
68+
}
69+
70+
return -1;
6671
}
6772

6873
pub fn get_key(self: *const NavigationHistoryEntry) []const u8 {
6974
return self.key;
7075
}
7176

72-
pub fn get_sameDocument(self: *const NavigationHistoryEntry) bool {
73-
return self.same_document;
77+
pub fn get_sameDocument(self: *const NavigationHistoryEntry, page: *Page) !bool {
78+
const _url = self.url orelse return false;
79+
const url = try URL.parse(_url, null);
80+
return page.url.eqlDocument(&url, page.arena);
7481
}
7582

7683
pub fn get_url(self: *const NavigationHistoryEntry) ?[]const u8 {
7784
return self.url;
7885
}
7986

80-
pub fn _getState(self: *const NavigationHistoryEntry, page: *Page) !?Js.Value {
87+
pub fn _getState(self: *const NavigationHistoryEntry, page: *Page) !?js.Value {
8188
if (self.state) |state| {
82-
return try Js.Value.fromJson(page.main_context, state);
89+
return try js.Value.fromJson(page.js, state);
8390
} else {
8491
return null;
8592
}
8693
}
94+
95+
pub fn navigate(entry: NavigationHistoryEntry, reload: enum { none, force }, page: *Page) !NavigationReturn {
96+
const arena = page.session.arena;
97+
const url = entry.url orelse return error.MissingURL;
98+
99+
// https://github.com/WICG/navigation-api/issues/95
100+
//
101+
// These will only settle on same-origin navigation (mostly intended for SPAs).
102+
// It is fine (and expected) for these to not settle on cross-origin requests :)
103+
const committed = try page.js.createPromiseResolver(.page);
104+
const finished = try page.js.createPromiseResolver(.page);
105+
106+
const new_url = try URL.parse(url, null);
107+
if (try page.url.eqlDocument(&new_url, arena) or reload == .force) {
108+
page.url = new_url;
109+
try committed.resolve({});
110+
111+
// todo: Fire navigate event
112+
113+
try finished.resolve({});
114+
} else {
115+
// TODO: Change to history
116+
try page.navigateFromWebAPI(url, .{ .reason = .history });
117+
}
118+
119+
return .{
120+
.committed = committed.promise(),
121+
.finished = finished.promise(),
122+
};
123+
}
87124
};
88125

89126
// https://developer.mozilla.org/en-US/docs/Web/API/NavigationActivation
@@ -124,98 +161,140 @@ pub fn get_canGoForward(self: *const Navigation) bool {
124161
return self.entries.items.len > self.index + 1;
125162
}
126163

127-
pub fn get_currentEntry(_: *const Navigation) NavigationHistoryEntry {
128-
// TODO
129-
unreachable;
164+
pub fn currentEntry(self: *Navigation) *NavigationHistoryEntry {
165+
return &self.entries.items[self.index];
166+
}
167+
168+
pub fn get_currentEntry(self: *const Navigation) NavigationHistoryEntry {
169+
return self.entries.items[self.index];
130170
}
131171

132172
const NavigationReturn = struct {
133-
comitted: Js.Promise,
134-
finished: Js.Promise,
173+
committed: js.Promise,
174+
finished: js.Promise,
135175
};
136176

137-
pub fn _back(_: *const Navigation) !NavigationReturn {
138-
unreachable;
177+
pub fn _back(self: *Navigation, page: *Page) !NavigationReturn {
178+
if (!self.get_canGoBack()) {
179+
return error.InvalidStateError;
180+
}
181+
182+
const new_index = self.index - 1;
183+
const next_entry = self.entries.items[new_index];
184+
self.index = new_index;
185+
186+
return next_entry.navigate(.none, page);
139187
}
140188

141189
pub fn _entries(self: *const Navigation) []NavigationHistoryEntry {
142190
return self.entries.items;
143191
}
144192

145-
pub fn _forward(_: *const Navigation) !NavigationReturn {
146-
unreachable;
147-
}
193+
pub fn _forward(self: *Navigation, page: *Page) !NavigationReturn {
194+
if (!self.get_canGoForward()) {
195+
return error.InvalidStateError;
196+
}
148197

149-
const NavigateOptions = struct {
150-
const NavigateOptionsHistory = enum {
151-
auto,
152-
push,
153-
replace,
154-
};
198+
const new_index = self.index + 1;
199+
const next_entry = self.entries.items[new_index];
200+
self.index = new_index;
155201

156-
state: ?Js.Object = null,
157-
info: ?Js.Object = null,
158-
history: NavigateOptionsHistory = .auto,
159-
};
202+
return next_entry.navigate(.none, page);
203+
}
160204

161-
pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, page: *Page) !NavigationReturn {
205+
/// Pushes an entry into the Navigation stack WITHOUT actually navigating to it.
206+
/// For that, use `navigate`.
207+
pub fn pushEntry(self: *Navigation, _url: ?[]const u8, _opts: ?NavigateOptions, page: *Page) !NavigationHistoryEntry {
162208
const arena = page.session.arena;
163209

164210
const options = _opts orelse NavigateOptions{};
165-
const url = try arena.dupe(u8, _url);
211+
const url = if (_url) |u| try arena.dupe(u8, u) else null;
166212

167-
// TODO: handle push history NotSupportedError.
213+
// truncates our history here.
214+
if (self.entries.items.len > self.index + 1) {
215+
self.entries.shrinkRetainingCapacity(self.index + 1);
216+
}
217+
self.index = self.entries.items.len;
168218

169-
const index = self.entries.items.len;
170219
const id = self.next_entry_id;
171220
self.next_entry_id += 1;
172221

173222
const id_str = try std.fmt.allocPrint(arena, "{d}", .{id});
174223

175224
const state: ?[]const u8 = blk: {
176225
if (options.state) |s| {
177-
break :blk try s.toJson(arena);
226+
break :blk s.toJson(arena) catch return error.DataClone;
178227
} else {
179228
break :blk null;
180229
}
181230
};
182231

183232
const entry = NavigationHistoryEntry{
184233
.id = id_str,
185-
.index = index,
186-
.same_document = false,
187-
.url = url,
188234
.key = id_str,
235+
.url = url,
189236
.state = state,
190237
};
191238

192239
try self.entries.append(arena, entry);
193240

194-
// https://github.com/WICG/navigation-api/issues/95
195-
//
196-
// These will only settle on same-origin navigation (mostly intended for SPAs).
197-
// It is fine (and expected) for these to not settle on cross-origin requests :)
198-
const committed = try page.main_context.createPersistentPromiseResolver(.page);
199-
const finished = try page.main_context.createPersistentPromiseResolver(.page);
241+
return entry;
242+
}
200243

201-
if (entry.same_document) {
202-
page.url = try URL.parse(url, null);
203-
try committed.resolve(void);
244+
const NavigateOptions = struct {
245+
const NavigateOptionsHistory = enum {
246+
pub const ENUM_JS_USE_TAG = true;
204247

205-
// todo: Fire navigate event
206-
//
248+
auto,
249+
push,
250+
replace,
251+
};
252+
253+
state: ?js.Object = null,
254+
info: ?js.Object = null,
255+
history: NavigateOptionsHistory = .auto,
256+
};
257+
258+
pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, page: *Page) !NavigationReturn {
259+
const entry = try self.pushEntry(_url, _opts, page);
260+
return entry.navigate(.none, page);
261+
}
262+
263+
pub const ReloadOptions = struct {
264+
state: ?js.Object = null,
265+
info: ?js.Object = null,
266+
};
267+
268+
pub fn _reload(self: *Navigation, _opts: ?ReloadOptions, page: *Page) !NavigationReturn {
269+
const arena = page.session.arena;
207270

208-
} else {
209-
page.navigateFromWebAPI(url, .{ .reason = .navigation });
271+
const opts = _opts orelse ReloadOptions{};
272+
const entry = self.currentEntry();
273+
if (opts.state) |state| {
274+
entry.state = state.toJson(arena) catch return error.DataClone;
210275
}
211276

212-
return .{
213-
.comitted = committed,
214-
.finished = finished,
215-
};
277+
return entry.navigate(.force, page);
278+
}
279+
280+
pub fn _transition(_: *const Navigation) !NavigationReturn {
281+
unreachable;
216282
}
217283

218-
// const testing = @import("../../testing.zig");
219-
// test "Browser: Navigation" {
220-
// try testing.htmlRunner("html/navigation.html");
221-
// }
284+
pub fn _traverseTo(_: *const Navigation, _: []const u8) !NavigationReturn {
285+
unreachable;
286+
}
287+
288+
pub const UpdateCurrentEntryOptions = struct {
289+
state: js.Object,
290+
};
291+
292+
pub fn _updateCurrentEntry(self: *Navigation, options: UpdateCurrentEntryOptions, page: *Page) !void {
293+
const arena = page.session.arena;
294+
self.currentEntry().state = options.state.toJson(arena) catch return error.DataClone;
295+
}
296+
297+
const testing = @import("../../testing.zig");
298+
test "Browser: Navigation" {
299+
try testing.htmlRunner("html/navigation.html");
300+
}

src/browser/html/html.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub const Interfaces = .{
3434
Window,
3535
Navigator,
3636
History,
37+
@import("Navigation.zig").Interfaces,
3738
Location,
3839
MediaQueryList,
3940
@import("DataSet.zig"),

src/browser/html/window.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const Page = @import("../page.zig").Page;
2525

2626
const Navigator = @import("navigator.zig").Navigator;
2727
const History = @import("History.zig");
28+
const Navigation = @import("Navigation.zig");
2829
const Location = @import("location.zig").Location;
2930
const Crypto = @import("../crypto/crypto.zig").Crypto;
3031
const Console = @import("../console/console.zig").Console;
@@ -217,6 +218,10 @@ pub const Window = struct {
217218
return &page.session.history;
218219
}
219220

221+
pub fn get_navigation(_: *Window, page: *Page) *Navigation {
222+
return &page.session.navigation;
223+
}
224+
220225
// The interior height of the window in pixels, including the height of the horizontal scroll bar, if present.
221226
pub fn get_innerHeight(_: *Window, page: *Page) u32 {
222227
// We do not have scrollbars or padding so this is the same as Element.clientHeight

0 commit comments

Comments
 (0)