mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-30 07:31:47 +00:00 
			
		
		
		
	functional NavigationCurrentEntryChangeEvent
This commit is contained in:
		| @@ -34,6 +34,7 @@ pub const Union = union(enum) { | ||||
|     screen_orientation: *@import("../html/screen.zig").ScreenOrientation, | ||||
|     performance: *@import("performance.zig").Performance, | ||||
|     media_query_list: *@import("../html/media_query_list.zig").MediaQueryList, | ||||
|     navigation: *@import("../navigation/Navigation.zig"), | ||||
| }; | ||||
|  | ||||
| // EventTarget implementation | ||||
| @@ -82,6 +83,11 @@ pub const EventTarget = struct { | ||||
|             .media_query_list => { | ||||
|                 return .{ .media_query_list = @fieldParentPtr("base", @as(*parser.EventTargetTBase, @ptrCast(et))) }; | ||||
|             }, | ||||
|             .navigation => { | ||||
|                 const NavigationEventTarget = @import("../navigation/NavigationEventTarget.zig"); | ||||
|                 const base: *NavigationEventTarget = @fieldParentPtr("base", @as(*parser.EventTargetTBase, @ptrCast(et))); | ||||
|                 return .{ .navigation = @fieldParentPtr("proto", base) }; | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -38,7 +38,7 @@ const KeyboardEvent = @import("keyboard_event.zig").KeyboardEvent; | ||||
| const ErrorEvent = @import("../html/error_event.zig").ErrorEvent; | ||||
| const MessageEvent = @import("../dom/MessageChannel.zig").MessageEvent; | ||||
| const PopStateEvent = @import("../html/History.zig").PopStateEvent; | ||||
| const NavigationCurrentEntryChangeEvent = @import("../html/Navigation.zig").NavigationCurrentEntryChangeEvent; | ||||
| const NavigationCurrentEntryChangeEvent = @import("../navigation/navigation.zig").NavigationCurrentEntryChangeEvent; | ||||
|  | ||||
| // Event interfaces | ||||
| pub const Interfaces = .{ | ||||
|   | ||||
| @@ -21,6 +21,7 @@ const log = @import("../../log.zig"); | ||||
|  | ||||
| const js = @import("../js/js.zig"); | ||||
| const Page = @import("../page.zig").Page; | ||||
| const Window = @import("window.zig").Window; | ||||
|  | ||||
| // https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-history-interface | ||||
| const History = @This(); | ||||
| @@ -60,7 +61,7 @@ pub fn _pushState(_: *const History, state: js.Object, _: ?[]const u8, _url: ?[] | ||||
|     const url = if (_url) |u| try arena.dupe(u8, u) else try arena.dupe(u8, page.url.raw); | ||||
|  | ||||
|     const json = state.toJson(arena) catch return error.DataClone; | ||||
|     _ = try page.session.navigation.pushEntry(url, json, page); | ||||
|     _ = try page.session.navigation.pushEntry(url, json, page, true); | ||||
| } | ||||
|  | ||||
| pub fn _replaceState(_: *const History, state: js.Object, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void { | ||||
| @@ -163,7 +164,7 @@ pub const PopStateEvent = struct { | ||||
|         }; | ||||
|  | ||||
|         _ = parser.eventTargetDispatchEvent( | ||||
|             @as(*parser.EventTarget, @ptrCast(&page.window)), | ||||
|             parser.toEventTarget(Window, &page.window), | ||||
|             &evt.proto, | ||||
|         ) catch |err| { | ||||
|             log.err(.app, "dispatch popstate event error", .{ | ||||
|   | ||||
| @@ -34,7 +34,6 @@ pub const Interfaces = .{ | ||||
|     Window, | ||||
|     Navigator, | ||||
|     History, | ||||
|     @import("Navigation.zig").Interfaces, | ||||
|     Location, | ||||
|     MediaQueryList, | ||||
|     @import("DataSet.zig"), | ||||
|   | ||||
| @@ -73,6 +73,10 @@ pub const Location = struct { | ||||
|         return self.url.get_origin(page); | ||||
|     } | ||||
|  | ||||
|     pub fn set_href(_: *Location, url: []const u8, page: *Page) !void { | ||||
|         return page.navigateFromWebAPI(url, .{ .reason = .script }, .{ .push = null }); | ||||
|     } | ||||
|  | ||||
|     pub fn _assign(_: *const Location, url: []const u8, page: *Page) !void { | ||||
|         return page.navigateFromWebAPI(url, .{ .reason = .script }, .{ .push = null }); | ||||
|     } | ||||
|   | ||||
| @@ -25,7 +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 Navigation = @import("../navigation/Navigation.zig"); | ||||
| const Location = @import("location.zig").Location; | ||||
| const Crypto = @import("../crypto/crypto.zig").Crypto; | ||||
| const Console = @import("../console/console.zig").Console; | ||||
|   | ||||
| @@ -16,6 +16,7 @@ const Interfaces = generate.Tuple(.{ | ||||
|     @import("../storage/storage.zig").Interfaces, | ||||
|     @import("../url/url.zig").Interfaces, | ||||
|     @import("../xhr/xhr.zig").Interfaces, | ||||
|     @import("../navigation/navigation.zig").Interfaces, | ||||
|     @import("../xhr/form_data.zig").Interfaces, | ||||
|     @import("../xhr/File.zig"), | ||||
|     @import("../xmlserializer/xmlserializer.zig").Interfaces, | ||||
|   | ||||
| @@ -32,122 +32,21 @@ const parser = @import("../netsurf.zig"); | ||||
| // https://developer.mozilla.org/en-US/docs/Web/API/Navigation | ||||
| const Navigation = @This(); | ||||
| 
 | ||||
| pub const Interfaces = .{ | ||||
|     Navigation, | ||||
|     NavigationActivation, | ||||
|     NavigationTransition, | ||||
|     NavigationHistoryEntry, | ||||
| }; | ||||
| const NavigationKind = @import("navigation.zig").NavigationKind; | ||||
| const NavigationHistoryEntry = @import("navigation.zig").NavigationHistoryEntry; | ||||
| const NavigationTransition = @import("navigation.zig").NavigationTransition; | ||||
| const NavigationEventTarget = @import("NavigationEventTarget.zig"); | ||||
| 
 | ||||
| pub const NavigationType = enum { | ||||
|     pub const ENUM_JS_USE_TAG = true; | ||||
| const NavigationCurrentEntryChangeEvent = @import("navigation.zig").NavigationCurrentEntryChangeEvent; | ||||
| 
 | ||||
|     push, | ||||
|     replace, | ||||
|     traverse, | ||||
|     reload, | ||||
| }; | ||||
| 
 | ||||
| pub const NavigationKind = union(NavigationType) { | ||||
|     push: ?[]const u8, | ||||
|     replace, | ||||
|     traverse: usize, | ||||
|     reload, | ||||
| }; | ||||
| 
 | ||||
| pub const prototype = *EventTarget; | ||||
| base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain }, | ||||
| pub const prototype = *NavigationEventTarget; | ||||
| proto: NavigationEventTarget = NavigationEventTarget{}, | ||||
| 
 | ||||
| index: usize = 0, | ||||
| // Need to be stable pointers, because Events can reference entries. | ||||
| entries: std.ArrayListUnmanaged(*NavigationHistoryEntry) = .empty, | ||||
| next_entry_id: usize = 0, | ||||
| 
 | ||||
| oncurrententrychange_callback: ?js.Function = null, | ||||
| 
 | ||||
| // https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry | ||||
| const NavigationHistoryEntry = struct { | ||||
|     pub const prototype = *EventTarget; | ||||
|     base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain }, | ||||
| 
 | ||||
|     id: []const u8, | ||||
|     key: []const u8, | ||||
|     url: ?[]const u8, | ||||
|     state: ?[]const u8, | ||||
| 
 | ||||
|     pub fn get_id(self: *const NavigationHistoryEntry) []const u8 { | ||||
|         return self.id; | ||||
|     } | ||||
| 
 | ||||
|     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, 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 { | ||||
|         if (self.state) |state| { | ||||
|             return try js.Value.fromJson(page.js, state); | ||||
|         } else { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // https://developer.mozilla.org/en-US/docs/Web/API/NavigationActivation | ||||
| const NavigationActivation = struct { | ||||
|     const NavigationActivationType = enum { | ||||
|         pub const ENUM_JS_USE_TAG = true; | ||||
| 
 | ||||
|         push, | ||||
|         reload, | ||||
|         replace, | ||||
|         traverse, | ||||
|     }; | ||||
| 
 | ||||
|     entry: NavigationHistoryEntry, | ||||
|     from: ?NavigationHistoryEntry = null, | ||||
|     type: NavigationActivationType, | ||||
| 
 | ||||
|     pub fn get_entry(self: *const NavigationActivation) NavigationHistoryEntry { | ||||
|         return self.entry; | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_from(self: *const NavigationActivation) ?NavigationHistoryEntry { | ||||
|         return self.from; | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_navigationType(self: *const NavigationActivation) NavigationActivationType { | ||||
|         return self.type; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // https://developer.mozilla.org/en-US/docs/Web/API/NavigationTransition | ||||
| const NavigationTransition = struct { | ||||
|     finished: js.Promise, | ||||
|     from: NavigationHistoryEntry, | ||||
|     navigation_type: NavigationActivation.NavigationActivationType, | ||||
| }; | ||||
| 
 | ||||
| pub fn get_canGoBack(self: *const Navigation) bool { | ||||
|     return self.index > 0; | ||||
| } | ||||
| @@ -202,16 +101,6 @@ pub fn _forward(self: *Navigation, page: *Page) !NavigationReturn { | ||||
|     return self.navigate(next_entry.url, .{ .traverse = new_index }, page); | ||||
| } | ||||
| 
 | ||||
| /// Returns `oncurrententrychange_callback`. | ||||
| pub fn get_oncurrententrychange(self: *const Navigation) ?js.Function { | ||||
|     return self.oncurrententrychange_callback; | ||||
| } | ||||
| 
 | ||||
| /// Sets `oncurrententrychange_callback`. | ||||
| pub fn set_oncurrententrychange(self: *Navigation, maybe_listener: ?EventHandler.Listener, page: *Page) !void { | ||||
|     try DirectEventHandler(Navigation, self, "currententrychange", maybe_listener, &self.oncurrententrychange_callback, page.arena); | ||||
| } | ||||
| 
 | ||||
| // This is for after true navigation processing, where we need to ensure that our entries are up to date. | ||||
| // This is only really safe to run in the `pageDoneCallback` where we can guarantee that the URL and NavigationKind are correct. | ||||
| pub fn processNavigation(self: *Navigation, page: *Page) !void { | ||||
| @@ -227,18 +116,18 @@ pub fn processNavigation(self: *Navigation, page: *Page) !void { | ||||
|                 entry.state = null; | ||||
|             }, | ||||
|             .push => |state| { | ||||
|                 _ = try self.pushEntry(url, state, page); | ||||
|                 _ = try self.pushEntry(url, state, page, false); | ||||
|             }, | ||||
|             .traverse, .reload => {}, | ||||
|         } | ||||
|     } else { | ||||
|         _ = try self.pushEntry(url, null, page); | ||||
|         _ = try self.pushEntry(url, null, page, false); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Pushes an entry into the Navigation stack WITHOUT actually navigating to it. | ||||
| /// For that, use `navigate`. | ||||
| pub fn pushEntry(self: *Navigation, _url: ?[]const u8, state: ?[]const u8, page: *Page) !*NavigationHistoryEntry { | ||||
| pub fn pushEntry(self: *Navigation, _url: ?[]const u8, state: ?[]const u8, page: *Page, dispatch: bool) !*NavigationHistoryEntry { | ||||
|     const arena = page.session.arena; | ||||
| 
 | ||||
|     const url = if (_url) |u| try arena.dupe(u8, u) else null; | ||||
| @@ -267,7 +156,9 @@ pub fn pushEntry(self: *Navigation, _url: ?[]const u8, state: ?[]const u8, page: | ||||
|     const previous = if (self.entries.items.len > 0) self.currentEntry() else null; | ||||
|     try self.entries.append(arena, entry); | ||||
|     if (previous) |prev| { | ||||
|         NavigationCurrentEntryChangeEvent.dispatch(prev, .push, page); | ||||
|         if (dispatch) { | ||||
|             NavigationCurrentEntryChangeEvent.dispatch(self, prev, .push); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     self.index = index; | ||||
| @@ -312,11 +203,12 @@ pub fn navigate( | ||||
|         .push => |state| { | ||||
|             if (is_same_document) { | ||||
|                 page.url = new_url; | ||||
| 
 | ||||
|                 try committed.resolve({}); | ||||
|                 // todo: Fire navigate event | ||||
|                 try finished.resolve({}); | ||||
| 
 | ||||
|                 _ = try self.pushEntry(url, state, page); | ||||
|                 _ = try self.pushEntry(url, state, page, true); | ||||
|             } else { | ||||
|                 try page.navigateFromWebAPI(url, .{ .reason = .navigation }, kind); | ||||
|             } | ||||
| @@ -365,7 +257,7 @@ pub fn _reload(self: *Navigation, _opts: ?ReloadOptions, page: *Page) !Navigatio | ||||
|     if (opts.state) |state| { | ||||
|         const previous = entry; | ||||
|         entry.state = state.toJson(arena) catch return error.DataClone; | ||||
|         NavigationCurrentEntryChangeEvent.dispatch(previous, .reload, page); | ||||
|         NavigationCurrentEntryChangeEvent.dispatch(self, previous, .reload); | ||||
|     } | ||||
| 
 | ||||
|     return self.navigate(entry.url, .reload, page); | ||||
| @@ -396,78 +288,5 @@ pub fn _updateCurrentEntry(self: *Navigation, options: UpdateCurrentEntryOptions | ||||
| 
 | ||||
|     const previous = self.currentEntry(); | ||||
|     self.currentEntry().state = options.state.toJson(arena) catch return error.DataClone; | ||||
|     NavigationCurrentEntryChangeEvent.dispatch(previous, null, page); | ||||
| } | ||||
| 
 | ||||
| const Event = @import("../events/event.zig").Event; | ||||
| 
 | ||||
| pub const NavigationCurrentEntryChangeEvent = struct { | ||||
|     pub const prototype = *Event; | ||||
|     pub const union_make_copy = true; | ||||
| 
 | ||||
|     pub const EventInit = struct { | ||||
|         from: *NavigationHistoryEntry, | ||||
|         navigation_type: ?NavigationType = null, | ||||
|     }; | ||||
| 
 | ||||
|     proto: parser.Event, | ||||
|     from: *NavigationHistoryEntry, | ||||
|     navigation_type: ?NavigationType, | ||||
| 
 | ||||
|     pub fn constructor(event_type: []const u8, opts: EventInit) !NavigationCurrentEntryChangeEvent { | ||||
|         const event = try parser.eventCreate(); | ||||
|         defer parser.eventDestroy(event); | ||||
|         try parser.eventInit(event, event_type, .{}); | ||||
|         parser.eventSetInternalType(event, .navigation_current_entry_change_event); | ||||
| 
 | ||||
|         return .{ | ||||
|             .proto = event.*, | ||||
|             .from = opts.from, | ||||
|             .navigation_type = opts.navigation_type, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_from(self: *NavigationCurrentEntryChangeEvent) *NavigationHistoryEntry { | ||||
|         return self.from; | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_navigationType(self: *const NavigationCurrentEntryChangeEvent) ?NavigationType { | ||||
|         return self.navigation_type; | ||||
|     } | ||||
| 
 | ||||
|     pub fn dispatch(from: *NavigationHistoryEntry, typ: ?NavigationType, page: *Page) void { | ||||
|         log.debug(.script_event, "dispatch event", .{ | ||||
|             .type = "currententrychange", | ||||
|             .source = "navigation", | ||||
|         }); | ||||
| 
 | ||||
|         var evt = NavigationCurrentEntryChangeEvent.constructor( | ||||
|             "currententrychange", | ||||
|             .{ .from = from, .navigation_type = typ }, | ||||
|         ) catch |err| { | ||||
|             log.err(.app, "event constructor error", .{ | ||||
|                 .err = err, | ||||
|                 .type = "currententrychange", | ||||
|                 .source = "navigation", | ||||
|             }); | ||||
| 
 | ||||
|             return; | ||||
|         }; | ||||
| 
 | ||||
|         _ = parser.eventTargetDispatchEvent( | ||||
|             @as(*parser.EventTarget, @ptrCast(&page.session.navigation)), | ||||
|             &evt.proto, | ||||
|         ) catch |err| { | ||||
|             log.err(.app, "dispatch event error", .{ | ||||
|                 .err = err, | ||||
|                 .type = "currententrychange", | ||||
|                 .source = "navigation", | ||||
|             }); | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| const testing = @import("../../testing.zig"); | ||||
| test "Browser: Navigation" { | ||||
|     try testing.htmlRunner("html/navigation/navigation.html"); | ||||
|     NavigationCurrentEntryChangeEvent.dispatch(self, previous, null); | ||||
| } | ||||
							
								
								
									
										56
									
								
								src/browser/navigation/NavigationEventTarget.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/browser/navigation/NavigationEventTarget.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| const std = @import("std"); | ||||
|  | ||||
| const js = @import("../js/js.zig"); | ||||
| const Page = @import("../page.zig").Page; | ||||
|  | ||||
| const EventTarget = @import("../dom/event_target.zig").EventTarget; | ||||
| const EventHandler = @import("../events/event.zig").EventHandler; | ||||
|  | ||||
| const parser = @import("../netsurf.zig"); | ||||
|  | ||||
| pub const NavigationEventTarget = @This(); | ||||
|  | ||||
| pub const prototype = *EventTarget; | ||||
| // Extend libdom event target for pure zig struct. | ||||
| base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .navigation }, | ||||
|  | ||||
| oncurrententrychange_cbk: ?js.Function = null, | ||||
|  | ||||
| fn register( | ||||
|     self: *NavigationEventTarget, | ||||
|     alloc: std.mem.Allocator, | ||||
|     typ: []const u8, | ||||
|     listener: EventHandler.Listener, | ||||
| ) !?js.Function { | ||||
|     const target = @as(*parser.EventTarget, @ptrCast(self)); | ||||
|  | ||||
|     // The only time this can return null if the listener is already | ||||
|     // registered. But before calling `register`, all of our functions | ||||
|     // remove any existing listener, so it should be impossible to get null | ||||
|     // from this function call. | ||||
|     const eh = (try EventHandler.register(alloc, target, typ, listener, null)) orelse unreachable; | ||||
|     return eh.callback; | ||||
| } | ||||
|  | ||||
| fn unregister(self: *NavigationEventTarget, typ: []const u8, cbk_id: usize) !void { | ||||
|     const et = @as(*parser.EventTarget, @ptrCast(self)); | ||||
|     // check if event target has already this listener | ||||
|     const lst = try parser.eventTargetHasListener(et, typ, false, cbk_id); | ||||
|     if (lst == null) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // remove listener | ||||
|     try parser.eventTargetRemoveEventListener(et, typ, lst.?, false); | ||||
| } | ||||
|  | ||||
| pub fn get_oncurrententrychange(self: *NavigationEventTarget) ?js.Function { | ||||
|     return self.oncurrententrychange_cbk; | ||||
| } | ||||
|  | ||||
| pub fn set_oncurrententrychange(self: *NavigationEventTarget, listener: ?EventHandler.Listener, page: *Page) !void { | ||||
|     if (self.oncurrententrychange_cbk) |cbk| try self.unregister("currententrychange", cbk.id); | ||||
|     if (listener) |listen| { | ||||
|         self.oncurrententrychange_cbk = try self.register(page.arena, "currententrychange", listen); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										219
									
								
								src/browser/navigation/navigation.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								src/browser/navigation/navigation.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | ||||
| // Copyright (C) 2023-2024  Lightpanda (Selecy SAS) | ||||
| // | ||||
| // Francis Bouvier <francis@lightpanda.io> | ||||
| // Pierre Tachoire <pierre@lightpanda.io> | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Affero General Public License as | ||||
| // published by the Free Software Foundation, either version 3 of the | ||||
| // License, or (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU Affero General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Affero General Public License | ||||
| // along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| const std = @import("std"); | ||||
| const log = @import("../../log.zig"); | ||||
| const URL = @import("../../url.zig").URL; | ||||
|  | ||||
| const js = @import("../js/js.zig"); | ||||
| const Page = @import("../page.zig").Page; | ||||
|  | ||||
| const DirectEventHandler = @import("../events/event.zig").DirectEventHandler; | ||||
| const EventTarget = @import("../dom/event_target.zig").EventTarget; | ||||
| const EventHandler = @import("../events/event.zig").EventHandler; | ||||
|  | ||||
| const parser = @import("../netsurf.zig"); | ||||
|  | ||||
| const Navigation = @import("Navigation.zig"); | ||||
| const NavigationEventTarget = @import("NavigationEventTarget.zig"); | ||||
|  | ||||
| pub const Interfaces = .{ | ||||
|     Navigation, | ||||
|     NavigationEventTarget, | ||||
|     NavigationActivation, | ||||
|     NavigationTransition, | ||||
|     NavigationHistoryEntry, | ||||
| }; | ||||
|  | ||||
| pub const NavigationType = enum { | ||||
|     pub const ENUM_JS_USE_TAG = true; | ||||
|  | ||||
|     push, | ||||
|     replace, | ||||
|     traverse, | ||||
|     reload, | ||||
| }; | ||||
|  | ||||
| pub const NavigationKind = union(NavigationType) { | ||||
|     push: ?[]const u8, | ||||
|     replace, | ||||
|     traverse: usize, | ||||
|     reload, | ||||
| }; | ||||
|  | ||||
| // https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry | ||||
| pub const NavigationHistoryEntry = struct { | ||||
|     pub const prototype = *EventTarget; | ||||
|     base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain }, | ||||
|  | ||||
|     id: []const u8, | ||||
|     key: []const u8, | ||||
|     url: ?[]const u8, | ||||
|     state: ?[]const u8, | ||||
|  | ||||
|     pub fn get_id(self: *const NavigationHistoryEntry) []const u8 { | ||||
|         return self.id; | ||||
|     } | ||||
|  | ||||
|     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, 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 { | ||||
|         if (self.state) |state| { | ||||
|             return try js.Value.fromJson(page.js, state); | ||||
|         } else { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
| // https://developer.mozilla.org/en-US/docs/Web/API/NavigationActivation | ||||
| pub const NavigationActivation = struct { | ||||
|     const NavigationActivationType = enum { | ||||
|         pub const ENUM_JS_USE_TAG = true; | ||||
|  | ||||
|         push, | ||||
|         reload, | ||||
|         replace, | ||||
|         traverse, | ||||
|     }; | ||||
|  | ||||
|     entry: NavigationHistoryEntry, | ||||
|     from: ?NavigationHistoryEntry = null, | ||||
|     type: NavigationActivationType, | ||||
|  | ||||
|     pub fn get_entry(self: *const NavigationActivation) NavigationHistoryEntry { | ||||
|         return self.entry; | ||||
|     } | ||||
|  | ||||
|     pub fn get_from(self: *const NavigationActivation) ?NavigationHistoryEntry { | ||||
|         return self.from; | ||||
|     } | ||||
|  | ||||
|     pub fn get_navigationType(self: *const NavigationActivation) NavigationActivationType { | ||||
|         return self.type; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| // https://developer.mozilla.org/en-US/docs/Web/API/NavigationTransition | ||||
| pub const NavigationTransition = struct { | ||||
|     finished: js.Promise, | ||||
|     from: NavigationHistoryEntry, | ||||
|     navigation_type: NavigationActivation.NavigationActivationType, | ||||
| }; | ||||
|  | ||||
| const Event = @import("../events/event.zig").Event; | ||||
|  | ||||
| pub const NavigationCurrentEntryChangeEvent = struct { | ||||
|     pub const prototype = *Event; | ||||
|     pub const union_make_copy = true; | ||||
|  | ||||
|     pub const EventInit = struct { | ||||
|         from: *NavigationHistoryEntry, | ||||
|         navigation_type: ?NavigationType = null, | ||||
|     }; | ||||
|  | ||||
|     proto: parser.Event, | ||||
|     from: *NavigationHistoryEntry, | ||||
|     navigation_type: ?NavigationType, | ||||
|  | ||||
|     pub fn constructor(event_type: []const u8, opts: EventInit) !NavigationCurrentEntryChangeEvent { | ||||
|         const event = try parser.eventCreate(); | ||||
|         defer parser.eventDestroy(event); | ||||
|  | ||||
|         try parser.eventInit(event, event_type, .{}); | ||||
|         parser.eventSetInternalType(event, .navigation_current_entry_change_event); | ||||
|  | ||||
|         return .{ | ||||
|             .proto = event.*, | ||||
|             .from = opts.from, | ||||
|             .navigation_type = opts.navigation_type, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     pub fn get_from(self: *NavigationCurrentEntryChangeEvent) *NavigationHistoryEntry { | ||||
|         return self.from; | ||||
|     } | ||||
|  | ||||
|     pub fn get_navigationType(self: *const NavigationCurrentEntryChangeEvent) ?NavigationType { | ||||
|         return self.navigation_type; | ||||
|     } | ||||
|  | ||||
|     pub fn dispatch(navigation: *Navigation, from: *NavigationHistoryEntry, typ: ?NavigationType) void { | ||||
|         log.debug(.script_event, "dispatch event", .{ | ||||
|             .type = "currententrychange", | ||||
|             .source = "navigation", | ||||
|         }); | ||||
|  | ||||
|         var evt = NavigationCurrentEntryChangeEvent.constructor( | ||||
|             "currententrychange", | ||||
|             .{ .from = from, .navigation_type = typ }, | ||||
|         ) catch |err| { | ||||
|             log.err(.app, "event constructor error", .{ | ||||
|                 .err = err, | ||||
|                 .type = "currententrychange", | ||||
|                 .source = "navigation", | ||||
|             }); | ||||
|  | ||||
|             return; | ||||
|         }; | ||||
|  | ||||
|         _ = parser.eventTargetDispatchEvent( | ||||
|             @as(*parser.EventTarget, @ptrCast(navigation)), | ||||
|             &evt.proto, | ||||
|         ) catch |err| { | ||||
|             log.err(.app, "dispatch event error", .{ | ||||
|                 .err = err, | ||||
|                 .type = "currententrychange", | ||||
|                 .source = "navigation", | ||||
|             }); | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const testing = @import("../../testing.zig"); | ||||
| test "Browser: Navigation" { | ||||
|     try testing.htmlRunner("html/navigation/navigation.html"); | ||||
|     // try testing.htmlRunner("html/navigation/navigation_currententrychange.html"); | ||||
| } | ||||
|  | ||||
| test "Browser: NavigationCurrentEntry" { | ||||
|     try testing.htmlRunner("html/navigation/navigation_currententrychange.html"); | ||||
| } | ||||
| @@ -831,6 +831,7 @@ pub const EventTargetTBase = extern struct { | ||||
|         message_port = 7, | ||||
|         screen = 8, | ||||
|         screen_orientation = 9, | ||||
|         navigation = 10, | ||||
|     }; | ||||
|  | ||||
|     vtable: ?*const c.struct_dom_event_target_vtable = &c.struct_dom_event_target_vtable{ | ||||
|   | ||||
| @@ -737,6 +737,9 @@ pub const Page = struct { | ||||
|         var self: *Page = @ptrCast(@alignCast(ctx)); | ||||
|         self.clearTransferArena(); | ||||
|  | ||||
|         // We need to handle different navigation types differently. | ||||
|         try self.session.navigation.processNavigation(self); | ||||
|  | ||||
|         switch (self.mode) { | ||||
|             .pre => { | ||||
|                 // Received a response without a body like: https://httpbin.io/status/200 | ||||
| @@ -815,9 +818,6 @@ pub const Page = struct { | ||||
|                 unreachable; | ||||
|             }, | ||||
|         } | ||||
|  | ||||
|         // We need to handle different navigation types differently. | ||||
|         try self.session.navigation.processNavigation(self); | ||||
|     } | ||||
|  | ||||
|     fn pageErrorCallback(ctx: *anyopaque, err: anyerror) void { | ||||
| @@ -1021,6 +1021,26 @@ pub const Page = struct { | ||||
|     // specifically for this type of lifetime. | ||||
|     pub fn navigateFromWebAPI(self: *Page, url: []const u8, opts: NavigateOpts, kind: NavigationKind) !void { | ||||
|         const session = self.session; | ||||
|  | ||||
|         // Force will force a page load. | ||||
|         // | ||||
|         // TODO: This needs to be reworked but just ensuring events get fired right. | ||||
|         if (!opts.force) { | ||||
|             // If we are navigating within the same document, just change URL. | ||||
|             const new_url = try URL.parse(url, null); | ||||
|             if (try self.url.eqlDocument(&new_url, self.arena)) { | ||||
|                 const new_duped_url = try session.arena.dupe(u8, url); | ||||
|                 self.url = try URL.parse(new_duped_url, null); | ||||
|  | ||||
|                 // TODO: remove this temporary snippet. | ||||
|                 const prev = session.navigation.currentEntry(); | ||||
|                 const NavigationCurrentEntryChangeEvent = @import("navigation/navigation.zig").NavigationCurrentEntryChangeEvent; | ||||
|                 NavigationCurrentEntryChangeEvent.dispatch(&self.session.navigation, prev, kind); | ||||
|  | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (session.queued_navigation != null) { | ||||
|             // It might seem like this should never happen. And it might not, | ||||
|             // BUT..consider the case where we have script like: | ||||
| @@ -1165,6 +1185,7 @@ pub const NavigateOpts = struct { | ||||
|     method: Http.Method = .GET, | ||||
|     body: ?[]const u8 = null, | ||||
|     header: ?[:0]const u8 = null, | ||||
|     force: bool = false, | ||||
| }; | ||||
|  | ||||
| const IdleNotification = union(enum) { | ||||
|   | ||||
| @@ -22,11 +22,11 @@ const Allocator = std.mem.Allocator; | ||||
|  | ||||
| const js = @import("js/js.zig"); | ||||
| const Page = @import("page.zig").Page; | ||||
| const NavigationKind = @import("html/Navigation.zig").NavigationKind; | ||||
| const NavigationKind = @import("navigation/navigation.zig").NavigationKind; | ||||
| const Browser = @import("browser.zig").Browser; | ||||
| const NavigateOpts = @import("page.zig").NavigateOpts; | ||||
| const History = @import("html/History.zig"); | ||||
| const Navigation = @import("html/Navigation.zig"); | ||||
| const Navigation = @import("navigation/Navigation.zig"); | ||||
|  | ||||
| const log = @import("../log.zig"); | ||||
| const parser = @import("netsurf.zig"); | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
|     const currentIndex = navigation.currentEntry.index; | ||||
|  | ||||
|     navigation.navigate( | ||||
|         'http://127.0.0.1:9582/src/tests/html/navigation/navigation2.html', | ||||
|         'http://localhost:9582/src/tests/html/navigation/navigation2.html', | ||||
|         { state: { currentIndex: currentIndex, navTestInProgress: true } } | ||||
|     ); | ||||
| </script> | ||||
|   | ||||
							
								
								
									
										15
									
								
								src/tests/html/navigation/navigation_currententrychange.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/tests/html/navigation/navigation_currententrychange.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| <!DOCTYPE html> | ||||
| <script src="../../testing.js"></script> | ||||
|  | ||||
| <script id=navigation_currententrychange> | ||||
|     let currentEntryChanged = false; | ||||
|  | ||||
|     navigation.addEventListener("currententrychange", () => { | ||||
|       currentEntryChanged = true; | ||||
|     }); | ||||
|  | ||||
|     // Doesn't fully navigate if same document. | ||||
|     location.href = location.href + "#1"; | ||||
|  | ||||
|     testing.expectEqual(true, currentEntryChanged); | ||||
| </script> | ||||
		Reference in New Issue
	
	Block a user
	 Muki Kiboigo
					Muki Kiboigo