mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +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,
|
screen_orientation: *@import("../html/screen.zig").ScreenOrientation,
|
||||||
performance: *@import("performance.zig").Performance,
|
performance: *@import("performance.zig").Performance,
|
||||||
media_query_list: *@import("../html/media_query_list.zig").MediaQueryList,
|
media_query_list: *@import("../html/media_query_list.zig").MediaQueryList,
|
||||||
|
navigation: *@import("../navigation/Navigation.zig"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// EventTarget implementation
|
// EventTarget implementation
|
||||||
@@ -82,6 +83,11 @@ pub const EventTarget = struct {
|
|||||||
.media_query_list => {
|
.media_query_list => {
|
||||||
return .{ .media_query_list = @fieldParentPtr("base", @as(*parser.EventTargetTBase, @ptrCast(et))) };
|
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) };
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const ErrorEvent = @import("../html/error_event.zig").ErrorEvent;
|
|||||||
const MessageEvent = @import("../dom/MessageChannel.zig").MessageEvent;
|
const MessageEvent = @import("../dom/MessageChannel.zig").MessageEvent;
|
||||||
const PopStateEvent = @import("../html/History.zig").PopStateEvent;
|
const PopStateEvent = @import("../html/History.zig").PopStateEvent;
|
||||||
const CompositionEvent = @import("composition_event.zig").CompositionEvent;
|
const CompositionEvent = @import("composition_event.zig").CompositionEvent;
|
||||||
const NavigationCurrentEntryChangeEvent = @import("../html/Navigation.zig").NavigationCurrentEntryChangeEvent;
|
const NavigationCurrentEntryChangeEvent = @import("../navigation/navigation.zig").NavigationCurrentEntryChangeEvent;
|
||||||
|
|
||||||
// Event interfaces
|
// Event interfaces
|
||||||
pub const Interfaces = .{
|
pub const Interfaces = .{
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const log = @import("../../log.zig");
|
|||||||
|
|
||||||
const js = @import("../js/js.zig");
|
const js = @import("../js/js.zig");
|
||||||
const Page = @import("../page.zig").Page;
|
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
|
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-history-interface
|
||||||
const History = @This();
|
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 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;
|
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 {
|
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(
|
_ = parser.eventTargetDispatchEvent(
|
||||||
@as(*parser.EventTarget, @ptrCast(&page.window)),
|
parser.toEventTarget(Window, &page.window),
|
||||||
&evt.proto,
|
&evt.proto,
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.err(.app, "dispatch popstate event error", .{
|
log.err(.app, "dispatch popstate event error", .{
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ pub const Interfaces = .{
|
|||||||
Window,
|
Window,
|
||||||
Navigator,
|
Navigator,
|
||||||
History,
|
History,
|
||||||
@import("Navigation.zig").Interfaces,
|
|
||||||
Location,
|
Location,
|
||||||
MediaQueryList,
|
MediaQueryList,
|
||||||
@import("DataSet.zig"),
|
@import("DataSet.zig"),
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ pub const Location = struct {
|
|||||||
return self.url.get_origin(page);
|
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 {
|
pub fn _assign(_: *const Location, url: []const u8, page: *Page) !void {
|
||||||
return page.navigateFromWebAPI(url, .{ .reason = .script }, .{ .push = null });
|
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 Navigator = @import("navigator.zig").Navigator;
|
||||||
const History = @import("History.zig");
|
const History = @import("History.zig");
|
||||||
const Navigation = @import("Navigation.zig");
|
const Navigation = @import("../navigation/Navigation.zig");
|
||||||
const Location = @import("location.zig").Location;
|
const Location = @import("location.zig").Location;
|
||||||
const Crypto = @import("../crypto/crypto.zig").Crypto;
|
const Crypto = @import("../crypto/crypto.zig").Crypto;
|
||||||
const Console = @import("../console/console.zig").Console;
|
const Console = @import("../console/console.zig").Console;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const Interfaces = generate.Tuple(.{
|
|||||||
@import("../storage/storage.zig").Interfaces,
|
@import("../storage/storage.zig").Interfaces,
|
||||||
@import("../url/url.zig").Interfaces,
|
@import("../url/url.zig").Interfaces,
|
||||||
@import("../xhr/xhr.zig").Interfaces,
|
@import("../xhr/xhr.zig").Interfaces,
|
||||||
|
@import("../navigation/navigation.zig").Interfaces,
|
||||||
@import("../xhr/form_data.zig").Interfaces,
|
@import("../xhr/form_data.zig").Interfaces,
|
||||||
@import("../xhr/File.zig"),
|
@import("../xhr/File.zig"),
|
||||||
@import("../xmlserializer/xmlserializer.zig").Interfaces,
|
@import("../xmlserializer/xmlserializer.zig").Interfaces,
|
||||||
|
|||||||
@@ -32,122 +32,21 @@ const parser = @import("../netsurf.zig");
|
|||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation
|
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation
|
||||||
const Navigation = @This();
|
const Navigation = @This();
|
||||||
|
|
||||||
pub const Interfaces = .{
|
const NavigationKind = @import("navigation.zig").NavigationKind;
|
||||||
Navigation,
|
const NavigationHistoryEntry = @import("navigation.zig").NavigationHistoryEntry;
|
||||||
NavigationActivation,
|
const NavigationTransition = @import("navigation.zig").NavigationTransition;
|
||||||
NavigationTransition,
|
const NavigationEventTarget = @import("NavigationEventTarget.zig");
|
||||||
NavigationHistoryEntry,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const NavigationType = enum {
|
const NavigationCurrentEntryChangeEvent = @import("navigation.zig").NavigationCurrentEntryChangeEvent;
|
||||||
pub const ENUM_JS_USE_TAG = true;
|
|
||||||
|
|
||||||
push,
|
pub const prototype = *NavigationEventTarget;
|
||||||
replace,
|
proto: NavigationEventTarget = NavigationEventTarget{},
|
||||||
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 },
|
|
||||||
|
|
||||||
index: usize = 0,
|
index: usize = 0,
|
||||||
// Need to be stable pointers, because Events can reference entries.
|
// Need to be stable pointers, because Events can reference entries.
|
||||||
entries: std.ArrayListUnmanaged(*NavigationHistoryEntry) = .empty,
|
entries: std.ArrayListUnmanaged(*NavigationHistoryEntry) = .empty,
|
||||||
next_entry_id: usize = 0,
|
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 {
|
pub fn get_canGoBack(self: *const Navigation) bool {
|
||||||
return self.index > 0;
|
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);
|
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 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.
|
// 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 {
|
pub fn processNavigation(self: *Navigation, page: *Page) !void {
|
||||||
@@ -227,18 +116,18 @@ pub fn processNavigation(self: *Navigation, page: *Page) !void {
|
|||||||
entry.state = null;
|
entry.state = null;
|
||||||
},
|
},
|
||||||
.push => |state| {
|
.push => |state| {
|
||||||
_ = try self.pushEntry(url, state, page);
|
_ = try self.pushEntry(url, state, page, false);
|
||||||
},
|
},
|
||||||
.traverse, .reload => {},
|
.traverse, .reload => {},
|
||||||
}
|
}
|
||||||
} else {
|
} 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.
|
/// Pushes an entry into the Navigation stack WITHOUT actually navigating to it.
|
||||||
/// For that, use `navigate`.
|
/// 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 arena = page.session.arena;
|
||||||
|
|
||||||
const url = if (_url) |u| try arena.dupe(u8, u) else null;
|
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;
|
const previous = if (self.entries.items.len > 0) self.currentEntry() else null;
|
||||||
try self.entries.append(arena, entry);
|
try self.entries.append(arena, entry);
|
||||||
if (previous) |prev| {
|
if (previous) |prev| {
|
||||||
NavigationCurrentEntryChangeEvent.dispatch(prev, .push, page);
|
if (dispatch) {
|
||||||
|
NavigationCurrentEntryChangeEvent.dispatch(self, prev, .push);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.index = index;
|
self.index = index;
|
||||||
@@ -312,11 +203,12 @@ pub fn navigate(
|
|||||||
.push => |state| {
|
.push => |state| {
|
||||||
if (is_same_document) {
|
if (is_same_document) {
|
||||||
page.url = new_url;
|
page.url = new_url;
|
||||||
|
|
||||||
try committed.resolve({});
|
try committed.resolve({});
|
||||||
// todo: Fire navigate event
|
// todo: Fire navigate event
|
||||||
try finished.resolve({});
|
try finished.resolve({});
|
||||||
|
|
||||||
_ = try self.pushEntry(url, state, page);
|
_ = try self.pushEntry(url, state, page, true);
|
||||||
} else {
|
} else {
|
||||||
try page.navigateFromWebAPI(url, .{ .reason = .navigation }, kind);
|
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| {
|
if (opts.state) |state| {
|
||||||
const previous = entry;
|
const previous = entry;
|
||||||
entry.state = state.toJson(arena) catch return error.DataClone;
|
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);
|
return self.navigate(entry.url, .reload, page);
|
||||||
@@ -396,78 +288,5 @@ pub fn _updateCurrentEntry(self: *Navigation, options: UpdateCurrentEntryOptions
|
|||||||
|
|
||||||
const previous = self.currentEntry();
|
const previous = self.currentEntry();
|
||||||
self.currentEntry().state = options.state.toJson(arena) catch return error.DataClone;
|
self.currentEntry().state = options.state.toJson(arena) catch return error.DataClone;
|
||||||
NavigationCurrentEntryChangeEvent.dispatch(previous, null, page);
|
NavigationCurrentEntryChangeEvent.dispatch(self, previous, null);
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
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");
|
||||||
|
}
|
||||||
@@ -832,6 +832,7 @@ pub const EventTargetTBase = extern struct {
|
|||||||
message_port = 7,
|
message_port = 7,
|
||||||
screen = 8,
|
screen = 8,
|
||||||
screen_orientation = 9,
|
screen_orientation = 9,
|
||||||
|
navigation = 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
vtable: ?*const c.struct_dom_event_target_vtable = &c.struct_dom_event_target_vtable{
|
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));
|
var self: *Page = @ptrCast(@alignCast(ctx));
|
||||||
self.clearTransferArena();
|
self.clearTransferArena();
|
||||||
|
|
||||||
|
// We need to handle different navigation types differently.
|
||||||
|
try self.session.navigation.processNavigation(self);
|
||||||
|
|
||||||
switch (self.mode) {
|
switch (self.mode) {
|
||||||
.pre => {
|
.pre => {
|
||||||
// Received a response without a body like: https://httpbin.io/status/200
|
// Received a response without a body like: https://httpbin.io/status/200
|
||||||
@@ -815,9 +818,6 @@ pub const Page = struct {
|
|||||||
unreachable;
|
unreachable;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to handle different navigation types differently.
|
|
||||||
try self.session.navigation.processNavigation(self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pageErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
fn pageErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||||
@@ -1046,6 +1046,26 @@ pub const Page = struct {
|
|||||||
// specifically for this type of lifetime.
|
// specifically for this type of lifetime.
|
||||||
pub fn navigateFromWebAPI(self: *Page, url: []const u8, opts: NavigateOpts, kind: NavigationKind) !void {
|
pub fn navigateFromWebAPI(self: *Page, url: []const u8, opts: NavigateOpts, kind: NavigationKind) !void {
|
||||||
const session = self.session;
|
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) {
|
if (session.queued_navigation != null) {
|
||||||
// It might seem like this should never happen. And it might not,
|
// It might seem like this should never happen. And it might not,
|
||||||
// BUT..consider the case where we have script like:
|
// BUT..consider the case where we have script like:
|
||||||
@@ -1190,6 +1210,7 @@ pub const NavigateOpts = struct {
|
|||||||
method: Http.Method = .GET,
|
method: Http.Method = .GET,
|
||||||
body: ?[]const u8 = null,
|
body: ?[]const u8 = null,
|
||||||
header: ?[:0]const u8 = null,
|
header: ?[:0]const u8 = null,
|
||||||
|
force: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const IdleNotification = union(enum) {
|
const IdleNotification = union(enum) {
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const js = @import("js/js.zig");
|
const js = @import("js/js.zig");
|
||||||
const Page = @import("page.zig").Page;
|
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 Browser = @import("browser.zig").Browser;
|
||||||
const NavigateOpts = @import("page.zig").NavigateOpts;
|
const NavigateOpts = @import("page.zig").NavigateOpts;
|
||||||
const History = @import("html/History.zig");
|
const History = @import("html/History.zig");
|
||||||
const Navigation = @import("html/Navigation.zig");
|
const Navigation = @import("navigation/Navigation.zig");
|
||||||
|
|
||||||
const log = @import("../log.zig");
|
const log = @import("../log.zig");
|
||||||
const parser = @import("netsurf.zig");
|
const parser = @import("netsurf.zig");
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
const currentIndex = navigation.currentEntry.index;
|
const currentIndex = navigation.currentEntry.index;
|
||||||
|
|
||||||
navigation.navigate(
|
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 } }
|
{ state: { currentIndex: currentIndex, navTestInProgress: true } }
|
||||||
);
|
);
|
||||||
</script>
|
</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