@@ -20,18 +20,18 @@ const std = @import("std");
2020const log = @import ("../../log.zig" );
2121const URL = @import ("../../url.zig" ).URL ;
2222
23- const Js = @import ("../js/js.zig" );
23+ const js = @import ("../js/js.zig" );
2424const Page = @import ("../page.zig" ).Page ;
2525
26- // https://developer.mozilla.org/en-US/docs/Web/API/Navigation
27- const Navigation = @This ();
28-
2926const EventTarget = @import ("../dom/event_target.zig" ).EventTarget ;
3027const EventHandler = @import ("../events/event.zig" ).EventHandler ;
3128
3229const 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
132172const 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
141189pub 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+ }
0 commit comments