@@ -34,16 +34,24 @@ const Navigation = @This();
3434pub const Interfaces = .{
3535 Navigation ,
3636 NavigationActivation ,
37+ NavigationTransition ,
3738 NavigationHistoryEntry ,
3839};
3940
41+ pub const NavigationKind = union (enum ) {
42+ initial ,
43+ push : ? []const u8 ,
44+ replace ,
45+ traverse : usize ,
46+ reload ,
47+ };
48+
4049pub const prototype = * EventTarget ;
4150base : parser.EventTargetTBase = parser.EventTargetTBase { .internal_target_type = .plain },
4251
4352index : usize = 0 ,
4453entries : std .ArrayListUnmanaged (NavigationHistoryEntry ) = .empty ,
4554next_entry_id : usize = 0 ,
46- // TODO: key->index mapping
4755
4856// https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry
4957const NavigationHistoryEntry = struct {
@@ -91,49 +99,17 @@ const NavigationHistoryEntry = struct {
9199 return null ;
92100 }
93101 }
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- }
124102};
125103
126104// https://developer.mozilla.org/en-US/docs/Web/API/NavigationActivation
127105const NavigationActivation = struct {
128106 const NavigationActivationType = enum {
107+ pub const ENUM_JS_USE_TAG = true ;
108+
129109 push ,
130110 reload ,
131111 replace ,
132112 traverse ,
133-
134- pub fn toString (self : NavigationActivationType ) []const u8 {
135- return @tagName (self );
136- }
137113 };
138114
139115 entry : NavigationHistoryEntry ,
@@ -153,6 +129,13 @@ const NavigationActivation = struct {
153129 }
154130};
155131
132+ // https://developer.mozilla.org/en-US/docs/Web/API/NavigationTransition
133+ const NavigationTransition = struct {
134+ finished : js.Promise ,
135+ from : NavigationHistoryEntry ,
136+ navigation_type : NavigationActivation.NavigationActivationType ,
137+ };
138+
156139pub fn get_canGoBack (self : * const Navigation ) bool {
157140 return self .index > 0 ;
158141}
@@ -169,6 +152,11 @@ pub fn get_currentEntry(self: *const Navigation) NavigationHistoryEntry {
169152 return self .entries .items [self .index ];
170153}
171154
155+ pub fn get_transition (_ : * const Navigation ) ? NavigationTransition {
156+ // For now, all transitions are just considered complete.
157+ return null ;
158+ }
159+
172160const NavigationReturn = struct {
173161 committed : js.Promise ,
174162 finished : js.Promise ,
@@ -183,7 +171,7 @@ pub fn _back(self: *Navigation, page: *Page) !NavigationReturn {
183171 const next_entry = self .entries .items [new_index ];
184172 self .index = new_index ;
185173
186- return next_entry .navigate (.none , page );
174+ return self .navigate (next_entry . url , .{ . traverse = new_index } , page );
187175}
188176
189177pub fn _entries (self : * const Navigation ) []NavigationHistoryEntry {
@@ -199,15 +187,33 @@ pub fn _forward(self: *Navigation, page: *Page) !NavigationReturn {
199187 const next_entry = self .entries .items [new_index ];
200188 self .index = new_index ;
201189
202- return next_entry .navigate (.none , page );
190+ return self .navigate (next_entry .url , .{ .traverse = new_index }, page );
191+ }
192+
193+ // This is for after true navigation processing, where we need to ensure that our entries are up to date.
194+ pub fn processNavigation (self : * Navigation , url : []const u8 , kind : NavigationKind , page : * Page ) ! void {
195+ switch (kind ) {
196+ .initial = > {
197+ _ = try self .pushEntry (url , null , page );
198+ },
199+ .replace = > {
200+ // When replacing, we just update the URL but the state is nullified.
201+ const entry = self .currentEntry ();
202+ entry .url = url ;
203+ entry .state = null ;
204+ },
205+ .push = > | state | {
206+ _ = try self .pushEntry (url , state , page );
207+ },
208+ .traverse , .reload = > {},
209+ }
203210}
204211
205212/// Pushes an entry into the Navigation stack WITHOUT actually navigating to it.
206213/// For that, use `navigate`.
207- pub fn pushEntry (self : * Navigation , _url : ? []const u8 , _opts : ? NavigateOptions , page : * Page ) ! NavigationHistoryEntry {
214+ pub fn pushEntry (self : * Navigation , _url : ? []const u8 , state : ? [] const u8 , page : * Page ) ! NavigationHistoryEntry {
208215 const arena = page .session .arena ;
209216
210- const options = _opts orelse NavigateOptions {};
211217 const url = if (_url ) | u | try arena .dupe (u8 , u ) else null ;
212218
213219 // truncates our history here.
@@ -221,14 +227,6 @@ pub fn pushEntry(self: *Navigation, _url: ?[]const u8, _opts: ?NavigateOptions,
221227
222228 const id_str = try std .fmt .allocPrint (arena , "{d}" , .{id });
223229
224- const state : ? []const u8 = blk : {
225- if (options .state ) | s | {
226- break :blk s .toJson (arena ) catch return error .DataClone ;
227- } else {
228- break :blk null ;
229- }
230- };
231-
232230 const entry = NavigationHistoryEntry {
233231 .id = id_str ,
234232 .key = id_str ,
@@ -237,7 +235,6 @@ pub fn pushEntry(self: *Navigation, _url: ?[]const u8, _opts: ?NavigateOptions,
237235 };
238236
239237 try self .entries .append (arena , entry );
240-
241238 return entry ;
242239}
243240
@@ -255,9 +252,67 @@ const NavigateOptions = struct {
255252 history : NavigateOptionsHistory = .auto ,
256253};
257254
255+ pub fn navigate (
256+ self : * Navigation ,
257+ _url : ? []const u8 ,
258+ kind : NavigationKind ,
259+ page : * Page ,
260+ ) ! NavigationReturn {
261+ const arena = page .session .arena ;
262+ const url = _url orelse return error .MissingURL ;
263+
264+ // https://github.com/WICG/navigation-api/issues/95
265+ //
266+ // These will only settle on same-origin navigation (mostly intended for SPAs).
267+ // It is fine (and expected) for these to not settle on cross-origin requests :)
268+ const committed = try page .js .createPromiseResolver (.page );
269+ const finished = try page .js .createPromiseResolver (.page );
270+
271+ const new_url = try URL .parse (url , null );
272+ const is_same_document = try page .url .eqlDocument (& new_url , arena );
273+
274+ switch (kind ) {
275+ .push = > | state | {
276+ if (is_same_document ) {
277+ page .url = new_url ;
278+ try committed .resolve ({});
279+ // todo: Fire navigate event
280+ try finished .resolve ({});
281+
282+ _ = try self .pushEntry (url , state , page );
283+ } else {
284+ try page .navigateFromWebAPI (url , .{ .reason = .navigation }, kind );
285+ }
286+ },
287+ .traverse = > | index | {
288+ self .index = index ;
289+
290+ if (is_same_document ) {
291+ page .url = new_url ;
292+
293+ try committed .resolve ({});
294+ // todo: Fire navigate event
295+ try finished .resolve ({});
296+ } else {
297+ try page .navigateFromWebAPI (url , .{ .reason = .navigation }, kind );
298+ }
299+ },
300+ .reload = > {
301+ try page .navigateFromWebAPI (url , .{ .reason = .navigation }, kind );
302+ },
303+ else = > unreachable ,
304+ }
305+
306+ return .{
307+ .committed = committed .promise (),
308+ .finished = finished .promise (),
309+ };
310+ }
311+
258312pub 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 );
313+ const opts = _opts orelse NavigateOptions {};
314+ const json = if (opts .state ) | state | state .toJson (page .session .arena ) catch return error .DataClone else null ;
315+ return try self .navigate (_url , .{ .push = json }, page );
261316}
262317
263318pub const ReloadOptions = struct {
@@ -274,15 +329,23 @@ pub fn _reload(self: *Navigation, _opts: ?ReloadOptions, page: *Page) !Navigatio
274329 entry .state = state .toJson (arena ) catch return error .DataClone ;
275330 }
276331
277- return entry .navigate (.force , page );
332+ return self .navigate (entry . url , .reload , page );
278333}
279334
280- pub fn _transition (_ : * const Navigation ) ! NavigationReturn {
281- unreachable ;
282- }
335+ pub const TraverseToOptions = struct {
336+ info : ? js.Object = null ,
337+ };
338+
339+ pub fn _traverseTo (self : * Navigation , key : []const u8 , _ : ? TraverseToOptions , page : * Page ) ! NavigationReturn {
340+ // const opts = _opts orelse TraverseToOptions{};
341+
342+ for (self .entries .items , 0.. ) | entry , i | {
343+ if (std .mem .eql (u8 , key , entry .key )) {
344+ return try self .navigate (entry .url , .{ .traverse = i }, page );
345+ }
346+ }
283347
284- pub fn _traverseTo (_ : * const Navigation , _ : []const u8 ) ! NavigationReturn {
285- unreachable ;
348+ return error .InvalidStateError ;
286349}
287350
288351pub const UpdateCurrentEntryOptions = struct {
0 commit comments