mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
add functional Navigation
This commit is contained in:
@@ -20,18 +20,18 @@ const std = @import("std");
|
|||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
const URL = @import("../../url.zig").URL;
|
const URL = @import("../../url.zig").URL;
|
||||||
|
|
||||||
const Js = @import("../js/js.zig");
|
const js = @import("../js/js.zig");
|
||||||
const Page = @import("../page.zig").Page;
|
const Page = @import("../page.zig").Page;
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation
|
|
||||||
const Navigation = @This();
|
|
||||||
|
|
||||||
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||||
const EventHandler = @import("../events/event.zig").EventHandler;
|
const EventHandler = @import("../events/event.zig").EventHandler;
|
||||||
|
|
||||||
const parser = @import("../netsurf.zig");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const Interfaces = .{
|
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation
|
||||||
|
const Navigation = @This();
|
||||||
|
|
||||||
|
pub const Interfaces = .{
|
||||||
Navigation,
|
Navigation,
|
||||||
NavigationActivation,
|
NavigationActivation,
|
||||||
NavigationHistoryEntry,
|
NavigationHistoryEntry,
|
||||||
@@ -51,39 +51,76 @@ const NavigationHistoryEntry = struct {
|
|||||||
base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain },
|
base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain },
|
||||||
|
|
||||||
id: []const u8,
|
id: []const u8,
|
||||||
index: usize,
|
|
||||||
key: []const u8,
|
key: []const u8,
|
||||||
url: ?[]const u8,
|
url: ?[]const u8,
|
||||||
same_document: bool,
|
|
||||||
state: ?[]const u8,
|
state: ?[]const u8,
|
||||||
|
|
||||||
pub fn get_id(self: *const NavigationHistoryEntry) []const u8 {
|
pub fn get_id(self: *const NavigationHistoryEntry) []const u8 {
|
||||||
return self.id;
|
return self.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_index(self: *const NavigationHistoryEntry) usize {
|
pub fn get_index(self: *const NavigationHistoryEntry, page: *Page) i32 {
|
||||||
return self.index;
|
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 {
|
pub fn get_key(self: *const NavigationHistoryEntry) []const u8 {
|
||||||
return self.key;
|
return self.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sameDocument(self: *const NavigationHistoryEntry) bool {
|
pub fn get_sameDocument(self: *const NavigationHistoryEntry, page: *Page) !bool {
|
||||||
return self.same_document;
|
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 {
|
pub fn get_url(self: *const NavigationHistoryEntry) ?[]const u8 {
|
||||||
return self.url;
|
return self.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _getState(self: *const NavigationHistoryEntry, page: *Page) !?Js.Value {
|
pub fn _getState(self: *const NavigationHistoryEntry, page: *Page) !?js.Value {
|
||||||
if (self.state) |state| {
|
if (self.state) |state| {
|
||||||
return try Js.Value.fromJson(page.main_context, state);
|
return try js.Value.fromJson(page.js, state);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn navigate(entry: NavigationHistoryEntry, reload: enum { none, force }, page: *Page) !NavigationReturn {
|
||||||
|
const arena = page.session.arena;
|
||||||
|
const url = entry.url orelse return error.MissingURL;
|
||||||
|
|
||||||
|
// https://github.com/WICG/navigation-api/issues/95
|
||||||
|
//
|
||||||
|
// These will only settle on same-origin navigation (mostly intended for SPAs).
|
||||||
|
// It is fine (and expected) for these to not settle on cross-origin requests :)
|
||||||
|
const committed = try page.js.createPromiseResolver(.page);
|
||||||
|
const finished = try page.js.createPromiseResolver(.page);
|
||||||
|
|
||||||
|
const new_url = try URL.parse(url, null);
|
||||||
|
if (try page.url.eqlDocument(&new_url, arena) or reload == .force) {
|
||||||
|
page.url = new_url;
|
||||||
|
try committed.resolve({});
|
||||||
|
|
||||||
|
// todo: Fire navigate event
|
||||||
|
|
||||||
|
try finished.resolve({});
|
||||||
|
} else {
|
||||||
|
// TODO: Change to history
|
||||||
|
try page.navigateFromWebAPI(url, .{ .reason = .history });
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.committed = committed.promise(),
|
||||||
|
.finished = finished.promise(),
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/NavigationActivation
|
// https://developer.mozilla.org/en-US/docs/Web/API/NavigationActivation
|
||||||
@@ -124,49 +161,61 @@ pub fn get_canGoForward(self: *const Navigation) bool {
|
|||||||
return self.entries.items.len > self.index + 1;
|
return self.entries.items.len > self.index + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_currentEntry(_: *const Navigation) NavigationHistoryEntry {
|
pub fn currentEntry(self: *Navigation) *NavigationHistoryEntry {
|
||||||
// TODO
|
return &self.entries.items[self.index];
|
||||||
unreachable;
|
}
|
||||||
|
|
||||||
|
pub fn get_currentEntry(self: *const Navigation) NavigationHistoryEntry {
|
||||||
|
return self.entries.items[self.index];
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavigationReturn = struct {
|
const NavigationReturn = struct {
|
||||||
comitted: Js.Promise,
|
committed: js.Promise,
|
||||||
finished: Js.Promise,
|
finished: js.Promise,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn _back(_: *const Navigation) !NavigationReturn {
|
pub fn _back(self: *Navigation, page: *Page) !NavigationReturn {
|
||||||
unreachable;
|
if (!self.get_canGoBack()) {
|
||||||
|
return error.InvalidStateError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const new_index = self.index - 1;
|
||||||
|
const next_entry = self.entries.items[new_index];
|
||||||
|
self.index = new_index;
|
||||||
|
|
||||||
|
return next_entry.navigate(.none, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _entries(self: *const Navigation) []NavigationHistoryEntry {
|
pub fn _entries(self: *const Navigation) []NavigationHistoryEntry {
|
||||||
return self.entries.items;
|
return self.entries.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _forward(_: *const Navigation) !NavigationReturn {
|
pub fn _forward(self: *Navigation, page: *Page) !NavigationReturn {
|
||||||
unreachable;
|
if (!self.get_canGoForward()) {
|
||||||
|
return error.InvalidStateError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const new_index = self.index + 1;
|
||||||
|
const next_entry = self.entries.items[new_index];
|
||||||
|
self.index = new_index;
|
||||||
|
|
||||||
|
return next_entry.navigate(.none, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavigateOptions = struct {
|
/// Pushes an entry into the Navigation stack WITHOUT actually navigating to it.
|
||||||
const NavigateOptionsHistory = enum {
|
/// For that, use `navigate`.
|
||||||
auto,
|
pub fn pushEntry(self: *Navigation, _url: ?[]const u8, _opts: ?NavigateOptions, page: *Page) !NavigationHistoryEntry {
|
||||||
push,
|
|
||||||
replace,
|
|
||||||
};
|
|
||||||
|
|
||||||
state: ?Js.Object = null,
|
|
||||||
info: ?Js.Object = null,
|
|
||||||
history: NavigateOptionsHistory = .auto,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, page: *Page) !NavigationReturn {
|
|
||||||
const arena = page.session.arena;
|
const arena = page.session.arena;
|
||||||
|
|
||||||
const options = _opts orelse NavigateOptions{};
|
const options = _opts orelse NavigateOptions{};
|
||||||
const url = try arena.dupe(u8, _url);
|
const url = if (_url) |u| try arena.dupe(u8, u) else null;
|
||||||
|
|
||||||
// TODO: handle push history NotSupportedError.
|
// truncates our history here.
|
||||||
|
if (self.entries.items.len > self.index + 1) {
|
||||||
|
self.entries.shrinkRetainingCapacity(self.index + 1);
|
||||||
|
}
|
||||||
|
self.index = self.entries.items.len;
|
||||||
|
|
||||||
const index = self.entries.items.len;
|
|
||||||
const id = self.next_entry_id;
|
const id = self.next_entry_id;
|
||||||
self.next_entry_id += 1;
|
self.next_entry_id += 1;
|
||||||
|
|
||||||
@@ -174,7 +223,7 @@ pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, p
|
|||||||
|
|
||||||
const state: ?[]const u8 = blk: {
|
const state: ?[]const u8 = blk: {
|
||||||
if (options.state) |s| {
|
if (options.state) |s| {
|
||||||
break :blk try s.toJson(arena);
|
break :blk s.toJson(arena) catch return error.DataClone;
|
||||||
} else {
|
} else {
|
||||||
break :blk null;
|
break :blk null;
|
||||||
}
|
}
|
||||||
@@ -182,40 +231,70 @@ pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, p
|
|||||||
|
|
||||||
const entry = NavigationHistoryEntry{
|
const entry = NavigationHistoryEntry{
|
||||||
.id = id_str,
|
.id = id_str,
|
||||||
.index = index,
|
|
||||||
.same_document = false,
|
|
||||||
.url = url,
|
|
||||||
.key = id_str,
|
.key = id_str,
|
||||||
|
.url = url,
|
||||||
.state = state,
|
.state = state,
|
||||||
};
|
};
|
||||||
|
|
||||||
try self.entries.append(arena, entry);
|
try self.entries.append(arena, entry);
|
||||||
|
|
||||||
// https://github.com/WICG/navigation-api/issues/95
|
return entry;
|
||||||
//
|
|
||||||
// These will only settle on same-origin navigation (mostly intended for SPAs).
|
|
||||||
// It is fine (and expected) for these to not settle on cross-origin requests :)
|
|
||||||
const committed = try page.main_context.createPersistentPromiseResolver(.page);
|
|
||||||
const finished = try page.main_context.createPersistentPromiseResolver(.page);
|
|
||||||
|
|
||||||
if (entry.same_document) {
|
|
||||||
page.url = try URL.parse(url, null);
|
|
||||||
try committed.resolve(void);
|
|
||||||
|
|
||||||
// todo: Fire navigate event
|
|
||||||
//
|
|
||||||
|
|
||||||
} else {
|
|
||||||
page.navigateFromWebAPI(url, .{ .reason = .navigation });
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.comitted = committed,
|
|
||||||
.finished = finished,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// const testing = @import("../../testing.zig");
|
const NavigateOptions = struct {
|
||||||
// test "Browser: Navigation" {
|
const NavigateOptionsHistory = enum {
|
||||||
// try testing.htmlRunner("html/navigation.html");
|
pub const ENUM_JS_USE_TAG = true;
|
||||||
// }
|
|
||||||
|
auto,
|
||||||
|
push,
|
||||||
|
replace,
|
||||||
|
};
|
||||||
|
|
||||||
|
state: ?js.Object = null,
|
||||||
|
info: ?js.Object = null,
|
||||||
|
history: NavigateOptionsHistory = .auto,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, page: *Page) !NavigationReturn {
|
||||||
|
const entry = try self.pushEntry(_url, _opts, page);
|
||||||
|
return entry.navigate(.none, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ReloadOptions = struct {
|
||||||
|
state: ?js.Object = null,
|
||||||
|
info: ?js.Object = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn _reload(self: *Navigation, _opts: ?ReloadOptions, page: *Page) !NavigationReturn {
|
||||||
|
const arena = page.session.arena;
|
||||||
|
|
||||||
|
const opts = _opts orelse ReloadOptions{};
|
||||||
|
const entry = self.currentEntry();
|
||||||
|
if (opts.state) |state| {
|
||||||
|
entry.state = state.toJson(arena) catch return error.DataClone;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.navigate(.force, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _transition(_: *const Navigation) !NavigationReturn {
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _traverseTo(_: *const Navigation, _: []const u8) !NavigationReturn {
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const UpdateCurrentEntryOptions = struct {
|
||||||
|
state: js.Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn _updateCurrentEntry(self: *Navigation, options: UpdateCurrentEntryOptions, page: *Page) !void {
|
||||||
|
const arena = page.session.arena;
|
||||||
|
self.currentEntry().state = options.state.toJson(arena) catch return error.DataClone;
|
||||||
|
}
|
||||||
|
|
||||||
|
const testing = @import("../../testing.zig");
|
||||||
|
test "Browser: Navigation" {
|
||||||
|
try testing.htmlRunner("html/navigation.html");
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ pub const Interfaces = .{
|
|||||||
Window,
|
Window,
|
||||||
Navigator,
|
Navigator,
|
||||||
History,
|
History,
|
||||||
|
@import("Navigation.zig").Interfaces,
|
||||||
Location,
|
Location,
|
||||||
MediaQueryList,
|
MediaQueryList,
|
||||||
@import("DataSet.zig"),
|
@import("DataSet.zig"),
|
||||||
|
|||||||
@@ -25,6 +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 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;
|
||||||
@@ -190,6 +191,10 @@ pub const Window = struct {
|
|||||||
return &page.session.history;
|
return &page.session.history;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_navigation(_: *Window, page: *Page) *Navigation {
|
||||||
|
return &page.session.navigation;
|
||||||
|
}
|
||||||
|
|
||||||
// The interior height of the window in pixels, including the height of the horizontal scroll bar, if present.
|
// The interior height of the window in pixels, including the height of the horizontal scroll bar, if present.
|
||||||
pub fn get_innerHeight(_: *Window, page: *Page) u32 {
|
pub fn get_innerHeight(_: *Window, page: *Page) u32 {
|
||||||
// We do not have scrollbars or padding so this is the same as Element.clientHeight
|
// We do not have scrollbars or padding so this is the same as Element.clientHeight
|
||||||
|
|||||||
Reference in New Issue
Block a user