Skip to content

Commit e74d7fa

Browse files
committed
add popstate event for History
1 parent 464f42a commit e74d7fa

File tree

6 files changed

+98
-8
lines changed

6 files changed

+98
-8
lines changed

src/browser/events/event.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const MouseEvent = @import("mouse_event.zig").MouseEvent;
3636
const KeyboardEvent = @import("keyboard_event.zig").KeyboardEvent;
3737
const ErrorEvent = @import("../html/error_event.zig").ErrorEvent;
3838
const MessageEvent = @import("../dom/MessageChannel.zig").MessageEvent;
39+
const PopStateEvent = @import("../html/History.zig").PopStateEvent;
3940

4041
// Event interfaces
4142
pub const Interfaces = .{
@@ -46,6 +47,7 @@ pub const Interfaces = .{
4647
KeyboardEvent,
4748
ErrorEvent,
4849
MessageEvent,
50+
PopStateEvent,
4951
};
5052

5153
pub const Union = generate.Union(Interfaces);
@@ -73,6 +75,7 @@ pub const Event = struct {
7375
.error_event => .{ .ErrorEvent = @as(*ErrorEvent, @ptrCast(evt)).* },
7476
.message_event => .{ .MessageEvent = @as(*MessageEvent, @ptrCast(evt)).* },
7577
.keyboard_event => .{ .KeyboardEvent = @as(*parser.KeyboardEvent, @ptrCast(evt)) },
78+
.pop_state => .{ .PopStateEvent = @as(*PopStateEvent, @ptrCast(evt)).* },
7679
};
7780
}
7881

src/browser/fetch/fetch.zig

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ pub const FetchContext = struct {
6464
var headers: Headers = .{};
6565

6666
// seems to be the highest priority
67-
const same_origin = try isSameOriginAsPage(self.url, self.page);
67+
const same_origin = try self.page.isSameOrigin(self.url);
6868

6969
// If the mode is "no-cors", we need to return this opaque/stripped Response.
7070
// https://developer.mozilla.org/en-US/docs/Web/API/Response/type
@@ -237,11 +237,6 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
237237
return resolver.promise();
238238
}
239239

240-
fn isSameOriginAsPage(url: []const u8, page: *const Page) !bool {
241-
const origin = try page.origin(page.call_arena);
242-
return std.mem.startsWith(u8, url, origin);
243-
}
244-
245240
const testing = @import("../../testing.zig");
246241
test "fetch: fetch" {
247242
try testing.htmlRunner("fetch/fetch.html");

src/browser/html/History.zig

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20+
const log = @import("../../log.zig");
2021

2122
const Env = @import("../env.zig").Env;
2223
const Page = @import("../page.zig").Page;
@@ -87,6 +88,32 @@ pub fn pushNavigation(self: *History, _url: []const u8, page: *Page) !void {
8788
self.current = self.stack.items.len - 1;
8889
}
8990

91+
pub fn dispatchPopStateEvent(state: ?[]const u8, page: *Page) void {
92+
log.debug(.script_event, "dispatch popstate event", .{
93+
.type = "popstate",
94+
.source = "history",
95+
});
96+
History._dispatchPopStateEvent(state, page) catch |err| {
97+
log.err(.app, "dispatch popstate event error", .{
98+
.err = err,
99+
.type = "popstate",
100+
.source = "history",
101+
});
102+
};
103+
}
104+
105+
fn _dispatchPopStateEvent(
106+
state: ?[]const u8,
107+
page: *Page,
108+
) !void {
109+
var evt = try PopStateEvent.constructor("popstate", .{ .state = state });
110+
111+
_ = try parser.eventTargetDispatchEvent(
112+
@as(*parser.EventTarget, @ptrCast(&page.window)),
113+
@as(*parser.Event, @ptrCast(&evt)),
114+
);
115+
}
116+
90117
pub fn _pushState(self: *History, state: Env.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
91118
const arena = page.session.arena;
92119

@@ -123,6 +150,15 @@ pub fn go(self: *History, delta: i32, page: *Page) !void {
123150
const index = @as(usize, @intCast(index_s));
124151
const entry = self.stack.items[index];
125152
self.current = index;
153+
154+
if (try page.isSameOrigin(entry.url)) {
155+
if (entry.state) |state| {
156+
History.dispatchPopStateEvent(state, page);
157+
} else {
158+
History.dispatchPopStateEvent(null, page);
159+
}
160+
}
161+
126162
try page.navigateFromWebAPI(entry.url, .{ .reason = .history });
127163
}
128164

@@ -138,6 +174,46 @@ pub fn _forward(self: *History, page: *Page) !void {
138174
try self.go(1, page);
139175
}
140176

177+
const parser = @import("../netsurf.zig");
178+
const Event = @import("../events/event.zig").Event;
179+
180+
pub const PopStateEvent = struct {
181+
pub const prototype = *Event;
182+
pub const union_make_copy = true;
183+
184+
pub const EventInit = struct {
185+
state: ?[]const u8 = null,
186+
};
187+
188+
proto: parser.Event,
189+
state: ?[]const u8,
190+
191+
pub fn constructor(event_type: []const u8, opts: ?EventInit) !PopStateEvent {
192+
const event = try parser.eventCreate();
193+
defer parser.eventDestroy(event);
194+
try parser.eventInit(event, event_type, .{});
195+
parser.eventSetInternalType(event, .pop_state);
196+
197+
const o = opts orelse EventInit{};
198+
199+
return .{
200+
.proto = event.*,
201+
.state = o.state,
202+
};
203+
}
204+
205+
// `hasUAVisualTransition` is not implemented. It isn't baseline so this is okay.
206+
207+
pub fn get_state(self: *const PopStateEvent, page: *Page) !?Env.Value {
208+
if (self.state) |state| {
209+
const value = try Env.Value.fromJson(page.main_context, state);
210+
return value;
211+
} else {
212+
return null;
213+
}
214+
}
215+
};
216+
141217
const testing = @import("../../testing.zig");
142218
test "Browser: HTML.History" {
143219
try testing.htmlRunner("html/history.html");

src/browser/netsurf.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ pub const EventType = enum(u8) {
558558
xhr_event = 6,
559559
message_event = 7,
560560
keyboard_event = 8,
561+
pop_state = 9,
561562
};
562563

563564
pub const MutationEvent = c.dom_mutation_event;

src/browser/page.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,11 @@ pub const Page = struct {
11321132
}
11331133
self.slot_change_monitor = try SlotChangeMonitor.init(self);
11341134
}
1135+
1136+
pub fn isSameOrigin(self: *const Page, url: []const u8) !bool {
1137+
const current_origin = try self.origin(self.call_arena);
1138+
return std.mem.startsWith(u8, url, current_origin);
1139+
}
11351140
};
11361141

11371142
pub const NavigateReason = enum {

src/tests/html/history.html

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,18 @@
1919
let state = { "new": "field", testComplete: true };
2020
testing.expectEqual(state, history.state);
2121

22-
testing.expectEqual(undefined, history.back());
23-
testing.expectEqual(undefined, history.forward());
22+
let popstateEventFired = false;
23+
let popstateEventState = null;
24+
25+
window.addEventListener('popstate', (event) => {
26+
popstateEventFired = true;
27+
popstateEventState = event.state;
28+
});
29+
30+
testing.eventually(() => {
31+
testing.expectEqual(true, popstateEventFired);
32+
testing.expectEqual(state, popstateEventState);
33+
})
2434

2535
testing.onPageWait(() => {
2636
testing.expectEqual(true, history.state && history.state.testComplete);

0 commit comments

Comments
 (0)