Skip to content

Commit 3d8cc2a

Browse files
committed
Add readystate change event to XHR
Deal with non-node current target crashing. Builds ontop of abort_signal, but with the new event-target specific union, I think this will work in for all future cases.
1 parent bf1db50 commit 3d8cc2a

File tree

6 files changed

+56
-9
lines changed

6 files changed

+56
-9
lines changed

src/browser/dom/event_target.zig

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@
1919
const Env = @import("../env.zig").Env;
2020
const parser = @import("../netsurf.zig");
2121
const Page = @import("../page.zig").Page;
22+
const generate = @import("../../runtime/generate.zig");
2223

2324
const EventHandler = @import("../events/event.zig").EventHandler;
2425

2526
const DOMException = @import("exceptions.zig").DOMException;
26-
const Nod = @import("node.zig");
27+
const nod = @import("node.zig");
2728

28-
// EventTarget interfaces
29-
pub const Union = Nod.Union;
29+
pub const Union = union(enum) {
30+
node: nod.Union,
31+
xhr: *@import("../xhr/xhr.zig").XMLHttpRequest,
32+
};
3033

3134
// EventTarget implementation
3235
pub const EventTarget = struct {
@@ -39,18 +42,22 @@ pub const EventTarget = struct {
3942
// The window is a common non-node target, but it's easy to handle as
4043
// its a singleton.
4144
if (@intFromPtr(et) == @intFromPtr(&page.window.base)) {
42-
return .{ .Window = &page.window };
45+
return .{ .node = .{ .Window = &page.window } };
4346
}
4447

4548
// AbortSignal is another non-node target. It has a distinct usage though
4649
// so we hijack the event internal type to identity if.
4750
switch (try parser.eventGetInternalType(e)) {
4851
.abort_signal => {
49-
return .{ .AbortSignal = @fieldParentPtr("proto", @as(*parser.EventTargetTBase, @ptrCast(et))) };
52+
return .{ .node = .{ .AbortSignal = @fieldParentPtr("proto", @as(*parser.EventTargetTBase, @ptrCast(et))) } };
53+
},
54+
.xhr_event => {
55+
const XMLHttpRequestEventTarget = @import("../xhr/event_target.zig").XMLHttpRequestEventTarget;
56+
const base: *XMLHttpRequestEventTarget = @fieldParentPtr("base", @as(*parser.EventTargetTBase, @ptrCast(et)));
57+
return .{ .xhr = @fieldParentPtr("proto", base) };
5058
},
5159
else => {
52-
// some of these probably need to be special-cased like abort_signal
53-
return Nod.Node.toInterface(@as(*parser.Node, @ptrCast(et)));
60+
return .{ .node = try nod.Node.toInterface(@as(*parser.Node, @ptrCast(et))) };
5461
},
5562
}
5663
}

src/browser/events/event.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub const Event = struct {
5454

5555
pub fn toInterface(evt: *parser.Event) !Union {
5656
return switch (try parser.eventGetInternalType(evt)) {
57-
.event, .abort_signal => .{ .Event = evt },
57+
.event, .abort_signal, .xhr_event => .{ .Event = evt },
5858
.custom_event => .{ .CustomEvent = @as(*CustomEvent, @ptrCast(evt)).* },
5959
.progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* },
6060
.mouse_event => .{ .MouseEvent = @as(*parser.MouseEvent, @ptrCast(evt)) },

src/browser/netsurf.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ pub const EventType = enum(u8) {
527527
mouse_event = 3,
528528
error_event = 4,
529529
abort_signal = 5,
530+
xhr_event = 6,
530531
};
531532

532533
pub const MutationEvent = c.dom_mutation_event;

src/browser/page.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ const Script = struct {
10031003

10041004
const src: []const u8 = blk: {
10051005
const s = self.src orelse break :blk page.url.raw;
1006-
break :blk try URL.stitch(page.arena, s, page.url.raw, .{.alloc = .if_needed});
1006+
break :blk try URL.stitch(page.arena, s, page.url.raw, .{ .alloc = .if_needed });
10071007
};
10081008

10091009
// if self.src is null, then this is an inline script, and it should

src/browser/xhr/event_target.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub const XMLHttpRequestEventTarget = struct {
3939
onload_cbk: ?Function = null,
4040
ontimeout_cbk: ?Function = null,
4141
onloadend_cbk: ?Function = null,
42+
onreadystatechange_cbk: ?Function = null,
4243

4344
fn register(
4445
self: *XMLHttpRequestEventTarget,
@@ -86,6 +87,9 @@ pub const XMLHttpRequestEventTarget = struct {
8687
pub fn get_onloadend(self: *XMLHttpRequestEventTarget) ?Function {
8788
return self.onloadend_cbk;
8889
}
90+
pub fn get_onreadystatechange(self: *XMLHttpRequestEventTarget) ?Function {
91+
return self.onreadystatechange_cbk;
92+
}
8993

9094
pub fn set_onloadstart(self: *XMLHttpRequestEventTarget, listener: EventHandler.Listener, page: *Page) !void {
9195
if (self.onloadstart_cbk) |cbk| try self.unregister("loadstart", cbk.id);
@@ -111,4 +115,8 @@ pub const XMLHttpRequestEventTarget = struct {
111115
if (self.onloadend_cbk) |cbk| try self.unregister("loadend", cbk.id);
112116
self.onloadend_cbk = try self.register(page.arena, "loadend", listener);
113117
}
118+
pub fn set_onreadystatechange(self: *XMLHttpRequestEventTarget, listener: EventHandler.Listener, page: *Page) !void {
119+
if (self.onreadystatechange_cbk) |cbk| try self.unregister("readystatechange", cbk.id);
120+
self.onreadystatechange_cbk = try self.register(page.arena, "readystatechange", listener);
121+
}
114122
};

src/browser/xhr/xhr.zig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ pub const XMLHttpRequest = struct {
138138
done = 4,
139139
};
140140

141+
// class attributes
142+
pub const _UNSENT = @intFromEnum(State.unsent);
143+
pub const _OPENED = @intFromEnum(State.opened);
144+
pub const _HEADERS_RECEIVED = @intFromEnum(State.headers_received);
145+
pub const _LOADING = @intFromEnum(State.loading);
146+
pub const _DONE = @intFromEnum(State.done);
147+
141148
// https://xhr.spec.whatwg.org/#response-type
142149
const ResponseType = enum {
143150
Empty,
@@ -360,6 +367,8 @@ pub const XMLHttpRequest = struct {
360367
// We can we defer event destroy once the event is dispatched.
361368
defer parser.eventDestroy(evt);
362369

370+
try parser.eventSetInternalType(evt, .xhr_event);
371+
363372
try parser.eventInit(evt, typ, .{ .bubbles = true, .cancelable = true });
364373
_ = try parser.eventTargetDispatchEvent(@as(*parser.EventTarget, @ptrCast(self)), evt);
365374
}
@@ -923,4 +932,26 @@ test "Browser.XHR.XMLHttpRequest" {
923932
// So the url has been retrieved.
924933
.{ "status", "200" },
925934
}, .{});
935+
936+
try runner.testCases(&.{
937+
.{ "const req6 = new XMLHttpRequest()", null },
938+
.{
939+
\\ var readyStates = [];
940+
\\ var currentTarget = null;
941+
\\ req6.onreadystatechange = (e) => {
942+
\\ currentTarget = e.currentTarget;
943+
\\ readyStates.push(req6.readyState);
944+
\\ }
945+
,
946+
null,
947+
},
948+
.{ "req6.open('GET', 'https://127.0.0.1:9581/xhr')", null },
949+
.{ "req6.send()", null },
950+
.{ "readyStates.length", "4" },
951+
.{ "readyStates[0] === XMLHttpRequest.OPENED", "true" },
952+
.{ "readyStates[1] === XMLHttpRequest.HEADERS_RECEIVED", "true" },
953+
.{ "readyStates[2] === XMLHttpRequest.LOADING", "true" },
954+
.{ "readyStates[3] === XMLHttpRequest.DONE", "true" },
955+
.{ "currentTarget == req6", "true" },
956+
}, .{});
926957
}

0 commit comments

Comments
 (0)