Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/browser/dom/event_target.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) };
},
}
}

Expand Down
42 changes: 40 additions & 2 deletions src/browser/events/event.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

const std = @import("std");
const js = @import("../js/js.zig");
const Allocator = std.mem.Allocator;

const log = @import("../../log.zig");
Expand All @@ -38,6 +39,7 @@ const ErrorEvent = @import("../html/error_event.zig").ErrorEvent;
const MessageEvent = @import("../dom/MessageChannel.zig").MessageEvent;
const PopStateEvent = @import("../html/History.zig").PopStateEvent;
const CompositionEvent = @import("composition_event.zig").CompositionEvent;
const NavigationCurrentEntryChangeEvent = @import("../navigation/navigation.zig").NavigationCurrentEntryChangeEvent;

// Event interfaces
pub const Interfaces = .{
Expand All @@ -50,6 +52,7 @@ pub const Interfaces = .{
MessageEvent,
PopStateEvent,
CompositionEvent,
NavigationCurrentEntryChangeEvent,
};

pub const Union = generate.Union(Interfaces);
Expand Down Expand Up @@ -79,6 +82,9 @@ pub const Event = struct {
.keyboard_event => .{ .KeyboardEvent = @as(*parser.KeyboardEvent, @ptrCast(evt)) },
.pop_state => .{ .PopStateEvent = @as(*PopStateEvent, @ptrCast(evt)).* },
.composition_event => .{ .CompositionEvent = (@as(*CompositionEvent, @fieldParentPtr("proto", evt))).* },
.navigation_current_entry_change_event => .{
.NavigationCurrentEntryChangeEvent = @as(*NavigationCurrentEntryChangeEvent, @ptrCast(evt)).*,
},
};
}

Expand Down Expand Up @@ -226,8 +232,6 @@ pub const EventHandler = struct {
node: parser.EventNode,
listener: *parser.EventListener,

const js = @import("../js/js.zig");

pub const Listener = union(enum) {
function: js.Function,
object: js.Object,
Expand Down Expand Up @@ -399,6 +403,40 @@ const SignalCallback = struct {
}
};

pub fn DirectEventHandler(
comptime TargetT: type,
target: *TargetT,
event_type: []const u8,
maybe_listener: ?EventHandler.Listener,
cb: *?js.Function,
page_arena: std.mem.Allocator,
) !void {
const event_target = parser.toEventTarget(TargetT, target);

// Check if we have a listener set.
if (cb.*) |callback| {
const listener = try parser.eventTargetHasListener(event_target, event_type, false, callback.id);
std.debug.assert(listener != null);
try parser.eventTargetRemoveEventListener(event_target, event_type, listener.?, false);
}

if (maybe_listener) |listener| {
switch (listener) {
// If an object is given as listener, do nothing.
.object => {},
.function => |callback| {
_ = try EventHandler.register(page_arena, event_target, event_type, listener, null) orelse unreachable;
cb.* = callback;

return;
},
}
}

// Just unset the listener.
cb.* = null;
}

const testing = @import("../../testing.zig");
test "Browser: Event" {
try testing.htmlRunner("events/event.html");
Expand Down
148 changes: 58 additions & 90 deletions src/browser/html/History.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,140 +21,79 @@ 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();

const HistoryEntry = struct {
url: []const u8,
// This is serialized as JSON because
// History must survive a JsContext.
state: ?[]u8,
};

const ScrollRestorationMode = enum {
pub const ENUM_JS_USE_TAG = true;

auto,
manual,

pub fn fromString(str: []const u8) ?ScrollRestorationMode {
for (std.enums.values(ScrollRestorationMode)) |mode| {
if (std.ascii.eqlIgnoreCase(str, @tagName(mode))) {
return mode;
}
} else {
return null;
}
}

pub fn toString(self: ScrollRestorationMode) []const u8 {
return @tagName(self);
}
};

scroll_restoration: ScrollRestorationMode = .auto,
stack: std.ArrayListUnmanaged(HistoryEntry) = .empty,
current: ?usize = null,

pub fn get_length(self: *History) u32 {
return @intCast(self.stack.items.len);
pub fn get_length(_: *History, page: *Page) u32 {
return @intCast(page.session.navigation.entries.items.len);
}

pub fn get_scrollRestoration(self: *History) ScrollRestorationMode {
return self.scroll_restoration;
}

pub fn set_scrollRestoration(self: *History, mode: []const u8) void {
self.scroll_restoration = ScrollRestorationMode.fromString(mode) orelse self.scroll_restoration;
pub fn set_scrollRestoration(self: *History, mode: ScrollRestorationMode) void {
self.scroll_restoration = mode;
}

pub fn get_state(self: *History, page: *Page) !?js.Value {
if (self.current) |curr| {
const entry = self.stack.items[curr];
if (entry.state) |state| {
const value = try js.Value.fromJson(page.js, state);
return value;
} else {
return null;
}
pub fn get_state(_: *History, page: *Page) !?js.Value {
if (page.session.navigation.currentEntry().state) |state| {
const value = try js.Value.fromJson(page.js, state);
return value;
} else {
return null;
}
}

pub fn pushNavigation(self: *History, _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 url = try arena.dupe(u8, _url);

const entry = HistoryEntry{ .state = null, .url = url };
try self.stack.append(arena, entry);
self.current = self.stack.items.len - 1;
}

pub fn dispatchPopStateEvent(state: ?[]const u8, page: *Page) void {
log.debug(.script_event, "dispatch popstate event", .{
.type = "popstate",
.source = "history",
});
History._dispatchPopStateEvent(state, page) catch |err| {
log.err(.app, "dispatch popstate event error", .{
.err = err,
.type = "popstate",
.source = "history",
});
};
}

fn _dispatchPopStateEvent(state: ?[]const u8, page: *Page) !void {
var evt = try PopStateEvent.constructor("popstate", .{ .state = state });
const url = if (_url) |u| try arena.dupe(u8, u) else try arena.dupe(u8, page.url.raw);

_ = try parser.eventTargetDispatchEvent(
@as(*parser.EventTarget, @ptrCast(&page.window)),
&evt.proto,
);
const json = state.toJson(arena) catch return error.DataClone;
_ = try page.session.navigation.pushEntry(url, json, page, true);
}

pub fn _pushState(self: *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 {
const arena = page.session.arena;

const entry = page.session.navigation.currentEntry();
const json = try state.toJson(arena);
const url = if (_url) |u| try arena.dupe(u8, u) else try arena.dupe(u8, page.url.raw);
const entry = HistoryEntry{ .state = json, .url = url };
try self.stack.append(arena, entry);
self.current = self.stack.items.len - 1;
}

pub fn _replaceState(self: *History, state: js.Object, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
const arena = page.session.arena;

if (self.current) |curr| {
const entry = &self.stack.items[curr];
const json = try state.toJson(arena);
const url = if (_url) |u| try arena.dupe(u8, u) else try arena.dupe(u8, page.url.raw);
entry.* = HistoryEntry{ .state = json, .url = url };
} else {
try self._pushState(state, "", _url, page);
}
entry.state = json;
entry.url = url;
}

pub fn go(self: *History, delta: i32, page: *Page) !void {
pub fn go(_: *const History, delta: i32, page: *Page) !void {
// 0 behaves the same as no argument, both reloading the page.
// If this is getting called, there SHOULD be an entry, atleast from pushNavigation.
const current = self.current.?;

const current = page.session.navigation.index;
const index_s: i64 = @intCast(@as(i64, @intCast(current)) + @as(i64, @intCast(delta)));
if (index_s < 0 or index_s > self.stack.items.len - 1) {
if (index_s < 0 or index_s > page.session.navigation.entries.items.len - 1) {
return;
}

const index = @as(usize, @intCast(index_s));
const entry = self.stack.items[index];
self.current = index;
const entry = page.session.navigation.entries.items[index];

if (try page.isSameOrigin(entry.url)) {
History.dispatchPopStateEvent(entry.state, page);
if (entry.url) |url| {
if (try page.isSameOrigin(url)) {
PopStateEvent.dispatch(entry.state, page);
}
}

try page.navigateFromWebAPI(entry.url, .{ .reason = .history });
_ = try page.session.navigation.navigate(entry.url, .{ .traverse = index }, page);
}

pub fn _go(self: *History, _delta: ?i32, page: *Page) !void {
Expand Down Expand Up @@ -207,9 +146,38 @@ pub const PopStateEvent = struct {
return null;
}
}

pub fn dispatch(state: ?[]const u8, page: *Page) void {
log.debug(.script_event, "dispatch popstate event", .{
.type = "popstate",
.source = "history",
});

var evt = PopStateEvent.constructor("popstate", .{ .state = state }) catch |err| {
log.err(.app, "event constructor error", .{
.err = err,
.type = "popstate",
.source = "history",
});

return;
};

_ = parser.eventTargetDispatchEvent(
parser.toEventTarget(Window, &page.window),
&evt.proto,
) catch |err| {
log.err(.app, "dispatch popstate event error", .{
.err = err,
.type = "popstate",
.source = "history",
});
};
}
};

const testing = @import("../../testing.zig");
test "Browser: HTML.History" {
try testing.htmlRunner("html/history.html");
try testing.htmlRunner("html/history/history.html");
try testing.htmlRunner("html/history/history2.html");
}
2 changes: 1 addition & 1 deletion src/browser/html/document.zig
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ pub const HTMLDocument = struct {
}

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 {
Expand Down
8 changes: 4 additions & 4 deletions src/browser/html/location.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub const Location = struct {
}

pub fn set_href(_: *const Location, href: []const u8, page: *Page) !void {
return page.navigateFromWebAPI(href, .{ .reason = .script });
return page.navigateFromWebAPI(href, .{ .reason = .script }, .{ .push = null });
}

pub fn get_protocol(self: *Location) []const u8 {
Expand Down Expand Up @@ -74,15 +74,15 @@ pub const Location = struct {
}

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 {
return page.navigateFromWebAPI(url, .{ .reason = .script });
return page.navigateFromWebAPI(url, .{ .reason = .script }, .replace);
}

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 {
Expand Down
Loading