mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
fix navigation and related tests
This commit is contained in:
@@ -81,7 +81,9 @@ fn _dispatchPopStateEvent(state: ?[]const u8, page: *Page) !void {
|
|||||||
pub fn _pushState(_: *const History, state: js.Object, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
|
pub fn _pushState(_: *const History, state: js.Object, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
|
||||||
const arena = page.session.arena;
|
const arena = page.session.arena;
|
||||||
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);
|
||||||
_ = try page.session.navigation.pushEntry(url, .{ .state = state }, page);
|
|
||||||
|
const json = state.toJson(arena) catch return error.DataClone;
|
||||||
|
_ = try page.session.navigation.pushEntry(url, json, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -113,7 +115,7 @@ pub fn go(_: *const History, delta: i32, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = try entry.navigate(page, .force);
|
_ = try page.session.navigation.navigate(entry.url, .{ .traverse = index }, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _go(self: *History, _delta: ?i32, page: *Page) !void {
|
pub fn _go(self: *History, _delta: ?i32, page: *Page) !void {
|
||||||
|
|||||||
@@ -34,16 +34,24 @@ const Navigation = @This();
|
|||||||
pub const Interfaces = .{
|
pub const Interfaces = .{
|
||||||
Navigation,
|
Navigation,
|
||||||
NavigationActivation,
|
NavigationActivation,
|
||||||
|
NavigationTransition,
|
||||||
NavigationHistoryEntry,
|
NavigationHistoryEntry,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const NavigationKind = union(enum) {
|
||||||
|
initial,
|
||||||
|
push: ?[]const u8,
|
||||||
|
replace,
|
||||||
|
traverse: usize,
|
||||||
|
reload,
|
||||||
|
};
|
||||||
|
|
||||||
pub const prototype = *EventTarget;
|
pub const prototype = *EventTarget;
|
||||||
base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain },
|
base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain },
|
||||||
|
|
||||||
index: usize = 0,
|
index: usize = 0,
|
||||||
entries: std.ArrayListUnmanaged(NavigationHistoryEntry) = .empty,
|
entries: std.ArrayListUnmanaged(NavigationHistoryEntry) = .empty,
|
||||||
next_entry_id: usize = 0,
|
next_entry_id: usize = 0,
|
||||||
// TODO: key->index mapping
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry
|
// https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry
|
||||||
const NavigationHistoryEntry = struct {
|
const NavigationHistoryEntry = struct {
|
||||||
@@ -91,49 +99,17 @@ const NavigationHistoryEntry = struct {
|
|||||||
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
|
||||||
const NavigationActivation = struct {
|
const NavigationActivation = struct {
|
||||||
const NavigationActivationType = enum {
|
const NavigationActivationType = enum {
|
||||||
|
pub const ENUM_JS_USE_TAG = true;
|
||||||
|
|
||||||
push,
|
push,
|
||||||
reload,
|
reload,
|
||||||
replace,
|
replace,
|
||||||
traverse,
|
traverse,
|
||||||
|
|
||||||
pub fn toString(self: NavigationActivationType) []const u8 {
|
|
||||||
return @tagName(self);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
entry: NavigationHistoryEntry,
|
entry: NavigationHistoryEntry,
|
||||||
@@ -153,6 +129,13 @@ const NavigationActivation = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
@@ -169,6 +152,11 @@ pub fn get_currentEntry(self: *const Navigation) NavigationHistoryEntry {
|
|||||||
return self.entries.items[self.index];
|
return self.entries.items[self.index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_transition(_: *const Navigation) ?NavigationTransition {
|
||||||
|
// For now, all transitions are just considered complete.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const NavigationReturn = struct {
|
const NavigationReturn = struct {
|
||||||
committed: js.Promise,
|
committed: js.Promise,
|
||||||
finished: js.Promise,
|
finished: js.Promise,
|
||||||
@@ -183,7 +171,7 @@ pub fn _back(self: *Navigation, page: *Page) !NavigationReturn {
|
|||||||
const next_entry = self.entries.items[new_index];
|
const next_entry = self.entries.items[new_index];
|
||||||
self.index = new_index;
|
self.index = new_index;
|
||||||
|
|
||||||
return next_entry.navigate(.none, page);
|
return self.navigate(next_entry.url, .{ .traverse = new_index }, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _entries(self: *const Navigation) []NavigationHistoryEntry {
|
pub fn _entries(self: *const Navigation) []NavigationHistoryEntry {
|
||||||
@@ -199,15 +187,33 @@ pub fn _forward(self: *Navigation, page: *Page) !NavigationReturn {
|
|||||||
const next_entry = self.entries.items[new_index];
|
const next_entry = self.entries.items[new_index];
|
||||||
self.index = new_index;
|
self.index = new_index;
|
||||||
|
|
||||||
return next_entry.navigate(.none, page);
|
return self.navigate(next_entry.url, .{ .traverse = new_index }, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for after true navigation processing, where we need to ensure that our entries are up to date.
|
||||||
|
pub fn processNavigation(self: *Navigation, url: []const u8, kind: NavigationKind, page: *Page) !void {
|
||||||
|
switch (kind) {
|
||||||
|
.initial => {
|
||||||
|
_ = try self.pushEntry(url, null, page);
|
||||||
|
},
|
||||||
|
.replace => {
|
||||||
|
// When replacing, we just update the URL but the state is nullified.
|
||||||
|
const entry = self.currentEntry();
|
||||||
|
entry.url = url;
|
||||||
|
entry.state = null;
|
||||||
|
},
|
||||||
|
.push => |state| {
|
||||||
|
_ = try self.pushEntry(url, state, page);
|
||||||
|
},
|
||||||
|
.traverse, .reload => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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, _opts: ?NavigateOptions, page: *Page) !NavigationHistoryEntry {
|
pub fn pushEntry(self: *Navigation, _url: ?[]const u8, state: ?[]const u8, page: *Page) !NavigationHistoryEntry {
|
||||||
const arena = page.session.arena;
|
const arena = page.session.arena;
|
||||||
|
|
||||||
const options = _opts orelse NavigateOptions{};
|
|
||||||
const url = if (_url) |u| try arena.dupe(u8, u) else null;
|
const url = if (_url) |u| try arena.dupe(u8, u) else null;
|
||||||
|
|
||||||
// truncates our history here.
|
// truncates our history here.
|
||||||
@@ -221,14 +227,6 @@ pub fn pushEntry(self: *Navigation, _url: ?[]const u8, _opts: ?NavigateOptions,
|
|||||||
|
|
||||||
const id_str = try std.fmt.allocPrint(arena, "{d}", .{id});
|
const id_str = try std.fmt.allocPrint(arena, "{d}", .{id});
|
||||||
|
|
||||||
const state: ?[]const u8 = blk: {
|
|
||||||
if (options.state) |s| {
|
|
||||||
break :blk s.toJson(arena) catch return error.DataClone;
|
|
||||||
} else {
|
|
||||||
break :blk null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const entry = NavigationHistoryEntry{
|
const entry = NavigationHistoryEntry{
|
||||||
.id = id_str,
|
.id = id_str,
|
||||||
.key = id_str,
|
.key = id_str,
|
||||||
@@ -237,7 +235,6 @@ pub fn pushEntry(self: *Navigation, _url: ?[]const u8, _opts: ?NavigateOptions,
|
|||||||
};
|
};
|
||||||
|
|
||||||
try self.entries.append(arena, entry);
|
try self.entries.append(arena, entry);
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,9 +252,67 @@ const NavigateOptions = struct {
|
|||||||
history: NavigateOptionsHistory = .auto,
|
history: NavigateOptionsHistory = .auto,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn navigate(
|
||||||
|
self: *Navigation,
|
||||||
|
_url: ?[]const u8,
|
||||||
|
kind: NavigationKind,
|
||||||
|
page: *Page,
|
||||||
|
) !NavigationReturn {
|
||||||
|
const arena = page.session.arena;
|
||||||
|
const url = _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);
|
||||||
|
const is_same_document = try page.url.eqlDocument(&new_url, arena);
|
||||||
|
|
||||||
|
switch (kind) {
|
||||||
|
.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);
|
||||||
|
} else {
|
||||||
|
try page.navigateFromWebAPI(url, .{ .reason = .navigation }, kind);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.traverse => |index| {
|
||||||
|
self.index = index;
|
||||||
|
|
||||||
|
if (is_same_document) {
|
||||||
|
page.url = new_url;
|
||||||
|
|
||||||
|
try committed.resolve({});
|
||||||
|
// todo: Fire navigate event
|
||||||
|
try finished.resolve({});
|
||||||
|
} else {
|
||||||
|
try page.navigateFromWebAPI(url, .{ .reason = .navigation }, kind);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.reload => {
|
||||||
|
try page.navigateFromWebAPI(url, .{ .reason = .navigation }, kind);
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.committed = committed.promise(),
|
||||||
|
.finished = finished.promise(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, page: *Page) !NavigationReturn {
|
pub fn _navigate(self: *Navigation, _url: []const u8, _opts: ?NavigateOptions, page: *Page) !NavigationReturn {
|
||||||
const entry = try self.pushEntry(_url, _opts, page);
|
const opts = _opts orelse NavigateOptions{};
|
||||||
return entry.navigate(.none, page);
|
const json = if (opts.state) |state| state.toJson(page.session.arena) catch return error.DataClone else null;
|
||||||
|
return try self.navigate(_url, .{ .push = json }, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ReloadOptions = struct {
|
pub const ReloadOptions = struct {
|
||||||
@@ -274,15 +329,23 @@ pub fn _reload(self: *Navigation, _opts: ?ReloadOptions, page: *Page) !Navigatio
|
|||||||
entry.state = state.toJson(arena) catch return error.DataClone;
|
entry.state = state.toJson(arena) catch return error.DataClone;
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry.navigate(.force, page);
|
return self.navigate(entry.url, .reload, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _transition(_: *const Navigation) !NavigationReturn {
|
pub const TraverseToOptions = struct {
|
||||||
unreachable;
|
info: ?js.Object = null,
|
||||||
}
|
};
|
||||||
|
|
||||||
pub fn _traverseTo(_: *const Navigation, _: []const u8) !NavigationReturn {
|
pub fn _traverseTo(self: *Navigation, key: []const u8, _: ?TraverseToOptions, page: *Page) !NavigationReturn {
|
||||||
unreachable;
|
// const opts = _opts orelse TraverseToOptions{};
|
||||||
|
|
||||||
|
for (self.entries.items, 0..) |entry, i| {
|
||||||
|
if (std.mem.eql(u8, key, entry.key)) {
|
||||||
|
return try self.navigate(entry.url, .{ .traverse = i }, page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.InvalidStateError;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const UpdateCurrentEntryOptions = struct {
|
pub const UpdateCurrentEntryOptions = struct {
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ pub const HTMLDocument = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_location(_: *const parser.DocumentHTML, url: []const u8, page: *Page) !void {
|
pub fn set_location(_: *const parser.DocumentHTML, url: []const u8, page: *Page) !void {
|
||||||
return page.navigateFromWebAPI(url, .{ .reason = .script });
|
return page.navigateFromWebAPI(url, .{ .reason = .script }, .{ .push = null });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_designMode(_: *parser.DocumentHTML) []const u8 {
|
pub fn get_designMode(_: *parser.DocumentHTML) []const u8 {
|
||||||
|
|||||||
@@ -74,15 +74,15 @@ pub const Location = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 });
|
return page.navigateFromWebAPI(url, .{ .reason = .script }, .{ .push = null });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _replace(_: *const Location, url: []const u8, page: *Page) !void {
|
pub fn _replace(_: *const Location, url: []const u8, page: *Page) !void {
|
||||||
return page.navigateFromWebAPI(url, .{ .reason = .script });
|
return page.navigateFromWebAPI(url, .{ .reason = .script }, .replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _reload(_: *const Location, page: *Page) !void {
|
pub fn _reload(_: *const Location, page: *Page) !void {
|
||||||
return page.navigateFromWebAPI(page.url.raw, .{ .reason = .script });
|
return page.navigateFromWebAPI(page.url.raw, .{ .reason = .script }, .reload);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _toString(self: *Location, page: *Page) ![]const u8 {
|
pub fn _toString(self: *Location, page: *Page) ![]const u8 {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ pub const Window = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_location(_: *const Window, url: []const u8, page: *Page) !void {
|
pub fn set_location(_: *const Window, url: []const u8, page: *Page) !void {
|
||||||
return page.navigateFromWebAPI(url, .{ .reason = .script });
|
return page.navigateFromWebAPI(url, .{ .reason = .script }, .{ .push = null });
|
||||||
}
|
}
|
||||||
|
|
||||||
// frames return the window itself, but accessing it via a pseudo
|
// frames return the window itself, but accessing it via a pseudo
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const Http = @import("../http/Http.zig");
|
|||||||
const ScriptManager = @import("ScriptManager.zig");
|
const ScriptManager = @import("ScriptManager.zig");
|
||||||
const SlotChangeMonitor = @import("SlotChangeMonitor.zig");
|
const SlotChangeMonitor = @import("SlotChangeMonitor.zig");
|
||||||
const HTMLDocument = @import("html/document.zig").HTMLDocument;
|
const HTMLDocument = @import("html/document.zig").HTMLDocument;
|
||||||
|
const NavigationKind = @import("html/Navigation.zig").NavigationKind;
|
||||||
|
|
||||||
const js = @import("js/js.zig");
|
const js = @import("js/js.zig");
|
||||||
const URL = @import("../url.zig").URL;
|
const URL = @import("../url.zig").URL;
|
||||||
@@ -815,8 +816,8 @@ pub const Page = struct {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the navigation after a successful load.
|
// We need to handle different navigation types differently.
|
||||||
_ = try self.session.navigation.pushEntry(self.url.raw, null, self);
|
try self.session.navigation.processNavigation(self.url.raw, self.session.navigation_kind, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pageErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
fn pageErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||||
@@ -906,7 +907,7 @@ pub const Page = struct {
|
|||||||
.a => {
|
.a => {
|
||||||
const element: *parser.Element = @ptrCast(node);
|
const element: *parser.Element = @ptrCast(node);
|
||||||
const href = (try parser.elementGetAttribute(element, "href")) orelse return;
|
const href = (try parser.elementGetAttribute(element, "href")) orelse return;
|
||||||
try self.navigateFromWebAPI(href, .{});
|
try self.navigateFromWebAPI(href, .{}, .{ .push = null });
|
||||||
},
|
},
|
||||||
.input => {
|
.input => {
|
||||||
const element: *parser.Element = @ptrCast(node);
|
const element: *parser.Element = @ptrCast(node);
|
||||||
@@ -1043,7 +1044,7 @@ pub const Page = struct {
|
|||||||
// As such we schedule the function to be called as soon as possible.
|
// As such we schedule the function to be called as soon as possible.
|
||||||
// The page.arena is safe to use here, but the transfer_arena exists
|
// The page.arena is safe to use here, but the transfer_arena exists
|
||||||
// specifically for this type of lifetime.
|
// specifically for this type of lifetime.
|
||||||
pub fn navigateFromWebAPI(self: *Page, url: []const u8, opts: NavigateOpts) !void {
|
pub fn navigateFromWebAPI(self: *Page, url: []const u8, opts: NavigateOpts, kind: NavigationKind) !void {
|
||||||
const session = self.session;
|
const session = self.session;
|
||||||
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,
|
||||||
@@ -1070,6 +1071,8 @@ pub const Page = struct {
|
|||||||
.url = try URL.stitch(session.transfer_arena, url, self.url.raw, .{ .alloc = .always }),
|
.url = try URL.stitch(session.transfer_arena, url, self.url.raw, .{ .alloc = .always }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
session.navigation_kind = kind;
|
||||||
|
|
||||||
self.http_client.abort();
|
self.http_client.abort();
|
||||||
|
|
||||||
// In v8, this throws an exception which JS code cannot catch.
|
// In v8, this throws an exception which JS code cannot catch.
|
||||||
@@ -1120,7 +1123,7 @@ pub const Page = struct {
|
|||||||
} else {
|
} else {
|
||||||
action = try URL.concatQueryString(transfer_arena, action, buf.items);
|
action = try URL.concatQueryString(transfer_arena, action, buf.items);
|
||||||
}
|
}
|
||||||
try self.navigateFromWebAPI(action, opts);
|
try self.navigateFromWebAPI(action, opts, .{ .push = null });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isNodeAttached(self: *const Page, node: *parser.Node) bool {
|
pub fn isNodeAttached(self: *const Page, node: *parser.Node) bool {
|
||||||
@@ -1178,6 +1181,7 @@ pub const NavigateReason = enum {
|
|||||||
form,
|
form,
|
||||||
script,
|
script,
|
||||||
history,
|
history,
|
||||||
|
navigation,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const NavigateOpts = struct {
|
pub const NavigateOpts = struct {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ 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 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");
|
||||||
@@ -59,6 +60,7 @@ pub const Session = struct {
|
|||||||
// https://developer.mozilla.org/en-US/docs/Web/API/History
|
// https://developer.mozilla.org/en-US/docs/Web/API/History
|
||||||
history: History = .{},
|
history: History = .{},
|
||||||
navigation: Navigation = .{},
|
navigation: Navigation = .{},
|
||||||
|
navigation_kind: NavigationKind = .initial,
|
||||||
|
|
||||||
page: ?Page = null,
|
page: ?Page = null,
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
var cdp = bc.cdp;
|
var cdp = bc.cdp;
|
||||||
const reason_: ?[]const u8 = switch (event.opts.reason) {
|
const reason_: ?[]const u8 = switch (event.opts.reason) {
|
||||||
.anchor => "anchorClick",
|
.anchor => "anchorClick",
|
||||||
.script, .history => "scriptInitiated",
|
.script, .history, .navigation => "scriptInitiated",
|
||||||
.form => switch (event.opts.method) {
|
.form => switch (event.opts.method) {
|
||||||
.GET => "formSubmissionGet",
|
.GET => "formSubmissionGet",
|
||||||
.POST => "formSubmissionPost",
|
.POST => "formSubmissionPost",
|
||||||
|
|||||||
@@ -402,19 +402,13 @@ pub fn htmlRunner(file: []const u8) !void {
|
|||||||
|
|
||||||
const url = try std.fmt.allocPrint(arena_allocator, "http://localhost:9582/src/tests/{s}", .{file});
|
const url = try std.fmt.allocPrint(arena_allocator, "http://localhost:9582/src/tests/{s}", .{file});
|
||||||
try page.navigate(url, .{});
|
try page.navigate(url, .{});
|
||||||
_ = page.wait(2000);
|
test_session.fetchWait(2000);
|
||||||
|
|
||||||
// page exits more aggressively in tests. We want to make sure this is called
|
// page exits more aggressively in tests. We want to make sure this is called
|
||||||
// at lease once.
|
// at lease once.
|
||||||
page.session.browser.runMicrotasks();
|
page.session.browser.runMicrotasks();
|
||||||
page.session.browser.runMessageLoop();
|
page.session.browser.runMessageLoop();
|
||||||
|
|
||||||
const needs_second_wait = try js_context.exec("testing._onPageWait.length > 0", "check_onPageWait");
|
|
||||||
if (needs_second_wait.value.toBool(page.js.isolate)) {
|
|
||||||
// sets the isSecondWait flag in testing.
|
|
||||||
_ = js_context.exec("testing._isSecondWait = true", "set_second_wait_flag") catch {};
|
|
||||||
_ = page.wait(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@import("root").js_runner_duration += std.time.Instant.since(try std.time.Instant.now(), start);
|
@import("root").js_runner_duration += std.time.Instant.since(try std.time.Instant.now(), start);
|
||||||
|
|
||||||
const value = js_context.exec("testing.getStatus()", "testing.getStatus()") catch |err| {
|
const value = js_context.exec("testing.getStatus()", "testing.getStatus()") catch |err| {
|
||||||
|
|||||||
@@ -11,10 +11,12 @@
|
|||||||
testing.expectEqual('auto', history.scrollRestoration);
|
testing.expectEqual('auto', history.scrollRestoration);
|
||||||
testing.expectEqual(null, history.state)
|
testing.expectEqual(null, history.state)
|
||||||
|
|
||||||
history.pushState({ testInProgress: true }, null, 'http://127.0.0.1:9582/xhr/json');
|
history.pushState({ testInProgress: true }, null, 'http://127.0.0.1:9582/src/tests/html/history2.html');
|
||||||
testing.expectEqual({ testInProgress: true }, history.state);
|
testing.expectEqual({ testInProgress: true }, history.state);
|
||||||
|
|
||||||
|
history.pushState({ testInProgress: false }, null, 'http://127.0.0.1:9582/xhr/json');
|
||||||
history.replaceState({ "new": "field", testComplete: true }, null);
|
history.replaceState({ "new": "field", testComplete: true }, null);
|
||||||
|
|
||||||
let state = { "new": "field", testComplete: true };
|
let state = { "new": "field", testComplete: true };
|
||||||
testing.expectEqual(state, history.state);
|
testing.expectEqual(state, history.state);
|
||||||
|
|
||||||
@@ -31,10 +33,5 @@
|
|||||||
testing.expectEqual(state, popstateEventState);
|
testing.expectEqual(state, popstateEventState);
|
||||||
})
|
})
|
||||||
|
|
||||||
testing.onPageWait(() => {
|
history.back();
|
||||||
testing.expectEqual(true, history.state && history.state.testComplete);
|
|
||||||
testing.expectEqual(state, history.state);
|
|
||||||
});
|
|
||||||
|
|
||||||
testing.expectEqual(undefined, history.go());
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
6
src/tests/html/history2.html
Normal file
6
src/tests/html/history2.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
|
<script id=history2>
|
||||||
|
testing.expectEqual(true, history.state && history.state.testInProgress);
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<script src="../testing.js"></script>
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
<script id=navigation>
|
<script id=navigation>
|
||||||
testing.expectEqual('object', typeof navigation);
|
testing.expectEqual('object', typeof navigation);
|
||||||
testing.expectEqual('object', typeof navigation.currentEntry);
|
testing.expectEqual('object', typeof navigation.currentEntry);
|
||||||
@@ -7,43 +8,11 @@
|
|||||||
testing.expectEqual('string', typeof navigation.currentEntry.id);
|
testing.expectEqual('string', typeof navigation.currentEntry.id);
|
||||||
testing.expectEqual('string', typeof navigation.currentEntry.key);
|
testing.expectEqual('string', typeof navigation.currentEntry.key);
|
||||||
testing.expectEqual('string', typeof navigation.currentEntry.url);
|
testing.expectEqual('string', typeof navigation.currentEntry.url);
|
||||||
testing.expectEqual(0, navigation.currentEntry.index);
|
|
||||||
testing.expectEqual(true, navigation.currentEntry.sameDocument);
|
const currentIndex = navigation.currentEntry.index;
|
||||||
|
|
||||||
let result = navigation.navigate('http://127.0.0.1:9582/xhr/json', {
|
navigation.navigate(
|
||||||
state: { testInProgress: true }
|
'http://127.0.0.1:9582/src/tests/html/navigation2.html',
|
||||||
});
|
{ state: { currentIndex: currentIndex, navTestInProgress: true } }
|
||||||
testing.expectEqual('object', typeof result);
|
);
|
||||||
testing.expectEqual('object', typeof result.committed);
|
|
||||||
testing.expectEqual('object', typeof result.finished);
|
|
||||||
|
|
||||||
testing.expectEqual({ testInProgress: true }, navigation.currentEntry.getState());
|
|
||||||
testing.expectEqual(1, navigation.currentEntry.index);
|
|
||||||
|
|
||||||
testing.expectEqual(true, navigation.canGoBack);
|
|
||||||
testing.expectEqual(false, navigation.canGoForward);
|
|
||||||
|
|
||||||
testing.expectEqual(undefined, navigation.back());
|
|
||||||
|
|
||||||
testing.onPageWait(() => {
|
|
||||||
testing.expectEqual(0, navigation.currentEntry.index);
|
|
||||||
testing.expectEqual(true, navigation.canGoForward);
|
|
||||||
|
|
||||||
testing.expectEqual(undefined, navigation.forward());
|
|
||||||
});
|
|
||||||
|
|
||||||
testing.onPageWait(() => {
|
|
||||||
testing.expectEqual(1, navigation.currentEntry.index);
|
|
||||||
testing.expectEqual({ testInProgress: true }, navigation.currentEntry.getState());
|
|
||||||
|
|
||||||
let targetKey = navigation.currentEntry.key;
|
|
||||||
testing.expectEqual(undefined, navigation.traverseTo(targetKey));
|
|
||||||
});
|
|
||||||
|
|
||||||
navigation.updateCurrentEntry({ state: { updated: true, testComplete: true } });
|
|
||||||
testing.expectEqual({ updated: true, testComplete: true }, navigation.currentEntry.getState());
|
|
||||||
|
|
||||||
testing.onPageWait(() => {
|
|
||||||
testing.expectEqual(true, navigation.currentEntry.getState().testComplete);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
8
src/tests/html/navigation2.html
Normal file
8
src/tests/html/navigation2.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
|
<script id=navigation2>
|
||||||
|
const state = navigation.currentEntry.getState();
|
||||||
|
testing.expectEqual(true, state.navTestInProgress);
|
||||||
|
testing.expectEqual(state.currentIndex + 1, navigation.currentEntry.index);
|
||||||
|
</script>
|
||||||
@@ -51,14 +51,6 @@
|
|||||||
// if we're already in a fail state, return fail, nothing can recover this
|
// if we're already in a fail state, return fail, nothing can recover this
|
||||||
if (testing._status === 'fail') return 'fail';
|
if (testing._status === 'fail') return 'fail';
|
||||||
|
|
||||||
if (testing._isSecondWait) {
|
|
||||||
for (const pw of (testing._onPageWait)) {
|
|
||||||
testing._captured = pw[1];
|
|
||||||
pw[0]();
|
|
||||||
testing._captured = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run any eventually's that we've captured
|
// run any eventually's that we've captured
|
||||||
for (const ev of testing._eventually) {
|
for (const ev of testing._eventually) {
|
||||||
testing._captured = ev[1];
|
testing._captured = ev[1];
|
||||||
@@ -101,18 +93,6 @@
|
|||||||
_registerErrorCallback();
|
_registerErrorCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set expectations to happen on the next time that `page.wait` is executed.
|
|
||||||
//
|
|
||||||
// History specifically uses this as it queues navigation that needs to be checked
|
|
||||||
// when the next page is loaded.
|
|
||||||
function onPageWait(fn) {
|
|
||||||
// Store callbacks to run when page.wait() happens
|
|
||||||
testing._onPageWait.push([fn, {
|
|
||||||
script_id: document.currentScript.id,
|
|
||||||
stack: new Error().stack,
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function async(promise, cb) {
|
async function async(promise, cb) {
|
||||||
const script_id = document.currentScript ? document.currentScript.id : '<script id is unavailable in browsers>';
|
const script_id = document.currentScript ? document.currentScript.id : '<script id is unavailable in browsers>';
|
||||||
const stack = new Error().stack;
|
const stack = new Error().stack;
|
||||||
@@ -192,15 +172,12 @@
|
|||||||
window.testing = {
|
window.testing = {
|
||||||
_status: 'empty',
|
_status: 'empty',
|
||||||
_eventually: [],
|
_eventually: [],
|
||||||
_onPageWait: [],
|
|
||||||
_executed_scripts: new Set(),
|
_executed_scripts: new Set(),
|
||||||
_captured: null,
|
_captured: null,
|
||||||
_isSecondWait: false,
|
|
||||||
skip: skip,
|
skip: skip,
|
||||||
async: async,
|
async: async,
|
||||||
getStatus: getStatus,
|
getStatus: getStatus,
|
||||||
eventually: eventually,
|
eventually: eventually,
|
||||||
onPageWait: onPageWait,
|
|
||||||
expectEqual: expectEqual,
|
expectEqual: expectEqual,
|
||||||
expectError: expectError,
|
expectError: expectError,
|
||||||
withError: withError,
|
withError: withError,
|
||||||
|
|||||||
Reference in New Issue
Block a user