Skip to content

Commit 2b613a4

Browse files
committed
initial Navigation scaffolding
1 parent a65aa9f commit 2b613a4

File tree

2 files changed

+223
-0
lines changed

2 files changed

+223
-0
lines changed

src/browser/html/Navigation.zig

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
const std = @import("std");
20+
const log = @import("../../log.zig");
21+
const URL = @import("../../url.zig").URL;
22+
23+
const Js = @import("../js/js.zig");
24+
const Page = @import("../page.zig").Page;
25+
26+
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation
27+
const Navigation = @This();
28+
29+
const EventTarget = @import("../dom/event_target.zig").EventTarget;
30+
const EventHandler = @import("../events/event.zig").EventHandler;
31+
32+
const parser = @import("../netsurf.zig");
33+
34+
const Interfaces = .{
35+
Navigation,
36+
NavigationActivation,
37+
NavigationHistoryEntry,
38+
};
39+
40+
pub const prototype = *EventTarget;
41+
base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain },
42+
43+
index: usize = 0,
44+
entries: std.ArrayListUnmanaged(NavigationHistoryEntry) = .empty,
45+
next_entry_id: usize = 0,
46+
// TODO: key->index mapping
47+
48+
// https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry
49+
const NavigationHistoryEntry = struct {
50+
pub const prototype = *EventTarget;
51+
base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain },
52+
53+
id: []const u8,
54+
index: usize,
55+
key: []const u8,
56+
url: ?[]const u8,
57+
same_document: bool,
58+
state: ?[]const u8,
59+
60+
pub fn get_id(self: *const NavigationHistoryEntry) []const u8 {
61+
return self.id;
62+
}
63+
64+
pub fn get_index(self: *const NavigationHistoryEntry) usize {
65+
return self.index;
66+
}
67+
68+
pub fn get_key(self: *const NavigationHistoryEntry) []const u8 {
69+
return self.key;
70+
}
71+
72+
pub fn get_sameDocument(self: *const NavigationHistoryEntry) bool {
73+
return self.same_document;
74+
}
75+
76+
pub fn get_url(self: *const NavigationHistoryEntry) ?[]const u8 {
77+
return self.url;
78+
}
79+
80+
pub fn _getState(self: *const NavigationHistoryEntry, page: *Page) !?Js.Value {
81+
if (self.state) |state| {
82+
return try Js.Value.fromJson(page.main_context, state);
83+
} else {
84+
return null;
85+
}
86+
}
87+
};
88+
89+
// https://developer.mozilla.org/en-US/docs/Web/API/NavigationActivation
90+
const NavigationActivation = struct {
91+
const NavigationActivationType = enum {
92+
push,
93+
reload,
94+
replace,
95+
traverse,
96+
97+
pub fn toString(self: NavigationActivationType) []const u8 {
98+
return @tagName(self);
99+
}
100+
};
101+
102+
entry: NavigationHistoryEntry,
103+
from: ?NavigationHistoryEntry = null,
104+
type: NavigationActivationType,
105+
106+
pub fn get_entry(self: *const NavigationActivation) NavigationHistoryEntry {
107+
return self.entry;
108+
}
109+
110+
pub fn get_from(self: *const NavigationActivation) ?NavigationHistoryEntry {
111+
return self.from;
112+
}
113+
114+
pub fn get_navigationType(self: *const NavigationActivation) NavigationActivationType {
115+
return self.type;
116+
}
117+
};
118+
119+
pub fn get_canGoBack(self: *const Navigation) bool {
120+
return self.index > 0;
121+
}
122+
123+
pub fn get_canGoForward(self: *const Navigation) bool {
124+
return self.entries.items.len > self.index + 1;
125+
}
126+
127+
pub fn get_currentEntry(_: *const Navigation) NavigationHistoryEntry {
128+
// TODO
129+
unreachable;
130+
}
131+
132+
const NavigationReturn = struct {
133+
comitted: Js.Promise,
134+
finished: Js.Promise,
135+
};
136+
137+
pub fn _back(_: *const Navigation) !NavigationReturn {
138+
unreachable;
139+
}
140+
141+
pub fn _entries(self: *const Navigation) []NavigationHistoryEntry {
142+
return self.entries.items;
143+
}
144+
145+
pub fn _forward(_: *const Navigation) !NavigationReturn {
146+
unreachable;
147+
}
148+
149+
const NavigateOptions = struct {
150+
const NavigateOptionsHistory = enum {
151+
auto,
152+
push,
153+
replace,
154+
};
155+
156+
state: ?Js.Object = null,
157+
info: ?Js.Object = null,
158+
history: NavigateOptionsHistory = .auto,
159+
};
160+
161+
pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, page: *Page) !NavigationReturn {
162+
const arena = page.session.arena;
163+
164+
const options = _opts orelse NavigateOptions{};
165+
const url = try arena.dupe(u8, _url);
166+
167+
// TODO: handle push history NotSupportedError.
168+
169+
const index = self.entries.items.len;
170+
const id = self.next_entry_id;
171+
self.next_entry_id += 1;
172+
173+
const id_str = try std.fmt.allocPrint(arena, "{d}", .{id});
174+
175+
const state: ?[]const u8 = blk: {
176+
if (options.state) |s| {
177+
break :blk try s.toJson(arena);
178+
} else {
179+
break :blk null;
180+
}
181+
};
182+
183+
const entry = NavigationHistoryEntry{
184+
.id = id_str,
185+
.index = index,
186+
.same_document = false,
187+
.url = url,
188+
.key = id_str,
189+
.state = state,
190+
};
191+
192+
try self.entries.append(arena, entry);
193+
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);
200+
201+
if (entry.same_document) {
202+
page.url = try URL.parse(url, null);
203+
try committed.resolve(void);
204+
205+
// todo: Fire navigate event
206+
//
207+
208+
} else {
209+
page.navigateFromWebAPI(url, .{ .reason = .navigation });
210+
}
211+
212+
return .{
213+
.comitted = committed,
214+
.finished = finished,
215+
};
216+
}
217+
218+
// const testing = @import("../../testing.zig");
219+
// test "Browser: Navigation" {
220+
// try testing.htmlRunner("html/navigation.html");
221+
// }

src/browser/session.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const Page = @import("page.zig").Page;
2525
const Browser = @import("browser.zig").Browser;
2626
const NavigateOpts = @import("page.zig").NavigateOpts;
2727
const History = @import("html/History.zig");
28+
const Navigation = @import("html/Navigation.zig");
2829

2930
const log = @import("../log.zig");
3031
const parser = @import("netsurf.zig");
@@ -57,6 +58,7 @@ pub const Session = struct {
5758
// History is persistent across the "tab".
5859
// https://developer.mozilla.org/en-US/docs/Web/API/History
5960
history: History = .{},
61+
navigation: Navigation = .{},
6062

6163
page: ?Page = null,
6264

0 commit comments

Comments
 (0)